Compare commits

..

No commits in common. "master" and "v1.15.0" have entirely different histories.

500 changed files with 22309 additions and 51068 deletions
.docker
.editorconfig.env.env.dev.env.test
.github/workflows
.gitignoreDockerfileDockerfile-frankenphpMakefileREADME.mdVERSION
assets
composer.jsoncomposer.lock
config
docs

View file

@ -40,49 +40,7 @@ if [ -d /var/www/html/var/db ]; then
fi fi
# Start PHP-FPM (the PHP_VERSION is replaced by the configured version in the Dockerfile) # Start PHP-FPM (the PHP_VERSION is replaced by the configured version in the Dockerfile)
php-fpmPHP_VERSION -F & service phpPHP_VERSION-fpm start
# Run migrations if automigration is enabled via env variable DB_AUTOMIGRATE
if [ "$DB_AUTOMIGRATE" = "true" ]; then
echo "Waiting for database to be ready..."
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(sudo -E -u www-data php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do
if [ $? -eq 255 ]; then
# If the Doctrine command exits with 255, an unrecoverable error occurred
ATTEMPTS_LEFT_TO_REACH_DATABASE=0
break
fi
sleep 1
ATTEMPTS_LEFT_TO_REACH_DATABASE=$((ATTEMPTS_LEFT_TO_REACH_DATABASE - 1))
echo "Still waiting for database to be ready... Or maybe the database is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left."
done
if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then
echo "The database is not up or not reachable:"
echo "$DATABASE_ERROR"
exit 1
else
echo "The database is now ready and reachable"
fi
# Check if there are any available migrations to do, by executing doctrine:migrations:up-to-date
# and checking if the exit code is 0 (up to date) or 1 (not up to date)
if sudo -E -u www-data php bin/console doctrine:migrations:up-to-date --no-interaction; then
echo "Database is up to date, no migrations necessary."
else
echo "Migrations available..."
echo "Do backup of database..."
sudo -E -u www-data mkdir -p /var/www/html/uploads/.automigration-backup/
# Backup the database
sudo -E -u www-data php bin/console partdb:backup -n --database /var/www/html/uploads/.automigration-backup/backup-$(date +%Y-%m-%d_%H-%M-%S).zip
# Check if there are any migration files
sudo -E -u www-data php bin/console doctrine:migrations:migrate --no-interaction
fi
fi
# first arg is `-f` or `--some-option` (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/docker-php-entrypoint) # first arg is `-f` or `--some-option` (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/docker-php-entrypoint)
if [ "${1#-}" != "$1" ]; then if [ "${1#-}" != "$1" ]; then

View file

@ -24,6 +24,28 @@
ErrorLog ${APACHE_LOG_DIR}/error.log ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined CustomLog ${APACHE_LOG_DIR}/access.log combined
# Pass the configuration from the docker env to the PHP environment (here you should list all .env options)
PassEnv APP_ENV APP_DEBUG APP_SECRET REDIRECT_TO_HTTPS DISABLE_YEAR2038_BUG_CHECK
PassEnv TRUSTED_PROXIES TRUSTED_HOSTS LOCK_DSN
PassEnv DATABASE_URL ENFORCE_CHANGE_COMMENTS_FOR DATABASE_MYSQL_USE_SSL_CA DATABASE_MYSQL_SSL_VERIFY_CERT
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI CHECK_FOR_UPDATES ATTACHMENT_DOWNLOAD_BY_DEFAULT
PassEnv MAILER_DSN ALLOW_EMAIL_PW_RESET EMAIL_SENDER_EMAIL EMAIL_SENDER_NAME
PassEnv HISTORY_SAVE_CHANGED_FIELDS HISTORY_SAVE_CHANGED_DATA HISTORY_SAVE_REMOVED_DATA HISTORY_SAVE_NEW_DATA
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_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
PassEnv PROVIDER_LCSC_ENABLED PROVIDER_LCSC_CURRENCY
PassEnv PROVIDER_OEMSECRETS_KEY PROVIDER_OEMSECRETS_COUNTRY_CODE PROVIDER_OEMSECRETS_CURRENCY PROVIDER_OEMSECRETS_ZERO_PRICE PROVIDER_OEMSECRETS_SET_PARAM PROVIDER_OEMSECRETS_SORT_CRITERIA
PassEnv EDA_KICAD_CATEGORY_DEPTH
# For most configuration files from conf-available/, which are # For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to # enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the # include a line for only one particular virtual host. For example the

View file

@ -1,17 +0,0 @@
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[{compose.yaml,compose.*.yaml}]
indent_size = 2
[*.md]
trim_trailing_whitespace = false

178
.env
View file

@ -31,10 +31,37 @@ DATABASE_EMULATE_NATURAL_SORT=0
# General settings # General settings
################################################################################### ###################################################################################
# The language to use serverwide as default (en, de, ru, etc.)
DEFAULT_LANG="en"
# The default timezone to use serverwide (e.g. Europe/Berlin)
DEFAULT_TIMEZONE="Europe/Berlin"
# The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country
BASE_CURRENCY="EUR"
# The name of this installation. This will be shown as title in the browser and in the header of the website
INSTANCE_NAME="Part-DB"
# Allow users to download attachments to the server by providing an URL
# This could be a potential security issue, as the user can retrieve any file the server has access to (via internet)
ALLOW_ATTACHMENT_DOWNLOADS=0
# Set this to 1, if the "download external files" checkbox should be checked by default for new attachments
ATTACHMENT_DOWNLOAD_BY_DEFAULT=0
# Use gravatars for user avatars, when user has no own avatar defined
USE_GRAVATAR=0
# The maximum allowed size for attachment files in bytes (you can use M for megabytes and G for gigabytes)
# Please note that the php.ini setting upload_max_filesize also limits the maximum size of uploaded files
MAX_ATTACHMENT_FILE_SIZE="100M"
# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates # The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates
# This must end with a slash! # This must end with a slash!
DEFAULT_URI="https://partdb.changeme.invalid/" DEFAULT_URI="https://partdb.changeme.invalid/"
# With this option you can configure, where users are enforced to give a change reason, which will be logged
# This is a comma separated list of values, see documentation for available values
# Leave this empty, to make all change reasons optional
ENFORCE_CHANGE_COMMENTS_FOR=""
# Disable that if you do not want that Part-DB connects to GitHub to check for available updates, or if your server can not connect to the internet
CHECK_FOR_UPDATES=1
################################################################################### ###################################################################################
# Email settings # Email settings
################################################################################### ###################################################################################
@ -51,6 +78,21 @@ EMAIL_SENDER_NAME="Part-DB Mailer"
# Set this to 1 to allow reset of a password per email # Set this to 1 to allow reset of a password per email
ALLOW_EMAIL_PW_RESET=0 ALLOW_EMAIL_PW_RESET=0
######################################################################################
# History/Eventlog settings
######################################################################################
# If you want to use full timetrave functionality all values below have to be set to 1
# Save which fields were changed in a ElementEdited log entry
HISTORY_SAVE_CHANGED_FIELDS=1
# Save the old data in the ElementEdited log entry (warning this could increase the database size in short time)
HISTORY_SAVE_CHANGED_DATA=1
# Save the data of an element that gets removed into log entry. This allows to undelete an element
HISTORY_SAVE_REMOVED_DATA=1
# Save the new data of an element that gets changed or added. This allows an easy comparison of the old and new data on the detail page
# This option only becomes active when HISTORY_SAVE_CHANGED_DATA is set to 1
HISTORY_SAVE_NEW_DATA=1
################################################################################### ###################################################################################
# Error pages settings # Error pages settings
################################################################################### ###################################################################################
@ -60,6 +102,127 @@ ERROR_PAGE_ADMIN_EMAIL=''
# If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them... # If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them...
ERROR_PAGE_SHOW_HELP=1 ERROR_PAGE_SHOW_HELP=1
##################################################################################
# Part table settings
##################################################################################
# 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
##################################################################################
# Digikey Provider:
# You can get your client id and secret from https://developer.digikey.com/
PROVIDER_DIGIKEY_CLIENT_ID=
PROVIDER_DIGIKEY_SECRET=
# The currency to get prices in
PROVIDER_DIGIKEY_CURRENCY=EUR
# The language to get results in (en, de, fr, it, es, zh, ja, ko)
PROVIDER_DIGIKEY_LANGUAGE=en
# The country to get results for
PROVIDER_DIGIKEY_COUNTRY=DE
# Farnell Provider:
# You can get your API key from https://partner.element14.com/
PROVIDER_ELEMENT14_KEY=
# Configure the store domain you want to use. This decides the language and currency of results. You can get a list of available stores from https://partner.element14.com/docs/Product_Search_API_REST__Description
PROVIDER_ELEMENT14_STORE_ID=de.farnell.com
# TME Provider:
# You can get your API key from https://developers.tme.eu/en/
PROVIDER_TME_KEY=
PROVIDER_TME_SECRET=
# The currency to get prices in
PROVIDER_TME_CURRENCY=EUR
# The language to get results in (en, de, pl)
PROVIDER_TME_LANGUAGE=en
# The country to get results for
PROVIDER_TME_COUNTRY=DE
# Set this to 1 to get gross prices (including VAT) instead of net prices
PROVIDER_TME_GET_GROSS_PRICES=1
# Octopart / Nexar Provider:
# You can get your API key from https://nexar.com/api
PROVIDER_OCTOPART_CLIENT_ID=
PROVIDER_OCTOPART_SECRET=
# The currency and country to get prices for (you have to set both to get meaningful results)
# 3 letter ISO currency code (e.g. EUR, USD, GBP)
PROVIDER_OCTOPART_CURRENCY=EUR
# 2 letter ISO country code (e.g. DE, US, GB)
PROVIDER_OCTOPART_COUNTRY=DE
# The number of results to get from Octopart while searching (please note that this counts towards your API limits)
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'
# LCSC Provider:
# LCSC does not provide an offical API, so this used the API LCSC uses to render their webshop.
# LCSC did not intended the use of this API and it could break any time, so use it at your own risk.
# We dont require an API key for LCSC, just set this to 1 to enable LCSC support
PROVIDER_LCSC_ENABLED=0
# The currency to get prices in (e.g. EUR, USD, etc.)
PROVIDER_LCSC_CURRENCY=EUR
# Oemsecrets Provider API 3.0.1:
# You can get your API key from https://www.oemsecrets.com/api
PROVIDER_OEMSECRETS_KEY=
# The country you want the output for
PROVIDER_OEMSECRETS_COUNTRY_CODE=DE
# Available country code are:
# AD, AE, AQ, AR, AT, AU, BE, BO, BR, BV, BY, CA, CH, CL, CN, CO, CZ, DE, DK, EC, EE, EH,
# ES, FI, FK, FO, FR, GB, GE, GF, GG, GI, GL, GR, GS, GY, HK, HM, HR, HU, IE, IM, IN, IS,
# IT, JM, JP, KP, KR, KZ, LI, LK, LT, LU, MC, MD, ME, MK, MT, NL, NO, NZ, PE, PH, PL, PT,
# PY, RO, RS, RU, SB, SD, SE, SG, SI, SJ, SK, SM, SO, SR, SY, SZ, TC, TF, TG, TH, TJ, TK,
# TM, TN, TO, TR, TT, TV, TW, TZ, UA, UG, UM, US, UY, UZ, VA, VE, VG, VI, VN, VU, WF, YE,
# ZA, ZM, ZW
#
# The currency you want the prices to be displayed in
PROVIDER_OEMSECRETS_CURRENCY=EUR
# Available currency are:AUD, CAD, CHF, CNY, DKK, EUR, GBP, HKD, ILS, INR, JPY, KRW, NOK,
# NZD, RUB, SEK, SGD, TWD, USD
#
# If PROVIDER_OEMSECRETS_ZERO_PRICE is set to 0, distributors with zero prices
# will be discarded from the creation of a new part (set to 1 otherwise)
PROVIDER_OEMSECRETS_ZERO_PRICE=0
#
# When PROVIDER_OEMSECRETS_SET_PARAM is set to 1 the parameters for the part are generated
# from the description transforming unstructured descriptions into structured parameters;
# each parameter in description should have the form: "...;name1:value1;name2:value2"
PROVIDER_OEMSECRETS_SET_PARAM=1
#
# This environment variable determines the sorting criteria for product results.
# The sorting process first arranges items based on the provided keyword.
# Then, if set to 'C', it further sorts by completeness (prioritizing items with the most
# detailed information). If set to 'M', it further sorts by manufacturer name.
#If unset or set to any other value, no sorting is performed.
PROVIDER_OEMSECRETS_SORT_CRITERIA=C
##################################################################################
# EDA integration related settings
##################################################################################
# This value determines the depth of the category tree, that is visible inside KiCad
# 0 means that only the top level categories are visible. Set to a value > 0 to show more levels.
# Set to -1, to show all parts of Part-DB inside a single category in KiCad
EDA_KICAD_CATEGORY_DEPTH=0
################################################################################### ###################################################################################
# SAML Single sign on-settings # SAML Single sign on-settings
@ -113,6 +276,16 @@ NO_URL_REWRITE_AVAILABLE=0
# Set to 1, if Part-DB should redirect all HTTP requests to HTTPS. You dont need to configure this, if your webserver already does this. # Set to 1, if Part-DB should redirect all HTTP requests to HTTPS. You dont need to configure this, if your webserver already does this.
REDIRECT_TO_HTTPS=0 REDIRECT_TO_HTTPS=0
# If you want to use fixer.io for currency conversion, you have to set this to your API key
FIXER_API_KEY=CHANGEME
# Override value if you want to show to show a given text on homepage.
# When this is empty the content of config/banner.md is used as banner
BANNER=""
APP_ENV=prod
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
# Set this to zero, if you want to disable the year 2038 bug check on 32-bit systems (it will cause errors with current 32-bit PHP versions) # Set this to zero, if you want to disable the year 2038 bug check on 32-bit systems (it will cause errors with current 32-bit PHP versions)
DISABLE_YEAR2038_BUG_CHECK=0 DISABLE_YEAR2038_BUG_CHECK=0
@ -130,8 +303,3 @@ LOCK_DSN=flock
###> nelmio/cors-bundle ### ###> nelmio/cors-bundle ###
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
###< nelmio/cors-bundle ### ###< nelmio/cors-bundle ###
###> symfony/framework-bundle ###
APP_ENV=prod
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
###< symfony/framework-bundle ###

View file

@ -1,4 +0,0 @@
###> symfony/framework-bundle ###
APP_SECRET=318b5d659e07a0b3f96d9b3a83b254ca
###< symfony/framework-bundle ###

View file

@ -11,5 +11,3 @@ DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"
# Disable update checks, as tests would fail, when github is not reachable # Disable update checks, as tests would fail, when github is not reachable
CHECK_FOR_UPDATES=0 CHECK_FOR_UPDATES=0
INSTANCE_NAME="Part-DB"

View file

@ -1,8 +1,5 @@
name: Build assets artifact name: Build assets artifact
permissions:
contents: read
on: on:
push: push:
branches: branches:
@ -22,7 +19,7 @@ jobs:
APP_ENV: prod APP_ENV: prod
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
@ -60,9 +57,9 @@ jobs:
${{ runner.os }}-yarn- ${{ runner.os }}-yarn-
- name: Setup node - name: Setup node
uses: actions/setup-node@v5 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '18'
- name: Install yarn dependencies - name: Install yarn dependencies
run: yarn install run: yarn install

View file

@ -1,8 +1,5 @@
name: Docker Image Build name: Docker Image Build
permissions:
contents: read
on: on:
#schedule: #schedule:
# - cron: '0 10 * * *' # everyday at 10am # - cron: '0 10 * * *' # everyday at 10am
@ -20,7 +17,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v4
- -
name: Docker meta name: Docker meta
id: docker_meta id: docker_meta

View file

@ -1,8 +1,5 @@
name: Docker Image Build (FrankenPHP) name: Docker Image Build (FrankenPHP)
permissions:
contents: read
on: on:
#schedule: #schedule:
# - cron: '0 10 * * *' # everyday at 10am # - cron: '0 10 * * *' # everyday at 10am
@ -20,7 +17,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v4
- -
name: Docker meta name: Docker meta
id: docker_meta id: docker_meta

View file

@ -1,8 +1,5 @@
name: Static analysis name: Static analysis
permissions:
contents: read
on: on:
push: push:
branches: branches:
@ -19,7 +16,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2

View file

@ -1,8 +1,5 @@
name: PHPUnit Tests name: PHPUnit Tests
permissions:
contents: read
on: on:
push: push:
branches: branches:
@ -21,7 +18,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: ['8.2', '8.3', '8.4', '8.5' ] php-versions: [ '8.1', '8.2', '8.3', '8.4' ]
db-type: [ 'mysql', 'sqlite', 'postgres' ] db-type: [ 'mysql', 'sqlite', 'postgres' ]
env: env:
@ -46,7 +43,7 @@ jobs:
if: matrix.db-type == 'postgres' if: matrix.db-type == 'postgres'
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v4
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
@ -104,9 +101,9 @@ jobs:
run: composer install --prefer-dist --no-progress run: composer install --prefer-dist --no-progress
- name: Setup node - name: Setup node
uses: actions/setup-node@v5 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '18'
- name: Install yarn dependencies - name: Install yarn dependencies
run: yarn install run: yarn install

6
.gitignore vendored
View file

@ -2,7 +2,6 @@
/.env.local /.env.local
/.env.local.php /.env.local.php
/.env.*.local /.env.*.local
/.env.local.bak
/config/secrets/prod/prod.decrypt.private.php /config/secrets/prod/prod.decrypt.private.php
/public/bundles/ /public/bundles/
/var/ /var/
@ -42,12 +41,9 @@ yarn-error.log
###> phpunit/phpunit ### ###> phpunit/phpunit ###
/phpunit.xml /phpunit.xml
/.phpunit.cache/ .phpunit.result.cache
###< phpunit/phpunit ### ###< phpunit/phpunit ###
###> phpstan/phpstan ### ###> phpstan/phpstan ###
phpstan.neon phpstan.neon
###< phpstan/phpstan ### ###< phpstan/phpstan ###
.claude/
CLAUDE.md

View file

@ -1,5 +1,5 @@
ARG BASE_IMAGE=debian:bookworm-slim ARG BASE_IMAGE=debian:bookworm-slim
ARG PHP_VERSION=8.4 ARG PHP_VERSION=8.3
FROM ${BASE_IMAGE} AS base FROM ${BASE_IMAGE} AS base
ARG PHP_VERSION ARG PHP_VERSION
@ -48,7 +48,7 @@ RUN apt-get update && apt-get -y install \
# Install node and yarn # Install node and yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
curl -sL https://deb.nodesource.com/setup_22.x | bash - && \ curl -sL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get update && apt-get install -y \ apt-get update && apt-get install -y \
nodejs \ nodejs \
yarn \ yarn \
@ -119,12 +119,12 @@ realpath_cache_size=4096K
realpath_cache_ttl=600 realpath_cache_ttl=600
EOF EOF
# Increase upload limit and enable preloading (disabled for now, as it does not seem to work properly, and require prod env anyway) # Increase upload limit and enable preloading
COPY <<EOF /etc/php/${PHP_VERSION}/fpm/conf.d/partdb.ini COPY <<EOF /etc/php/${PHP_VERSION}/fpm/conf.d/partdb.ini
upload_max_filesize=256M upload_max_filesize=256M
post_max_size=300M post_max_size=300M
;opcache.preload_user=www-data opcache.preload_user=www-data
;opcache.preload=/var/www/html/config/preload.php opcache.preload=/var/www/html/config/preload.php
log_limit=8096 log_limit=8096
EOF EOF

View file

@ -1,4 +1,4 @@
FROM dunglas/frankenphp:1-php8.4 AS frankenphp_upstream FROM dunglas/frankenphp:1-php8.3 AS frankenphp_upstream
RUN apt-get update && apt-get -y install \ RUN apt-get update && apt-get -y install \
curl \ curl \
@ -13,33 +13,13 @@ RUN apt-get update && apt-get -y install \
zip \ zip \
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*; && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*;
RUN set -eux; \ # Install node and yarn
# Prepare keyrings directory RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
mkdir -p /etc/apt/keyrings; \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
\ curl -sL https://deb.nodesource.com/setup_20.x | bash - && \
# Import Yarn GPG key apt-get update && apt-get install -y \
curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg \ nodejs yarn \
| tee /etc/apt/keyrings/yarn.gpg >/dev/null; \ && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
chmod 644 /etc/apt/keyrings/yarn.gpg; \
\
# Add Yarn repo with signed-by
echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian stable main" \
| tee /etc/apt/sources.list.d/yarn.list; \
\
# Run NodeSource setup script (unchanged)
curl -sL https://deb.nodesource.com/setup_22.x | bash -; \
\
# Install Node.js + Yarn
apt-get update; \
apt-get install -y --no-install-recommends \
nodejs \
yarn; \
\
# Cleanup
apt-get -y autoremove; \
apt-get clean autoclean; \
rm -rf /var/lib/apt/lists/*
# Install PHP # Install PHP
RUN set -eux; \ RUN set -eux; \

View file

@ -1,91 +0,0 @@
# PartDB Makefile for Test Environment Management
.PHONY: help deps-install lint format format-check test coverage pre-commit all test-typecheck \
test-setup test-clean test-db-create test-db-migrate test-cache-clear test-fixtures test-run test-reset \
section-dev dev-setup dev-clean dev-db-create dev-db-migrate dev-cache-clear dev-warmup dev-reset
# Default target
help: ## Show this help
@awk 'BEGIN {FS = ":.*##"}; /^[a-zA-Z0-9][a-zA-Z0-9_-]+:.*##/ {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
# Dependencies
deps-install: ## Install PHP dependencies with unlimited memory
@echo "📦 Installing PHP dependencies..."
COMPOSER_MEMORY_LIMIT=-1 composer install
yarn install
@echo "✅ Dependencies installed"
# Complete test environment setup
test-setup: test-clean test-db-create test-db-migrate test-fixtures ## Complete test setup (clean, create DB, migrate, fixtures)
@echo "✅ Test environment setup complete!"
# Clean test environment
test-clean: ## Clean test cache and database files
@echo "🧹 Cleaning test environment..."
rm -rf var/cache/test
rm -f var/app_test.db
@echo "✅ Test environment cleaned"
# Create test database
test-db-create: ## Create test database (if not exists)
@echo "🗄️ Creating test database..."
-php bin/console doctrine:database:create --if-not-exists --env test || echo "⚠️ Database creation failed (expected for SQLite) - continuing..."
# Run database migrations for test environment
test-db-migrate: ## Run database migrations for test environment
@echo "🔄 Running database migrations..."
COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env test
# Clear test cache
test-cache-clear: ## Clear test cache
@echo "🗑️ Clearing test cache..."
rm -rf var/cache/test
@echo "✅ Test cache cleared"
# Load test fixtures
test-fixtures: ## Load test fixtures
@echo "📦 Loading test fixtures..."
php bin/console partdb:fixtures:load -n --env test
# Run PHPUnit tests
test-run: ## Run PHPUnit tests
@echo "🧪 Running tests..."
php bin/phpunit
# Quick test reset (clean + migrate + fixtures, skip DB creation)
test-reset: test-cache-clear test-db-migrate test-fixtures
@echo "✅ Test environment reset complete!"
test-typecheck: ## Run static analysis (PHPStan)
@echo "🧪 Running type checks..."
COMPOSER_MEMORY_LIMIT=-1 composer phpstan
# Development helpers
dev-setup: dev-clean dev-db-create dev-db-migrate dev-warmup ## Complete development setup (clean, create DB, migrate, warmup)
@echo "✅ Development environment setup complete!"
dev-clean: ## Clean development cache and database files
@echo "🧹 Cleaning development environment..."
rm -rf var/cache/dev
rm -f var/app_dev.db
@echo "✅ Development environment cleaned"
dev-db-create: ## Create development database (if not exists)
@echo "🗄️ Creating development database..."
-php bin/console doctrine:database:create --if-not-exists --env dev || echo "⚠️ Database creation failed (expected for SQLite) - continuing..."
dev-db-migrate: ## Run database migrations for development environment
@echo "🔄 Running database migrations..."
COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env dev
dev-cache-clear: ## Clear development cache
@echo "🗑️ Clearing development cache..."
rm -rf var/cache/dev
@echo "✅ Development cache cleared"
dev-warmup: ## Warm up development cache
@echo "🔥 Warming up development cache..."
COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G bin/console cache:warmup --env dev -n
dev-reset: dev-cache-clear dev-db-migrate ## Quick development reset (cache clear + migrate)
@echo "✅ Development environment reset complete!"

View file

@ -3,7 +3,7 @@
![Static analysis](https://github.com/Part-DB/Part-DB-symfony/workflows/Static%20analysis/badge.svg) ![Static analysis](https://github.com/Part-DB/Part-DB-symfony/workflows/Static%20analysis/badge.svg)
[![codecov](https://codecov.io/gh/Part-DB/Part-DB-server/branch/master/graph/badge.svg)](https://codecov.io/gh/Part-DB/Part-DB-server) [![codecov](https://codecov.io/gh/Part-DB/Part-DB-server/branch/master/graph/badge.svg)](https://codecov.io/gh/Part-DB/Part-DB-server)
![GitHub License](https://img.shields.io/github/license/Part-DB/Part-DB-symfony) ![GitHub License](https://img.shields.io/github/license/Part-DB/Part-DB-symfony)
![PHP Version](https://img.shields.io/badge/PHP-%3E%3D%208.2-green) ![PHP Version](https://img.shields.io/badge/PHP-%3E%3D%208.1-green)
![Docker Pulls](https://img.shields.io/docker/pulls/jbtronics/part-db1) ![Docker Pulls](https://img.shields.io/docker/pulls/jbtronics/part-db1)
![Docker Build Status](https://github.com/Part-DB/Part-DB-symfony/workflows/Docker%20Image%20Build/badge.svg) ![Docker Build Status](https://github.com/Part-DB/Part-DB-symfony/workflows/Docker%20Image%20Build/badge.svg)
@ -75,10 +75,10 @@ Part-DB is also used by small companies and universities for managing their inve
* A **web server** (like Apache2 or nginx) that is capable of * A **web server** (like Apache2 or nginx) that is capable of
running [Symfony 6](https://symfony.com/doc/current/reference/requirements.html), running [Symfony 6](https://symfony.com/doc/current/reference/requirements.html),
this includes a minimum PHP version of **PHP 8.2** this includes a minimum PHP version of **PHP 8.1**
* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.4) database server, or **PostgreSQL** 10+ if you do not want to use SQLite. * A **MySQL** (at least 5.7) /**MariaDB** (at least 10.4) database server, or **PostgreSQL** 10+ if you do not want to use SQLite.
* Shell access to your server is highly recommended! * Shell access to your server is highly recommended!
* For building the client-side assets **yarn** and **nodejs** (>= 20.0) is needed. * For building the client-side assets **yarn** and **nodejs** (>= 18.0) is needed.
## Installation ## Installation

View file

@ -1 +1 @@
2.2.0 1.15.0

File diff suppressed because one or more lines are too long

View file

@ -1,63 +1,66 @@
import {ClassicEditor} from 'ckeditor5' /**
import {Alignment} from 'ckeditor5'; * @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved.
import {Autoformat} from 'ckeditor5'; * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
import {Base64UploadAdapter} from 'ckeditor5'; */
import {BlockQuote} from 'ckeditor5'; import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js';
import {Bold} from 'ckeditor5'; import Alignment from '@ckeditor/ckeditor5-alignment/src/alignment.js';
import {Code} from 'ckeditor5'; import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat.js';
import {CodeBlock} from 'ckeditor5'; import Base64UploadAdapter from '@ckeditor/ckeditor5-upload/src/adapters/base64uploadadapter.js';
import {Essentials} from 'ckeditor5'; import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote.js';
import {FindAndReplace} from 'ckeditor5'; import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js';
import {FontBackgroundColor} from 'ckeditor5'; import Code from '@ckeditor/ckeditor5-basic-styles/src/code.js';
import {FontColor} from 'ckeditor5'; import CodeBlock from '@ckeditor/ckeditor5-code-block/src/codeblock.js';
import {FontFamily} from 'ckeditor5'; import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials.js';
import {FontSize} from 'ckeditor5'; import FindAndReplace from '@ckeditor/ckeditor5-find-and-replace/src/findandreplace.js';
import {GeneralHtmlSupport} from 'ckeditor5'; import FontBackgroundColor from '@ckeditor/ckeditor5-font/src/fontbackgroundcolor.js';
import {Heading} from 'ckeditor5'; import FontColor from '@ckeditor/ckeditor5-font/src/fontcolor.js';
import {Highlight} from 'ckeditor5'; import FontFamily from '@ckeditor/ckeditor5-font/src/fontfamily.js';
import {HorizontalLine} from 'ckeditor5'; import FontSize from '@ckeditor/ckeditor5-font/src/fontsize.js';
import {HtmlComment} from 'ckeditor5'; import GeneralHtmlSupport from '@ckeditor/ckeditor5-html-support/src/generalhtmlsupport.js';
import {HtmlEmbed} from 'ckeditor5'; import Heading from '@ckeditor/ckeditor5-heading/src/heading.js';
import {Image} from 'ckeditor5'; import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight.js';
import {ImageResize} from 'ckeditor5'; import HorizontalLine from '@ckeditor/ckeditor5-horizontal-line/src/horizontalline.js';
import {ImageStyle} from 'ckeditor5'; import HtmlComment from '@ckeditor/ckeditor5-html-support/src/htmlcomment.js';
import {ImageToolbar} from 'ckeditor5'; import HtmlEmbed from '@ckeditor/ckeditor5-html-embed/src/htmlembed.js';
import {ImageUpload} from 'ckeditor5'; import Image from '@ckeditor/ckeditor5-image/src/image.js';
import {Indent} from 'ckeditor5'; import ImageResize from '@ckeditor/ckeditor5-image/src/imageresize.js';
import {IndentBlock} from 'ckeditor5'; import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle.js';
import {Italic} from 'ckeditor5'; import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar.js';
import {Link} from 'ckeditor5'; import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload.js';
import {LinkImage} from 'ckeditor5'; import Indent from '@ckeditor/ckeditor5-indent/src/indent.js';
import {List} from 'ckeditor5'; import IndentBlock from '@ckeditor/ckeditor5-indent/src/indentblock.js';
import {ListProperties} from 'ckeditor5'; import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic.js';
import {Markdown} from 'ckeditor5'; import Link from '@ckeditor/ckeditor5-link/src/link.js';
import {MediaEmbed} from 'ckeditor5'; import LinkImage from '@ckeditor/ckeditor5-link/src/linkimage.js';
import {MediaEmbedToolbar} from 'ckeditor5'; import List from '@ckeditor/ckeditor5-list/src/list.js';
import {Paragraph} from 'ckeditor5'; import ListProperties from '@ckeditor/ckeditor5-list/src/listproperties.js';
import {PasteFromOffice} from 'ckeditor5'; import Markdown from '@ckeditor/ckeditor5-markdown-gfm/src/markdown.js';
import {RemoveFormat} from 'ckeditor5'; import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed.js';
import {SourceEditing} from 'ckeditor5'; import MediaEmbedToolbar from '@ckeditor/ckeditor5-media-embed/src/mediaembedtoolbar.js';
import {SpecialCharacters} from 'ckeditor5'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph.js';
import {SpecialCharactersArrows} from 'ckeditor5'; import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice.js';
import {SpecialCharactersCurrency} from 'ckeditor5'; import RemoveFormat from '@ckeditor/ckeditor5-remove-format/src/removeformat.js';
import {SpecialCharactersEssentials} from 'ckeditor5'; import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting.js';
import {SpecialCharactersLatin} from 'ckeditor5'; import SpecialCharacters from '@ckeditor/ckeditor5-special-characters/src/specialcharacters.js';
import {SpecialCharactersMathematical} from 'ckeditor5'; import SpecialCharactersArrows from '@ckeditor/ckeditor5-special-characters/src/specialcharactersarrows.js';
import {SpecialCharactersText} from 'ckeditor5'; import SpecialCharactersCurrency from '@ckeditor/ckeditor5-special-characters/src/specialcharacterscurrency.js';
import {Strikethrough} from 'ckeditor5'; import SpecialCharactersEssentials from '@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials.js';
import {Subscript} from 'ckeditor5'; import SpecialCharactersLatin from '@ckeditor/ckeditor5-special-characters/src/specialcharacterslatin.js';
import {Superscript} from 'ckeditor5'; import SpecialCharactersMathematical from '@ckeditor/ckeditor5-special-characters/src/specialcharactersmathematical.js';
import {Table} from 'ckeditor5'; import SpecialCharactersText from '@ckeditor/ckeditor5-special-characters/src/specialcharacterstext.js';
import {TableCaption} from 'ckeditor5'; import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough.js';
import {TableCellProperties} from 'ckeditor5'; import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js';
import {TableColumnResize} from 'ckeditor5'; import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js';
import {TableProperties} from 'ckeditor5'; import Table from '@ckeditor/ckeditor5-table/src/table.js';
import {TableToolbar} from 'ckeditor5'; import TableCaption from '@ckeditor/ckeditor5-table/src/tablecaption.js';
import {Underline} from 'ckeditor5'; import TableCellProperties from '@ckeditor/ckeditor5-table/src/tablecellproperties';
import {WordCount} from 'ckeditor5'; import TableColumnResize from '@ckeditor/ckeditor5-table/src/tablecolumnresize.js';
import {EditorWatchdog} from 'ckeditor5'; import TableProperties from '@ckeditor/ckeditor5-table/src/tableproperties';
import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar.js';
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline.js';
import WordCount from '@ckeditor/ckeditor5-word-count/src/wordcount.js';
import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog.js';
import PartDBLabel from "./plugins/PartDBLabel/PartDBLabel"; import PartDBLabel from "./plugins/PartDBLabel/PartDBLabel";
import SpecialCharactersGreek from "./plugins/special_characters_emoji";
class Editor extends ClassicEditor {} class Editor extends ClassicEditor {}
@ -119,8 +122,7 @@ Editor.builtinPlugins = [
Underline, Underline,
WordCount, WordCount,
PartDBLabel, PartDBLabel
SpecialCharactersGreek
]; ];
// Editor configuration. // Editor configuration.

View file

@ -2,69 +2,68 @@
* @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved. * @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/ */
import {ClassicEditor} from 'ckeditor5'; import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js';
import {Alignment} from 'ckeditor5'; import Alignment from '@ckeditor/ckeditor5-alignment/src/alignment.js';
import {Autoformat} from 'ckeditor5'; import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat.js';
import {Base64UploadAdapter} from 'ckeditor5'; import Base64UploadAdapter from '@ckeditor/ckeditor5-upload/src/adapters/base64uploadadapter.js';
import {BlockQuote} from 'ckeditor5'; import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote.js';
import {Bold} from 'ckeditor5'; import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js';
import {Code} from 'ckeditor5'; import Code from '@ckeditor/ckeditor5-basic-styles/src/code.js';
import {CodeBlock} from 'ckeditor5'; import CodeBlock from '@ckeditor/ckeditor5-code-block/src/codeblock.js';
import {Essentials} from 'ckeditor5'; import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials.js';
import {FindAndReplace} from 'ckeditor5'; import FindAndReplace from '@ckeditor/ckeditor5-find-and-replace/src/findandreplace.js';
import {FontBackgroundColor} from 'ckeditor5'; import FontBackgroundColor from '@ckeditor/ckeditor5-font/src/fontbackgroundcolor.js';
import {FontColor} from 'ckeditor5'; import FontColor from '@ckeditor/ckeditor5-font/src/fontcolor.js';
import {FontFamily} from 'ckeditor5'; import FontFamily from '@ckeditor/ckeditor5-font/src/fontfamily.js';
import {FontSize} from 'ckeditor5'; import FontSize from '@ckeditor/ckeditor5-font/src/fontsize.js';
import {GeneralHtmlSupport} from 'ckeditor5'; import GeneralHtmlSupport from '@ckeditor/ckeditor5-html-support/src/generalhtmlsupport.js';
import {Heading} from 'ckeditor5'; import Heading from '@ckeditor/ckeditor5-heading/src/heading.js';
import {Highlight} from 'ckeditor5'; import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight.js';
import {HorizontalLine} from 'ckeditor5'; import HorizontalLine from '@ckeditor/ckeditor5-horizontal-line/src/horizontalline.js';
import {HtmlComment} from 'ckeditor5'; import HtmlComment from '@ckeditor/ckeditor5-html-support/src/htmlcomment.js';
import {HtmlEmbed} from 'ckeditor5'; import HtmlEmbed from '@ckeditor/ckeditor5-html-embed/src/htmlembed.js';
import {Image} from 'ckeditor5'; import Image from '@ckeditor/ckeditor5-image/src/image.js';
import {ImageResize} from 'ckeditor5'; import ImageResize from '@ckeditor/ckeditor5-image/src/imageresize.js';
import {ImageStyle} from 'ckeditor5'; import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle.js';
import {ImageToolbar} from 'ckeditor5'; import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar.js';
import {ImageUpload} from 'ckeditor5'; import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload.js';
import {Indent} from 'ckeditor5'; import Indent from '@ckeditor/ckeditor5-indent/src/indent.js';
import {IndentBlock} from 'ckeditor5'; import IndentBlock from '@ckeditor/ckeditor5-indent/src/indentblock.js';
import {Italic} from 'ckeditor5'; import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic.js';
import {Link} from 'ckeditor5'; import Link from '@ckeditor/ckeditor5-link/src/link.js';
import {LinkImage} from 'ckeditor5'; import LinkImage from '@ckeditor/ckeditor5-link/src/linkimage.js';
import {List} from 'ckeditor5'; import List from '@ckeditor/ckeditor5-list/src/list.js';
import {ListProperties} from 'ckeditor5'; import ListProperties from '@ckeditor/ckeditor5-list/src/listproperties.js';
import {Markdown} from 'ckeditor5'; import Markdown from '@ckeditor/ckeditor5-markdown-gfm/src/markdown.js';
import {MediaEmbed} from 'ckeditor5'; import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed.js';
import {MediaEmbedToolbar} from 'ckeditor5'; import MediaEmbedToolbar from '@ckeditor/ckeditor5-media-embed/src/mediaembedtoolbar.js';
import {Paragraph} from 'ckeditor5'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph.js';
import {PasteFromOffice} from 'ckeditor5'; import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice.js';
import {RemoveFormat} from 'ckeditor5'; import RemoveFormat from '@ckeditor/ckeditor5-remove-format/src/removeformat.js';
import {SourceEditing} from 'ckeditor5'; import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting.js';
import {SpecialCharacters} from 'ckeditor5'; import SpecialCharacters from '@ckeditor/ckeditor5-special-characters/src/specialcharacters.js';
import {SpecialCharactersArrows} from 'ckeditor5'; import SpecialCharactersArrows from '@ckeditor/ckeditor5-special-characters/src/specialcharactersarrows.js';
import {SpecialCharactersCurrency} from 'ckeditor5'; import SpecialCharactersCurrency from '@ckeditor/ckeditor5-special-characters/src/specialcharacterscurrency.js';
import {SpecialCharactersEssentials} from 'ckeditor5'; import SpecialCharactersEssentials from '@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials.js';
import {SpecialCharactersLatin} from 'ckeditor5'; import SpecialCharactersLatin from '@ckeditor/ckeditor5-special-characters/src/specialcharacterslatin.js';
import {SpecialCharactersMathematical} from 'ckeditor5'; import SpecialCharactersMathematical from '@ckeditor/ckeditor5-special-characters/src/specialcharactersmathematical.js';
import {SpecialCharactersText} from 'ckeditor5'; import SpecialCharactersText from '@ckeditor/ckeditor5-special-characters/src/specialcharacterstext.js';
import {Strikethrough} from 'ckeditor5'; import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough.js';
import {Subscript} from 'ckeditor5'; import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js';
import {Superscript} from 'ckeditor5'; import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js';
import {Table} from 'ckeditor5'; import Table from '@ckeditor/ckeditor5-table/src/table.js';
import {TableCaption} from 'ckeditor5'; import TableCaption from '@ckeditor/ckeditor5-table/src/tablecaption.js';
import {TableCellProperties} from 'ckeditor5'; import TableCellProperties from '@ckeditor/ckeditor5-table/src/tablecellproperties';
import {TableColumnResize} from 'ckeditor5'; import TableColumnResize from '@ckeditor/ckeditor5-table/src/tablecolumnresize.js';
import {TableProperties} from 'ckeditor5'; import TableProperties from '@ckeditor/ckeditor5-table/src/tableproperties';
import {TableToolbar} from 'ckeditor5'; import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar.js';
import {Underline} from 'ckeditor5'; import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline.js';
import {WordCount} from 'ckeditor5'; import WordCount from '@ckeditor/ckeditor5-word-count/src/wordcount.js';
import {EditorWatchdog} from 'ckeditor5'; import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog.js';
import {TodoList} from 'ckeditor5'; import TodoList from '@ckeditor/ckeditor5-list/src/todolist';
import ExtendedMarkdown from "./plugins/extendedMarkdown.js"; import ExtendedMarkdown from "./plugins/extendedMarkdown.js";
import SpecialCharactersGreek from "./plugins/special_characters_emoji"; import SpecialCharactersEmoji from "./plugins/special_characters_emoji";
import {Mention, Emoji} from "ckeditor5";
class Editor extends ClassicEditor {} class Editor extends ClassicEditor {}
@ -118,11 +117,9 @@ Editor.builtinPlugins = [
Underline, Underline,
TodoList, TodoList,
Mention, Emoji,
//Our own extensions //Our own extensions
ExtendedMarkdown, ExtendedMarkdown,
SpecialCharactersGreek SpecialCharactersEmoji
]; ];
// Editor configuration. // Editor configuration.
@ -151,7 +148,6 @@ Editor.defaultConfig = {
'indent', 'indent',
'|', '|',
'specialCharacters', 'specialCharacters',
"emoji",
'horizontalLine', 'horizontalLine',
'|', '|',
'imageUpload', 'imageUpload',

View file

@ -2,36 +2,35 @@
* @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved. * @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/ */
import {ClassicEditor} from 'ckeditor5'; import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js';
import {Autoformat} from 'ckeditor5'; import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat.js';
import {AutoLink} from 'ckeditor5'; import AutoLink from '@ckeditor/ckeditor5-link/src/autolink.js';
import {Bold} from 'ckeditor5'; import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js';
import {Code} from 'ckeditor5'; import Code from '@ckeditor/ckeditor5-basic-styles/src/code.js';
import {Essentials} from 'ckeditor5'; import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials.js';
import {FindAndReplace} from 'ckeditor5'; import FindAndReplace from '@ckeditor/ckeditor5-find-and-replace/src/findandreplace.js';
import {Highlight} from 'ckeditor5'; import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight.js';
import {Italic} from 'ckeditor5'; import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic.js';
import {Link} from 'ckeditor5'; import Link from '@ckeditor/ckeditor5-link/src/link.js';
import {Paragraph} from 'ckeditor5'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph.js';
import {RemoveFormat} from 'ckeditor5'; import RemoveFormat from '@ckeditor/ckeditor5-remove-format/src/removeformat.js';
import {SourceEditing} from 'ckeditor5'; import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting.js';
import {SpecialCharacters} from 'ckeditor5'; import SpecialCharacters from '@ckeditor/ckeditor5-special-characters/src/specialcharacters.js';
import {SpecialCharactersArrows} from 'ckeditor5'; import SpecialCharactersArrows from '@ckeditor/ckeditor5-special-characters/src/specialcharactersarrows.js';
import {SpecialCharactersCurrency} from 'ckeditor5'; import SpecialCharactersCurrency from '@ckeditor/ckeditor5-special-characters/src/specialcharacterscurrency.js';
import {SpecialCharactersEssentials} from 'ckeditor5'; import SpecialCharactersEssentials from '@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials.js';
import {SpecialCharactersLatin} from 'ckeditor5'; import SpecialCharactersLatin from '@ckeditor/ckeditor5-special-characters/src/specialcharacterslatin.js';
import {SpecialCharactersMathematical} from 'ckeditor5'; import SpecialCharactersMathematical from '@ckeditor/ckeditor5-special-characters/src/specialcharactersmathematical.js';
import {SpecialCharactersText} from 'ckeditor5'; import SpecialCharactersText from '@ckeditor/ckeditor5-special-characters/src/specialcharacterstext.js';
import {Strikethrough} from 'ckeditor5'; import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough.js';
import {Subscript} from 'ckeditor5'; import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js';
import {Superscript} from 'ckeditor5'; import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js';
import {Underline} from 'ckeditor5'; import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline.js';
import {EditorWatchdog} from 'ckeditor5'; import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog.js';
import {Mention, Emoji} from "ckeditor5";
import ExtendedMarkdownInline from "./plugins/extendedMarkdownInline"; import ExtendedMarkdownInline from "./plugins/extendedMarkdownInline";
import SingleLinePlugin from "./plugins/singleLine"; import SingleLinePlugin from "./plugins/singleLine";
import SpecialCharactersGreek from "./plugins/special_characters_emoji"; import SpecialCharactersEmoji from "./plugins/special_characters_emoji";
class Editor extends ClassicEditor {} class Editor extends ClassicEditor {}
@ -63,8 +62,7 @@ Editor.builtinPlugins = [
ExtendedMarkdownInline, ExtendedMarkdownInline,
SingleLinePlugin, SingleLinePlugin,
SpecialCharactersGreek, SpecialCharactersEmoji
Mention, Emoji
]; ];
// Editor configuration. // Editor configuration.
@ -83,7 +81,6 @@ Editor.defaultConfig = {
'link', 'link',
'code', 'code',
'specialCharacters', 'specialCharacters',
'emoji',
'|', '|',
'undo', 'undo',
'redo', 'redo',

View file

@ -22,7 +22,7 @@ import PartDBLabelEditing from "./PartDBLabelEditing";
import "./PartDBLabel.css"; import "./PartDBLabel.css";
import {Plugin} from "ckeditor5"; import Plugin from "@ckeditor/ckeditor5-core/src/plugin";
export default class PartDBLabel extends Plugin { export default class PartDBLabel extends Plugin {
static get requires() { static get requires() {

View file

@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Command} from 'ckeditor5'; import Command from '@ckeditor/ckeditor5-core/src/command';
export default class PartDBLabelCommand extends Command { export default class PartDBLabelCommand extends Command {
execute( { value } ) { execute( { value } ) {

View file

@ -17,11 +17,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Plugin} from 'ckeditor5'; import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import PartDBLabelCommand from "./PartDBLabelCommand"; import PartDBLabelCommand from "./PartDBLabelCommand";
import { toWidget } from 'ckeditor5'; import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
import {Widget} from 'ckeditor5'; import Widget from '@ckeditor/ckeditor5-widget/src/widget';
export default class PartDBLabelEditing extends Plugin { export default class PartDBLabelEditing extends Plugin {
static get requires() { // ADDED static get requires() { // ADDED

View file

@ -17,14 +17,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Plugin} from 'ckeditor5'; import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
require('./lang/de.js'); require('./lang/de.js');
import { addListToDropdown, createDropdown } from 'ckeditor5'; import { addListToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
import {Collection} from 'ckeditor5'; import Collection from '@ckeditor/ckeditor5-utils/src/collection';
import {Model} from 'ckeditor5'; import Model from '@ckeditor/ckeditor5-ui/src/model';
export default class PartDBLabelUI extends Plugin { export default class PartDBLabelUI extends Plugin {
init() { init() {
@ -128,8 +128,6 @@ const PLACEHOLDERS = [
['[[BARCODE_QR]]', 'QR code linking to this element'], ['[[BARCODE_QR]]', 'QR code linking to this element'],
['[[BARCODE_C128]]', 'Code 128 barcode linking to this element'], ['[[BARCODE_C128]]', 'Code 128 barcode linking to this element'],
['[[BARCODE_C39]]', 'Code 39 barcode linking to this element'], ['[[BARCODE_C39]]', 'Code 39 barcode linking to this element'],
['[[BARCODE_C93]]', 'Code 93 barcode linking to this element'],
['[[BARCODE_DATAMATRIX]]', 'Datamatrix code linking to this element'],
] ]
}, },
{ {

View file

@ -69,8 +69,6 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
'QR code linking to this element': 'QR Code verknüpft mit diesem Element', 'QR code linking to this element': 'QR Code verknüpft mit diesem Element',
'Code 128 barcode linking to this element': 'Code 128 Barcode verknüpft mit diesem Element', 'Code 128 barcode linking to this element': 'Code 128 Barcode verknüpft mit diesem Element',
'Code 39 barcode linking to this element': 'Code 39 Barcode verknüpft mit diesem Element', 'Code 39 barcode linking to this element': 'Code 39 Barcode verknüpft mit diesem Element',
'Code 93 barcode linking to this element': 'Code 93 Barcode verknüpft mit diesem Element',
'Datamatrix code linking to this element': 'Datamatrix Code verknüpft mit diesem Element',
'Location ID': 'Lagerort ID', 'Location ID': 'Lagerort ID',
'Name': 'Name', 'Name': 'Name',

View file

@ -17,7 +17,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Plugin, MarkdownGfmDataProcessor } from 'ckeditor5'; import { Plugin } from 'ckeditor5/src/core';
import GFMDataProcessor from '@ckeditor/ckeditor5-markdown-gfm/src/gfmdataprocessor';
const ALLOWED_TAGS = [ const ALLOWED_TAGS = [
//Common elements //Common elements
@ -33,6 +34,7 @@ const ALLOWED_TAGS = [
//Block elements //Block elements
'span', 'span',
'p',
'img', 'img',
@ -55,7 +57,7 @@ export default class ExtendedMarkdown extends Plugin {
constructor( editor ) { constructor( editor ) {
super( editor ); super( editor );
editor.data.processor = new MarkdownGfmDataProcessor( editor.data.viewDocument ); editor.data.processor = new GFMDataProcessor( editor.data.viewDocument );
for (const tag of ALLOWED_TAGS) { for (const tag of ALLOWED_TAGS) {
editor.data.processor.keepHtml(tag); editor.data.processor.keepHtml(tag);
} }

View file

@ -17,8 +17,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Plugin} from 'ckeditor5'; import { Plugin } from 'ckeditor5/src/core';
import {MarkdownGfmDataProcessor} from 'ckeditor5'; import GFMDataProcessor from '@ckeditor/ckeditor5-markdown-gfm/src/gfmdataprocessor';
const ALLOWED_TAGS = [ const ALLOWED_TAGS = [
//Common elements //Common elements
@ -46,7 +46,7 @@ export default class ExtendedMarkdownInline extends Plugin {
constructor( editor ) { constructor( editor ) {
super( editor ); super( editor );
editor.data.processor = new MarkdownGfmDataProcessor( editor.data.viewDocument ); editor.data.processor = new GFMDataProcessor( editor.data.viewDocument );
for (const tag of ALLOWED_TAGS) { for (const tag of ALLOWED_TAGS) {
editor.data.processor.keepHtml(tag); editor.data.processor.keepHtml(tag);
} }

View file

@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Plugin} from 'ckeditor5'; import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
export default class SingleLinePlugin extends Plugin { export default class SingleLinePlugin extends Plugin {
init() { init() {

View file

@ -17,12 +17,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import SpecialCharacters from 'ckeditor5'; import SpecialCharacters from '@ckeditor/ckeditor5-special-characters/src/specialcharacters';
import SpecialCharactersEssentials from 'ckeditor5'; import SpecialCharactersEssentials from '@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials';
import {Plugin} from 'ckeditor5'; import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
export default class SpecialCharactersGreek extends Plugin { const emoji = require('emoji.json');
export default class SpecialCharactersEmoji extends Plugin {
init() { init() {
const editor = this.editor; const editor = this.editor;
@ -30,6 +32,9 @@ export default class SpecialCharactersGreek extends Plugin {
//Add greek characters to special characters //Add greek characters to special characters
specialCharsPlugin.addItems('Greek', this.getGreek()); specialCharsPlugin.addItems('Greek', this.getGreek());
//Add Emojis to special characters
specialCharsPlugin.addItems('Emoji', this.getEmojis());
} }
getGreek() { getGreek() {
@ -91,4 +96,14 @@ export default class SpecialCharactersGreek extends Plugin {
{ title: 'san', character: 'Ϻ' }, { title: 'san', character: 'Ϻ' },
]; ];
} }
getEmojis() {
//Map our emoji data to the format the plugin expects
return emoji.map(emoji => {
return {
title: emoji.name,
character: emoji.char
};
});
}
} }

View file

@ -1,359 +0,0 @@
import { Controller } from "@hotwired/stimulus"
import { generateCsrfHeaders } from "./csrf_protection_controller"
export default class extends Controller {
static targets = ["progressBar", "progressText"]
static values = {
jobId: Number,
partId: Number,
researchUrl: String,
researchAllUrl: String,
markCompletedUrl: String,
markSkippedUrl: String,
markPendingUrl: String
}
connect() {
// Auto-refresh progress if job is in progress
if (this.hasProgressBarTarget) {
this.startProgressUpdates()
}
// Restore scroll position after page reload (if any)
this.restoreScrollPosition()
}
getHeaders() {
const headers = {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
// Add CSRF headers if available
const form = document.querySelector('form')
if (form) {
const csrfHeaders = generateCsrfHeaders(form)
Object.assign(headers, csrfHeaders)
}
return headers
}
async fetchWithErrorHandling(url, options = {}, timeout = 30000) {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
try {
const response = await fetch(url, {
...options,
headers: { ...this.getHeaders(), ...options.headers },
signal: controller.signal
})
clearTimeout(timeoutId)
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Server error (${response.status}): ${errorText}`)
}
return await response.json()
} catch (error) {
clearTimeout(timeoutId)
if (error.name === 'AbortError') {
throw new Error('Request timed out. Please try again.')
} else if (error.message.includes('Failed to fetch')) {
throw new Error('Network error. Please check your connection and try again.')
} else {
throw error
}
}
}
disconnect() {
if (this.progressInterval) {
clearInterval(this.progressInterval)
}
}
startProgressUpdates() {
// Progress updates are handled via page reload for better reliability
// No need for periodic updates since state changes trigger page refresh
}
restoreScrollPosition() {
const savedPosition = sessionStorage.getItem('bulkImportScrollPosition')
if (savedPosition) {
// Restore scroll position after a small delay to ensure page is fully loaded
setTimeout(() => {
window.scrollTo(0, parseInt(savedPosition))
// Clear the saved position so it doesn't interfere with normal navigation
sessionStorage.removeItem('bulkImportScrollPosition')
}, 100)
}
}
async markCompleted(event) {
const partId = event.currentTarget.dataset.partId
try {
const url = this.markCompletedUrlValue.replace('__PART_ID__', partId)
const data = await this.fetchWithErrorHandling(url, { method: 'POST' })
if (data.success) {
this.updateProgressDisplay(data)
this.markRowAsCompleted(partId)
if (data.job_completed) {
this.showJobCompletedMessage()
}
} else {
this.showErrorMessage(data.error || 'Failed to mark part as completed')
}
} catch (error) {
console.error('Error marking part as completed:', error)
this.showErrorMessage(error.message || 'Failed to mark part as completed')
}
}
async markSkipped(event) {
const partId = event.currentTarget.dataset.partId
const reason = prompt('Reason for skipping (optional):') || ''
try {
const url = this.markSkippedUrlValue.replace('__PART_ID__', partId)
const data = await this.fetchWithErrorHandling(url, {
method: 'POST',
body: JSON.stringify({ reason })
})
if (data.success) {
this.updateProgressDisplay(data)
this.markRowAsSkipped(partId)
} else {
this.showErrorMessage(data.error || 'Failed to mark part as skipped')
}
} catch (error) {
console.error('Error marking part as skipped:', error)
this.showErrorMessage(error.message || 'Failed to mark part as skipped')
}
}
async markPending(event) {
const partId = event.currentTarget.dataset.partId
try {
const url = this.markPendingUrlValue.replace('__PART_ID__', partId)
const data = await this.fetchWithErrorHandling(url, { method: 'POST' })
if (data.success) {
this.updateProgressDisplay(data)
this.markRowAsPending(partId)
} else {
this.showErrorMessage(data.error || 'Failed to mark part as pending')
}
} catch (error) {
console.error('Error marking part as pending:', error)
this.showErrorMessage(error.message || 'Failed to mark part as pending')
}
}
updateProgressDisplay(data) {
if (this.hasProgressBarTarget) {
this.progressBarTarget.style.width = `${data.progress}%`
this.progressBarTarget.setAttribute('aria-valuenow', data.progress)
}
if (this.hasProgressTextTarget) {
this.progressTextTarget.textContent = `${data.completed_count} / ${data.total_count} completed`
}
}
markRowAsCompleted(partId) {
// Save scroll position and refresh page to show updated state
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
window.location.reload()
}
markRowAsSkipped(partId) {
// Save scroll position and refresh page to show updated state
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
window.location.reload()
}
markRowAsPending(partId) {
// Save scroll position and refresh page to show updated state
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
window.location.reload()
}
showJobCompletedMessage() {
const alert = document.createElement('div')
alert.className = 'alert alert-success alert-dismissible fade show'
alert.innerHTML = `
<i class="fas fa-check-circle"></i>
Job completed! All parts have been processed.
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`
const container = document.querySelector('.card-body')
container.insertBefore(alert, container.firstChild)
}
async researchPart(event) {
event.preventDefault()
event.stopPropagation()
const partId = event.currentTarget.dataset.partId
const spinner = event.currentTarget.querySelector(`[data-research-spinner="${partId}"]`)
const button = event.currentTarget
// Show loading state
if (spinner) {
spinner.style.display = 'inline-block'
}
button.disabled = true
try {
const url = this.researchUrlValue.replace('__PART_ID__', partId)
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 30000) // 30 second timeout
const response = await fetch(url, {
method: 'POST',
headers: this.getHeaders(),
signal: controller.signal
})
clearTimeout(timeoutId)
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Server error (${response.status}): ${errorText}`)
}
const data = await response.json()
if (data.success) {
this.showSuccessMessage(`Research completed for part. Found ${data.results_count} results.`)
// Save scroll position and reload to show updated results
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
window.location.reload()
} else {
this.showErrorMessage(data.error || 'Research failed')
}
} catch (error) {
console.error('Error researching part:', error)
if (error.name === 'AbortError') {
this.showErrorMessage('Research timed out. Please try again.')
} else if (error.message.includes('Failed to fetch')) {
this.showErrorMessage('Network error. Please check your connection and try again.')
} else {
this.showErrorMessage(error.message || 'Research failed due to an unexpected error')
}
} finally {
// Hide loading state
if (spinner) {
spinner.style.display = 'none'
}
button.disabled = false
}
}
async researchAllParts(event) {
event.preventDefault()
event.stopPropagation()
const spinner = document.getElementById('research-all-spinner')
const button = event.currentTarget
// Show loading state
if (spinner) {
spinner.style.display = 'inline-block'
}
button.disabled = true
try {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 120000) // 2 minute timeout for bulk operations
const response = await fetch(this.researchAllUrlValue, {
method: 'POST',
headers: this.getHeaders(),
signal: controller.signal
})
clearTimeout(timeoutId)
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Server error (${response.status}): ${errorText}`)
}
const data = await response.json()
if (data.success) {
this.showSuccessMessage(`Research completed for ${data.researched_count} parts.`)
// Save scroll position and reload to show updated results
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
window.location.reload()
} else {
this.showErrorMessage(data.error || 'Bulk research failed')
}
} catch (error) {
console.error('Error researching all parts:', error)
if (error.name === 'AbortError') {
this.showErrorMessage('Bulk research timed out. This may happen with large batches. Please try again or process smaller batches.')
} else if (error.message.includes('Failed to fetch')) {
this.showErrorMessage('Network error. Please check your connection and try again.')
} else {
this.showErrorMessage(error.message || 'Bulk research failed due to an unexpected error')
}
} finally {
// Hide loading state
if (spinner) {
spinner.style.display = 'none'
}
button.disabled = false
}
}
showSuccessMessage(message) {
this.showToast('success', message)
}
showErrorMessage(message) {
this.showToast('error', message)
}
showToast(type, message) {
// Create a simple alert that doesn't disrupt layout
const alertId = 'alert-' + Date.now()
const iconClass = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-triangle'
const alertClass = type === 'success' ? 'alert-success' : 'alert-danger'
const alertHTML = `
<div class="alert ${alertClass} alert-dismissible fade show position-fixed"
style="top: 20px; right: 20px; z-index: 9999; max-width: 400px;"
id="${alertId}">
<i class="fas ${iconClass} me-2"></i>
${message}
<button type="button" class="btn-close" onclick="this.parentElement.remove()" aria-label="Close"></button>
</div>
`
// Add alert to body
document.body.insertAdjacentHTML('beforeend', alertHTML)
// Auto-remove after 5 seconds
setTimeout(() => {
const alertElement = document.getElementById(alertId)
if (alertElement) {
alertElement.remove()
}
}, 5000)
}
}

View file

@ -1,92 +0,0 @@
import { Controller } from "@hotwired/stimulus"
import { generateCsrfHeaders } from "./csrf_protection_controller"
export default class extends Controller {
static values = {
deleteUrl: String,
stopUrl: String,
deleteConfirmMessage: String,
stopConfirmMessage: String
}
connect() {
// Controller initialized
}
getHeaders() {
const headers = {
'X-Requested-With': 'XMLHttpRequest'
}
// Add CSRF headers if available
const form = document.querySelector('form')
if (form) {
const csrfHeaders = generateCsrfHeaders(form)
Object.assign(headers, csrfHeaders)
}
return headers
}
async deleteJob(event) {
const jobId = event.currentTarget.dataset.jobId
const confirmMessage = this.deleteConfirmMessageValue || 'Are you sure you want to delete this job?'
if (confirm(confirmMessage)) {
try {
const deleteUrl = this.deleteUrlValue.replace('__JOB_ID__', jobId)
const response = await fetch(deleteUrl, {
method: 'DELETE',
headers: this.getHeaders()
})
if (!response.ok) {
const errorText = await response.text()
throw new Error(`HTTP ${response.status}: ${errorText}`)
}
const data = await response.json()
if (data.success) {
location.reload()
} else {
alert('Error deleting job: ' + (data.error || 'Unknown error'))
}
} catch (error) {
console.error('Error deleting job:', error)
alert('Error deleting job: ' + error.message)
}
}
}
async stopJob(event) {
const jobId = event.currentTarget.dataset.jobId
const confirmMessage = this.stopConfirmMessageValue || 'Are you sure you want to stop this job?'
if (confirm(confirmMessage)) {
try {
const stopUrl = this.stopUrlValue.replace('__JOB_ID__', jobId)
const response = await fetch(stopUrl, {
method: 'POST',
headers: this.getHeaders()
})
if (!response.ok) {
const errorText = await response.text()
throw new Error(`HTTP ${response.status}: ${errorText}`)
}
const data = await response.json()
if (data.success) {
location.reload()
} else {
alert('Error stopping job: ' + (data.error || 'Unknown error'))
}
} catch (error) {
console.error('Error stopping job:', error)
alert('Error stopping job: ' + error.message)
}
}
}
}

View file

@ -56,16 +56,12 @@ export default class MarkdownController extends Controller {
this.element.innerHTML = DOMPurify.sanitize(MarkdownController._marked.parse(this.unescapeHTML(raw))); this.element.innerHTML = DOMPurify.sanitize(MarkdownController._marked.parse(this.unescapeHTML(raw)));
for(let a of this.element.querySelectorAll('a')) { for(let a of this.element.querySelectorAll('a')) {
// test if link is absolute //Mark all links as external
var r = new RegExp('^(?:[a-z+]+:)?//', 'i'); a.classList.add('link-external');
if (r.test(a.getAttribute('href'))) { //Open links in new tag
//Mark all links as external a.setAttribute('target', '_blank');
a.classList.add('link-external'); //Dont track
//Open links in new tag a.setAttribute('rel', 'noopener');
a.setAttribute('target', '_blank');
//Dont track
a.setAttribute('rel', 'noopener');
}
} }
//Apply bootstrap styles to tables //Apply bootstrap styles to tables

View file

@ -1,79 +0,0 @@
const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/;
const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/;
// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager
document.addEventListener('submit', function (event) {
generateCsrfToken(event.target);
}, true);
// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie
// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked
document.addEventListener('turbo:submit-start', function (event) {
const h = generateCsrfHeaders(event.detail.formSubmission.formElement);
Object.keys(h).map(function (k) {
event.detail.formSubmission.fetchRequest.headers[k] = h[k];
});
});
// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted
document.addEventListener('turbo:submit-end', function (event) {
removeCsrfToken(event.detail.formSubmission.formElement);
});
export function generateCsrfToken (formElement) {
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
if (!csrfField) {
return;
}
let csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
let csrfToken = csrfField.value;
if (!csrfCookie && nameCheck.test(csrfToken)) {
csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken);
csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
csrfField.dispatchEvent(new Event('change', { bubbles: true }));
}
if (csrfCookie && tokenCheck.test(csrfToken)) {
const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict';
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
}
export function generateCsrfHeaders (formElement) {
const headers = {};
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
if (!csrfField) {
return headers;
}
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
headers[csrfCookie] = csrfField.value;
}
return headers;
}
export function removeCsrfToken (formElement) {
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
if (!csrfField) {
return;
}
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
const cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0';
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
}
/* stimulusFetch: 'lazy' */
export default 'csrf-protection-controller';

View file

@ -23,12 +23,6 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
import '../../css/components/tom-select_extensions.css'; import '../../css/components/tom-select_extensions.css';
import TomSelect from "tom-select"; import TomSelect from "tom-select";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
export default class extends Controller { export default class extends Controller {
_tomSelect; _tomSelect;
@ -42,7 +36,6 @@ export default class extends Controller {
selectOnTab: true, selectOnTab: true,
//This a an ugly solution to disable the delimiter parsing of the TomSelect plugin //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING',
dropdownParent: 'body',
render: { render: {
item: (data, escape) => { item: (data, escape) => {
return '<span>' + escape(data.label) + '</span>'; return '<span>' + escape(data.label) + '</span>';
@ -53,12 +46,6 @@ export default class extends Controller {
} }
return '<div>' + escape(data.label) + '</div>'; return '<div>' + escape(data.label) + '</div>';
} }
},
plugins: {
'autoselect_typed': {},
'click_to_edit': {},
'clear_button': {},
"restore_on_backspace": {}
} }
}; };

View file

@ -23,9 +23,8 @@ import { default as FullEditor } from "../../ckeditor/markdown_full";
import { default as SingleLineEditor} from "../../ckeditor/markdown_single_line"; import { default as SingleLineEditor} from "../../ckeditor/markdown_single_line";
import { default as HTMLLabelEditor } from "../../ckeditor/html_label"; import { default as HTMLLabelEditor } from "../../ckeditor/html_label";
import {EditorWatchdog} from 'ckeditor5'; import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog';
import "ckeditor5/ckeditor5.css";;
import "../../css/components/ckeditor.css"; import "../../css/components/ckeditor.css";
/* stimulusFetch: 'lazy' */ /* stimulusFetch: 'lazy' */
@ -52,15 +51,9 @@ export default class extends Controller {
const language = document.body.dataset.locale ?? "en"; const language = document.body.dataset.locale ?? "en";
const emojiURL = new URL('../../ckeditor/emojis.json', import.meta.url).href;
const config = { const config = {
language: language, language: language,
licenseKey: "GPL", licenseKey: "GPL",
emoji: {
definitionsUrl: emojiURL
}
} }
const watchdog = new EditorWatchdog(); const watchdog = new EditorWatchdog();

View file

@ -45,10 +45,8 @@ export default class extends DatatablesController {
//Hide/Unhide panel with the selection tools //Hide/Unhide panel with the selection tools
if (count > 0) { if (count > 0) {
selectPanel.classList.remove('d-none'); selectPanel.classList.remove('d-none');
selectPanel.classList.add('sticky-select-bar');
} else { } else {
selectPanel.classList.add('d-none'); selectPanel.classList.add('d-none');
selectPanel.classList.remove('sticky-select-bar');
} }
//Update selection count text //Update selection count text

View file

@ -16,7 +16,6 @@ export default class extends Controller {
searchField: ["name", "description", "category", "footprint"], searchField: ["name", "description", "category", "footprint"],
valueField: "id", valueField: "id",
labelField: "name", labelField: "name",
dropdownParent: 'body',
preload: "focus", preload: "focus",
render: { render: {
item: (data, escape) => { item: (data, escape) => {

View file

@ -40,11 +40,9 @@ export default class extends Controller {
let settings = { let settings = {
plugins: ["clear_button"],
allowEmptyOption: true, allowEmptyOption: true,
selectOnTab: true, selectOnTab: true,
maxOptions: null, maxOptions: null,
dropdownParent: 'body',
render: { render: {
item: this.renderItem.bind(this), item: this.renderItem.bind(this),
@ -52,24 +50,7 @@ export default class extends Controller {
} }
}; };
//Load the drag_drop plugin if the select is ordered
if (this.element.dataset.orderedValue) {
settings.plugins.push('drag_drop');
settings.plugins.push("caret_position");
}
//If multiple items can be selected, enable the remove_button plugin
if (this.element.multiple) {
settings.plugins.push('remove_button');
}
this._tomSelect = new TomSelect(this.element, settings); this._tomSelect = new TomSelect(this.element, settings);
//If the select is ordered, we need to update the value field (with the decoded value from the orderedValue field)
if (this.element.dataset.orderedValue) {
const data = JSON.parse(this.element.dataset.orderedValue);
this._tomSelect.setValue(data);
}
} }
getTomSelect() { getTomSelect() {

View file

@ -20,8 +20,6 @@
import {Controller} from "@hotwired/stimulus"; import {Controller} from "@hotwired/stimulus";
import TomSelect from "tom-select"; import TomSelect from "tom-select";
// TODO: Merge with select_controller.js
export default class extends Controller { export default class extends Controller {
_tomSelect; _tomSelect;
@ -29,7 +27,6 @@ export default class extends Controller {
this._tomSelect = new TomSelect(this.element, { this._tomSelect = new TomSelect(this.element, {
maxItems: 1000, maxItems: 1000,
allowEmptyOption: true, allowEmptyOption: true,
dropdownParent: 'body',
plugins: ['remove_button'], plugins: ['remove_button'],
}); });
} }

View file

@ -23,12 +23,6 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
import '../../css/components/tom-select_extensions.css'; import '../../css/components/tom-select_extensions.css';
import TomSelect from "tom-select"; import TomSelect from "tom-select";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
/** /**
* This is the frontend controller for StaticFileAutocompleteType form element. * This is the frontend controller for StaticFileAutocompleteType form element.
* Basically it loads a text file from the given url (via data-url) and uses it as a source for the autocomplete. * Basically it loads a text file from the given url (via data-url) and uses it as a source for the autocomplete.
@ -50,16 +44,9 @@ export default class extends Controller {
valueField: 'text', valueField: 'text',
searchField: 'text', searchField: 'text',
orderField: 'text', orderField: 'text',
dropdownParent: 'body',
//This a an ugly solution to disable the delimiter parsing of the TomSelect plugin //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING'
plugins: {
'autoselect_typed': {},
'click_to_edit': {},
'clear_button': {},
'restore_on_backspace': {}
}
}; };
if (this.element.dataset.url) { if (this.element.dataset.url) {

View file

@ -24,9 +24,6 @@ import {Controller} from "@hotwired/stimulus";
import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js' import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
export default class extends Controller { export default class extends Controller {
_tomSelect; _tomSelect;
@ -40,21 +37,16 @@ export default class extends Controller {
const allowAdd = this.element.getAttribute("data-allow-add") === "true"; const allowAdd = this.element.getAttribute("data-allow-add") === "true";
const addHint = this.element.getAttribute("data-add-hint") ?? ""; const addHint = this.element.getAttribute("data-add-hint") ?? "";
let settings = { let settings = {
allowEmptyOption: true, allowEmptyOption: true,
selectOnTab: true, selectOnTab: true,
maxOptions: null, maxOptions: null,
create: allowAdd ? this.createItem.bind(this) : false, create: allowAdd ? this.createItem.bind(this) : false,
createFilter: this.createFilter.bind(this),
// This three options allow us to paste element names with commas: (see issue #538) // This three options allow us to paste element names with commas: (see issue #538)
maxItems: 1, maxItems: 1,
delimiter: "$$VERY_LONG_DELIMITER_THAT_SHOULD_NEVER_APPEAR$$", delimiter: "$$VERY_LONG_DELIMITER_THAT_SHOULD_NEVER_APPEAR$$",
splitOn: null, splitOn: null,
dropdownParent: 'body',
searchField: [ searchField: [
{field: "text", weight : 2}, {field: "text", weight : 2},
@ -89,17 +81,8 @@ export default class extends Controller {
//Add callbacks to update validity //Add callbacks to update validity
onInitialize: this.updateValidity.bind(this), onInitialize: this.updateValidity.bind(this),
onChange: this.updateValidity.bind(this), onChange: this.updateValidity.bind(this),
plugins: {
"autoselect_typed": {},
}
}; };
//Add clear button plugin, if an empty option is present
if (this.element.querySelector("option[value='']") !== null) {
settings.plugins["clear_button"] = {};
}
this._tomSelect = new TomSelect(this.element, settings); this._tomSelect = new TomSelect(this.element, settings);
//Do not do a sync here as this breaks the initial rendering of the empty option //Do not do a sync here as this breaks the initial rendering of the empty option
//this._tomSelect.sync(); //this._tomSelect.sync();
@ -130,31 +113,6 @@ export default class extends Controller {
}); });
} }
createFilter(input) {
//Normalize the input (replace spacing around arrows)
if (input.includes("->")) {
const inputs = input.split("->");
inputs.forEach((value, index) => {
inputs[index] = value.trim();
});
input = inputs.join("->");
} else {
input = input.trim();
}
const options = this._tomSelect.options;
//Iterate over all options and check if the input is already present
for (let index in options) {
const option = options[index];
if (option.path === input) {
return false;
}
}
return true;
}
updateValidity() { updateValidity() {
//Mark this input as invalid, if the selected option is disabled //Mark this input as invalid, if the selected option is disabled

View file

@ -23,27 +23,19 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
import '../../css/components/tom-select_extensions.css'; import '../../css/components/tom-select_extensions.css';
import TomSelect from "tom-select"; import TomSelect from "tom-select";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
export default class extends Controller { export default class extends Controller {
_tomSelect; _tomSelect;
connect() { connect() {
let settings = { let settings = {
plugins: { plugins: {
remove_button:{}, remove_button:{
'autoselect_typed': {}, }
'click_to_edit': {},
}, },
persistent: false, persistent: false,
selectOnTab: true, selectOnTab: true,
createOnBlur: true, createOnBlur: true,
create: true, create: true,
dropdownParent: 'body',
}; };
if(this.element.dataset.autocomplete) { if(this.element.dataset.autocomplete) {

View file

@ -1,136 +0,0 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["tbody", "addButton", "submitButton"]
static values = {
mappingIndex: Number,
maxMappings: Number,
prototype: String,
maxMappingsReachedMessage: String
}
connect() {
this.updateAddButtonState()
this.updateFieldOptions()
this.attachEventListeners()
}
attachEventListeners() {
// Add event listeners to existing field selects
const fieldSelects = this.tbodyTarget.querySelectorAll('select[name*="[field]"]')
fieldSelects.forEach(select => {
select.addEventListener('change', this.updateFieldOptions.bind(this))
})
// Note: Add button click is handled by Stimulus action in template (data-action="click->field-mapping#addMapping")
// No manual event listener needed
// Form submit handler
const form = this.element.querySelector('form')
if (form && this.hasSubmitButtonTarget) {
form.addEventListener('submit', this.handleFormSubmit.bind(this))
}
}
addMapping() {
const currentMappings = this.tbodyTarget.querySelectorAll('.mapping-row').length
if (currentMappings >= this.maxMappingsValue) {
alert(this.maxMappingsReachedMessageValue)
return
}
const newRowHtml = this.prototypeValue.replace(/__name__/g, this.mappingIndexValue)
const tempDiv = document.createElement('div')
tempDiv.innerHTML = newRowHtml
const fieldWidget = tempDiv.querySelector('select[name*="[field]"]') || tempDiv.children[0]
const providerWidget = tempDiv.querySelector('select[name*="[providers]"]') || tempDiv.children[1]
const priorityWidget = tempDiv.querySelector('input[name*="[priority]"]') || tempDiv.children[2]
const newRow = document.createElement('tr')
newRow.className = 'mapping-row'
newRow.innerHTML = `
<td>${fieldWidget ? fieldWidget.outerHTML : ''}</td>
<td>${providerWidget ? providerWidget.outerHTML : ''}</td>
<td>${priorityWidget ? priorityWidget.outerHTML : ''}</td>
<td>
<button type="button" class="btn btn-danger btn-sm" data-action="click->field-mapping#removeMapping">
<i class="fas fa-trash"></i>
</button>
</td>
`
this.tbodyTarget.appendChild(newRow)
this.mappingIndexValue++
const newFieldSelect = newRow.querySelector('select[name*="[field]"]')
if (newFieldSelect) {
newFieldSelect.value = ''
newFieldSelect.addEventListener('change', this.updateFieldOptions.bind(this))
}
this.updateFieldOptions()
this.updateAddButtonState()
}
removeMapping(event) {
const row = event.target.closest('tr')
row.remove()
this.updateFieldOptions()
this.updateAddButtonState()
}
updateFieldOptions() {
const fieldSelects = this.tbodyTarget.querySelectorAll('select[name*="[field]"]')
const selectedFields = Array.from(fieldSelects)
.map(select => select.value)
.filter(value => value && value !== '')
fieldSelects.forEach(select => {
Array.from(select.options).forEach(option => {
const isCurrentValue = option.value === select.value
const isEmptyOption = !option.value || option.value === ''
const isAlreadySelected = selectedFields.includes(option.value)
if (!isEmptyOption && isAlreadySelected && !isCurrentValue) {
option.disabled = true
option.style.display = 'none'
} else {
option.disabled = false
option.style.display = ''
}
})
})
}
updateAddButtonState() {
const currentMappings = this.tbodyTarget.querySelectorAll('.mapping-row').length
if (this.hasAddButtonTarget) {
if (currentMappings >= this.maxMappingsValue) {
this.addButtonTarget.disabled = true
this.addButtonTarget.title = this.maxMappingsReachedMessageValue
} else {
this.addButtonTarget.disabled = false
this.addButtonTarget.title = ''
}
}
}
handleFormSubmit(event) {
if (this.hasSubmitButtonTarget) {
this.submitButtonTarget.disabled = true
// Disable the entire form to prevent changes during processing
const form = event.target
const formElements = form.querySelectorAll('input, select, textarea, button')
formElements.forEach(element => {
if (element !== this.submitButtonTarget) {
element.disabled = true
}
})
}
}
}

View file

@ -25,23 +25,9 @@ import "katex/dist/katex.css";
export default class extends Controller { export default class extends Controller {
static targets = ["input", "preview"]; static targets = ["input", "preview"];
static values = {
unit: {type: Boolean, default: false} //Render as upstanding (non-italic) text, useful for units
}
updatePreview() updatePreview()
{ {
let value = ""; katex.render(this.inputTarget.value, this.previewTarget, {
if (this.unitValue) {
//Escape percentage signs
value = this.inputTarget.value.replace(/%/g, '\\%');
value = "\\mathrm{" + value + "}";
} else {
value = this.inputTarget.value;
}
katex.render(value, this.previewTarget, {
throwOnError: false, throwOnError: false,
}); });
} }

View file

@ -22,13 +22,6 @@ import TomSelect from "tom-select";
import katex from "katex"; import katex from "katex";
import "katex/dist/katex.css"; import "katex/dist/katex.css";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
/* stimulusFetch: 'lazy' */ /* stimulusFetch: 'lazy' */
export default class extends Controller export default class extends Controller
{ {
@ -60,10 +53,7 @@ export default class extends Controller
connect() { connect() {
const settings = { const settings = {
plugins: { plugins: {
'autoselect_typed': {}, clear_button:{}
'click_to_edit': {},
'clear_button': {},
'restore_on_backspace': {}
}, },
persistent: false, persistent: false,
maxItems: 1, maxItems: 1,
@ -85,9 +75,7 @@ export default class extends Controller
tmp += '<span>' + katex.renderToString(data.symbol) + '</span>' tmp += '<span>' + katex.renderToString(data.symbol) + '</span>'
} }
if (data.unit) { if (data.unit) {
let unit = data.unit.replace(/%/g, '\\%'); tmp += '<span class="ms-2">' + katex.renderToString('[' + data.unit + ']') + '</span>'
unit = "\\mathrm{" + unit + "}";
tmp += '<span class="ms-2">' + katex.renderToString('[' + unit + ']') + '</span>'
} }

View file

@ -1,86 +0,0 @@
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 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';
import '../css/components/toggle_password.css';
export default class extends Controller {
static values = {
visibleLabel: { type: String, default: 'Show' },
visibleIcon: { type: String, default: 'Default' },
hiddenLabel: { type: String, default: 'Hide' },
hiddenIcon: { type: String, default: 'Default' },
buttonClasses: Array,
};
isDisplayed = false;
visibleIcon = `<svg xmlns="http://www.w3.org/2000/svg" class="toggle-password-icon" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
<path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd" />
</svg>`;
hiddenIcon = `<svg xmlns="http://www.w3.org/2000/svg" class="toggle-password-icon" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z" clip-rule="evenodd" />
<path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z" />
</svg>`;
connect() {
if (this.visibleIconValue !== 'Default') {
this.visibleIcon = this.visibleIconValue;
}
if (this.hiddenIconValue !== 'Default') {
this.hiddenIcon = this.hiddenIconValue;
}
const button = this.createButton();
this.element.insertAdjacentElement('afterend', button);
this.dispatchEvent('connect', { element: this.element, button });
}
/**
* @returns {HTMLButtonElement}
*/
createButton() {
const button = document.createElement('button');
button.type = 'button';
button.classList.add(...this.buttonClassesValue);
button.setAttribute('tabindex', '-1');
button.addEventListener('click', this.toggle.bind(this));
button.innerHTML = `${this.visibleIcon} ${this.visibleLabelValue}`;
return button;
}
/**
* Toggle input type between "text" or "password" and update label accordingly
*/
toggle(event) {
this.isDisplayed = !this.isDisplayed;
const toggleButtonElement = event.currentTarget;
toggleButtonElement.innerHTML = this.isDisplayed
? `${this.hiddenIcon} ${this.hiddenLabelValue}`
: `${this.visibleIcon} ${this.visibleLabelValue}`;
this.element.setAttribute('type', this.isDisplayed ? 'text' : 'password');
this.dispatchEvent(this.isDisplayed ? 'show' : 'hide', { element: this.element, button: toggleButtonElement });
}
dispatchEvent(name, payload) {
this.dispatch(name, { detail: payload, prefix: 'toggle-password' });
}
}

View file

@ -121,10 +121,3 @@ del {
background-color: #f09595; background-color: #f09595;
font-weight: bold; font-weight: bold;
} }
/****************************************
* Password toggle
****************************************/
.toggle-password-button {
top: 0.7rem !important;
}

View file

@ -112,10 +112,3 @@ ul.structural_link li a:hover {
background-color: var(--bs-success); background-color: var(--bs-success);
border-color: var(--bs-success); border-color: var(--bs-success);
} }
/***********************************************
* Katex rendering with same height as text
***********************************************/
.katex-same-height-as-text .katex {
font-size: 1.0em;
}

View file

@ -18,8 +18,8 @@
*/ */
.hoverpic { .hoverpic {
min-width: var(--table-image-preview-min-size, 20px); min-width: 10px;
max-width: var(--table-image-preview-max-size, 35px); max-width: 30px;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@ -49,7 +49,7 @@
} }
.part-table-image { .part-table-image {
max-height: calc(1.2*var(--table-image-preview-max-size, 35px)); /** Aspect ratio of maximum 1.2 */ max-height: 40px;
object-fit: contain; object-fit: contain;
} }

View file

@ -17,16 +17,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/****************************************
* Action bar
****************************************/
.sticky-select-bar {
position: sticky;
top: 120px;
z-index: 1000; /* Ensure the bar is above other content */
}
/**************************************** /****************************************
* Tables * Tables
****************************************/ ****************************************/
@ -94,11 +84,6 @@ th.select-checkbox {
display: inline-flex; display: inline-flex;
} }
/** Add spacing between column visibility button and length menu */
.buttons-colvis {
margin-right: 0.2em !important;
}
/** Fix datatables select-checkbox position */ /** Fix datatables select-checkbox position */
table.dataTable tr.selected td.select-checkbox:after table.dataTable tr.selected td.select-checkbox:after
{ {

View file

@ -71,8 +71,6 @@
--ck-color-button-on-hover-background: var(--bs-secondary-bg); --ck-color-button-on-hover-background: var(--bs-secondary-bg);
--ck-color-button-on-active-background: var(--bs-secondary-bg); --ck-color-button-on-active-background: var(--bs-secondary-bg);
--ck-color-button-on-disabled-background: var(--bs-secondary-bg); --ck-color-button-on-disabled-background: var(--bs-secondary-bg);
--ck-color-button-on-color: var(--bs-primary); --ck-color-button-on-color: var(--bs-primary)
--ck-content-font-color: var(--ck-color-base-text);
} }

View file

@ -1,41 +0,0 @@
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 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/>.
*/
.toggle-password-container {
position: relative;
}
.toggle-password-icon {
height: 1rem;
width: 1rem;
}
.toggle-password-button {
align-items: center;
background-color: transparent;
border: none;
column-gap: 0.25rem;
display: flex;
flex-direction: row;
font-size: 0.875rem;
justify-items: center;
height: 1rem;
line-height: 1.25rem;
position: absolute;
right: 0.5rem;
top: -1.25rem;
}

View file

@ -49,7 +49,7 @@ window.$ = window.jQuery = require("jquery");
//Use the local WASM file for the ZXing library //Use the local WASM file for the ZXing library
import { import {
setZXingModuleOverrides, setZXingModuleOverrides,
} from "barcode-detector/ponyfill"; } from "barcode-detector/pure";
import wasmFile from "../../node_modules/zxing-wasm/dist/reader/zxing_reader.wasm"; import wasmFile from "../../node_modules/zxing-wasm/dist/reader/zxing_reader.wasm";
setZXingModuleOverrides({ setZXingModuleOverrides({
locateFile: (path, prefix) => { locateFile: (path, prefix) => {

View file

@ -75,10 +75,11 @@
request._dt = config.name; request._dt = config.name;
//Try to resolve the original column index when the column was reordered (using the ColReorder plugin) //Try to resolve the original column index when the column was reordered (using the ColReorder plugin)
if (dt.colReorder && dt.colReorder.transpose) { //Only do this when _ColReorder_iOrigCol is available
if (settings.aoColumns && settings.aoColumns.length && settings.aoColumns[0]._ColReorder_iOrigCol !== undefined) {
if (request.order && request.order.length) { if (request.order && request.order.length) {
request.order.forEach(function (order) { request.order.forEach(function (order) {
order.column = dt.colReorder.transpose(order.column, "toOriginal"); order.column = settings.aoColumns[order.column]._ColReorder_iOrigCol;
}); });
} }
} }

View file

@ -1,63 +0,0 @@
/**
* Autoselect Typed plugin for Tomselect
*
* This plugin allows automatically selecting an option matching the typed text when the Tomselect element goes out of
* focus (is blurred) and/or when the delimiter is typed.
*
* #select_on_blur option
* Tomselect natively supports the "createOnBlur" option. This option picks up any remaining text in the input field
* and uses it to create a new option and selects that option. It does behave a bit strangely though, in that it will
* not select an already existing option when the input is blurred, so if you typed something that matches an option in
* the list and then click outside the box (without pressing enter) the entered text is just removed (unless you have
* allow duplicates on in which case it will create a new option).
* This plugin fixes that, such that Tomselect will first try to select an option matching the remaining uncommitted
* text and only when no matching option is found tries to create a new one (if createOnBlur and create is on)
*
* #select_on_delimiter option
* Normally when typing the delimiter (space by default) Tomselect will try to create a new option (and select it) (if
* create is on), but if the typed text matches an option (and allow duplicates is off) it refuses to react at all until
* you press enter. With this option, the delimiter will also allow selecting an option, not just creating it.
*/
function select_current_input(self){
if(self.isLocked){
return
}
const val = self.inputValue()
//Do nothing if the input is empty
if (!val) {
return
}
if (self.options[val]) {
self.addItem(val)
self.setTextboxValue()
}
}
export default function(plugin_options_) {
const plugin_options = Object.assign({
//Autoselect the typed text when the input element goes out of focus
select_on_blur: true,
//Autoselect the typed text when the delimiter is typed
select_on_delimiter: true,
}, plugin_options_);
const self = this
if(plugin_options.select_on_blur) {
this.hook("before", "onBlur", function () {
select_current_input(self)
})
}
if(plugin_options.select_on_delimiter) {
this.hook("before", "onKeyPress", function (e) {
const character = String.fromCharCode(e.keyCode || e.which);
if (self.settings.mode === 'multi' && character === self.settings.delimiter) {
select_current_input(self)
}
})
}
}

View file

@ -1,93 +0,0 @@
/**
* click_to_edit plugin for Tomselect
*
* This plugin allows editing (and selecting text in) any selected item by clicking it.
*
* Usually, when the user typed some text and created an item in Tomselect that item cannot be edited anymore. To make
* a change, the item has to be deleted and retyped completely. There is also generally no way to copy text out of a
* tomselect item. The "restore_on_backspace" plugin improves that somewhat, by allowing the user to edit an item after
* pressing backspace. However, it is somewhat confusing to first have to focus the field an then hit backspace in order
* to copy a piece of text. It may also not be immediately obvious for editing.
* This plugin transforms an item into editable text when it is clicked, e.g. when the user tries to place the caret
* within an item or when they try to drag across the text to highlight it.
* It also plays nice with the remove_button plugin which still removes (deselects) an option entirely.
*
* It is recommended to also enable the autoselect_typed plugin when using this plugin. Without it, the text in the
* input field (i.e. the item that was just clicked) is lost when the user clicks outside the field. Also, when the user
* clicks an option (making it text) and then tries to enter another one by entering the delimiter (e.g. space) nothing
* happens until enter is pressed or the text is changed from what it was.
*/
/**
* Return a dom element from either a dom query string, jQuery object, a dom element or html string
* https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
*
* param query should be {}
*/
const getDom = query => {
if (query.jquery) {
return query[0];
}
if (query instanceof HTMLElement) {
return query;
}
if (isHtmlString(query)) {
var tpl = document.createElement('template');
tpl.innerHTML = query.trim(); // Never return a text node of whitespace as the result
return tpl.content.firstChild;
}
return document.querySelector(query);
};
const isHtmlString = arg => {
if (typeof arg === 'string' && arg.indexOf('<') > -1) {
return true;
}
return false;
};
function plugin(plugin_options_) {
const self = this
const plugin_options = Object.assign({
//If there is unsubmitted text in the input field, should that text be automatically used to select a matching
//element? If this is off, clicking on item1 and then clicking on item2 will result in item1 being deselected
auto_select_before_edit: true,
//If there is unsubmitted text in the input field, should that text be automatically used to create a matching
//element if no matching element was found or auto_select_before_edit is off?
auto_create_before_edit: true,
//customize this function to change which text the item is replaced with when clicking on it
text: option => {
return option[self.settings.labelField];
}
}, plugin_options_);
self.hook('after', 'setupTemplates', () => {
const orig_render_item = self.settings.render.item;
self.settings.render.item = (data, escape) => {
const item = getDom(orig_render_item.call(self, data, escape));
item.addEventListener('click', evt => {
if (self.isLocked) {
return;
}
const val = self.inputValue();
if (self.options[val]) {
self.addItem(val)
} else if (self.settings.create) {
self.createItem();
}
const option = self.options[item.dataset.value]
self.setTextboxValue(plugin_options.text.call(self, option));
self.focus();
self.removeItem(item);
}
);
return item;
}
});
}
export { plugin as default };

View file

@ -3,7 +3,7 @@
"type": "project", "type": "project",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"require": { "require": {
"php": "^8.2", "php": "^8.1",
"ext-ctype": "*", "ext-ctype": "*",
"ext-dom": "*", "ext-dom": "*",
"ext-gd": "*", "ext-gd": "*",
@ -12,12 +12,10 @@
"ext-json": "*", "ext-json": "*",
"ext-mbstring": "*", "ext-mbstring": "*",
"amphp/http-client": "^5.1", "amphp/http-client": "^5.1",
"api-platform/doctrine-orm": "^4.1", "api-platform/core": "^3.1",
"api-platform/json-api": "^4.0.0",
"api-platform/symfony": "^4.0.0",
"beberlei/doctrineextensions": "^1.2", "beberlei/doctrineextensions": "^1.2",
"brick/math": "^0.13.1", "brick/math": "0.12.1 as 0.11.0",
"composer/ca-bundle": "^1.5", "composer/ca-bundle": "^1.3",
"composer/package-versions-deprecated": "^1.11.99.5", "composer/package-versions-deprecated": "^1.11.99.5",
"doctrine/data-fixtures": "^2.0.0", "doctrine/data-fixtures": "^2.0.0",
"doctrine/dbal": "^4.0.0", "doctrine/dbal": "^4.0.0",
@ -25,69 +23,63 @@
"doctrine/doctrine-migrations-bundle": "^3.0", "doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^3.2.0", "doctrine/orm": "^3.2.0",
"dompdf/dompdf": "^v3.0.0", "dompdf/dompdf": "^v3.0.0",
"erusev/parsedown": "^1.7",
"florianv/swap": "^4.0",
"florianv/swap-bundle": "dev-master",
"gregwar/captcha-bundle": "^2.1.0", "gregwar/captcha-bundle": "^2.1.0",
"hshn/base64-encoded-file": "^5.0", "hshn/base64-encoded-file": "^5.0",
"jbtronics/2fa-webauthn": "^3.0.0", "jbtronics/2fa-webauthn": "^v2.2.0",
"jbtronics/dompdf-font-loader-bundle": "^1.0.0", "jbtronics/dompdf-font-loader-bundle": "^1.0.0",
"jbtronics/settings-bundle": "^3.0.0",
"jfcherng/php-diff": "^6.14", "jfcherng/php-diff": "^6.14",
"knpuniversity/oauth2-client-bundle": "^2.15", "knpuniversity/oauth2-client-bundle": "^2.15",
"league/commonmark": "^2.7",
"league/csv": "^9.8.0", "league/csv": "^9.8.0",
"league/html-to-markdown": "^5.0.1", "league/html-to-markdown": "^5.0.1",
"liip/imagine-bundle": "^2.2", "liip/imagine-bundle": "^2.2",
"maennchen/zipstream-php": "2.1", "nbgrp/onelogin-saml-bundle": "^1.3",
"nbgrp/onelogin-saml-bundle": "^v2.0.2",
"nelexa/zip": "^4.0", "nelexa/zip": "^4.0",
"nelmio/cors-bundle": "^2.3", "nelmio/cors-bundle": "^2.3",
"nelmio/security-bundle": "^3.0", "nelmio/security-bundle": "^3.0",
"nyholm/psr7": "^1.1", "nyholm/psr7": "^1.1",
"omines/datatables-bundle": "^0.10.0", "omines/datatables-bundle": "^0.9.1",
"paragonie/sodium_compat": "^1.21", "paragonie/sodium_compat": "^1.21",
"part-db/label-fonts": "^1.0", "part-db/label-fonts": "^1.0",
"part-db/swap-bundle": "^6.0.0",
"phpoffice/phpspreadsheet": "^5.0.0",
"rhukster/dom-sanitizer": "^1.0",
"runtime/frankenphp-symfony": "^0.2.0", "runtime/frankenphp-symfony": "^0.2.0",
"s9e/text-formatter": "^2.1", "s9e/text-formatter": "^2.1",
"scheb/2fa-backup-code": "^v7.11.0", "scheb/2fa-backup-code": "^6.8.0",
"scheb/2fa-bundle": "^v7.11.0", "scheb/2fa-bundle": "^6.8.0",
"scheb/2fa-google-authenticator": "^v7.11.0", "scheb/2fa-google-authenticator": "^6.8.0",
"scheb/2fa-trusted-device": "^v7.11.0", "scheb/2fa-trusted-device": "^6.8.0",
"shivas/versioning-bundle": "^4.0", "shivas/versioning-bundle": "^4.0",
"spatie/db-dumper": "^3.3.1", "spatie/db-dumper": "^3.3.1",
"symfony/apache-pack": "^1.0", "symfony/apache-pack": "^1.0",
"symfony/asset": "7.3.*", "symfony/asset": "6.4.*",
"symfony/console": "7.3.*", "symfony/console": "6.4.*",
"symfony/css-selector": "7.3.*", "symfony/dotenv": "6.4.*",
"symfony/dom-crawler": "7.3.*", "symfony/expression-language": "6.4.*",
"symfony/dotenv": "7.3.*",
"symfony/expression-language": "7.3.*",
"symfony/flex": "^v2.3.1", "symfony/flex": "^v2.3.1",
"symfony/form": "7.3.*", "symfony/form": "6.4.*",
"symfony/framework-bundle": "7.3.*", "symfony/framework-bundle": "6.4.*",
"symfony/http-client": "7.3.*", "symfony/http-client": "6.4.*",
"symfony/http-kernel": "7.3.*", "symfony/http-kernel": "6.4.*",
"symfony/mailer": "7.3.*", "symfony/mailer": "6.4.*",
"symfony/monolog-bundle": "^3.1", "symfony/monolog-bundle": "^3.1",
"symfony/polyfill-php82": "^1.28", "symfony/polyfill-php82": "^1.28",
"symfony/process": "7.3.*", "symfony/process": "6.4.*",
"symfony/property-access": "7.3.*", "symfony/property-access": "6.4.*",
"symfony/property-info": "7.3.*", "symfony/property-info": "6.4.*",
"symfony/rate-limiter": "7.3.*", "symfony/rate-limiter": "6.4.*",
"symfony/runtime": "7.3.*", "symfony/runtime": "6.4.*",
"symfony/security-bundle": "7.3.*", "symfony/security-bundle": "6.4.*",
"symfony/serializer": "7.3.*", "symfony/serializer": "6.4.*",
"symfony/string": "7.3.*", "symfony/string": "6.4.*",
"symfony/translation": "7.3.*", "symfony/translation": "6.4.*",
"symfony/twig-bundle": "7.3.*", "symfony/twig-bundle": "6.4.*",
"symfony/ux-translator": "^2.10", "symfony/ux-translator": "^2.10",
"symfony/ux-turbo": "^2.0", "symfony/ux-turbo": "^2.0",
"symfony/validator": "7.3.*", "symfony/validator": "6.4.*",
"symfony/web-link": "7.3.*", "symfony/web-link": "6.4.*",
"symfony/webpack-encore-bundle": "^v2.0.1", "symfony/webpack-encore-bundle": "^v2.0.1",
"symfony/yaml": "7.3.*", "symfony/yaml": "6.4.*",
"symplify/easy-coding-standard": "^12.5.20",
"tecnickcom/tc-lib-barcode": "^2.1.4", "tecnickcom/tc-lib-barcode": "^2.1.4",
"twig/cssinliner-extra": "^3.0", "twig/cssinliner-extra": "^3.0",
"twig/extra-bundle": "^3.8", "twig/extra-bundle": "^3.8",
@ -96,7 +88,7 @@
"twig/intl-extra": "^3.8", "twig/intl-extra": "^3.8",
"twig/markdown-extra": "^3.8", "twig/markdown-extra": "^3.8",
"twig/string-extra": "^3.8", "twig/string-extra": "^3.8",
"web-auth/webauthn-symfony-bundle": "^5.0.0" "web-auth/webauthn-symfony-bundle": "^4.0.0"
}, },
"require-dev": { "require-dev": {
"dama/doctrine-test-bundle": "^v8.0.0", "dama/doctrine-test-bundle": "^v8.0.0",
@ -108,15 +100,17 @@
"phpstan/phpstan-doctrine": "^2.0.1", "phpstan/phpstan-doctrine": "^2.0.1",
"phpstan/phpstan-strict-rules": "^2.0.1", "phpstan/phpstan-strict-rules": "^2.0.1",
"phpstan/phpstan-symfony": "^2.0.0", "phpstan/phpstan-symfony": "^2.0.0",
"phpunit/phpunit": "^11.5.0", "phpunit/phpunit": "^9.5",
"rector/rector": "^2.0.4", "rector/rector": "^2.0.4",
"roave/security-advisories": "dev-latest", "roave/security-advisories": "dev-latest",
"symfony/browser-kit": "7.3.*", "symfony/browser-kit": "6.4.*",
"symfony/debug-bundle": "7.3.*", "symfony/css-selector": "6.4.*",
"symfony/debug-bundle": "6.4.*",
"symfony/maker-bundle": "^1.13", "symfony/maker-bundle": "^1.13",
"symfony/phpunit-bridge": "7.3.*", "symfony/phpunit-bridge": "6.4.*",
"symfony/stopwatch": "7.3.*", "symfony/stopwatch": "6.4.*",
"symfony/web-profiler-bundle": "7.3.*" "symfony/web-profiler-bundle": "6.4.*",
"symplify/easy-coding-standard": "^12.0"
}, },
"suggest": { "suggest": {
"ext-bcmath": "Used to improve price calculation performance", "ext-bcmath": "Used to improve price calculation performance",
@ -127,7 +121,7 @@
"*": "dist" "*": "dist"
}, },
"platform": { "platform": {
"php": "8.2.0" "php": "8.1.0"
}, },
"sort-packages": true, "sort-packages": true,
"allow-plugins": { "allow-plugins": {
@ -159,7 +153,7 @@
"post-update-cmd": [ "post-update-cmd": [
"@auto-scripts" "@auto-scripts"
], ],
"phpstan": "php -d memory_limit=1G vendor/bin/phpstan analyse src --level 5" "phpstan": "vendor/bin/phpstan analyse src --level 5 --memory-limit 1G"
}, },
"conflict": { "conflict": {
"symfony/symfony": "*" "symfony/symfony": "*"
@ -167,7 +161,7 @@
"extra": { "extra": {
"symfony": { "symfony": {
"allow-contrib": false, "allow-contrib": false,
"require": "7.3.*", "require": "6.4.*",
"docker": true "docker": true
} }
} }

8438
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,14 @@
**Attention**: Welcome to Part-DB.
Since Version 2.0.0 this file is no longer used.
You can now set the banner text directly in the admin interface, or by setting the `BANNER` environment variable. <small>If you want to change this banner, edit `config/banner.md` file or set the `BANNER` environment variable.</small>
<blockquote class="pb-0">
<p style="font-size: 12px">
And God said <br>
$\nabla \cdot \vec{D} = \rho$,
$\nabla \cdot \vec{B} = 0$,
$\nabla \times \vec{E} = -\frac{\partial \vec{B}}{\partial t}$,
$\nabla \times \vec{H} = \vec{j} + \frac{\partial \vec{D}}{\partial t}$, <br>
and then there was light.
</p>
</blockquote>

View file

@ -30,7 +30,6 @@ return [
Jbtronics\DompdfFontLoaderBundle\DompdfFontLoaderBundle::class => ['all' => true], Jbtronics\DompdfFontLoaderBundle\DompdfFontLoaderBundle::class => ['all' => true],
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true], KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
Jbtronics\SettingsBundle\JbtronicsSettingsBundle::class => ['all' => true],
Jbtronics\TranslationEditorBundle\JbtronicsTranslationEditorBundle::class => ['dev' => true],
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
Jbtronics\TranslationEditorBundle\JbtronicsTranslationEditorBundle::class => ['dev' => true],
]; ];

View file

@ -32,9 +32,6 @@ api_platform:
pagination_client_items_per_page: true # Allow clients to override the default items per page pagination_client_items_per_page: true # Allow clients to override the default items per page
keep_legacy_inflector: false
# Need to be true, or some tests will fail # Need to be true, or some tests will fail
use_symfony_listeners: true use_symfony_listeners: true
serializer:
# Change this to false later, to remove the hydra prefix on the API
hydra_prefix: true

View file

@ -1,12 +0,0 @@
# Enable stateless CSRF protection for forms and logins/logouts
framework:
form:
csrf_protection:
token_id: submit
csrf_protection:
check_header: true
stateless_token_ids:
- submit
- authenticate
- logout

View file

@ -8,9 +8,8 @@ datatables:
# Set options, as documented at https://datatables.net/reference/option/ # Set options, as documented at https://datatables.net/reference/option/
options: options:
lengthMenu : [[10, 25, 50, 100], [10, 25, 50, 100]] # We add the "All" option, when part tables are generated lengthMenu : [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]]
#pageLength: '%partdb.table.default_page_size%' # Set to -1 to disable pagination (i.e. show all rows) by default pageLength: '%partdb.table.default_page_size%' # Set to -1 to disable pagination (i.e. show all rows) by default
pageLength: 50 #TODO
dom: " <'row' <'col mb-2 input-group flex-nowrap' B l > <'col-auto mb-2' < p >>> dom: " <'row' <'col mb-2 input-group flex-nowrap' B l > <'col-auto mb-2' < p >>>
<'card' <'card'
rt rt
@ -18,7 +17,7 @@ datatables:
> >
<'row' <'col mt-2 input-group flex-nowrap' B l > <'col-auto mt-2' < p >>>" <'row' <'col mt-2 input-group flex-nowrap' B l > <'col-auto mt-2' < p >>>"
pagingType: 'simple_numbers' pagingType: 'simple_numbers'
searching: false searching: true
stateSave: true stateSave: true

View file

@ -1,33 +0,0 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 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);
/**
* This class extends the default doctrine ORM configuration to enable native lazy objects on PHP 8.4+.
* We have to do this in a PHP file, because the yaml file does not support conditionals on PHP version.
*/
return static function(\Symfony\Config\DoctrineConfig $doctrine) {
//On PHP 8.4+ we can use native lazy objects, which are much more efficient than proxies.
if (PHP_VERSION_ID >= 80400) {
$doctrine->orm()->enableNativeLazyObjects(true);
}
};

View file

@ -25,6 +25,10 @@ doctrine:
tinyint: tinyint:
class: App\Doctrine\Types\TinyIntType class: App\Doctrine\Types\TinyIntType
# This was removed in doctrine/orm 4.0 but we need it for the WebauthnKey entity
array:
class: App\Doctrine\Types\ArrayType
schema_filter: ~^(?!internal)~ schema_filter: ~^(?!internal)~
# Only enable this when needed # Only enable this when needed
profiling_collect_backtrace: false profiling_collect_backtrace: false
@ -35,8 +39,6 @@ doctrine:
report_fields_where_declared: true report_fields_where_declared: true
validate_xml_mapping: true validate_xml_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
identity_generation_preferences:
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
auto_mapping: true auto_mapping: true
controller_resolver: controller_resolver:
auto_mapping: true auto_mapping: true

View file

@ -1,6 +1,9 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html # see https://symfony.com/doc/current/reference/configuration/framework.html
framework: framework:
secret: '%env(APP_SECRET)%' secret: '%env(APP_SECRET)%'
csrf_protection: true
annotations: false
handle_all_throwables: true
# We set this header by ourselves, so we can disable it here # We set this header by ourselves, so we can disable it here
disallow_search_engine_index: false disallow_search_engine_index: false
@ -27,11 +30,8 @@ framework:
#esi: true #esi: true
#fragments: true #fragments: true
php_errors:
log: true
form: { csrf_protection: { token_id: 'submit' } }
csrf_protection:
stateless_token_ids: ['submit', 'authenticate', 'logout']
when@test: when@test:
framework: framework:

View file

@ -6,8 +6,8 @@ knpu_oauth2_client:
type: generic type: generic
provider_class: '\League\OAuth2\Client\Provider\GenericProvider' provider_class: '\League\OAuth2\Client\Provider\GenericProvider'
client_id: '%env(settings:digikey:clientId)%' client_id: '%env(PROVIDER_DIGIKEY_CLIENT_ID)%'
client_secret: '%env(settings:digikey:secret)%' client_secret: '%env(PROVIDER_DIGIKEY_SECRET)%'
redirect_route: 'oauth_client_check' redirect_route: 'oauth_client_check'
redirect_params: {name: 'ip_digikey_oauth'} redirect_params: {name: 'ip_digikey_oauth'}
@ -26,8 +26,8 @@ knpu_oauth2_client:
type: generic type: generic
provider_class: '\League\OAuth2\Client\Provider\GenericProvider' provider_class: '\League\OAuth2\Client\Provider\GenericProvider'
client_id: '%env(settings:octopart:clientId)%' client_id: '%env(PROVIDER_OCTOPART_CLIENT_ID)%'
client_secret: '%env(settings:octopart:secret)%' client_secret: '%env(PROVIDER_OCTOPART_SECRET)%'
redirect_route: 'oauth_client_check' redirect_route: 'oauth_client_check'
redirect_params: { name: 'ip_octopart_oauth' } redirect_params: { name: 'ip_octopart_oauth' }

View file

@ -69,7 +69,6 @@ when@docker:
excluded_http_codes: [404, 405] excluded_http_codes: [404, 405]
buffer_size: 50 # How many messages should be saved? Prevent memory leaks buffer_size: 50 # How many messages should be saved? Prevent memory leaks
include_stacktraces: true include_stacktraces: true
channels: ["!deprecation"]
nested: nested:
type: stream type: stream
path: "php://stderr" path: "php://stderr"

View file

@ -20,6 +20,12 @@ nelmio_security:
- 'digikey.com' - 'digikey.com'
- 'nexar.com' - 'nexar.com'
# forces Microsoft's XSS-Protection with
# its block mode
xss_protection:
enabled: true
mode_block: true
# Send a full URL in the `Referer` header when performing a same-origin request, # Send a full URL in the `Referer` header when performing a same-origin request,
# only send the origin of the document to secure destination (HTTPS->HTTPS), # only send the origin of the document to secure destination (HTTPS->HTTPS),
# and send no header to a less secure destination (HTTPS->HTTP). # and send no header to a less secure destination (HTTPS->HTTP).
@ -63,3 +69,9 @@ nelmio_security:
- 'data:' - 'data:'
block-all-mixed-content: true # defaults to false, blocks HTTP content over HTTPS transport block-all-mixed-content: true # defaults to false, blocks HTTP content over HTTPS transport
# upgrade-insecure-requests: true # defaults to false, upgrades HTTP requests to HTTPS transport # upgrade-insecure-requests: true # defaults to false, upgrades HTTP requests to HTTPS transport
when@dev:
# disables the Content-Security-Policy header
nelmio_security:
csp:
enabled: false

View file

@ -1,3 +0,0 @@
framework:
property_info:
with_constructor_extractor: true

View file

@ -1,5 +1,7 @@
framework: framework:
router: router:
utf8: true
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands. # Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
default_uri: '%env(DEFAULT_URI)%' default_uri: '%env(DEFAULT_URI)%'

View file

@ -13,7 +13,7 @@ security:
firewalls: firewalls:
dev: dev:
pattern: ^/(_(profiler|wdt)|css|images|js|\.well-known)/ pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false security: false
main: main:
provider: app_user_provider provider: app_user_provider

View file

@ -1,15 +0,0 @@
jbtronics_settings:
default_storage_adapter: Jbtronics\SettingsBundle\Storage\ORMStorageAdapter
cache:
default_cacheable: true
orm_storage:
default_entity_class: App\Entity\SettingsEntry
# Disable caching for development environment
when@dev:
jbtronics_settings:
cache:
default_cacheable: false

View file

@ -5,12 +5,6 @@ florianv_swap:
providers: providers:
european_central_bank: ~ # European Central Bank (only works for EUR base currency) european_central_bank: ~ # European Central Bank (only works for EUR base currency)
central_bank_of_czech_republic: ~ fixer: # Fixer.io (needs an API key)
central_bank_of_republic_turkey: ~ access_key: "%env(FIXER_API_KEY)%"
national_bank_of_romania: ~ #exchange_rates_api: ~
fixer: # Fixer.io (needs an API key)
access_key: "%env(string:settings:exchange_rate:fixerApiKey)%"
frankfurter: ~
fawazahmed_currency_api: ~

View file

@ -1,10 +1,11 @@
framework: framework:
default_locale: 'en' default_locale: '%partdb.locale%'
# Just enable the locales we need for performance reasons. # Just enable the locales we need for performance reasons.
enabled_locale: '%partdb.locale_menu%' enabled_locale: '%partdb.locale_menu%'
translator: translator:
default_path: '%kernel.project_dir%/translations' default_path: '%kernel.project_dir%/translations'
fallbacks: fallbacks:
- '%partdb.locale%'
- 'en' - 'en'
providers: providers:
# crowdin: # crowdin:

View file

@ -6,12 +6,16 @@ twig:
'%kernel.project_dir%/assets/css': css '%kernel.project_dir%/assets/css': css
globals: globals:
partdb_title: '%partdb.title%'
default_currency: '%partdb.default_currency%'
global_theme: '%partdb.global_theme%'
allow_email_pw_reset: '%partdb.users.email_pw_reset%' allow_email_pw_reset: '%partdb.users.email_pw_reset%'
locale_menu: '%partdb.locale_menu%' locale_menu: '%partdb.locale_menu%'
attachment_manager: '@App\Services\Attachments\AttachmentManager' attachment_manager: '@App\Services\Attachments\AttachmentManager'
label_profile_dropdown_helper: '@App\Services\LabelSystem\LabelProfileDropdownHelper' label_profile_dropdown_helper: '@App\Services\LabelSystem\LabelProfileDropdownHelper'
error_page_admin_email: '%partdb.error_pages.admin_email%' error_page_admin_email: '%partdb.error_pages.admin_email%'
error_page_show_help: '%partdb.error_pages.show_help%' error_page_show_help: '%partdb.error_pages.show_help%'
sidebar_items: '%partdb.sidebar.items%'
sidebar_tree_updater: '@App\Services\Trees\SidebarTreeUpdater' sidebar_tree_updater: '@App\Services\Trees\SidebarTreeUpdater'
avatar_helper: '@App\Services\UserSystem\UserAvatarHelper' avatar_helper: '@App\Services\UserSystem\UserAvatarHelper'
available_themes: '%partdb.available_themes%' available_themes: '%partdb.available_themes%'

4
config/packages/uid.yaml Normal file
View file

@ -0,0 +1,4 @@
framework:
uid:
default_uuid_version: 7
time_based_uuid_version: 7

View file

@ -1,4 +0,0 @@
# Enable stateless CSRF protection for forms and logins/logouts
framework:
csrf_protection:
check_header: true

View file

@ -1,5 +1,7 @@
framework: framework:
validation: validation:
email_validation_mode: html5
# Enables validator auto-mapping support. # Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata. # For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping: #auto_mapping:

View file

@ -1,14 +1,17 @@
when@dev: when@dev:
web_profiler: web_profiler:
toolbar: toolbar: true
ajax_replace: true intercept_redirects: false
framework: framework:
profiler: profiler:
only_exceptions: false
collect_serializer_data: true collect_serializer_data: true
when@test: when@test:
web_profiler:
toolbar: false
intercept_redirects: false
framework: framework:
profiler: profiler: { collect: false }
collect: false
collect_serializer_data: true

View file

@ -5,10 +5,14 @@ parameters:
###################################################################################################################### ######################################################################################################################
# Common # Common
###################################################################################################################### ######################################################################################################################
partdb.locale: '%env(string:DEFAULT_LANG)%' # The default language to use serverwide
# This is used as workaround for places where we can not access the settings directly (like the 2FA application names) partdb.timezone: '%env(string:DEFAULT_TIMEZONE)%' # The default timezone
partdb.title: '%env(string:settings:customization:instanceName)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage) partdb.title: '%env(trim:string:INSTANCE_NAME)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage)
partdb.banner: '%env(trim:string:BANNER)%' # The info text shown in the homepage, if empty config/banner.md is used
partdb.default_currency: '%env(string:BASE_CURRENCY)%' # The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country
partdb.global_theme: '' # The theme to use globally (see public/build/themes/ for choices, use name without .css). Set to '' for default bootstrap theme
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] # The languages that are shown in user drop down menu partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] # The languages that are shown in user drop down menu
partdb.enforce_change_comments_for: '%env(csv:ENFORCE_CHANGE_COMMENTS_FOR)%' # The actions for which a change comment is required (e.g. "part_edit", "part_create", etc.). If this is empty, change comments are not required at all.
partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails
@ -18,8 +22,11 @@ parameters:
# Users and Privacy # Users and Privacy
###################################################################################################################### ######################################################################################################################
partdb.gdpr_compliance: true # If this option is activated, IP addresses are anonymized to be GDPR compliant partdb.gdpr_compliance: true # If this option is activated, IP addresses are anonymized to be GDPR compliant
partdb.users.use_gravatar: '%env(bool:USE_GRAVATAR)%' # Set to false, if no Gravatar images should be used for user profiles.
partdb.users.email_pw_reset: '%env(bool:ALLOW_EMAIL_PW_RESET)%' # Config if users are able, to reset their password by email. By default this enabled, when a mail server is configured. partdb.users.email_pw_reset: '%env(bool:ALLOW_EMAIL_PW_RESET)%' # Config if users are able, to reset their password by email. By default this enabled, when a mail server is configured.
partdb.check_for_updates: '%env(bool:CHECK_FOR_UPDATES)' # Set to false, if Part-DB should not contact the GitHub API to check for updates
###################################################################################################################### ######################################################################################################################
# Mail settings # Mail settings
###################################################################################################################### ######################################################################################################################
@ -29,8 +36,11 @@ parameters:
###################################################################################################################### ######################################################################################################################
# Attachments and files # Attachments and files
###################################################################################################################### ######################################################################################################################
partdb.attachments.allow_downloads: '%env(bool:ALLOW_ATTACHMENT_DOWNLOADS)%' # Allow users to download attachments to server. Warning: This can be dangerous, because via that feature attackers maybe can access ressources on your intranet!
partdb.attachments.download_by_default: '%env(bool:ATTACHMENT_DOWNLOAD_BY_DEFAULT)%' # If this is set the 'download external files' checkbox is set by default for new attachments (only if allow_downloads is set to true)
partdb.attachments.dir.media: 'public/media/' # The folder where uploaded attachment files are saved (must be in public folder) partdb.attachments.dir.media: 'public/media/' # The folder where uploaded attachment files are saved (must be in public folder)
partdb.attachments.dir.secure: 'uploads/' # The folder where secured attachment files are saved (must not be in public/) partdb.attachments.dir.secure: 'uploads/' # The folder where secured attachment files are saved (must not be in public/)
partdb.attachments.max_file_size: '%env(string:MAX_ATTACHMENT_FILE_SIZE)%' # The maximum size of an attachment file (in bytes, you can use M for megabytes and G for gigabytes)
###################################################################################################################### ######################################################################################################################
# Error pages # Error pages
@ -43,6 +53,22 @@ parameters:
###################################################################################################################### ######################################################################################################################
partdb.saml.enabled: '%env(bool:SAML_ENABLED)%' # If this is set to true, SAML authentication is enabled partdb.saml.enabled: '%env(bool:SAML_ENABLED)%' # If this is set to true, SAML authentication is enabled
######################################################################################################################
# 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.parts.default_columns: '%env(trim:string:TABLE_PARTS_DEFAULT_COLUMNS)%' # The default columns in part tables and their order
######################################################################################################################
# Sidebar
######################################################################################################################
# You can configures the default shown tree items in the sidebar here. You can add or remove entries here, to change the number of trees in the sidebar. The possible entries are: categories, locations, footprints, manufacturers, suppliers, devices, tools
partdb.sidebar.items:
- categories
- devices
- tools
partdb.sidebar.root_expanded: true # If this is set to true, the root node of the sidebar is expanded by default
partdb.sidebar.root_node_enable: true # Put all entities below a root node in the sidebar
###################################################################################################################### ######################################################################################################################
# Miscellaneous # Miscellaneous
@ -84,18 +110,30 @@ parameters:
# Env default values # Env default values
###################################################################################################################### ######################################################################################################################
env(DEFAULT_LANG): 'en'
env(DEFAULT_TIMEZONE): 'Europe/Berlin'
env(INSTANCE_NAME): 'Part-DB'
env(BASE_CURRENCY): 'EUR'
env(USE_GRAVATAR): '0'
env(MAX_ATTACHMENT_FILE_SIZE): '100M'
env(REDIRECT_TO_HTTPS): 0 env(REDIRECT_TO_HTTPS): 0
env(ENFORCE_CHANGE_COMMENTS_FOR): ''
env(ERROR_PAGE_ADMIN_EMAIL): '' env(ERROR_PAGE_ADMIN_EMAIL): ''
env(ERROR_PAGE_SHOW_HELP): 1 env(ERROR_PAGE_SHOW_HELP): 1
env(DEMO_MODE): 0 env(DEMO_MODE): 0
env(BANNER): ''
env(EMAIL_SENDER_EMAIL): 'noreply@partdb.changeme' env(EMAIL_SENDER_EMAIL): 'noreply@partdb.changeme'
env(EMAIL_SENDER_NAME): 'Part-DB Mailer' env(EMAIL_SENDER_NAME): 'Part-DB Mailer'
env(ALLOW_EMAIL_PW_RESET): 0 env(ALLOW_EMAIL_PW_RESET): 0
env(TABLE_DEFAULT_PAGE_SIZE): 50
env(TRUSTED_PROXIES): '127.0.0.1' #By default trust only our own server env(TRUSTED_PROXIES): '127.0.0.1' #By default trust only our own server
env(TRUSTED_HOSTS): '' # Trust all host names by default env(TRUSTED_HOSTS): '' # Trust all host names by default
@ -103,10 +141,11 @@ parameters:
env(SAML_ROLE_MAPPING): '{}' env(SAML_ROLE_MAPPING): '{}'
env(DATABASE_EMULATE_NATURAL_SORT): 0 env(HISTORY_SAVE_CHANGED_DATA): 1
env(HISTORY_SAVE_CHANGED_FIELDS): 1
env(HISTORY_SAVE_REMOVED_DATA): 1
env(HISTORY_SAVE_NEW_DATA): 1
###################################################################################################################### env(EDA_KICAD_CATEGORY_DEPTH): 0
# Bulk Info Provider Import Configuration
###################################################################################################################### env(DATABASE_EMULATE_NATURAL_SORT): 0
partdb.bulk_import.batch_size: 20 # Number of parts to process in each batch during bulk operations
partdb.bulk_import.max_parts_per_operation: 1000 # Maximum number of parts allowed per bulk import operation

View file

@ -265,13 +265,17 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
# label: "perm.database.write_db_settings" # label: "perm.database.write_db_settings"
# alsoSet: ['read_db_settings', 'see_status'] # alsoSet: ['read_db_settings', 'see_status']
config: #config:
label: "perm.config" # label: "perm.config"
group: "system" # group: "system"
operations: # operations:
change_system_settings: # read_config:
label: "perm.config.change_system_settings" # label: "perm.config.read_config"
apiTokenRole: ROLE_API_ADMIN # edit_config:
# label: "perm.config.edit_config"
# alsoSet: 'read_config'
# server_info:
# label: "perm.config.server_info"
system: system:
label: "perm.system" label: "perm.system"
@ -359,10 +363,6 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
label: "perm.revert_elements" label: "perm.revert_elements"
alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles', 'delete_profiles'] alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles', 'delete_profiles']
apiTokenRole: ROLE_API_EDIT apiTokenRole: ROLE_API_EDIT
import:
label: "perm.import"
alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles' ]
apiTokenRole: ROLE_API_EDIT
api: api:
label: "perm.api" label: "perm.api"

View file

@ -1,4 +1,4 @@
when@dev: when@dev:
_errors: _errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.php' resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
prefix: /_error prefix: /_error

View file

@ -1,8 +1,8 @@
when@dev: when@dev:
web_profiler_wdt: web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.php' resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt prefix: /_wdt
web_profiler_profiler: web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.php' resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /_profiler prefix: /_profiler

View file

@ -17,6 +17,8 @@ services:
bool $gdpr_compliance: '%partdb.gdpr_compliance%' bool $gdpr_compliance: '%partdb.gdpr_compliance%'
bool $kernel_debug_enabled: '%kernel.debug%' bool $kernel_debug_enabled: '%kernel.debug%'
string $kernel_cache_dir: '%kernel.cache_dir%' string $kernel_cache_dir: '%kernel.cache_dir%'
string $partdb_title: '%partdb.title%'
string $base_currency: '%partdb.default_currency%'
_instanceof: _instanceof:
App\Services\LabelSystem\PlaceholderProviders\PlaceholderProviderInterface: App\Services\LabelSystem\PlaceholderProviders\PlaceholderProviderInterface:
@ -29,6 +31,10 @@ services:
# this creates a service per class whose id is the fully-qualified class name # this creates a service per class whose id is the fully-qualified class name
App\: App\:
resource: '../src/' resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
# controllers are imported separately to make sure services can be injected # controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class # as action arguments even if you don't extend any base controller class
@ -70,10 +76,28 @@ services:
# Only the event classes specified here are saved to DB (set to []) to log all events # Only the event classes specified here are saved to DB (set to []) to log all events
$whitelist: [] $whitelist: []
App\EventListener\LogSystem\EventLoggerListener:
arguments:
$save_changed_fields: '%env(bool:HISTORY_SAVE_CHANGED_FIELDS)%'
$save_changed_data: '%env(bool:HISTORY_SAVE_CHANGED_DATA)%'
$save_removed_data: '%env(bool:HISTORY_SAVE_REMOVED_DATA)%'
$save_new_data: '%env(bool:HISTORY_SAVE_NEW_DATA)%'
App\Form\AttachmentFormType:
arguments:
$allow_attachments_download: '%partdb.attachments.allow_downloads%'
$max_file_size: '%partdb.attachments.max_file_size%'
$download_by_default: '%partdb.attachments.download_by_default%'
App\Services\Attachments\AttachmentSubmitHandler: App\Services\Attachments\AttachmentSubmitHandler:
arguments: arguments:
$allow_attachments_downloads: '%partdb.attachments.allow_downloads%'
$mimeTypes: '@mime_types' $mimeTypes: '@mime_types'
$max_upload_size: '%partdb.attachments.max_file_size%'
App\Services\LogSystem\EventCommentNeededHelper:
arguments:
$enforce_change_comments_for: '%partdb.enforce_change_comments_for%'
#################################################################################################################### ####################################################################################################################
# Attachment system # Attachment system
@ -132,6 +156,29 @@ services:
tags: tags:
- { name: doctrine.orm.entity_listener } - { name: doctrine.orm.entity_listener }
####################################################################################################################
# Price system
####################################################################################################################
App\Command\Currencies\UpdateExchangeRatesCommand:
arguments:
$base_current: '%partdb.default_currency%'
App\Form\Type\CurrencyEntityType:
arguments:
$base_currency: '%partdb.default_currency%'
App\Services\Parts\PricedetailHelper:
arguments:
$base_currency: '%partdb.default_currency%'
App\Services\Formatters\MoneyFormatter:
arguments:
$base_currency: '%partdb.default_currency%'
App\Services\Tools\ExchangeRateUpdater:
arguments:
$base_currency: '%partdb.default_currency%'
################################################################################################################### ###################################################################################################################
# User system # User system
#################################################################################################################### ####################################################################################################################
@ -139,6 +186,10 @@ services:
arguments: arguments:
$demo_mode: '%partdb.demo_mode%' $demo_mode: '%partdb.demo_mode%'
App\EventSubscriber\UserSystem\SetUserTimezoneSubscriber:
arguments:
$default_timezone: '%partdb.timezone%'
App\Controller\SecurityController: App\Controller\SecurityController:
arguments: arguments:
$allow_email_pw_reset: '%partdb.users.email_pw_reset%' $allow_email_pw_reset: '%partdb.users.email_pw_reset%'
@ -152,6 +203,10 @@ services:
tags: tags:
- { name: 'translation.extractor', alias: 'permissionExtractor'} - { name: 'translation.extractor', alias: 'permissionExtractor'}
App\Services\UserSystem\UserAvatarHelper:
arguments:
$use_gravatar: '%partdb.users.use_gravatar%'
App\Form\Type\ThemeChoiceType: App\Form\Type\ThemeChoiceType:
arguments: arguments:
$available_themes: '%partdb.available_themes%' $available_themes: '%partdb.available_themes%'
@ -167,6 +222,9 @@ services:
#################################################################################################################### ####################################################################################################################
# Table settings # Table settings
#################################################################################################################### ####################################################################################################################
App\DataTables\PartsDataTable:
arguments:
$visible_columns: '%partdb.table.parts.default_columns%'
App\DataTables\Helpers\ColumnSortHelper: App\DataTables\Helpers\ColumnSortHelper:
shared: false # Service has a state so not share it between different tables shared: false # Service has a state so not share it between different tables
@ -188,6 +246,14 @@ services:
$fontDirectory: '%kernel.project_dir%/var/dompdf/fonts/' $fontDirectory: '%kernel.project_dir%/var/dompdf/fonts/'
$tmpDirectory: '%kernel.project_dir%/var/dompdf/tmp/' $tmpDirectory: '%kernel.project_dir%/var/dompdf/tmp/'
####################################################################################################################
# Trees
####################################################################################################################
App\Services\Trees\TreeViewGenerator:
arguments:
$rootNodeExpandedByDefault: '%partdb.sidebar.root_expanded%'
$rootNodeEnabled: '%partdb.sidebar.root_node_enable%'
#################################################################################################################### ####################################################################################################################
# Part info provider system # Part info provider system
#################################################################################################################### ####################################################################################################################
@ -195,12 +261,76 @@ services:
arguments: arguments:
$providers: !tagged_iterator 'app.info_provider' $providers: !tagged_iterator 'app.info_provider'
App\Services\InfoProviderSystem\Providers\Element14Provider:
arguments:
$api_key: '%env(string:PROVIDER_ELEMENT14_KEY)%'
$store_id: '%env(string:PROVIDER_ELEMENT14_STORE_ID)%'
App\Services\InfoProviderSystem\Providers\DigikeyProvider:
arguments:
$clientId: '%env(string:PROVIDER_DIGIKEY_CLIENT_ID)%'
$currency: '%env(string:PROVIDER_DIGIKEY_CURRENCY)%'
$language: '%env(string:PROVIDER_DIGIKEY_LANGUAGE)%'
$country: '%env(string:PROVIDER_DIGIKEY_COUNTRY)%'
App\Services\InfoProviderSystem\Providers\TMEClient:
arguments:
$secret: '%env(string:PROVIDER_TME_SECRET)%'
$token: '%env(string:PROVIDER_TME_KEY)%'
App\Services\InfoProviderSystem\Providers\TMEProvider:
arguments:
$currency: '%env(string:PROVIDER_TME_CURRENCY)%'
$country: '%env(string:PROVIDER_TME_COUNTRY)%'
$language: '%env(string:PROVIDER_TME_LANGUAGE)%'
$get_gross_prices: '%env(bool:PROVIDER_TME_GET_GROSS_PRICES)%'
App\Services\InfoProviderSystem\Providers\OctopartProvider:
arguments:
$clientId: '&env(string:PROVIDER_OCTOPART_CLIENT_ID)%'
$secret: '%env(string:PROVIDER_OCTOPART_SECRET)%'
$country: '%env(string:PROVIDER_OCTOPART_COUNTRY)%'
$currency: '%env(string:PROVIDER_OCTOPART_CURRENCY)%'
$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)%'
App\Services\InfoProviderSystem\Providers\LCSCProvider:
arguments:
$enabled: '%env(bool:PROVIDER_LCSC_ENABLED)%'
$currency: '%env(string:PROVIDER_LCSC_CURRENCY)%'
App\Services\InfoProviderSystem\Providers\OEMSecretsProvider:
arguments:
$api_key: '%env(string:PROVIDER_OEMSECRETS_KEY)%'
$country_code: '%env(string:PROVIDER_OEMSECRETS_COUNTRY_CODE)%'
$currency: '%env(PROVIDER_OEMSECRETS_CURRENCY)%'
$zero_price: '%env(PROVIDER_OEMSECRETS_ZERO_PRICE)%'
$set_param: '%env(PROVIDER_OEMSECRETS_SET_PARAM)%'
$sort_criteria: '%env(PROVIDER_OEMSECRETS_SORT_CRITERIA)%'
#################################################################################################################### ####################################################################################################################
# API system # API system
#################################################################################################################### ####################################################################################################################
App\State\PartDBInfoProvider: App\State\PartDBInfoProvider:
arguments: arguments:
$default_uri: '%partdb.default_uri%' $default_uri: '%partdb.default_uri%'
$global_locale: '%partdb.locale%'
$global_timezone: '%partdb.timezone%'
####################################################################################################################
# EDA system
####################################################################################################################
App\Services\EDA\KiCadHelper:
arguments:
$category_depth: '%env(int:EDA_KICAD_CATEGORY_DEPTH)%'
#################################################################################################################### ####################################################################################################################
# Symfony overrides # Symfony overrides
@ -225,6 +355,7 @@ services:
#################################################################################################################### ####################################################################################################################
App\Controller\RedirectController: App\Controller\RedirectController:
arguments: arguments:
$default_locale: '%partdb.locale%'
$enforce_index_php: '%env(bool:NO_URL_REWRITE_AVAILABLE)%' $enforce_index_php: '%env(bool:NO_URL_REWRITE_AVAILABLE)%'
App\Doctrine\Purger\ResetAutoIncrementPurgerFactory: App\Doctrine\Purger\ResetAutoIncrementPurgerFactory:
@ -239,6 +370,14 @@ services:
arguments: arguments:
$project_dir: '%kernel.project_dir%' $project_dir: '%kernel.project_dir%'
App\Services\System\UpdateAvailableManager:
arguments:
$check_for_updates: '%partdb.check_for_updates%'
App\Services\System\BannerHelper:
arguments:
$partdb_banner: '%partdb.banner%'
$project_dir: '%kernel.project_dir%'
App\Doctrine\Middleware\MySQLSSLConnectionMiddlewareWrapper: App\Doctrine\Middleware\MySQLSSLConnectionMiddlewareWrapper:
arguments: arguments:

Binary file not shown.

Before

(image error) Size: 49 KiB

View file

@ -1,7 +1,4 @@
name;description;category;notes;footprint;tags;quantity;storage_location;mass;ipn;mpn;manufacturing_status;manufacturer;supplier;spn;price;favorite;needs_review;minamount;partUnit;eda_info.reference_prefix;eda_info.value;eda_info.visibility;eda_info.exclude_from_bom;eda_info.exclude_from_board;eda_info.exclude_from_sim;eda_info.kicad_symbol;eda_info.kicad_footprint name;description;category;notes;footprint;tags;quantity;storage_location;mass;ipn;mpn;manufacturing_status;manufacturer;supplier;spn;price;favorite;needs_review;minamount;partUnit;manufacturing_status
"MLCC; 0603; 0.22uF";Multilayer ceramic capacitor;Electrical Components->Passive Components->Capacitors_SMD;High quality MLCC;0603;Capacitor,SMD,MLCC,0603;500;Room 1->Shelf 1->Box 2;0.1;CL10B224KO8NNNC;CL10B224KO8NNNC;active;Samsung;LCSC;C160828;0.0023;0;0;1;pcs;C;0.22uF;1;0;0;0;Device:C;Capacitor_SMD:C_0603_1608Metric BC547;NPN transistor;Transistors -> NPN;very important notes;TO -> TO-92;NPN,Transistor;5;Room 1 -> Shelf 1 -> Box 2;10;;;Manufacturer;;You need to fill this line, to use spn and price;BC547C;2,3;0;;;;
"MLCC; 0402; 10pF";Small MLCC for high frequency;Electrical Components->Passive Components->Capacitors_SMD;;0402;Capacitor,SMD,MLCC,0402;500;Room 1->Shelf 1->Box 3;0.05;FCC0402N100J500AT;FCC0402N100J500AT;active;Fenghua;LCSC;C5137557;0.0015;0;0;1;pcs;C;10pF;1;0;0;0;Device:C;Capacitor_SMD:C_0402_1005Metric BC557;PNP transistor;<b>HTML</b>;;TO -> TO-92;PNP,Transistor;10;Room 2-> Box 3;;Internal1234;;;;;;;;1;;;active
"Diode; 1N4148W";Fast switching diode;Electrical Components->Semiconductors->Diodes;Fast recovery time;Diode_SMD:D_SOD-123;Diode,SMD,Schottky;100;Room 2->Box 1;0.2;1N4148W;1N4148W;active;Vishay;LCSC;C917030;0.008;0;0;1;pcs;D;1N4148W;1;0;0;0;Device:D;Diode_SMD:D_SOD-123 Copper Wire;;Wire;;;;;;;;;;;;;;;;;Meter;
BC547;NPN transistor;Transistors->NPN;very important notes;TO->TO-92;NPN,Transistor;5;Room 1->Shelf 1->Box 2;10;BC547;BC547;active;Generic;LCSC;BC547C;2.3;0;0;1;pcs;Q;BC547;1;0;0;0;Device:Q_NPN_EBC;TO_SOT_Packages_SMD:TO-92_HandSolder
BC557;PNP transistor;Transistors->PNP;PNP complement to BC547;TO->TO-92;PNP,Transistor;10;Room 2->Box 3;10;BC557;BC557;active;Generic;LCSC;BC557C;2.1;0;0;1;pcs;Q;BC557;1;0;0;0;Device:Q_PNP_EBC;TO_SOT_Packages_SMD:TO-92_HandSolder
Copper Wire;Bare copper wire;Wire->Copper;For prototyping;Wire;Wire,Copper;50;Room 3->Spool Rack;0.5;CW-22AWG;CW-22AWG;active;Generic;Local Supplier;LS-CW-22;0.15;0;0;1;Meter;W;22AWG;1;0;0;0;Device:Wire;Connector_PinHeader_2.54mm:PinHeader_1x01_P2.54mm_Vertical
1 name description category notes footprint tags quantity storage_location mass ipn mpn manufacturing_status manufacturer supplier spn price favorite needs_review minamount partUnit eda_info.reference_prefix manufacturing_status eda_info.value eda_info.visibility eda_info.exclude_from_bom eda_info.exclude_from_board eda_info.exclude_from_sim eda_info.kicad_symbol eda_info.kicad_footprint
2 MLCC; 0603; 0.22uF BC547 Multilayer ceramic capacitor NPN transistor Electrical Components->Passive Components->Capacitors_SMD Transistors -> NPN High quality MLCC very important notes 0603 TO -> TO-92 Capacitor,SMD,MLCC,0603 NPN,Transistor 500 5 Room 1->Shelf 1->Box 2 Room 1 -> Shelf 1 -> Box 2 0.1 10 CL10B224KO8NNNC CL10B224KO8NNNC active Manufacturer Samsung LCSC You need to fill this line, to use spn and price C160828 BC547C 0.0023 2,3 0 0 1 pcs C 0.22uF 1 0 0 0 Device:C Capacitor_SMD:C_0603_1608Metric
3 MLCC; 0402; 10pF BC557 Small MLCC for high frequency PNP transistor Electrical Components->Passive Components->Capacitors_SMD <b>HTML</b> 0402 TO -> TO-92 Capacitor,SMD,MLCC,0402 PNP,Transistor 500 10 Room 1->Shelf 1->Box 3 Room 2-> Box 3 0.05 FCC0402N100J500AT Internal1234 FCC0402N100J500AT active Fenghua LCSC C5137557 0.0015 0 0 1 1 pcs C active 10pF 1 0 0 0 Device:C Capacitor_SMD:C_0402_1005Metric
4 Diode; 1N4148W Copper Wire Fast switching diode Electrical Components->Semiconductors->Diodes Wire Fast recovery time Diode_SMD:D_SOD-123 Diode,SMD,Schottky 100 Room 2->Box 1 0.2 1N4148W 1N4148W active Vishay LCSC C917030 0.008 0 0 1 pcs Meter D 1N4148W 1 0 0 0 Device:D Diode_SMD:D_SOD-123
BC547 NPN transistor Transistors->NPN very important notes TO->TO-92 NPN,Transistor 5 Room 1->Shelf 1->Box 2 10 BC547 BC547 active Generic LCSC BC547C 2.3 0 0 1 pcs Q BC547 1 0 0 0 Device:Q_NPN_EBC TO_SOT_Packages_SMD:TO-92_HandSolder
BC557 PNP transistor Transistors->PNP PNP complement to BC547 TO->TO-92 PNP,Transistor 10 Room 2->Box 3 10 BC557 BC557 active Generic LCSC BC557C 2.1 0 0 1 pcs Q BC557 1 0 0 0 Device:Q_PNP_EBC TO_SOT_Packages_SMD:TO-92_HandSolder
Copper Wire Bare copper wire Wire->Copper For prototyping Wire Wire,Copper 50 Room 3->Spool Rack 0.5 CW-22AWG CW-22AWG active Generic Local Supplier LS-CW-22 0.15 0 0 1 Meter W 22AWG 1 0 0 0 Device:Wire Connector_PinHeader_2.54mm:PinHeader_1x01_P2.54mm_Vertical

View file

@ -10,7 +10,7 @@ Part-DBs behavior can be configured to your needs. There are different kinds of
user-changeable (changeable dynamically via frontend), options that can be configured by environment variables, and user-changeable (changeable dynamically via frontend), options that can be configured by environment variables, and
options that are only configurable via Symfony config files. options that are only configurable via Symfony config files.
## User configruation ## User changeable
The following things can be changed for every user and a user can change it for himself (if he has the correct permission The 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 user's own settings page (where you can also change the password) or via for it). Configuration is either possible via the user's own settings page (where you can also change the password) or via
@ -24,34 +24,15 @@ the user admin page:
* **Preferred currency**: One of the defined currencies, in which all prices should be shown, if possible. Prices with * **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 other currencies will be converted to the price selected here
## System configuration (via web interface)
Many common configuration options can be changed via the web interface. You can find the settings page in the sidebar under
"System" -> "Settings". You need to have the "Change system settings" permission to access this page.
If a setting is greyed out and cannot be changed, it means that this setting is currently overwritten by an environment
variable. You can either change the environment variable to change the setting, or you can migrate the setting to the
database, so that it can be changed via the web interface. To do this, you can use the `php bin/console settings:migrate-env-to-settings` command
and remove the environment variable afterward.
## Environment variables (.env.local) ## Environment variables (.env.local)
The following configuration options can only be changed by the server administrator, by either changing the server 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 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 the full list of possible env variables. options listed, see `.env` file for the full list of possible env variables.
Environment variables allow to overwrite settings in the web interface. This is useful, if you want to enforce certain
settings to be unchangable by users, or if you want to configure settings in a central place in a deployed environment.
On the settings page, you can hover over a setting to see, which environment variable can be used to overwrite it, it
is shown as tooltip. API keys or similar sensitve data which is overwritten by env variables, are redacted on the web
interface, so that even administrators cannot see them (only the last 2 characters and the length).
For technical and security reasons some settings can only be configured via environment variables and not via the web
interface. These settings are marked with "(env only)" in the description below.
### General options ### General options
* `DATABASE_URL` (env only): Configures the database which Part-DB uses: * `DATABASE_URL`: Configures the database which Part-DB uses:
* For MySQL (or MariaDB) use a string in the form of `mysql://<USERNAME>:<PASSWORD>@<HOST>:<PORT>/<TABLE_NAME>` here * For MySQL (or MariaDB) 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`). (e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`).
* For SQLite use the following format to specify the * For SQLite use the following format to specify the
@ -61,10 +42,10 @@ interface. These settings are marked with "(env only)" in the description below.
Please note that **`serverVersion=x.y`** variable is required due to dependency of Symfony framework. Please note that **`serverVersion=x.y`** variable is required due to dependency of Symfony framework.
* `DATABASE_MYSQL_USE_SSL_CA` (env only): If this value is set to `1` or `true` and a MySQL connection is used, then the connection * `DATABASE_MYSQL_USE_SSL_CA`: If this value is set to `1` or `true` and a MySQL connection is used, then the connection
is encrypted by SSL/TLS and the server certificate is verified against the system CA certificates or the CA certificate is encrypted by SSL/TLS and the server certificate is verified against the system CA certificates or the CA certificate
bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept all certificates. bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept all certificates.
* `DATABASE_EMULATE_NATURAL_SORT` (default 0) (env only): If set to 1, Part-DB will emulate natural sorting, even if the database * `DATABASE_EMULATE_NATURAL_SORT` (default 0): If set to 1, Part-DB will emulate natural sorting, even if the database
does not support it natively. However this is much slower than the native sorting, and contain bugs or quirks, so use does not support it natively. However this is much slower than the native sorting, and contain bugs or quirks, so use
it only, if you have to. it only, if you have to.
* `DEFAULT_LANG`: The default language to use server-wide (when no language is explicitly specified by a user or via * `DEFAULT_LANG`: The default language to use server-wide (when no language is explicitly specified by a user or via
@ -93,7 +74,7 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
to specify the size in kilobytes, megabytes or gigabytes. By default `100M` (100 megabytes). Please note that this is to specify the size in kilobytes, megabytes or gigabytes. By default `100M` (100 megabytes). Please note that this is
only the limit of Part-DB. You still need to configure the php.ini `upload_max_filesize` and `post_max_size` to allow 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. bigger files to be uploaded.
* `DEFAULT_URI` (env only): The default URI base to use for the Part-DB, when no URL can be determined from the browser request. * `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 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 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 proxy, you should set this to the URL of the reverse proxy (e.g. `https://part-db.example.com`). **This value must end
@ -110,14 +91,12 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
* `datastructure_create`: Creation of a new 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 * `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. versions, or if your server can not connect to the internet.
* `APP_SECRET` (env only): This variable is a configuration parameter used for various security-related purposes, * `APP_SECRET`: This variable is a configuration parameter used for various security-related purposes,
particularly for securing and protecting various aspects of your application. It's a secret key that is used for particularly for securing and protecting various aspects of your application. It's a secret key that is used for
cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this
value should be handled as confidential data and not shared publicly. value should be handled as confidential data and not shared publicly.
* `SHOW_PART_IMAGE_OVERLAY`: Set to 0 to disable the part image overlay, which appears if you hover over an image in the
part image gallery
### E-Mail settings (all env only) ### E-Mail settings
* `MAILER_DSN`: You can configure the mail provider which should be used for email delivery ( * `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 see https://symfony.com/doc/current/components/mailer.html for full documentation). If you just want to use an SMTP
@ -157,7 +136,7 @@ The following options are used to configure, which (and how much) data is writte
If you want to use want to revert changes or view older revisions of 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. then `HISTORY_SAVE_CHANGED_FIELDS`, `HISTORY_SAVE_CHANGED_DATA` and `HISTORY_SAVE_REMOVED_DATA` all have to be true.
### Error pages settings (all env only) ### 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 * `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) about the issue (e.g. an IT support email of your company)
@ -172,7 +151,7 @@ then `HISTORY_SAVE_CHANGED_FIELDS`, `HISTORY_SAVE_CHANGED_DATA` and `HISTORY_SAV
All parts in the selected category and all subcategories are shown in KiCad. Set this to a higher value, if you want to show more categories in KiCad. All parts in the selected category and all subcategories are shown in KiCad. Set this to a higher value, if you want to show more categories in KiCad.
When you set this value to -1, all parts are shown inside a single category in KiCad. When you set this value to -1, all parts are shown inside a single category in KiCad.
### SAML SSO settings (all env only) ### SAML SSO settings
The following settings can be used to enable and configure Single-Sign on via SAML. This allows users to log in to 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 Part-DB without entering a username and password, but instead they are redirected to a SAML Identity Provider (IdP) and
@ -220,26 +199,26 @@ See the [information providers]({% link usage/information_provider_system.md %})
### Other / less-used options ### Other / less-used options
* `TRUSTED_PROXIES` (env only): Set the IP addresses (or IP blocks) of trusted reverse proxies here. This is needed to get correct * `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). IP information (see [here](https://symfony.com/doc/current/deployment/proxies.html) for more info).
* `TRUSTED_HOSTS` (env only): To prevent `HTTP Host header attacks` you can set a regex containing all host names via which Part-DB * `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. should be accessible. If accessed via the wrong hostname, an error will be shown.
* `DEMO_MODE` (env only): Set Part-DB into demo mode, which forbids users to change their passwords and settings. Used for the demo * `DEMO_MODE`: Set Part-DB into demo mode, which forbids users to change their passwords and settings. Used for the demo
instance. This should not be needed for normal installations. instance. This should not be needed for normal installations.
* `NO_URL_REWRITE_AVAILABLE` (allowed values `true` or `false`) (env only): Set this value to true, if your webserver does not * `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 does support rewrite. In this case, all URL paths will contain index.php/, which is needed then. Normally this setting does
not need to be changed. not need to be changed.
* `REDIRECT_TO_HTTPS` (env only): If this is set to true, all requests to http will be redirected to https. This is useful if your * `REDIRECT_TO_HTTPS`: If this is set to true, all requests to http will be redirected to https. This is useful if your
web server does not already do this (like the one used in the demo instance). If your web server already redirects to web server does not already do this (like the one used in the demo instance). If your web server already redirects to
https, you don't need to set this. Ensure that Part-DB is accessible via HTTPS before you enable this setting. https, you don't need to set this. Ensure that Part-DB is accessible via HTTPS before you enable this setting.
* `FIXER_API_KEY`: If you want to automatically retrieve exchange rates for base currencies other than euros, you have to * `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 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. there and set the retrieved API key in this environment variable.
* `APP_ENV` (env only): This value should always be set to `prod` in normal use. Set it to `dev` to enable debug/development * `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!**) 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 * `BANNER`: You can configure the text that should be shown as the banner on the homepage. Useful especially for docker
containers. In all other applications you can just change the `config/banner.md` file. containers. In all other applications you can just change the `config/banner.md` file.
* `DISABLE_YEAR2038_BUG_CHECK` (env only): If set to `1`, the year 2038 bug check is disabled on 32-bit systems, and dates after * `DISABLE_YEAR2038_BUG_CHECK`: If set to `1`, the year 2038 bug check is disabled on 32-bit systems, and dates after
2038 are no longer forbidden. However this will lead to 500 error messages when rendering dates after 2038 as all current 2038 are no longer forbidden. However this will lead to 500 error messages when rendering dates after 2038 as all current
32-bit PHP versions can not format these dates correctly. This setting is for the case that future PHP versions will 32-bit PHP versions can not format these dates correctly. This setting is for the case that future PHP versions will
handle this correctly on 32-bit systems. 64-bit systems are not affected by this bug, and the check is always disabled. handle this correctly on 32-bit systems. 64-bit systems are not affected by this bug, and the check is always disabled.
@ -247,7 +226,7 @@ handle this correctly on 32-bit systems. 64-bit systems are not affected by this
## Banner ## Banner
To change the banner you can find on the homepage, you can either set the `BANNER` environment variable to the text you 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 change it in the system settings webinterface. The banner is written in markdown, so you can use all 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. markdown (and even some subset of HTML) syntax to format the text.
## parameters.yaml ## parameters.yaml
@ -262,6 +241,8 @@ command `bin/console cache:clear`.
The following options are available: 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 * `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. 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 * `partdb.gdpr_compliance`: When set to true (default value), IP addresses which are saved in the database will be

View file

@ -150,9 +150,9 @@ In the `serverVersion` parameter you can specify the version of the PostgreSQL s
The `charset` parameter specify the character set of the database. It should be set to `utf8` to ensure that all characters are stored correctly. The `charset` parameter specify the character set of the database. It should be set to `utf8` to ensure that all characters are stored correctly.
If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `host` parameter. If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `unix_socket` parameter.
```shell ```shell
DATABASE_URL="postgresql://db_user@localhost/db_name?serverVersion=16.6&charset=utf8&host=/var/run/postgresql" DATABASE_URL="postgresql://db_user:db_password@localhost/db_name?serverVersion=12.19&charset=utf8&unix_socket=/var/run/postgresql/.s.PGSQL.5432"
``` ```

View file

@ -48,12 +48,6 @@ services:
# In docker env logs will be redirected to stderr # In docker env logs will be redirected to stderr
- APP_ENV=docker - APP_ENV=docker
# Uncomment this, if you want to use the automatic database migration feature. With this you have you do not have to
# run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/
# folder (under .automigration-backup), so you can restore it, if the migration fails.
# This feature is currently experimental, so use it at your own risk!
# - DB_AUTOMIGRATE=true
# You can configure Part-DB using environment variables # You can configure Part-DB using environment variables
# Below you can find the most essential ones predefined # Below you can find the most essential ones predefined
# However you can add any other environment configuration you want here # However you can add any other environment configuration you want here
@ -136,18 +130,28 @@ services:
# In docker env logs will be redirected to stderr # In docker env logs will be redirected to stderr
- APP_ENV=docker - APP_ENV=docker
# Uncomment this, if you want to use the automatic database migration feature. With this you have you do not have to # You can configure Part-DB using environment variables
# run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/ # Below you can find the most essential ones predefined
# folder (under .automigration-backup), so you can restore it, if the migration fails.
# This feature is currently experimental, so use it at your own risk!
# - DB_AUTOMIGRATE=true
# You can configure Part-DB using the webUI or environment variables
# However you can add add any other environment configuration you want here # However you can add add any other environment configuration you want here
# See .env file for all available options or https://docs.part-db.de/configuration.html # See .env file for all available options or https://docs.part-db.de/configuration.html
# The language to use serverwide as default (en, de, ru, etc.)
- DEFAULT_LANG=en
# The default timezone to use serverwide (e.g. Europe/Berlin)
- DEFAULT_TIMEZONE=Europe/Berlin
# The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country
- BASE_CURRENCY=EUR
# The name of this installation. This will be shown as title in the browser and in the header of the website
- INSTANCE_NAME=Part-DB
# Allow users to download attachments to the server by providing an URL
# This could be a potential security issue, as the user can retrieve any file the server has access to (via internet)
- ALLOW_ATTACHMENT_DOWNLOADS=0
# Use gravatars for user avatars, when user has no own avatar defined
- USE_GRAVATAR=0
# Override value if you want to show to show a given text on homepage. # Override value if you want to show to show a given text on homepage.
# When this is outcommented the webUI can be used to configure the banner # 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 #- BANNER=This is a test banner<br>with a line break
database: database:
@ -197,10 +201,6 @@ You also have to create the database as described above in step 4.
You can run the console commands described in README by You can run the console commands described in README by
executing `docker exec --user=www-data -it partdb bin/console [command]` executing `docker exec --user=www-data -it partdb bin/console [command]`
{: .warning }
> If you run a root console inside the container, and wanna execute commands on the webserver behalf, be sure to use `sudo -E` command (with the `-E` flag) to preserve env variables from the current shell.
> Otherwise Part-DB console might use the wrong configuration to execute commands.
## Troubleshooting ## Troubleshooting
*Login is not possible. Login page is just reloading and no error message is shown or something like "CSFR token invalid"*: *Login is not possible. Login page is just reloading and no error message is shown or something like "CSFR token invalid"*:

View file

@ -1,13 +1,13 @@
--- ---
title: Direct Installation on Debian 12 title: Direct Installation on Debian 11
layout: default layout: default
parent: Installation parent: Installation
nav_order: 4 nav_order: 4
--- ---
# Part-DB installation guide for Debian 12 (Bookworm) # Part-DB installation guide for Debian 11 (Bullseye)
This guide shows you how to install Part-DB directly on Debian 12 using apache2 and SQLite. This guide should work with 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. 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 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 on the docker this many dependencies. See [here]({% link installation/installation_docker.md %}) for more information on the docker
@ -28,32 +28,40 @@ It is recommended to install Part-DB on a 64-bit system, as the 32-bit version o
For the installation of Part-DB, we need some prerequisites. They can be installed by running the following command: For the installation of Part-DB, we need some prerequisites. They can be installed by running the following command:
```bash ```bash
sudo apt update && apt upgrade sudo apt install git curl zip ca-certificates software-properties-common apt-transport-https lsb-release nano wget
sudo apt install git curl zip ca-certificates software-properties-common \
apt-transport-https lsb-release nano wget sqlite3
``` ```
Please run `sqlite3 --version` to assert that the SQLite version is 3.35 or higher.
Otherwise some database migrations will not succeed.
### Install PHP and apache2 ### Install PHP and apache2
Part-DB is written in [PHP](https://php.net) and therefore needs a PHP interpreter to run. Part-DB needs PHP 8.2 or Part-DB is written in [PHP](https://php.net) and therefore needs a 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 higher. However, it is recommended to use the most recent version of PHP for performance reasons and future
compatibility. compatibility.
Install PHP with required extensions and apache2: 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 ```bash
sudo apt install apache2 php8.2 libapache2-mod-php8.2 \ # Add sury repository for PHP 8.1
php8.2-opcache php8.2-curl php8.2-gd php8.2-mbstring \ sudo curl -sSL https://packages.sury.org/php/README.txt | sudo bash -x
php8.2-xml php8.2-bcmath php8.2-intl php8.2-zip php8.2-xsl \
php8.2-sqlite3 php8.2-mysql # Update package list
sudo apt update && sudo apt upgrade
``` ```
Now you can install PHP 8.1 and the 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 ### Install composer
Part-DB uses [composer](https://getcomposer.org/) to install required PHP libraries. Install the latest version manually: Part-DB uses [composer](https://getcomposer.org/) to install required PHP libraries. As the version shipped in the
repositories is pretty old, we will install it manually:
```bash ```bash
# Download composer installer script # Download composer installer script
@ -70,9 +78,10 @@ To build the front end (the user interface) Part-DB uses [yarn](https://yarnpkg.
shipped versions are pretty old, we install new versions from the official Node.js repository: shipped versions are pretty old, we install new versions from the official Node.js repository:
```bash ```bash
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - # Add recent node repository (nodejs 18 is supported until 2025)
sudo apt install -y nodejs curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
# Install nodejs
sudo apt install nodejs
``` ```
We can install yarn with the following commands: We can install yarn with the following commands:
@ -108,8 +117,8 @@ 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): see [GitHub Releases page](https://github.com/Part-DB/Part-DB-server/releases) for a list of available versions):
```bash ```bash
# This checks out the version 2.0.0 # This checks out the version 1.5.2
git checkout v2.0.0 git checkout v1.5.2
``` ```
Change ownership of the files to the apache user: Change ownership of the files to the apache user:
@ -133,10 +142,11 @@ configuration:
cp .env .env.local cp .env .env.local
``` ```
In your `.env.local` you can configure Part-DB according to your wishes and overwrite web interface settings. In your `.env.local` you can configure Part-DB according to your wishes. A full list of configuration options can be
A full list of configuration options can be found [here](../configuration.md). 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 configured base currency matches your mainly used currency, as 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. this can not be changed after creating price information.
### Install dependencies for Part-DB and build frontend ### Install dependencies for Part-DB and build frontend
@ -246,7 +256,6 @@ network to point to the server).
Navigate to the Part-DB web interface and login via the user icon in the top right corner. You can log in using the 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. username `admin` and the password you have written down earlier.
As first steps, you should check out the system settings and check if everything is correct.
## Update Part-DB ## Update Part-DB
@ -282,7 +291,7 @@ sudo -u www-data php bin/console cache:clear
## MySQL/MariaDB database ## MySQL/MariaDB database
To use a MySQL database, follow the steps from above (except the creation of the database, we will do this later). To use a MySQL database, follow the steps from above (except the creation of the database, we will do this later).
Debian 12 does not ship MySQL in its repositories anymore, so we use the compatible MariaDB instead: Debian 11 does not ship MySQL in its repositories anymore, so we use the compatible MariaDB instead:
1. Install maria-db with: 1. Install maria-db with:

View file

@ -53,11 +53,6 @@ server {
return 404; return 404;
} }
# Set Content-Security-Policy for svg files, to block embedded javascript in there
location ~* \.svg$ {
add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';";
}
error_log /var/log/nginx/parts.error.log; error_log /var/log/nginx/parts.error.log;
access_log /var/log/nginx/parts.access.log; access_log /var/log/nginx/parts.access.log;

View file

@ -1,89 +0,0 @@
---
layout: default
title: Upgrade from Part-DB 1.x to 2.x
nav_order: 1
has_children: false
parent: Upgrade
---
# Upgrade from Part-DB 1.x to 2.x
Part-DB 2.0 is a major release that changes a lot of things internally, but it is still compatible with Part-DB 1.x.
Depending on your preferences, you will have to do some changes to your Part-DB installation, this document will guide
you through the upgrade process.
## New requirements
*If you are running Part-DB inside a docker container, you can skip this section, as the new requirements are already
fulfilled by the official Part-DB docker image.*
Part-DB 2.0 requires at least PHP 8.2 (newer versions are recommended). So if your existing Part-DB installation is still
running PHP 8.1, you will have to upgrade your PHP version first.
The minimum required version of node.js is now 20.0 or newer, so if you are using 18.0, you will have to upgrade it too.
Most distributions should have the possibility to get backports for PHP 8.4 and modern nodejs, so you should be able to
easily upgrade your system to the new requirements. Otherwise, you can use the official Part-DB docker image, which
ships all required dependencies and is always up to date with the latest requirements, so that you do not have to worry
about the requirements at all.
## Changes
* Configuration is now preferably done via a web settings interface. You can still use environment variables, these overwrite
the settings in the web interface. Existing configuration will still work, but you should consider migriting them to the
web interface as described below.
* The `config/banner.md` file that could been used to customize the banner text, was removed. You can now set the banner text
directly in the admin interface, or by setting the `BANNER` environment variable. If you want to keep your existing
banner text, you will have to copy it from the `config/banner.md` file to the admin interface or set the `BANNER`
environment variable.
* The parameters `partdb.sidebar.items`, `partdb.sidebar.root_node_enable` and `partdb.sidebar.root_expanded` in `config/parameters.yaml`,
were removed. You can configure them now directly in the admin interface.
* Updated icon set. As fontawesome 7 is now used, some icons have changed slightly.
## Upgrade installation
The upgrade process works very similar to a normal (minor release) upgrade.
### Direct installation
**Be sure to execute the following steps as the user that owns the Part-DB files (e.g. `www-data`, or your webserver user). So prepend a `sudo -u wwww-data` where necessary.**
1. Make a backup of your existing Part-DB installation, including the database, data directories and the configuration files and `.env.local` file.
The `php bin/console partdb:backup` command can help you with this.
2. Pull the v2 version. For git installation you can do this with `git checkout v2.0.0` (or newer version)
3. Run `composer install --no-dev -o` to update the dependencies.
4. Run `yarn install` and `yarn build` to update the frontend assets.
5. Rund `php bin/console doctrine:migrations:migrate` to update the database schema.
6. Clear the cache with `php bin/console cache:clear`.
7. Open your Part-DB instance in the browser and log in as an admin user.
8. Go to the user or group permissions page, and give yourself (and other administrators) the right to change system settings (under "System" and "Configuration").
9. You can now go to the settings page (under "System" and "Settings") and check if all settings are correct.
10. Parameters which were previously set via environment variables are greyed out and cannot be changed in the web interface.
If you want to change them, you must migrate them to the settings interface as described below.
### Docker installation
1. Make a backup of your existing Part-DB installation, including the database, data directories and the configuration files and the file where you configure the docker environment variables.
2. Stop the existing Part-DB container with `docker compose down`
3. Ensure that your docker compose file uses the new latest images (either `latest` or `2` tag).
4. Pull the new images with `docker compose pull` and start the container with `docker compose up -d`
5. If you have database automigration disabled, run `docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate` to update the database schema.
6. Open your Part-DB instance in the browser and log in as an admin user.
7. Go to the user or group permissions page, and give yourself (and other administrators)
the right to change system settings (under "System" and "Configuration").
8. You can now go to the settings page (under "System" and "Settings")
9. Parameters which were previously set via environment variables are greyed out and cannot be changed in the web interface.
If you want to change them, you must migrate them to the settings interface as described below.
## Migrate environment variable configuration to settings interface
As described above, configuration can now be done via the web interface, and can be overwritten by environment variables, so
that existing configuration should still work. However, if a parameter is set via an environment variable, it cannot be changed in the web interface.
To change it, you must migrate your environment variable configuration to the new system.
For this there is the new console command `settings:migrate-env-to-settings`, which reads in all environment variables used to overwrite
settings and write them to the database, so that you can safely delete them from your environment variable configuration afterwards, without
loosing your configuration.
To run the command, execute `php bin/console settings:migrate-env-to-settings --all` as webserver user (or run `docker exec --user=www-data -it partdb php bin/console settings:migrate-env-to-settings --all` for docker containers).
It will list you all environment variables, it found and ask you for confirmation to migrate them. Answer with `yes` to migrate them and hit enter.
After the migration run successfully, the contents of your environment variables are now stored in the database and you can safely remove them from your environment variable configuration.
Go through the environment variables listed by the command and remove them from your environment variable configuration (e.g. `.env.local` file or docker compose file), or just comment them out for now.
If you want to keep some environment variables, just leave them as they are, they will still work as before, the migration command only affects the settings stored in the database.

View file

@ -1,9 +0,0 @@
---
layout: default
title: Upgrade
nav_order: 7
has_children: true
---
This section provides information on how to upgrade Part-DB to the latest version.
This is intended for major release upgrades, where requirements or things changes significantly.

View file

@ -2,8 +2,6 @@
layout: default layout: default
title: Upgrade from legacy Part-DB version (<1.0) title: Upgrade from legacy Part-DB version (<1.0)
nav_order: 100 nav_order: 100
redirect_from: /upgrade_legacy
parent: Upgrade
--- ---
# Upgrade from legacy Part-DB version # Upgrade from legacy Part-DB version
@ -18,8 +16,8 @@ sections carefully before proceeding to upgrade.
## Changes ## Changes
* PHP 8.2 or higher is required now (Part-DB 0.5 required PHP 5.4+, Part-DB 0.6 PHP 7.0). * 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.2 Releases are available for Windows too, so almost everybody should be able to use PHP 8.1
* **Console access is highly recommended.** The installation of composer and frontend dependencies require console access, * **Console access is highly recommended.** The installation of composer and frontend dependencies require console access,
also more sensitive stuff like database migration works via CLI now, so you should have console access on your server. also more sensitive stuff like database migration works 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. * Markdown/HTML is now used instead of BBCode for rich text in description and command fields.

View file

@ -34,12 +34,3 @@ select the BOM file you want to import and some options for the import process:
has a different format and does not work with this type. 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 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. the file to your desired location.
* **KiCAD Schematic BOM (CSV file)**: A CSV file of the Bill of Material (BOM) generated
by [KiCAD Eeschema](https://www.kicad.org/).
You can generate this BOM file by going to "Tools" -> "Generate Bill of Materials" in Eeschema and save the file to your
desired location. In the next step you can customize the mapping of the fields in Part-DB, if you have any special fields
in your BOM to locate your fields correctly.
* **Generic CSV file**: A generic CSV file. You can use this option if you use some different ECAD software or wanna create
your own CSV file. You will need to specify at least the designators, quantity and value fields in the CSV. In the next
step you can customize the mapping of the fields in Part-DB, if you have any special fields in your BOM to locate your
parts correctly.

View file

@ -25,12 +25,6 @@ is named `partdb`, you can execute the command `php bin/console cache:clear` wit
docker exec --user=www-data partdb php bin/console cache:clear docker exec --user=www-data partdb php bin/console cache:clear
``` ```
{: .warning }
> If you run a root console inside the docker container, and wanna execute commands on the webserver behalf, be sure to use `sudo -E` command (with the `-E` flag) to preserve env variables from the current shell.
> Otherwise Part-DB console might use the wrong configuration to execute commands.
## Troubleshooting
## User management commands ## User management commands
* `php bin/console partdb:users:list`: List all users of this Part-DB instance * `php bin/console partdb:users:list`: List all users of this Part-DB instance
@ -66,16 +60,8 @@ docker exec --user=www-data partdb php bin/console cache:clear
* `partdb:migrations:import-partkeepr`: Imports a mysqldump XML dump of a PartKeepr database into Part-DB. This is only * `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 needed for users, which want to migrate from PartKeepr to Part-DB. *All existing data in the Part-DB database is
deleted!* deleted!*
* `settings:migrate-env-to-settings`: Migrate configuration from environment variables to the settings interface.
The value of the environment variable is copied to the settings database, so the environment variable can be removed afterwards without losing the configuration.
## Database commands ## Database commands
* `php bin/console doctrine:migrations:migrate`: Migrate the database to the latest version * `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 * `php bin/console doctrine:migrations:up-to-date`: Check if the database is up-to-date
## Attachment commands
* `php bin/console partdb:attachments:download`: Download all attachments, which are not already downloaded, to the
local filesystem. This is useful to create local backups of the attachments, no matter what happens on the remote and
also makes pictures thumbnails available for the frontend for them

View file

@ -12,19 +12,11 @@ Before starting, it's useful to read a bit about the [concepts of Part-DB]({% li
1. TOC 1. TOC
{:toc} {:toc}
## Customize system settings ## Customize config files
Before starting creating datastructures, you should check the system settings to ensure that they fit your needs. Before you start creating data structures, you should configure Part-DB to your needs by changing possible configuration
After login as an administrator, you can find the settings in the sidebar under `Tools -> System -> Settings`. options.
![image]({% link assets/getting_started/system_settings.png %}) This is done either via changing the `.env.local` file in a direct installation or by changing the env variables in
Here you can change various settings, like the name of your Part-DB instance (which is shown in the title bar of the
browser), the default language (which is used if no user preference is set), the default timezone (which is used to
display times correctly), the default currency (which is used to display prices correctly), and many more.
Some more fundamental settings like database connection, mail server settings, SSO, etc. are configured via environment variables.
Environment variables also allow to overwrite various settings from the web interface.
Environment variables can be changed by editing the `.env.local` file in a direct installation or by changing the env variables in
your `docker-compose.yaml` file. your `docker-compose.yaml` file.
A list of possible configuration options can be found [here]({% link configuration.md %}). A list of possible configuration options can be found [here]({% link configuration.md %}).
@ -52,8 +44,8 @@ used.
## (Optional) Customize homepage banner ## (Optional) Customize homepage banner
The banner which is shown on the homepage, can be customized/changed via the homepage banner setting in system settings. The banner which is shown on the homepage, can be customized/changed by changing the `config/banner.md` file with a text
You can use markdown and (safe) HTML here, to style and customize the banner. 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: 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$$ $E=mc^2$) or `$$` (like `$$E=mc^2$$`) which will be rendered as a block, like so: $$E=mc^2$$

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