mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 09:35:49 +02:00
Compare commits
No commits in common. "master" and "v1.3.3" have entirely different histories.
1027 changed files with 27588 additions and 185073 deletions
|
@ -1,55 +0,0 @@
|
|||
{
|
||||
{$CADDY_GLOBAL_OPTIONS}
|
||||
|
||||
frankenphp {
|
||||
{$FRANKENPHP_CONFIG}
|
||||
}
|
||||
|
||||
# https://caddyserver.com/docs/caddyfile/directives#sorting-algorithm
|
||||
order mercure after encode
|
||||
order vulcain after reverse_proxy
|
||||
order php_server before file_server
|
||||
}
|
||||
|
||||
{$CADDY_EXTRA_CONFIG}
|
||||
|
||||
{$SERVER_NAME:localhost} {
|
||||
log {
|
||||
# Redact the authorization query parameter that can be set by Mercure
|
||||
format filter {
|
||||
wrap console
|
||||
fields {
|
||||
uri query {
|
||||
replace authorization REDACTED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
root * /app/public
|
||||
encode zstd br gzip
|
||||
|
||||
mercure {
|
||||
# Transport to use (default to Bolt)
|
||||
transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
|
||||
# Publisher JWT key
|
||||
publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
|
||||
# Subscriber JWT key
|
||||
subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
|
||||
# Allow anonymous subscribers (double-check that it's what you want)
|
||||
anonymous
|
||||
# Enable the subscription API (double-check that it's what you want)
|
||||
subscriptions
|
||||
# Extra directives
|
||||
{$MERCURE_EXTRA_DIRECTIVES}
|
||||
}
|
||||
|
||||
vulcain
|
||||
|
||||
{$CADDY_SERVER_EXTRA_DIRECTIVES}
|
||||
|
||||
# Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
|
||||
header ?Permissions-Policy "browsing-topics=()"
|
||||
|
||||
php_server
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
; See https://docs.docker.com/desktop/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host
|
||||
; See https://github.com/docker/for-linux/issues/264
|
||||
; The `client_host` below may optionally be replaced with `discover_client_host=yes`
|
||||
; Add `start_with_request=yes` to start debug session on each request
|
||||
xdebug.client_host = host.docker.internal
|
|
@ -1,18 +0,0 @@
|
|||
expose_php = 0
|
||||
date.timezone = UTC
|
||||
apc.enable_cli = 1
|
||||
session.use_strict_mode = 1
|
||||
zend.detect_unicode = 0
|
||||
|
||||
; https://symfony.com/doc/current/performance.html
|
||||
realpath_cache_size = 4096K
|
||||
realpath_cache_ttl = 600
|
||||
opcache.interned_strings_buffer = 16
|
||||
opcache.max_accelerated_files = 20000
|
||||
opcache.memory_consumption = 256
|
||||
opcache.enable_file_override = 1
|
||||
|
||||
memory_limit = 256M
|
||||
|
||||
upload_max_filesize=256M
|
||||
post_max_size=300M
|
|
@ -1,2 +0,0 @@
|
|||
opcache.preload_user = root
|
||||
opcache.preload = /app/config/preload.php
|
|
@ -1,60 +0,0 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
|
||||
# Install the project the first time PHP is started
|
||||
# After the installation, the following block can be deleted
|
||||
if [ ! -f composer.json ]; then
|
||||
rm -Rf tmp/
|
||||
composer create-project "symfony/skeleton $SYMFONY_VERSION" tmp --stability="$STABILITY" --prefer-dist --no-progress --no-interaction --no-install
|
||||
|
||||
cd tmp
|
||||
cp -Rp . ..
|
||||
cd -
|
||||
rm -Rf tmp/
|
||||
|
||||
composer require "php:>=$PHP_VERSION" runtime/frankenphp-symfony
|
||||
composer config --json extra.symfony.docker 'true'
|
||||
|
||||
if grep -q ^DATABASE_URL= .env; then
|
||||
echo "To finish the installation please press Ctrl+C to stop Docker Compose and run: docker compose up --build -d --wait"
|
||||
sleep infinity
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then
|
||||
composer install --prefer-dist --no-progress --no-interaction
|
||||
fi
|
||||
|
||||
if grep -q ^DATABASE_URL= .env; then
|
||||
echo "Waiting for database to be ready..."
|
||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
|
||||
until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(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
|
||||
|
||||
if [ "$( find ./migrations -iname '*.php' -print -quit )" ]; then
|
||||
php bin/console doctrine:migrations:migrate --no-interaction
|
||||
fi
|
||||
fi
|
||||
|
||||
setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var
|
||||
setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var
|
||||
fi
|
||||
|
||||
exec docker-php-entrypoint "$@"
|
|
@ -1,4 +0,0 @@
|
|||
worker {
|
||||
file ./public/index.php
|
||||
env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
|
||||
}
|
|
@ -39,50 +39,8 @@ 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)
|
||||
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
|
||||
# Start PHP-FPM
|
||||
service php8.1-fpm start
|
||||
|
||||
# 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
|
||||
|
|
|
@ -25,28 +25,15 @@
|
|||
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 APP_ENV APP_DEBUG APP_SECRET
|
||||
PassEnv DATABASE_URL ENFORCE_CHANGE_COMMENTS_FOR
|
||||
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI
|
||||
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 HISTORY_SAVE_CHANGED_FIELDS HISTORY_SAVE_CHANGED_DATA HISTORY_SAVE_REMOVED_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 SAML_ENABLED SAML_ROLE_MAPPING SAML_UPDATE_GROUP_ON_LOGIN SAML_IDP_ENTITY_ID SAML_IDP_SINGLE_SIGN_ON_SERVICE SAML_IDP_SINGLE_LOGOUT_SERVICE SAML_IDP_X509_CERT SAML_SP_ENTITY_ID SAML_SP_X509_CERT SAMLP_SP_PRIVATE_KEY
|
||||
|
||||
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 PROVIDER_REICHELT_ENABLED PROVIDER_REICHELT_CURRENCY PROVIDER_REICHELT_COUNTRY PROVIDER_REICHELT_LANGUAGE PROVIDER_REICHELT_INCLUDE_VAT
|
||||
PassEnv PROVIDER_POLLIN_ENABLED
|
||||
PassEnv EDA_KICAD_CATEGORY_DEPTH
|
||||
|
||||
# For most configuration files from conf-available/, which are
|
||||
# enabled or disabled at a global level, it is possible to
|
||||
|
|
|
@ -5,8 +5,6 @@ tests/
|
|||
docs/
|
||||
.git
|
||||
|
||||
/public/media/*
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
/.env.local
|
||||
/.env.local.php
|
||||
|
@ -44,39 +42,3 @@ yarn-error.log
|
|||
/phpunit.xml
|
||||
.phpunit.result.cache
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
|
||||
### From frankenphp
|
||||
|
||||
**/*.log
|
||||
**/*.php~
|
||||
**/*.dist.php
|
||||
**/*.dist
|
||||
**/*.cache
|
||||
**/._*
|
||||
**/.dockerignore
|
||||
**/.DS_Store
|
||||
**/.git/
|
||||
**/.gitattributes
|
||||
**/.gitignore
|
||||
**/.gitmodules
|
||||
**/compose.*.yaml
|
||||
**/compose.*.yml
|
||||
**/compose.yaml
|
||||
**/compose.yml
|
||||
**/docker-compose.*.yaml
|
||||
**/docker-compose.*.yml
|
||||
**/docker-compose.yaml
|
||||
**/docker-compose.yml
|
||||
**/Dockerfile
|
||||
**/Thumbs.db
|
||||
.github/
|
||||
public/bundles/
|
||||
var/
|
||||
vendor/
|
||||
.editorconfig
|
||||
.env.*.local
|
||||
.env.local
|
||||
.env.local.php
|
||||
.env.test
|
||||
|
||||
|
|
184
.env
184
.env
|
@ -14,19 +14,6 @@ DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"
|
|||
# Uncomment this line (and comment the line above to use a MySQL database
|
||||
#DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db?serverVersion=5.7
|
||||
|
||||
# Set this value to 1, if you want to use SSL to connect to the MySQL server. It will be tried to use the CA certificate
|
||||
# otherwise a CA bundle shipped with PHP will be used.
|
||||
# Leave it at 0, if you do not want to use SSL or if your server does not support it
|
||||
DATABASE_MYSQL_USE_SSL_CA=0
|
||||
|
||||
# Set this value to 0, if you don't want to verify the CA certificate of the MySQL server
|
||||
# Only do this, if you know what you are doing!
|
||||
DATABASE_MYSQL_SSL_VERIFY_CERT=1
|
||||
|
||||
# Emulate natural sorting of strings even on databases that do not support it (like SQLite, MySQL or MariaDB < 10.7)
|
||||
# This can be slow on big databases and might have some problems and quirks, so use it with caution
|
||||
DATABASE_EMULATE_NATURAL_SORT=0
|
||||
|
||||
###################################################################################
|
||||
# General settings
|
||||
###################################################################################
|
||||
|
@ -42,15 +29,13 @@ 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 to the website in emails and so on
|
||||
# This must end with a slash!
|
||||
DEFAULT_URI="https://partdb.changeme.invalid/"
|
||||
|
||||
|
@ -59,9 +44,6 @@ DEFAULT_URI="https://partdb.changeme.invalid/"
|
|||
# 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
|
||||
###################################################################################
|
||||
|
@ -89,9 +71,6 @@ HISTORY_SAVE_CHANGED_FIELDS=1
|
|||
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
|
||||
|
@ -102,160 +81,12 @@ 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...
|
||||
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
|
||||
# [DEPRECATED] Set this to 1 to get gross prices (including VAT) instead of net prices
|
||||
# With private API keys, this option cannot be used anymore is ignored by Part-DB. The VAT inclusion depends on your TME account settings.
|
||||
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
|
||||
|
||||
|
||||
# Reichelt provider:
|
||||
# Reichelt.com offers no official API, so this info provider webscrapes the website to extract info
|
||||
# It could break at any time, use it at your own risk
|
||||
# We dont require an API key for Reichelt, just set this to 1 to enable Reichelt support
|
||||
PROVIDER_REICHELT_ENABLED=0
|
||||
# The country to get prices for
|
||||
PROVIDER_REICHELT_COUNTRY=DE
|
||||
# The language to get results in (en, de, fr, nl, pl, it, es)
|
||||
PROVIDER_REICHELT_LANGUAGE=en
|
||||
# Include VAT in prices (set to 1 to include VAT, 0 to exclude VAT)
|
||||
PROVIDER_REICHELT_INCLUDE_VAT=1
|
||||
# The currency to get prices in (only for countries with countries other than EUR)
|
||||
PROVIDER_REICHELT_CURRENCY=EUR
|
||||
|
||||
# Pollin provider:
|
||||
# Pollin.de offers no official API, so this info provider webscrapes the website to extract info
|
||||
# It could break at any time, use it at your own risk
|
||||
# We dont require an API key for Pollin, just set this to 1 to enable Pollin support
|
||||
PROVIDER_POLLIN_ENABLED=0
|
||||
|
||||
##################################################################################
|
||||
# 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
|
||||
###################################################################################
|
||||
# Set this to 1 to enable SAML single sign on
|
||||
# Be also sure to set the correct values for DEFAULT_URI
|
||||
SAML_ENABLED=0
|
||||
|
||||
# Set to 1, if your Part-DB installation is behind a reverse proxy and you want to use SAML
|
||||
SAML_BEHIND_PROXY=0
|
||||
|
||||
# A JSON encoded array of role mappings in the form { "saml_role": PARTDB_GROUP_ID, "*": PARTDB_GROUP_ID }
|
||||
# The first match is used, so the order is important! Put the group mapping with the most privileges first.
|
||||
# Please not to only use single quotes to enclose the JSON string
|
||||
|
@ -282,7 +113,7 @@ SAML_SP_ENTITY_ID="https://partdb.changeme.invalid/sp"
|
|||
# The public certificate of the SAML SP
|
||||
SAML_SP_X509_CERT="MIIC..."
|
||||
# The private key of the SAML SP
|
||||
SAML_SP_PRIVATE_KEY="MIIE..."
|
||||
SAMLP_SP_PRIVATE_KEY="MIIE..."
|
||||
|
||||
|
||||
######################################################################################
|
||||
|
@ -295,9 +126,6 @@ DEMO_MODE=0
|
|||
# In that case all URL contains the index.php front controller in URL
|
||||
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.
|
||||
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
|
||||
|
||||
|
@ -308,11 +136,9 @@ 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)
|
||||
DISABLE_YEAR2038_BUG_CHECK=0
|
||||
|
||||
# Set the trusted IPs here, when using an reverse proxy
|
||||
#TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
#TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
#TRUSTED_HOSTS='^(localhost|example\.com)$'
|
||||
|
||||
|
||||
|
@ -321,7 +147,3 @@ DISABLE_YEAR2038_BUG_CHECK=0
|
|||
# postgresql+advisory://db_user:db_password@localhost/db_name
|
||||
LOCK_DSN=flock
|
||||
###< symfony/lock ###
|
||||
|
||||
###> nelmio/cors-bundle ###
|
||||
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
||||
###< nelmio/cors-bundle ###
|
||||
|
|
0
.env.dev
0
.env.dev
|
@ -5,9 +5,5 @@ SYMFONY_DEPRECATIONS_HELPER=999999
|
|||
PANTHER_APP_ENV=panther
|
||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
||||
|
||||
DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"
|
||||
# Doctrine automatically adds an _test suffix to database name in test env
|
||||
#DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db
|
||||
|
||||
# Disable update checks, as tests would fail, when github is not reachable
|
||||
CHECK_FOR_UPDATES=0
|
||||
DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db
|
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -1,2 +0,0 @@
|
|||
# For sh files, always use LF line endings
|
||||
*.sh text eol=lf
|
20
.github/workflows/assets_artifact_build.yml
vendored
20
.github/workflows/assets_artifact_build.yml
vendored
|
@ -19,22 +19,14 @@ jobs:
|
|||
APP_ENV: prod
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
coverage: none
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
|
@ -48,7 +40,7 @@ jobs:
|
|||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v3
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
@ -57,7 +49,7 @@ jobs:
|
|||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
|
@ -77,13 +69,13 @@ jobs:
|
|||
run: zip -r /tmp/partdb_assets.zip public/build/ vendor/
|
||||
|
||||
- name: Upload assets artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Only dependencies and built assets
|
||||
path: /tmp/partdb_assets.zip
|
||||
|
||||
- name: Upload full artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Full Part-DB including dependencies and built assets
|
||||
path: /tmp/partdb_with_assets.zip
|
||||
|
|
54
.github/workflows/codeql-analysis.yml
vendored
Normal file
54
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: '0 14 * * 3'
|
||||
|
||||
jobs:
|
||||
analyse:
|
||||
name: Analyse
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
12
.github/workflows/docker_build.yml
vendored
12
.github/workflows/docker_build.yml
vendored
|
@ -17,11 +17,11 @@ jobs:
|
|||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
# list of Docker images to use as base name for tags
|
||||
images: |
|
||||
|
@ -49,23 +49,23 @@ jobs:
|
|||
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: 'arm64,arm'
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
|
|
77
.github/workflows/docker_frankenphp.yml
vendored
77
.github/workflows/docker_frankenphp.yml
vendored
|
@ -1,77 +0,0 @@
|
|||
name: Docker Image Build (FrankenPHP)
|
||||
|
||||
on:
|
||||
#schedule:
|
||||
# - cron: '0 10 * * *' # everyday at 10am
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
- '!l10n_**'
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
- 'v*.*.*-**'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
# list of Docker images to use as base name for tags
|
||||
images: |
|
||||
partdborg/part-db
|
||||
# Mark the image build from master as latest (as we dont have really releases yet)
|
||||
tags: |
|
||||
type=edge,branch=master
|
||||
type=ref,event=branch,
|
||||
type=ref,event=tag,
|
||||
type=schedule
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
labels: |
|
||||
org.opencontainers.image.source=${{ github.event.repository.clone_url }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.title=Part-DB
|
||||
org.opencontainers.image.description=Part-DB is a web application for managing electronic components and your inventory.
|
||||
org.opencontainers.image.url=https://github.com/Part-DB/Part-DB-server
|
||||
org.opencontainers.image.source=https://github.com/Part-DB/Part-DB-server
|
||||
org.opencontainers.image.authors=Jan Böhmer
|
||||
org.opencontainers.licenses=AGPL-3.0-or-later
|
||||
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: 'arm64,arm'
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile-frankenphp
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
24
.github/workflows/static_analysis.yml
vendored
24
.github/workflows/static_analysis.yml
vendored
|
@ -16,22 +16,14 @@ jobs:
|
|||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
coverage: none
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
|
@ -46,20 +38,18 @@ jobs:
|
|||
|
||||
- name: Lint twig templates
|
||||
run: ./bin/console lint:twig templates --env=prod
|
||||
|
||||
# This causes problems with emtpy language files
|
||||
#- name: Lint translations
|
||||
# run: ./bin/console lint:xliff translations
|
||||
|
||||
- name: Lint translations
|
||||
run: ./bin/console lint:xliff translations
|
||||
|
||||
- name: Check dependencies for security
|
||||
uses: symfonycorp/security-checker-action@v5
|
||||
|
||||
- name: Check doctrine mapping
|
||||
run: ./bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction
|
||||
|
||||
# Use the -d option to raise the max nesting level
|
||||
|
||||
- name: Generate dev container
|
||||
run: php -d xdebug.max_nesting_level=1000 ./bin/console cache:clear --env dev
|
||||
run: ./bin/console cache:clear --env dev
|
||||
|
||||
- name: Run PHPstan
|
||||
run: composer phpstan
|
||||
|
|
57
.github/workflows/tests.yml
vendored
57
.github/workflows/tests.yml
vendored
|
@ -13,13 +13,13 @@ on:
|
|||
jobs:
|
||||
phpunit:
|
||||
name: PHPUnit and coverage Test (PHP ${{ matrix.php-versions }}, ${{ matrix.db-type }})
|
||||
# Ubuntu 20.04 ships MySQL 8.0 which causes problems with login, so we just use ubuntu 18.04 for now...
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: [ '8.1', '8.2', '8.3', '8.4' ]
|
||||
db-type: [ 'mysql', 'sqlite', 'postgres' ]
|
||||
php-versions: [ '7.4', '8.0', '8.1', '8.2' ]
|
||||
db-type: [ 'mysql', 'sqlite' ]
|
||||
|
||||
env:
|
||||
# Note that we set DATABASE URL later based on our db-type matrix value
|
||||
|
@ -27,45 +27,30 @@ jobs:
|
|||
SYMFONY_DEPRECATIONS_HELPER: disabled
|
||||
PHP_VERSION: ${{ matrix.php-versions }}
|
||||
DB_TYPE: ${{ matrix.db-type }}
|
||||
CHECK_FOR_UPDATES: false # Disable update checks for tests
|
||||
|
||||
steps:
|
||||
- name: Set Database env for MySQL
|
||||
run: echo "DATABASE_URL=mysql://root:root@127.0.0.1:3306/partdb?serverVersion=8.0.35" >> $GITHUB_ENV
|
||||
run: echo "DATABASE_URL=mysql://root:root@127.0.0.1:3306/test" >> $GITHUB_ENV
|
||||
if: matrix.db-type == 'mysql'
|
||||
|
||||
- name: Set Database env for SQLite
|
||||
run: echo "DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"" >> $GITHUB_ENV
|
||||
if: matrix.db-type == 'sqlite'
|
||||
|
||||
- name: Set Database env for PostgreSQL
|
||||
run: echo "DATABASE_URL=postgresql://postgres:postgres @127.0.0.1:5432/partdb?serverVersion=14&charset=utf8" >> $GITHUB_ENV
|
||||
if: matrix.db-type == 'postgres'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: pcov
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath
|
||||
|
||||
- name: Start MySQL
|
||||
run: sudo systemctl start mysql.service
|
||||
if: matrix.db-type == 'mysql'
|
||||
|
||||
# Replace the scram-sha-256 with trust for host connections, to avoid password authentication
|
||||
- name: Start PostgreSQL
|
||||
run: |
|
||||
sudo sed -i 's/^\(host.*all.*all.*\)scram-sha-256/\1trust/' /etc/postgresql/14/main/pg_hba.conf
|
||||
sudo systemctl start postgresql.service
|
||||
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres';"
|
||||
if: matrix.db-type == 'postgres'
|
||||
|
||||
#- name: Setup MySQL
|
||||
#- name: Setup MySQL
|
||||
# uses: mirromutth/mysql-action@v1.1
|
||||
# with:
|
||||
# mysql version: 5.7
|
||||
|
@ -78,7 +63,7 @@ jobs:
|
|||
id: composer-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
|
@ -89,7 +74,7 @@ jobs:
|
|||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v3
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
@ -101,7 +86,7 @@ jobs:
|
|||
run: composer install --prefer-dist --no-progress
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
|
@ -113,24 +98,26 @@ jobs:
|
|||
|
||||
- name: Create DB
|
||||
run: php bin/console --env test doctrine:database:create --if-not-exists -n
|
||||
if: matrix.db-type == 'mysql' || matrix.db-type == 'postgres'
|
||||
if: matrix.db-type == 'mysql'
|
||||
|
||||
# Checkinf for existance is not supported for sqlite, so do it without it
|
||||
- name: Create DB
|
||||
run: php bin/console --env test doctrine:database:create -n
|
||||
if: matrix.db-type == 'sqlite'
|
||||
|
||||
- name: Do migrations
|
||||
run: php bin/console --env test doctrine:migrations:migrate -n
|
||||
|
||||
# Use our own custom fixtures loading command to circumvent some problems with reset the autoincrement values
|
||||
|
||||
- name: Load fixtures
|
||||
run: php bin/console --env test partdb:fixtures:load -n
|
||||
run: php bin/console --env test doctrine:fixtures:load -n --purger reset_autoincrement_purger
|
||||
|
||||
- name: Run PHPunit and generate coverage
|
||||
run: ./bin/phpunit --coverage-clover=coverage.xml
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
env_vars: PHP_VERSION,DB_TYPE
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: true
|
||||
|
||||
- name: Test app:clean-attachments
|
||||
run: php bin/console partdb:attachments:clean-unused -n
|
||||
|
@ -144,11 +131,11 @@ jobs:
|
|||
- name: Test check-requirements command
|
||||
run: php bin/console partdb:check-requirements -n
|
||||
|
||||
- name: Test partdb:backup command
|
||||
run: php bin/console partdb:backup -n --full /tmp/test_backup.zip
|
||||
|
||||
- name: Test legacy Part-DB import
|
||||
run: bash .github/assets/legacy_import/test_legacy_import.sh
|
||||
if: matrix.db-type == 'mysql' && matrix.php-versions == '8.2'
|
||||
env:
|
||||
DATABASE_URL: mysql://root:root@localhost:3306/legacy_db
|
||||
|
||||
|
||||
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -43,7 +43,3 @@ yarn-error.log
|
|||
/phpunit.xml
|
||||
.phpunit.result.cache
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
###> phpstan/phpstan ###
|
||||
phpstan.neon
|
||||
###< phpstan/phpstan ###
|
||||
|
|
182
Dockerfile
182
Dockerfile
|
@ -1,64 +1,22 @@
|
|||
ARG BASE_IMAGE=debian:bookworm-slim
|
||||
ARG PHP_VERSION=8.3
|
||||
|
||||
FROM ${BASE_IMAGE} AS base
|
||||
ARG PHP_VERSION
|
||||
FROM debian:bullseye-slim
|
||||
|
||||
# Install needed dependencies for PHP build
|
||||
#RUN apt-get update && apt-get install -y pkg-config curl libcurl4-openssl-dev libicu-dev \
|
||||
# libpng-dev libjpeg-dev libfreetype6-dev gnupg zip libzip-dev libjpeg62-turbo-dev libonig-dev libxslt-dev libwebp-dev vim \
|
||||
# && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt-get update && apt-get -y install \
|
||||
apt-transport-https \
|
||||
lsb-release \
|
||||
ca-certificates \
|
||||
curl \
|
||||
zip \
|
||||
mariadb-client \
|
||||
postgresql-client \
|
||||
RUN apt-get update && apt-get -y install apt-transport-https lsb-release ca-certificates curl zip \
|
||||
&& curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg \
|
||||
&& sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' \
|
||||
&& apt-get update && apt-get upgrade -y \
|
||||
&& apt-get install -y \
|
||||
apache2 \
|
||||
php${PHP_VERSION} \
|
||||
php${PHP_VERSION}-fpm \
|
||||
php${PHP_VERSION}-opcache \
|
||||
php${PHP_VERSION}-curl \
|
||||
php${PHP_VERSION}-gd \
|
||||
php${PHP_VERSION}-mbstring \
|
||||
php${PHP_VERSION}-xml \
|
||||
php${PHP_VERSION}-bcmath \
|
||||
php${PHP_VERSION}-intl \
|
||||
php${PHP_VERSION}-zip \
|
||||
php${PHP_VERSION}-xsl \
|
||||
php${PHP_VERSION}-sqlite3 \
|
||||
php${PHP_VERSION}-mysql \
|
||||
php${PHP_VERSION}-pgsql \
|
||||
gpg \
|
||||
sudo \
|
||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/* \
|
||||
&& apt-get install -y apache2 php8.1 php8.1-fpm 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 gpg \
|
||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
ENV APACHE_CONFDIR /etc/apache2
|
||||
ENV APACHE_ENVVARS $APACHE_CONFDIR/envvars
|
||||
|
||||
# Create workdir and set permissions if directory does not exists
|
||||
&& mkdir -p /var/www/html \
|
||||
&& chown -R www-data:www-data /var/www/html \
|
||||
# delete the "index.html" that installing Apache drops in here
|
||||
&& rm -rvf /var/www/html/*
|
||||
|
||||
# Install node and yarn
|
||||
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 && \
|
||||
curl -sL https://deb.nodesource.com/setup_20.x | bash - && \
|
||||
apt-get update && apt-get install -y \
|
||||
nodejs \
|
||||
yarn \
|
||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
ENV APACHE_CONFDIR=/etc/apache2
|
||||
ENV APACHE_ENVVARS=$APACHE_CONFDIR/envvars
|
||||
RUN mkdir -p /var/www/html && chown -R www-data:www-data /var/www/html
|
||||
|
||||
# Configure apache 2 (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/Dockerfile)
|
||||
# generically convert lines like
|
||||
|
@ -69,94 +27,78 @@ ENV APACHE_ENVVARS=$APACHE_CONFDIR/envvars
|
|||
# so that they can be overridden at runtime ("-e APACHE_RUN_USER=...")
|
||||
RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"; \
|
||||
set -eux; . "$APACHE_ENVVARS"; \
|
||||
# delete the "index.html" that installing Apache drops in here
|
||||
rm -rvf /var/www/html/*; \
|
||||
\
|
||||
# logs should go to stdout / stderr
|
||||
ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \
|
||||
ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log"; \
|
||||
ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \
|
||||
ln -sfT /dev/stderr /var/log/php8.1-fpm.log; \
|
||||
chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR";
|
||||
|
||||
# ---
|
||||
|
||||
FROM scratch AS apache-config
|
||||
ARG PHP_VERSION
|
||||
# Configure php-fpm to log to stdout of the container (stdout of PID 1)
|
||||
# We have to use /proc/1/fd/1 because /dev/stdout or /proc/self/fd/1 does not point to the container stdout (because we use apache as entrypoint)
|
||||
# We also disable the clear_env option to allow the use of environment variables in php-fpm
|
||||
COPY <<EOF /etc/php/${PHP_VERSION}/fpm/pool.d/zz-docker.conf
|
||||
[global]
|
||||
error_log = /proc/1/fd/1
|
||||
|
||||
[www]
|
||||
access.log = /proc/1/fd/1
|
||||
catch_workers_output = yes
|
||||
decorate_workers_output = no
|
||||
clear_env = no
|
||||
EOF
|
||||
# Enable php-fpm
|
||||
RUN a2enmod proxy_fcgi setenvif && a2enconf php8.1-fpm
|
||||
|
||||
# PHP files should be handled by PHP, and should be preferred over any other file type
|
||||
COPY <<EOF /etc/apache2/conf-available/docker-php.conf
|
||||
<FilesMatch \\.php$>
|
||||
SetHandler application/x-httpd-php
|
||||
</FilesMatch>
|
||||
|
||||
DirectoryIndex disabled
|
||||
DirectoryIndex index.php index.html
|
||||
|
||||
<Directory /var/www/>
|
||||
Options -Indexes
|
||||
AllowOverride All
|
||||
</Directory>
|
||||
EOF
|
||||
RUN { \
|
||||
echo '<FilesMatch \.php$>'; \
|
||||
echo '\tSetHandler application/x-httpd-php'; \
|
||||
echo '</FilesMatch>'; \
|
||||
echo; \
|
||||
echo 'DirectoryIndex disabled'; \
|
||||
echo 'DirectoryIndex index.php index.html'; \
|
||||
echo; \
|
||||
echo '<Directory /var/www/>'; \
|
||||
echo '\tOptions -Indexes'; \
|
||||
echo '\tAllowOverride All'; \
|
||||
echo '</Directory>'; \
|
||||
} | tee "$APACHE_CONFDIR/conf-available/docker-php.conf" \
|
||||
&& a2enconf docker-php
|
||||
|
||||
# Enable opcache and configure it recommended for symfony (see https://symfony.com/doc/current/performance.html)
|
||||
COPY <<EOF /etc/php/${PHP_VERSION}/fpm/conf.d/symfony-recommended.ini
|
||||
opcache.memory_consumption=256
|
||||
opcache.max_accelerated_files=20000
|
||||
opcache.validate_timestamp=0
|
||||
# Configure Realpath cache for performance
|
||||
realpath_cache_size=4096K
|
||||
realpath_cache_ttl=600
|
||||
EOF
|
||||
RUN \
|
||||
{ \
|
||||
echo 'opcache.memory_consumption=256'; \
|
||||
echo 'opcache.max_accelerated_files=20000'; \
|
||||
echo 'opcache.validate_timestamp=0'; \
|
||||
# Configure Realpath cache for performance
|
||||
echo 'realpath_cache_size=4096K'; \
|
||||
echo 'realpath_cache_ttl=600'; \
|
||||
} > /etc/php/8.1/fpm/conf.d/symfony-recommended.ini
|
||||
|
||||
# Increase upload limit and enable preloading
|
||||
COPY <<EOF /etc/php/${PHP_VERSION}/fpm/conf.d/partdb.ini
|
||||
upload_max_filesize=256M
|
||||
post_max_size=300M
|
||||
opcache.preload_user=www-data
|
||||
opcache.preload=/var/www/html/config/preload.php
|
||||
log_limit=8096
|
||||
EOF
|
||||
RUN \
|
||||
{ \
|
||||
echo 'upload_max_filesize=256M'; \
|
||||
echo 'post_max_size=300M'; \
|
||||
echo 'opcache.preload_user=www-data'; \
|
||||
echo 'opcache.preload=/var/www/html/config/preload.php'; \
|
||||
} > /etc/php/8.1/fpm/conf.d/partdb.ini
|
||||
|
||||
COPY ./.docker/symfony.conf /etc/apache2/sites-available/symfony.conf
|
||||
# Install node and yarn
|
||||
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
||||
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
|
||||
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash - && apt-get update && apt-get install -y nodejs yarn && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ---
|
||||
# Install composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
FROM base
|
||||
ARG PHP_VERSION
|
||||
|
||||
# Set working dir
|
||||
WORKDIR /var/www/html
|
||||
COPY --from=apache-config / /
|
||||
COPY --chown=www-data:www-data . .
|
||||
|
||||
# Setup apache2
|
||||
RUN a2dissite 000-default.conf && \
|
||||
a2ensite symfony.conf && \
|
||||
# Enable php-fpm
|
||||
a2enmod proxy_fcgi setenvif && \
|
||||
a2enconf php${PHP_VERSION}-fpm && \
|
||||
a2enconf docker-php && \
|
||||
a2enmod rewrite
|
||||
RUN a2dissite 000-default.conf
|
||||
COPY ./.docker/symfony.conf /etc/apache2/sites-available/symfony.conf
|
||||
RUN a2ensite symfony.conf
|
||||
RUN a2enmod rewrite
|
||||
|
||||
# Install composer and yarn dependencies for Part-DB
|
||||
USER www-data
|
||||
RUN composer install -a --no-dev && \
|
||||
composer clear-cache
|
||||
RUN yarn install --network-timeout 600000 && \
|
||||
yarn build && \
|
||||
yarn cache clean && \
|
||||
rm -rf node_modules/
|
||||
RUN composer install -a --no-dev && composer clear-cache
|
||||
RUN yarn install --network-timeout 600000 && yarn build && yarn cache clean && rm -rf node_modules/
|
||||
|
||||
# Use docker env to output logs to stdout
|
||||
ENV APP_ENV=docker
|
||||
|
@ -164,12 +106,10 @@ ENV DATABASE_URL="sqlite:///%kernel.project_dir%/uploads/app.db"
|
|||
|
||||
USER root
|
||||
|
||||
# Replace the php version placeholder in the entry point, with our php version
|
||||
RUN sed -i "s/PHP_VERSION/${PHP_VERSION}/g" ./.docker/partdb-entrypoint.sh
|
||||
|
||||
# Copy entrypoint and apache2-foreground to /usr/local/bin and make it executable
|
||||
RUN install ./.docker/partdb-entrypoint.sh /usr/local/bin && \
|
||||
install ./.docker/apache2-foreground /usr/local/bin
|
||||
# Copy entrypoint to /usr/local/bin and make it executable
|
||||
RUN cp ./.docker/partdb-entrypoint.sh /usr/local/bin/partdb-entrypoint.sh && chmod +x /usr/local/bin/partdb-entrypoint.sh
|
||||
# Copy apache2-foreground to /usr/local/bin and make it executable
|
||||
RUN cp ./.docker/apache2-foreground /usr/local/bin/apache2-foreground && chmod +x /usr/local/bin/apache2-foreground
|
||||
ENTRYPOINT ["partdb-entrypoint.sh"]
|
||||
CMD ["apache2-foreground"]
|
||||
|
||||
|
@ -177,4 +117,4 @@ CMD ["apache2-foreground"]
|
|||
STOPSIGNAL SIGWINCH
|
||||
|
||||
EXPOSE 80
|
||||
VOLUME ["/var/www/html/uploads", "/var/www/html/public/media"]
|
||||
VOLUME ["/var/www/html/uploads", "/var/www/html/public/media"]
|
|
@ -1,101 +0,0 @@
|
|||
FROM dunglas/frankenphp:1-php8.3 AS frankenphp_upstream
|
||||
|
||||
RUN apt-get update && apt-get -y install \
|
||||
curl \
|
||||
ca-certificates \
|
||||
mariadb-client \
|
||||
postgresql-client \
|
||||
file \
|
||||
acl \
|
||||
git \
|
||||
gettext \
|
||||
gnupg \
|
||||
zip \
|
||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
# Install node and yarn
|
||||
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 && \
|
||||
curl -sL https://deb.nodesource.com/setup_20.x | bash - && \
|
||||
apt-get update && apt-get install -y \
|
||||
nodejs yarn \
|
||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install PHP
|
||||
RUN set -eux; \
|
||||
install-php-extensions \
|
||||
@composer \
|
||||
apcu \
|
||||
intl \
|
||||
opcache \
|
||||
zip \
|
||||
pdo_mysql \
|
||||
pdo_sqlite \
|
||||
pdo_pgsql \
|
||||
gd \
|
||||
bcmath \
|
||||
xsl \
|
||||
;
|
||||
|
||||
# Copy config files for php and caddy
|
||||
COPY --link .docker/frankenphp/conf.d/app.ini $PHP_INI_DIR/conf.d/
|
||||
COPY --chmod=755 .docker/frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
|
||||
COPY --link .docker/frankenphp/Caddyfile /etc/caddy/Caddyfile
|
||||
COPY --link .docker/frankenphp/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/
|
||||
COPY --link .docker/frankenphp/worker.Caddyfile /etc/caddy/worker.Caddyfile
|
||||
ENV FRANKENPHP_CONFIG="import worker.Caddyfile"
|
||||
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||
|
||||
# Install composer
|
||||
ENV COMPOSER_ALLOW_SUPERUSER=1
|
||||
#COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# Create workdir and set permissions if directory does not exists
|
||||
WORKDIR /app
|
||||
|
||||
# prevent the reinstallation of vendors at every changes in the source code
|
||||
COPY --link composer.* symfony.* ./
|
||||
RUN set -eux; \
|
||||
composer install --no-cache --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress
|
||||
|
||||
# copy sources
|
||||
COPY --link . ./
|
||||
|
||||
# Install composer and yarn dependencies for Part-DB
|
||||
RUN set -eux; \
|
||||
mkdir -p var/cache var/log; \
|
||||
composer dump-autoload --classmap-authoritative --no-dev; \
|
||||
composer dump-env prod; \
|
||||
composer run-script --no-dev post-install-cmd; \
|
||||
chmod +x bin/console; sync;
|
||||
|
||||
RUN yarn install --network-timeout 600000 && \
|
||||
yarn build && \
|
||||
yarn cache clean && \
|
||||
rm -rf node_modules/
|
||||
|
||||
# Use docker env to output logs to stdout
|
||||
ENV APP_ENV=docker
|
||||
ENV DATABASE_URL="sqlite:///%kernel.project_dir%/uploads/app.db"
|
||||
|
||||
USER root
|
||||
|
||||
ENTRYPOINT ["docker-entrypoint"]
|
||||
CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]
|
||||
|
||||
# https://httpd.apache.org/docs/2.4/stopping.html#gracefulstop
|
||||
STOPSIGNAL SIGWINCH
|
||||
|
||||
VOLUME ["/var/www/html/uploads", "/var/www/html/public/media"]
|
||||
|
||||
HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1
|
||||
|
||||
# See https://caddyserver.com/docs/conventions#file-locations for details
|
||||
ENV XDG_CONFIG_HOME /config
|
||||
ENV XDG_DATA_HOME /data
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
EXPOSE 443/udp
|
||||
EXPOSE 2019
|
151
README.md
151
README.md
|
@ -1,164 +1,127 @@
|
|||
[](https://scrutinizer-ci.com/g/Part-DB/Part-DB-symfony/?branch=master)
|
||||

|
||||

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

|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
[](https://part-db.crowdin.com/part-db)
|
||||
|
||||
**[Documentation](https://docs.part-db.de/)** | **[Demo](https://demo.part-db.de/)** | **[Docker Image](https://hub.docker.com/r/jbtronics/part-db1)**
|
||||
**[Documentation](https://docs.part-db.de/)** | **[Demo](https://part-db.herokuapp.com)** | **[Docker Image](https://hub.docker.com/r/jbtronics/part-db1)**
|
||||
|
||||
# Part-DB
|
||||
|
||||
Part-DB is an Open-Source inventory management system for your electronic components.
|
||||
Part-DB is an Open-Source inventory managment system for your electronic components.
|
||||
It is installed on a web server and so can be accessed with any browser without the need to install additional software.
|
||||
|
||||
The version in this repository is a complete rewrite of the legacy [Part-DB](https://github.com/Part-DB/Part-DB)
|
||||
(Version < 1.0) based on a modern framework and is the recommended version to use.
|
||||
The version in this Repository is a complete rewrite of the legacy [Part-DB](https://github.com/Part-DB/Part-DB) (Version < 1.0) based on a modern framework.
|
||||
Currently, it is still missing some (minor) features from the old version (see [UPGRADE.md](https://docs.part-db.de/upgrade_legacy.html)) for more details, but also many huge improvements and advantages compared to the old version.
|
||||
If you start completely new with Part-DB it is recommended that you use the version from this repository, as it is actively developed.
|
||||
|
||||
If you find a bug, please open an [Issue on GitHub,](https://github.com/Part-DB/Part-DB-server/issues) so it can be fixed
|
||||
for everybody.
|
||||
If you find a bug, please open an [Issue on Github](https://github.com/Part-DB/Part-DB-server/issues) so it can be fixed for everybody.
|
||||
|
||||
## Demo
|
||||
|
||||
If you want to test Part-DB without installing it, you can use [this](https://demo.part-db.de/) Heroku instance.
|
||||
(Or this link for the [German Version](https://demo.part-db.de/de/)).
|
||||
If you want to test Part-DB without installing it, you can use [this](https://part-db.herokuapp.com) Heroku instance.
|
||||
(Or this link for the [German Version](https://part-db.herokuapp.com/de/)).
|
||||
|
||||
You can log in with username: *user* and password: *user*.
|
||||
|
||||
Every change to the master branch gets automatically deployed, so it represents the current development progress and is
|
||||
may not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading
|
||||
the page
|
||||
maybe not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading the page
|
||||
for the first time.
|
||||
|
||||
<img src="https://github.com/Part-DB/Part-DB-server/raw/master/docs/assets/readme/part_info.png">
|
||||
<img src="https://github.com/Part-DB/Part-DB-server/raw/master/docs/assets/readme/parts_list.png">
|
||||
|
||||
## Features
|
||||
|
||||
* Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer,
|
||||
and multiple store locations and price information. Parts can be grouped using tags. You can associate various files
|
||||
like datasheets or pictures with the parts.
|
||||
* Multi-language support (currently German, English, Russian, Japanese, French, Czech, Danish, and Chinese)
|
||||
* Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer
|
||||
and multiple store locations and price information. Parts can be grouped using tags. You can associate various files like datasheets or pictures with the parts.
|
||||
* Multi-Language support (currently German, English, Russian, Japanese and French (experimental))
|
||||
* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner
|
||||
* User system with groups and detailed (fine granular) permissions.
|
||||
Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups.
|
||||
Password reset via email can be set up.
|
||||
* Optional support for single sign-on (SSO) via SAML (using an intermediate service
|
||||
like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server)
|
||||
* Import/Export system for parts and data structure. BOM import for projects from KiCAD is supported.
|
||||
* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build
|
||||
this project and directly withdraw all components needed from DB
|
||||
* Event log: Track what changes happen to your inventory, track which user does what. Revert your parts to older
|
||||
versions.
|
||||
* Responsive design: You can use Part-DB on your PC, your tablet, and your smartphone using the same interface.
|
||||
* MySQL, SQLite and PostgreSQL are supported as database backends
|
||||
* User system with groups and detailed (fine granular) permissions.
|
||||
Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups. Password reset via email can be setuped.
|
||||
* Optional support for single sign-on (SSO) via SAML (using an intermediate service like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server)
|
||||
* Import/Export system for parts and datastructure. BOM import for projects from KiCAD is supported.
|
||||
* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build this project and directly withdraw all components needed from DB
|
||||
* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older versions.
|
||||
* Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface.
|
||||
* MySQL and SQLite supported as database backends
|
||||
* Support for rich text descriptions and comments in parts
|
||||
* Support for multiple currencies and automatic update of exchange rates supported
|
||||
* Powerful search and filter function, including parametric search (search for parts according to some specifications)
|
||||
* Automatic thumbnail generation for pictures
|
||||
* Use cloud providers (like Octopart, Digikey, Farnell, LCSC or TME) to automatically get part information, datasheets, and
|
||||
prices for parts
|
||||
* API to access Part-DB from other applications/scripts
|
||||
* [Integration with KiCad](https://docs.part-db.de/usage/eda_integration.html): Use Part-DB as the central datasource for your
|
||||
KiCad and see available parts from Part-DB directly inside KiCad.
|
||||
|
||||
With these features, Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory,
|
||||
or maker spaces, where many users should have (controlled) access to the shared inventory.
|
||||
|
||||
With these features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory,
|
||||
or makerspaces, where many users have should have (controlled) access to the shared inventory.
|
||||
|
||||
Part-DB is also used by small companies and universities for managing their inventory.
|
||||
|
||||
## Requirements
|
||||
|
||||
* A **web server** (like Apache2 or nginx) that is capable of
|
||||
running [Symfony 6](https://symfony.com/doc/current/reference/requirements.html),
|
||||
this includes a minimum PHP version of **PHP 8.1**
|
||||
* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.4) database server, or **PostgreSQL** 10+ if you do not want to use SQLite.
|
||||
* Shell access to your server is highly recommended!
|
||||
* For building the client-side assets **yarn** and **nodejs** (>= 18.0) is needed.
|
||||
|
||||
* A **web server** (like Apache2 or nginx) that is capable of running [Symfony 5](https://symfony.com/doc/current/reference/requirements.html),
|
||||
this includes a minimum PHP version of **PHP 7.4**
|
||||
* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.2.2) database server if you do not want to use SQLite.
|
||||
* Shell access to your server is highly suggested!
|
||||
* For building the client side assets **yarn** and **nodejs** is needed.
|
||||
|
||||
## Installation
|
||||
If you want to upgrade your legacy (< 1.0.0) version of Part-DB to this version, please read [this](https://docs.part-db.de/upgrade_legacy.html) first.
|
||||
|
||||
If you want to upgrade your legacy (< 1.0.0) version of Part-DB to this version, please
|
||||
read [this](https://docs.part-db.de/upgrade_legacy.html) first.
|
||||
*Hint:* A docker image is available under [jbtronics/part-db1](https://hub.docker.com/r/jbtronics/part-db1). How to set up Part-DB via docker is described [here](https://docs.part-db.de/installation/installation_docker.html).
|
||||
|
||||
*Hint:* A docker image is available under [jbtronics/part-db1](https://hub.docker.com/r/jbtronics/part-db1). How to set
|
||||
up Part-DB via docker is described [here](https://docs.part-db.de/installation/installation_docker.html).
|
||||
|
||||
**Below you find a very rough outline of the installation process, see [here](https://docs.part-db.de/installation/)
|
||||
for a detailed guide on how to install Part-DB.**
|
||||
**Below you find some very rough outline of the installation process, see [here](https://docs.part-db.de/installation/) for a detailed guide how to install Part-DB.**
|
||||
|
||||
1. Copy or clone this repository into a folder on your server.
|
||||
2. Configure your webserver to serve from the `public/` folder.
|
||||
See [here](https://symfony.com/doc/current/setup/web_server_configuration.html)
|
||||
for additional information.
|
||||
2. Configure your webserver to serve from the `public/` folder. See [here](https://symfony.com/doc/current/setup/web_server_configuration.html)
|
||||
for additional information.
|
||||
3. Copy the global config file `cp .env .env.local` and edit `.env.local`:
|
||||
* Change the line `APP_ENV=dev` to `APP_ENV=prod`
|
||||
* If you do not want to use SQLite, change the value of `DATABASE_URL=` to your needs (
|
||||
see [here](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url))
|
||||
for the format.
|
||||
In bigger instances with concurrent accesses, MySQL is more performant. This can not be changed easily later, so
|
||||
choose wisely.
|
||||
* If you do not want to use SQLite, change the value of `DATABASE_URL=` to your needs (see [here](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url)) for the format.
|
||||
In bigger instances with concurrent accesses, MySQL is more performant. This can not be changed easily later, so choose wisely.
|
||||
4. Install composer dependencies and generate autoload files: `composer install -o --no-dev`
|
||||
5. Install client side dependencies and build it: `yarn install` and `yarn build`
|
||||
6. _Optional_ (speeds up first load): Warmup cache: `php bin/console cache:warmup`
|
||||
7. Upgrade database to new scheme (or create it, when it was empty): `php bin/console doctrine:migrations:migrate` and
|
||||
follow the instructions given. During the process the password for the admin is user is shown. Copy it. **Caution**:
|
||||
These steps tamper with your database and could potentially destroy it. So make sure to make a backup of your
|
||||
database.
|
||||
8. You can configure Part-DB via `config/parameters.yaml`. You should check if settings match your expectations after
|
||||
you installed/upgraded Part-DB. Check if `partdb.default_currency` matches your mainly used currency (this can not be
|
||||
changed after creating price information).
|
||||
Run `php bin/console cache:clear` when you change something.
|
||||
9. Access Part-DB in your browser (under the URL you put it) and log in with user *admin*. Password is the one outputted
|
||||
during DB setup.
|
||||
If you can not remember the password, set a new one with `php bin/console app:set-password admin`. You can create
|
||||
new users with the admin user and start using Part-DB.
|
||||
5. If you have put Part-DB into a sub-directory on your server (like `part-db/`), you have to edit the file
|
||||
`webpack.config.js` and uncomment the lines (remove the `//` before the lines) `.setPublicPath('/part-db/build')` (line 43) and
|
||||
`.setManifestKeyPrefix('build/')` (line 44). You have to replace `/part-db` with your own path on line 44.
|
||||
6. Install client side dependencies and build it: `yarn install` and `yarn build`
|
||||
7. _Optional_ (speeds up first load): Warmup cache: `php bin/console cache:warmup`
|
||||
8. Upgrade database to new scheme (or create it, when it was empty): `php bin/console doctrine:migrations:migrate` and follow the instructions given. During the process the password for the admin is user is shown. Copy it. **Caution**: This steps tamper with your database and could potentially destroy it. So make sure to make a backup of your database.
|
||||
9. You can configure Part-DB via `config/parameters.yaml`. You should check if settings match your expectations, after you installed/upgraded Part-DB. Check if `partdb.default_currency` matches your mainly used currency (this can not be changed after creating price informations).
|
||||
Run `php bin/console cache:clear` when you changed something.
|
||||
10. Access Part-DB in your browser (under the URL you put it) and login with user *admin*. Password is the one outputted during DB setup.
|
||||
If you can not remember the password, set a new one with `php bin/console app:set-password admin`. You can create new users with the admin user and start using Part-DB.
|
||||
|
||||
When you want to upgrade to a newer version, then just copy the new files into the folder
|
||||
and repeat the steps 4. to 7.
|
||||
|
||||
Normally a random password is generated when the admin user is created during initial database creation,
|
||||
however, you can set the initial admin password, by setting the `INITIAL_ADMIN_PW` env var.
|
||||
Normally a random password is generated when the admin user is created during inital database creation,
|
||||
however you can set the inital admin password, by setting the `INITIAL_ADMIN_PW` env var.
|
||||
|
||||
You can configure Part-DB to your needs by changing environment variables in the `.env.local` file.
|
||||
You can configure Part-DB to your needs by changing environment variables in the `.env.local` file.
|
||||
See [here](https://docs.part-db.de/configuration.html) for more information.
|
||||
|
||||
### Reverse proxy
|
||||
|
||||
If you are using a reverse proxy, you have to ensure that the proxies set the `X-Forwarded-*` headers correctly, or you
|
||||
will get HTTP/HTTPS mixup and wrong hostnames.
|
||||
If the reverse proxy is on a different server (or it cannot access Part-DB via localhost) you have to set
|
||||
the `TRUSTED_PROXIES` env variable to match your reverse proxy's IP address (or IP block). You can do this in
|
||||
your `.env.local` or (when using docker) in your `docker-compose.yml` file.
|
||||
If you are using a reverse proxy, you have to ensure that the proxies sets the `X-Forwarded-*` headers correctly, or you will get HTTP/HTTPS mixup and wrong hostnames.
|
||||
If the reverse proxy is on a different server (or it cannot access Part-DB via localhost) you have to set the `TRUSTED_PROXIES` env variable to match your reverse proxies IP-address (or IP block). You can do this in your `.env.local` or (when using docker) in your `docker-compose.yml` file.
|
||||
|
||||
## Donate for development
|
||||
|
||||
If you want to donate to the Part-DB developer, see the sponsor button in the top bar (next to the repo name).
|
||||
There you will find various methods to support development on a monthly or a one-time base.
|
||||
There you will find various methods to support development on a monthly or a one time base.
|
||||
|
||||
## Built with
|
||||
|
||||
* [Symfony 5](https://symfony.com/): The main framework used for the serverside PHP
|
||||
* [Bootstrap 5](https://getbootstrap.com/) and [Bootswatch](https://bootswatch.com/): Used as website theme
|
||||
* [Fontawesome](https://fontawesome.com/): Used as icon set
|
||||
* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend
|
||||
Javascript
|
||||
* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend Javascript
|
||||
|
||||
## Authors
|
||||
* **Jan Böhmer** - *Inital work* - [Github](https://github.com/jbtronics/)
|
||||
|
||||
* **Jan Böhmer** - *Initial work* - [GitHub](https://github.com/jbtronics/)
|
||||
|
||||
See also the list of [contributors](https://github.com/Part-DB/Part-DB-server/graphs/contributors) who participated in
|
||||
this project.
|
||||
See also the list of [contributors](https://github.com/Part-DB/Part-DB-server/graphs/contributors) who participated in this project.
|
||||
|
||||
Based on the original Part-DB by Christoph Lechner and K. Jacobs
|
||||
|
||||
## License
|
||||
|
||||
Part-DB is licensed under the GNU Affero General Public License v3.0 (or at your opinion any later).
|
||||
This mostly means that you can use Part-DB for whatever you want (even use it commercially)
|
||||
as long as you publish the source code for every change you make under the AGPL, too.
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.17.1
|
||||
1.3.3
|
||||
|
|
3
assets/bootstrap.js
vendored
3
assets/bootstrap.js
vendored
|
@ -4,7 +4,8 @@ import { startStimulusApp } from '@symfony/stimulus-bridge';
|
|||
export const app = startStimulusApp(require.context(
|
||||
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
|
||||
true,
|
||||
/\.[jt]sx?$/
|
||||
/\.(j|t)sx?$/
|
||||
));
|
||||
|
||||
// register any custom, 3rd party controllers here
|
||||
// app.register('some_controller_name', SomeImportedController);
|
||||
|
|
|
@ -181,8 +181,7 @@ Editor.defaultConfig = {
|
|||
'DejaVu Serif, serif',
|
||||
'Helvetica, Arial, sans-serif',
|
||||
'Times New Roman, Times, serif',
|
||||
'Courier New, Courier, monospace',
|
||||
'Unifont, monospace',
|
||||
'Courier New, Courier, monospace'
|
||||
],
|
||||
supportAllValues: true
|
||||
},
|
||||
|
|
|
@ -76,7 +76,6 @@ const PLACEHOLDERS = [
|
|||
['[[FOOTPRINT_FULL]]', 'Footprint (Full path)'],
|
||||
['[[MASS]]', 'Mass'],
|
||||
['[[MPN]]', 'Manufacturer Product Number (MPN)'],
|
||||
['[[IPN]]', 'Internal Part Number (IPN)'],
|
||||
['[[TAGS]]', 'Tags'],
|
||||
['[[M_STATUS]]', 'Manufacturing status'],
|
||||
['[[DESCRIPTION]]', 'Description'],
|
||||
|
@ -85,9 +84,6 @@ const PLACEHOLDERS = [
|
|||
['[[COMMENT_T]]', 'Comment (plain text)'],
|
||||
['[[LAST_MODIFIED]]', 'Last modified datetime'],
|
||||
['[[CREATION_DATE]]', 'Creation datetime'],
|
||||
['[[IPN_BARCODE_QR]]', 'IPN as QR code'],
|
||||
['[[IPN_BARCODE_C128]]', 'IPN as Code 128 barcode'],
|
||||
['[[IPN_BARCODE_C39]]', 'IPN as Code 39 barcode'],
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -128,8 +124,6 @@ const PLACEHOLDERS = [
|
|||
['[[BARCODE_QR]]', 'QR code linking to this element'],
|
||||
['[[BARCODE_C128]]', 'Code 128 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'],
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -39,7 +39,6 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
|
|||
'Footprint (Full path)': 'Footprint (Vollständiger Pfad)',
|
||||
'Mass': 'Gewicht',
|
||||
'Manufacturer Product Number (MPN)': 'Hersteller Produktnummer (MPN)',
|
||||
'Internal Part Number (IPN)': 'Internal Part Number (IPN)',
|
||||
'Tags': 'Tags',
|
||||
'Manufacturing status': 'Herstellungsstatus',
|
||||
'Description': 'Beschreibung',
|
||||
|
@ -48,9 +47,6 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
|
|||
'Comment (plain text)': 'Kommentar (Nur-Text)',
|
||||
'Last modified datetime': 'Zuletzt geändert',
|
||||
'Creation datetime': 'Erstellt',
|
||||
'IPN as QR code': 'IPN als QR Code',
|
||||
'IPN as Code 128 barcode': 'IPN als Code 128 Barcode',
|
||||
'IPN as Code 39 barcode': 'IPN als Code 39 Barcode',
|
||||
|
||||
'Lot ID': 'Lot ID',
|
||||
'Lot name': 'Lot Name',
|
||||
|
@ -69,8 +65,6 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
|
|||
'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 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',
|
||||
'Name': 'Name',
|
||||
|
|
|
@ -18,118 +18,43 @@
|
|||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
import Darkmode from "darkmode-js/src";
|
||||
import "darkmode-js"
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
_darkmode;
|
||||
|
||||
connect() {
|
||||
this.setMode(this.getMode());
|
||||
document.querySelectorAll('input[name="darkmode"]').forEach((radio) => {
|
||||
radio.addEventListener('change', this._radioChanged.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for the change of radio buttons
|
||||
* @private
|
||||
*/
|
||||
_radioChanged(event) {
|
||||
const new_mode = this.getSelectedMode();
|
||||
this.setMode(new_mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current mode from the local storage
|
||||
* @return {'dark', 'light', 'auto'}
|
||||
*/
|
||||
getMode() {
|
||||
return localStorage.getItem('darkmode') ?? 'auto';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode in the local storage and apply it and change the state of the radio buttons
|
||||
* @param mode
|
||||
*/
|
||||
setMode(mode) {
|
||||
if (mode !== 'dark' && mode !== 'light' && mode !== 'auto') {
|
||||
console.warn('Invalid darkmode mode: ' + mode);
|
||||
mode = 'auto';
|
||||
}
|
||||
|
||||
localStorage.setItem('darkmode', mode);
|
||||
|
||||
this.setButtonMode(mode);
|
||||
|
||||
if (mode === 'auto') {
|
||||
this._setDarkmodeAuto();
|
||||
} else if (mode === 'dark') {
|
||||
this._enableDarkmode();
|
||||
} else if (mode === 'light') {
|
||||
this._disableDarkmode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected mode via the radio buttons
|
||||
* @return {'dark', 'light', 'auto'}
|
||||
*/
|
||||
getSelectedMode() {
|
||||
return document.querySelector('input[name="darkmode"]:checked').value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state of the radio buttons
|
||||
* @param mode
|
||||
*/
|
||||
setButtonMode(mode) {
|
||||
document.querySelector('input[name="darkmode"][value="' + mode + '"]').checked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable darkmode by adding the data-bs-theme="dark" to the html tag
|
||||
* @private
|
||||
*/
|
||||
_enableDarkmode() {
|
||||
//Add data-bs-theme="dark" to the html tag
|
||||
document.documentElement.setAttribute('data-bs-theme', 'dark');
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable darkmode by adding the data-bs-theme="light" to the html tag
|
||||
* @private
|
||||
*/
|
||||
_disableDarkmode() {
|
||||
//Set data-bs-theme to light
|
||||
document.documentElement.setAttribute('data-bs-theme', 'light');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the darkmode to auto and enable/disable it depending on the system settings, also add
|
||||
* an event listener to change the darkmode if the system settings change
|
||||
* @private
|
||||
*/
|
||||
_setDarkmodeAuto() {
|
||||
if (this.getMode() !== 'auto') {
|
||||
if (typeof window.getComputedStyle(document.body).mixBlendMode == 'undefined') {
|
||||
console.warn("The browser does not support mix blend mode. Darkmode will not work.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
this._enableDarkmode();
|
||||
} else {
|
||||
this._disableDarkmode();
|
||||
try {
|
||||
const darkmode = new Darkmode();
|
||||
this._darkmode = darkmode;
|
||||
|
||||
//Unhide darkmode button
|
||||
this._showWidget();
|
||||
|
||||
//Set the switch according to our current darkmode state
|
||||
const toggler = document.getElementById("toggleDarkmode");
|
||||
toggler.checked = darkmode.isActivated();
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
||||
console.log('Prefered color scheme changed to ' + event.matches ? 'dark' : 'light');
|
||||
this._setDarkmodeAuto();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if darkmode is activated
|
||||
* @return {boolean}
|
||||
*/
|
||||
isDarkmodeActivated() {
|
||||
return document.documentElement.getAttribute('data-bs-theme') === 'dark';
|
||||
_showWidget() {
|
||||
this.element.classList.remove('hidden');
|
||||
}
|
||||
|
||||
toggleDarkmode() {
|
||||
this._darkmode.toggle();
|
||||
}
|
||||
}
|
|
@ -88,8 +88,5 @@ export default class extends Controller {
|
|||
} else {
|
||||
this.hideSidebar();
|
||||
}
|
||||
|
||||
//Hide the tootip on the button
|
||||
this._toggle_button.blur();
|
||||
}
|
||||
}
|
|
@ -20,26 +20,16 @@
|
|||
'use strict';
|
||||
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import { Marked } from "marked";
|
||||
import { mangle } from "marked-mangle";
|
||||
import { gfmHeadingId } from "marked-gfm-heading-id";
|
||||
import { marked } from "marked";
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
import "../../css/app/markdown.css";
|
||||
|
||||
export default class MarkdownController extends Controller {
|
||||
|
||||
static _marked = new Marked([
|
||||
{
|
||||
gfm: true,
|
||||
},
|
||||
gfmHeadingId(),
|
||||
mangle(),
|
||||
])
|
||||
;
|
||||
export default class extends Controller {
|
||||
|
||||
connect()
|
||||
{
|
||||
this.configureMarked();
|
||||
this.render();
|
||||
|
||||
//Dispatch an event that we are now finished
|
||||
|
@ -53,7 +43,7 @@ export default class MarkdownController extends Controller {
|
|||
let raw = this.element.dataset['markdown'];
|
||||
|
||||
//Apply purified parsed markdown
|
||||
this.element.innerHTML = DOMPurify.sanitize(MarkdownController._marked.parse(this.unescapeHTML(raw)));
|
||||
this.element.innerHTML = DOMPurify.sanitize(marked(this.unescapeHTML(raw)));
|
||||
|
||||
for(let a of this.element.querySelectorAll('a')) {
|
||||
//Mark all links as external
|
||||
|
@ -89,23 +79,10 @@ export default class MarkdownController extends Controller {
|
|||
/**
|
||||
* Configure the marked parser
|
||||
*/
|
||||
/*static newMarked()
|
||||
configureMarked()
|
||||
{
|
||||
const marked = new Marked([
|
||||
{
|
||||
gfm: true,
|
||||
},
|
||||
gfmHeadingId(),
|
||||
mangle(),
|
||||
])
|
||||
;
|
||||
|
||||
marked.use(mangle());
|
||||
marked.use(gfmHeadingId({
|
||||
}));
|
||||
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
});
|
||||
}*/
|
||||
}
|
||||
}
|
|
@ -23,12 +23,6 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
|
|||
import '../../css/components/tom-select_extensions.css';
|
||||
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 {
|
||||
_tomSelect;
|
||||
|
||||
|
@ -52,12 +46,6 @@ export default class extends Controller {
|
|||
}
|
||||
return '<div>' + escape(data.label) + '</div>';
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
'autoselect_typed': {},
|
||||
'click_to_edit': {},
|
||||
'clear_button': {},
|
||||
"restore_on_backspace": {}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -53,7 +53,6 @@ export default class extends Controller {
|
|||
|
||||
const config = {
|
||||
language: language,
|
||||
licenseKey: "GPL",
|
||||
}
|
||||
|
||||
const watchdog = new EditorWatchdog();
|
||||
|
@ -71,9 +70,7 @@ export default class extends Controller {
|
|||
editor_div.classList.add(...new_classes.split(","));
|
||||
}
|
||||
|
||||
//This return is important! Otherwise we get mysterious errors in the console
|
||||
//See: https://github.com/ckeditor/ckeditor5/issues/5897#issuecomment-628471302
|
||||
return editor;
|
||||
console.log(editor);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
|
|
|
@ -61,7 +61,7 @@ export default class extends Controller {
|
|||
|
||||
if(!prototype) {
|
||||
console.warn("Prototype is not set, we cannot create a new element. This is most likely due to missing permissions.");
|
||||
bootbox.alert("You do not have the permissions to create a new element. (No protoype element is set)");
|
||||
bootbox.alert("You do not have the permsissions to create a new element. (No protoype element is set)");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -75,49 +75,13 @@ export default class extends Controller {
|
|||
|
||||
//Insert new html after the last child element
|
||||
//If the table has a tbody, insert it there
|
||||
//Afterwards return the newly created row
|
||||
if(targetTable.tBodies[0]) {
|
||||
targetTable.tBodies[0].insertAdjacentHTML('beforeend', newElementStr);
|
||||
return targetTable.tBodies[0].lastElementChild;
|
||||
} else { //Otherwise just insert it
|
||||
targetTable.insertAdjacentHTML('beforeend', newElementStr);
|
||||
return targetTable.lastElementChild;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This action opens a file dialog to select multiple files and then creates a new element for each file, where
|
||||
* the file is assigned to the input field.
|
||||
* This should only be used for attachments collection types
|
||||
* @param event
|
||||
*/
|
||||
uploadMultipleFiles(event) {
|
||||
//Open a file dialog to select multiple files
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.multiple = true;
|
||||
input.click();
|
||||
|
||||
input.addEventListener('change', (event) => {
|
||||
//Create a element for each file
|
||||
|
||||
for (let i = 0; i < input.files.length; i++) {
|
||||
const file = input.files[i];
|
||||
|
||||
const newElement = this.createElement(event);
|
||||
const rowInput = newElement.querySelector("input[type='file']");
|
||||
|
||||
//We can not directly assign the file to the input, so we have to create a new DataTransfer object
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(file);
|
||||
|
||||
rowInput.files = dataTransfer.files;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to createEvent Pricedetails need some special handling to fill min amount
|
||||
* @param event
|
||||
|
|
|
@ -24,25 +24,18 @@ import 'datatables.net-bs5/css/dataTables.bootstrap5.css'
|
|||
import 'datatables.net-buttons-bs5/css/buttons.bootstrap5.css'
|
||||
import 'datatables.net-fixedheader-bs5/css/fixedHeader.bootstrap5.css'
|
||||
import 'datatables.net-responsive-bs5/css/responsive.bootstrap5.css';
|
||||
|
||||
//Use our own styles for the select extension which fit the bootstrap theme better
|
||||
//import 'datatables.net-select-bs5/css/select.bootstrap5.css';
|
||||
import '../../../css/components/datatables_select_bs5.css';
|
||||
import 'datatables.net-select-bs5/css/select.bootstrap5.css';
|
||||
|
||||
//JS
|
||||
import 'datatables.net-bs5';
|
||||
import 'datatables.net-buttons-bs5';
|
||||
import 'datatables.net-buttons/js/buttons.colVis.js';
|
||||
import 'datatables.net-fixedheader-bs5';
|
||||
import 'datatables.net-select-bs5';
|
||||
import 'datatables.net-colreorder-bs5';
|
||||
import 'datatables.net-responsive-bs5';
|
||||
import '../../../js/lib/datatables';
|
||||
|
||||
//import 'datatables.net-select-bs5';
|
||||
//Use the local version containing the fix for the select extension
|
||||
import '../../../js/lib/dataTables.select.mjs';
|
||||
|
||||
|
||||
const EVENT_DT_LOADED = 'dt:loaded';
|
||||
|
||||
export default class extends Controller {
|
||||
|
@ -72,19 +65,12 @@ export default class extends Controller {
|
|||
localStorage.setItem( this.getStateSaveKey(), JSON.stringify(data) );
|
||||
}
|
||||
|
||||
stateLoadCallback() {
|
||||
const json = localStorage.getItem(this.getStateSaveKey());
|
||||
if(json === null || json === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = JSON.parse(json);
|
||||
stateLoadCallback(settings) {
|
||||
const data = JSON.parse( localStorage.getItem(this.getStateSaveKey()) );
|
||||
|
||||
if (data) {
|
||||
//Do not save the start value (current page), as we want to always start at the first page on a page reload
|
||||
delete data.start;
|
||||
//Reset the data length to the default value by deleting the length property
|
||||
delete data.length;
|
||||
data.start = 0;
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -102,19 +88,6 @@ export default class extends Controller {
|
|||
//Add url info, as the one available in the history is not enough, as Turbo may have not changed it yet
|
||||
settings.url = this.element.dataset.dtUrl;
|
||||
|
||||
//Add initial_order info to the settings, so that the order on the initial page load is the one saved in the state
|
||||
const saved_state = this.stateLoadCallback();
|
||||
if (saved_state !== null) {
|
||||
const raw_order = saved_state.order;
|
||||
|
||||
settings.initial_order = raw_order.map((order) => {
|
||||
return {
|
||||
column: order[0],
|
||||
dir: order[1]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let options = {
|
||||
colReorder: true,
|
||||
responsive: true,
|
||||
|
@ -124,7 +97,7 @@ export default class extends Controller {
|
|||
},
|
||||
buttons: [{
|
||||
"extend": 'colvis',
|
||||
'className': 'mr-2 btn-outline-secondary',
|
||||
'className': 'mr-2 btn-light',
|
||||
'columns': ':not(.no-colvis)',
|
||||
"text": "<i class='fa fa-cog'></i>"
|
||||
}],
|
||||
|
@ -139,7 +112,7 @@ export default class extends Controller {
|
|||
if(this.isSelectable()) {
|
||||
options.select = {
|
||||
style: 'multi+shift',
|
||||
selector: 'td.dt-select',
|
||||
selector: 'td.select-checkbox'
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -150,28 +123,6 @@ export default class extends Controller {
|
|||
console.error("Error initializing datatables: " + err);
|
||||
});
|
||||
|
||||
//Fix height of the length selector
|
||||
promise.then((dt) => {
|
||||
|
||||
//Draw the rows to make sure the correct status text is displayed ("No matching records found" instead of "Loading...")
|
||||
if (dt.data().length === 0) {
|
||||
dt.rows().draw()
|
||||
}
|
||||
|
||||
//Find all length selectors (select with name dt_length), which are inside a label
|
||||
const lengthSelectors = document.querySelectorAll('label select[name="dt_length"]');
|
||||
//And remove the surrounding label, while keeping the select with all event handlers
|
||||
lengthSelectors.forEach((selector) => {
|
||||
selector.parentElement.replaceWith(selector);
|
||||
});
|
||||
|
||||
//Find all column visibility buttons (button with buttons-colvis class) and remove the btn-secondary class
|
||||
const colVisButtons = document.querySelectorAll('button.buttons-colvis');
|
||||
colVisButtons.forEach((button) => {
|
||||
button.classList.remove('btn-secondary');
|
||||
});
|
||||
});
|
||||
|
||||
//Dispatch an event to let others know that the datatables has been loaded
|
||||
promise.then((dt) => {
|
||||
const event = new CustomEvent(EVENT_DT_LOADED, {bubbles: true});
|
||||
|
@ -193,6 +144,27 @@ export default class extends Controller {
|
|||
dt.fixedHeader.headerOffset($("#navbar").outerHeight());
|
||||
});
|
||||
|
||||
//Register event handler to selectAllRows checkbox if available
|
||||
if (this.isSelectable()) {
|
||||
promise.then((dt) => {
|
||||
const selectAllCheckbox = this.element.querySelector('thead th.select-checkbox');
|
||||
selectAllCheckbox.addEventListener('click', () => {
|
||||
if(selectAllCheckbox.parentElement.classList.contains('selected')) {
|
||||
dt.rows().deselect();
|
||||
selectAllCheckbox.parentElement.classList.remove('selected');
|
||||
} else {
|
||||
dt.rows().select();
|
||||
selectAllCheckbox.parentElement.classList.add('selected');
|
||||
}
|
||||
});
|
||||
|
||||
//When any row is deselected, also deselect the selectAll checkbox
|
||||
dt.on('deselect.dt', () => {
|
||||
selectAllCheckbox.parentElement.classList.remove('selected');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//Allow to further configure the datatable
|
||||
promise.then(this._afterLoaded.bind(this));
|
||||
|
||||
|
@ -231,16 +203,4 @@ export default class extends Controller {
|
|||
return this.element.dataset.select ?? false;
|
||||
}
|
||||
|
||||
invertSelection() {
|
||||
//Do nothing if the datatable is not selectable
|
||||
if(!this.isSelectable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Invert the selected rows on the datatable
|
||||
const selected_rows = this._dt.rows({selected: true});
|
||||
this._dt.rows().select();
|
||||
selected_rows.deselect();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -43,9 +43,7 @@ export default class extends Controller
|
|||
const message = this.element.dataset.deleteMessage;
|
||||
const title = this.element.dataset.deleteTitle;
|
||||
|
||||
//Use event target, to find the form, where the submit button was clicked
|
||||
const form = event.target;
|
||||
const submitter = event.submitter;
|
||||
const form = this.element;
|
||||
const that = this;
|
||||
|
||||
const confirm = bootbox.confirm({
|
||||
|
@ -60,14 +58,6 @@ export default class extends Controller
|
|||
const submit_btn = document.createElement('button');
|
||||
submit_btn.type = 'submit';
|
||||
submit_btn.style.display = 'none';
|
||||
|
||||
//If the clicked button has a value, set it on the submit button
|
||||
if (submitter.value) {
|
||||
submit_btn.value = submitter.value;
|
||||
}
|
||||
if (submitter.name) {
|
||||
submit_btn.name = submitter.name;
|
||||
}
|
||||
form.appendChild(submit_btn);
|
||||
submit_btn.click();
|
||||
} else {
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import JSONFormatter from 'json-formatter-js';
|
||||
|
||||
/**
|
||||
* This controller implements an element that renders a JSON object as a collapsible tree.
|
||||
* The JSON object is passed as a data attribute.
|
||||
* You have to apply the controller to a div element or similar block element which can contain other elements.
|
||||
*/
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
const depth_to_open = this.element.dataset.depthToOpen ?? 0;
|
||||
const json_string = this.element.dataset.json;
|
||||
const json_object = JSON.parse(json_string);
|
||||
|
||||
const formatter = new JSONFormatter(json_object, depth_to_open);
|
||||
|
||||
this.element.appendChild(formatter.render());
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 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 * as bootbox from "bootbox";
|
||||
import "../../css/components/bootbox_extensions.css";
|
||||
|
||||
export default class extends Controller
|
||||
{
|
||||
|
||||
static values = {
|
||||
message: String,
|
||||
title: String
|
||||
}
|
||||
|
||||
|
||||
|
||||
connect()
|
||||
{
|
||||
this._confirmed = false;
|
||||
|
||||
this.element.addEventListener('click', this._onClick.bind(this));
|
||||
}
|
||||
|
||||
_onClick(event)
|
||||
{
|
||||
|
||||
//If a user has not already confirmed the deletion, just let turbo do its work
|
||||
if (this._confirmed) {
|
||||
this._confirmed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const that = this;
|
||||
|
||||
bootbox.confirm({
|
||||
title: this.titleValue,
|
||||
message: this.messageValue,
|
||||
callback: (result) => {
|
||||
if (result) {
|
||||
//Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form
|
||||
that._confirmed = true;
|
||||
|
||||
//Click the link
|
||||
that.element.click();
|
||||
} else {
|
||||
that._confirmed = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller
|
||||
{
|
||||
static values = {
|
||||
id: String
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.loadState()
|
||||
this.element.addEventListener('change', () => {
|
||||
this.saveState()
|
||||
});
|
||||
}
|
||||
|
||||
loadState() {
|
||||
let storageKey = this.getStorageKey();
|
||||
let value = localStorage.getItem(storageKey);
|
||||
if (value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === 'true') {
|
||||
this.element.checked = true
|
||||
}
|
||||
if (value === 'false') {
|
||||
this.element.checked = false
|
||||
}
|
||||
}
|
||||
|
||||
saveState() {
|
||||
let storageKey = this.getStorageKey();
|
||||
|
||||
if (this.element.checked) {
|
||||
localStorage.setItem(storageKey, 'true');
|
||||
} else {
|
||||
localStorage.setItem(storageKey, 'false');
|
||||
}
|
||||
}
|
||||
|
||||
getStorageKey() {
|
||||
if (this.hasIdValue) {
|
||||
return 'persistent_checkbox_' + this.idValue
|
||||
}
|
||||
|
||||
return 'persistent_checkbox_' + this.element.id;
|
||||
}
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2024 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 { autocomplete } from '@algolia/autocomplete-js';
|
||||
//import "@algolia/autocomplete-theme-classic/dist/theme.css";
|
||||
import "../../css/components/autocomplete_bootstrap_theme.css";
|
||||
import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches';
|
||||
import {marked} from "marked";
|
||||
|
||||
import {
|
||||
trans,
|
||||
SEARCH_PLACEHOLDER,
|
||||
SEARCH_SUBMIT,
|
||||
STATISTICS_PARTS
|
||||
} from '../../translator';
|
||||
|
||||
|
||||
/**
|
||||
* This controller is responsible for the search fields in the navbar and the homepage.
|
||||
* It uses the Algolia Autocomplete library to provide a fast and responsive search.
|
||||
*/
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = ["input"];
|
||||
|
||||
_autocomplete;
|
||||
|
||||
// Highlight the search query in the results
|
||||
_highlight = (text, query) => {
|
||||
if (!text) return text;
|
||||
if (!query) return text;
|
||||
|
||||
const HIGHLIGHT_PRE_TAG = '__aa-highlight__'
|
||||
const HIGHLIGHT_POST_TAG = '__/aa-highlight__'
|
||||
|
||||
const escaped = query.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
const regex = new RegExp(escaped, 'gi');
|
||||
|
||||
return text.replace(regex, (match) => `${HIGHLIGHT_PRE_TAG}${match}${HIGHLIGHT_POST_TAG}`);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
// The endpoint for searching parts
|
||||
const base_url = this.element.dataset.autocomplete;
|
||||
// The URL template for the part detail pages
|
||||
const part_detail_uri_template = this.element.dataset.detailUrl;
|
||||
|
||||
//The URL of the placeholder picture
|
||||
const placeholder_image = this.element.dataset.placeholderImage;
|
||||
|
||||
//If the element is in navbar mode, or not
|
||||
const navbar_mode = this.element.dataset.navbarMode === "true";
|
||||
|
||||
const that = this;
|
||||
|
||||
const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
|
||||
key: 'RECENT_SEARCH',
|
||||
limit: 5,
|
||||
});
|
||||
|
||||
this._autocomplete = autocomplete({
|
||||
container: this.element,
|
||||
//Place the panel in the navbar, if the element is in navbar mode
|
||||
panelContainer: navbar_mode ? document.getElementById("navbar-search-form") : document.body,
|
||||
panelPlacement: this.element.dataset.panelPlacement,
|
||||
plugins: [recentSearchesPlugin],
|
||||
openOnFocus: true,
|
||||
placeholder: trans(SEARCH_PLACEHOLDER),
|
||||
translations: {
|
||||
submitButtonTitle: trans(SEARCH_SUBMIT)
|
||||
},
|
||||
|
||||
// Use a navigator compatible with turbo:
|
||||
navigator: {
|
||||
navigate({ itemUrl }) {
|
||||
window.Turbo.visit(itemUrl, { action: "advance" });
|
||||
},
|
||||
navigateNewTab({ itemUrl }) {
|
||||
const windowReference = window.open(itemUrl, '_blank', 'noopener');
|
||||
|
||||
if (windowReference) {
|
||||
windowReference.focus();
|
||||
}
|
||||
},
|
||||
navigateNewWindow({ itemUrl }) {
|
||||
window.open(itemUrl, '_blank', 'noopener');
|
||||
},
|
||||
},
|
||||
|
||||
// If the form is submitted, forward the term to the form
|
||||
onSubmit({state, event, ...setters}) {
|
||||
//Put the current text into each target input field
|
||||
const input = that.inputTarget;
|
||||
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Do not submit the form, if the input is empty
|
||||
if (state.query === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
input.value = state.query;
|
||||
input.form.requestSubmit();
|
||||
},
|
||||
|
||||
|
||||
getSources({ query }) {
|
||||
return [
|
||||
// The parts source
|
||||
{
|
||||
sourceId: 'parts',
|
||||
getItems() {
|
||||
const url = base_url.replace('__QUERY__', encodeURIComponent(query));
|
||||
|
||||
const data = fetch(url)
|
||||
.then((response) => response.json())
|
||||
;
|
||||
|
||||
//Iterate over all fields besides the id and highlight them
|
||||
const fields = ["name", "description", "category", "footprint"];
|
||||
|
||||
data.then((items) => {
|
||||
items.forEach((item) => {
|
||||
for (const field of fields) {
|
||||
item[field] = that._highlight(item[field], query);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
getItemUrl({ item }) {
|
||||
return part_detail_uri_template.replace('__ID__', item.id);
|
||||
},
|
||||
templates: {
|
||||
header({ html }) {
|
||||
return html`<span class="aa-SourceHeaderTitle">${trans(STATISTICS_PARTS)}</span>
|
||||
<div class="aa-SourceHeaderLine" />`;
|
||||
},
|
||||
item({item, components, html}) {
|
||||
const details_url = part_detail_uri_template.replace('__ID__', item.id);
|
||||
|
||||
return html`
|
||||
<a class="aa-ItemLink" href="${details_url}">
|
||||
<div class="aa-ItemContent">
|
||||
<div class="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop">
|
||||
<img src="${item.image !== "" ? item.image : placeholder_image}" alt="${item.name}" width="30" height="30"/>
|
||||
</div>
|
||||
<div class="aa-ItemContentBody">
|
||||
<div class="aa-ItemContentTitle">
|
||||
<b>
|
||||
${components.Highlight({hit: item, attribute: 'name'})}
|
||||
</b>
|
||||
</div>
|
||||
<div class="aa-ItemContentDescription">
|
||||
${components.Highlight({hit: item, attribute: 'description'})}
|
||||
${item.category ? html`<p class="m-0"><span class="fa-solid fa-tags fa-fw"></span>${components.Highlight({hit: item, attribute: 'category'})}</p>` : ""}
|
||||
${item.footprint ? html`<p class="m-0"><span class="fa-solid fa-microchip fa-fw"></span>${components.Highlight({hit: item, attribute: 'footprint'})}</p>` : ""}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
`;
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
//Try to find the input field and register a defocus handler. This is necessarry, as by default the autocomplete
|
||||
//lib has problems when multiple inputs are present on the page. (see https://github.com/algolia/autocomplete/issues/1216)
|
||||
const inputs = this.element.getElementsByClassName('aa-Input');
|
||||
for (const input of inputs) {
|
||||
input.addEventListener('blur', () => {
|
||||
this._autocomplete.setIsOpen(false);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ export default class extends Controller {
|
|||
}
|
||||
|
||||
let tmp = '<div class="row m-0">' +
|
||||
"<div class='col-2 p-0 d-flex align-items-center' style='max-width: 80px;'>" +
|
||||
"<div class='col-2 p-0 d-flex align-items-center'>" +
|
||||
(data.image ? "<img class='typeahead-image' src='" + data.image + "'/>" : "") +
|
||||
"</div>" +
|
||||
"<div class='col-10'>" +
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core';
|
||||
import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common';
|
||||
import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en';
|
||||
import * as zxcvbnDePackage from '@zxcvbn-ts/language-de';
|
||||
import * as zxcvbnFrPackage from '@zxcvbn-ts/language-fr';
|
||||
import * as zxcvbnJaPackage from '@zxcvbn-ts/language-ja';
|
||||
import {trans, USER_PASSWORD_STRENGTH_VERY_WEAK, USER_PASSWORD_STRENGTH_WEAK, USER_PASSWORD_STRENGTH_MEDIUM,
|
||||
USER_PASSWORD_STRENGTH_STRONG, USER_PASSWORD_STRENGTH_VERY_STRONG} from '../../translator.js';
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
|
||||
_passwordInput;
|
||||
|
||||
static targets = ["badge", "warning"]
|
||||
|
||||
_getTranslations() {
|
||||
//Get the current locale
|
||||
const locale = document.documentElement.lang;
|
||||
if (locale.includes('de')) {
|
||||
return zxcvbnDePackage.translations;
|
||||
} else if (locale.includes('fr')) {
|
||||
return zxcvbnFrPackage.translations;
|
||||
} else if (locale.includes('ja')) {
|
||||
return zxcvbnJaPackage.translations;
|
||||
}
|
||||
|
||||
//Fallback to english
|
||||
return zxcvbnEnPackage.translations;
|
||||
}
|
||||
|
||||
connect() {
|
||||
//Find the password input field
|
||||
this._passwordInput = this.element.querySelector('input[type="password"]');
|
||||
|
||||
//Configure zxcvbn
|
||||
const options = {
|
||||
graphs: zxcvbnCommonPackage.adjacencyGraphs,
|
||||
dictionary: {
|
||||
...zxcvbnCommonPackage.dictionary,
|
||||
// We could use the english dictionary here too, but it is very big. So we just use the common words
|
||||
//...zxcvbnEnPackage.dictionary,
|
||||
},
|
||||
translations: this._getTranslations(),
|
||||
};
|
||||
zxcvbnOptions.setOptions(options);
|
||||
|
||||
//Add event listener to the password input field
|
||||
this._passwordInput.addEventListener('input', this._onPasswordInput.bind(this));
|
||||
}
|
||||
|
||||
_onPasswordInput() {
|
||||
//Retrieve the password
|
||||
const password = this._passwordInput.value;
|
||||
|
||||
//Estimate the password strength
|
||||
const result = zxcvbn(password);
|
||||
|
||||
//Update the badge
|
||||
this.badgeTarget.parentElement.classList.remove("d-none");
|
||||
this._setBadgeToLevel(result.score);
|
||||
|
||||
this.warningTarget.innerHTML = result.feedback.warning;
|
||||
}
|
||||
|
||||
_setBadgeToLevel(level) {
|
||||
let text, classes;
|
||||
|
||||
switch (level) {
|
||||
case 0:
|
||||
text = trans(USER_PASSWORD_STRENGTH_VERY_WEAK);
|
||||
classes = "bg-danger badge-danger";
|
||||
break;
|
||||
case 1:
|
||||
text = trans(USER_PASSWORD_STRENGTH_WEAK);
|
||||
classes = "bg-warning badge-warning";
|
||||
break;
|
||||
case 2:
|
||||
text = trans(USER_PASSWORD_STRENGTH_MEDIUM)
|
||||
classes = "bg-info badge-info";
|
||||
break;
|
||||
case 3:
|
||||
text = trans(USER_PASSWORD_STRENGTH_STRONG);
|
||||
classes = "bg-primary badge-primary";
|
||||
break;
|
||||
case 4:
|
||||
text = trans(USER_PASSWORD_STRENGTH_VERY_STRONG);
|
||||
classes = "bg-success badge-success";
|
||||
break;
|
||||
default:
|
||||
text = "???";
|
||||
classes = "bg-secondary badge-secondary";
|
||||
}
|
||||
|
||||
this.badgeTarget.innerHTML = text;
|
||||
//Remove all classes
|
||||
this.badgeTarget.className = '';
|
||||
//Re-add the classes
|
||||
this.badgeTarget.classList.add("badge");
|
||||
this.badgeTarget.classList.add(...classes.split(" "));
|
||||
}
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import "tom-select/dist/css/tom-select.bootstrap5.css";
|
||||
import '../../css/components/tom-select_extensions.css';
|
||||
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.
|
||||
* Basically it loads a text file from the given url (via data-url) and uses it as a source for the autocomplete.
|
||||
* The file is just a list of strings, one per line, which will be used as the autocomplete options.
|
||||
* Lines starting with # will be ignored.
|
||||
*/
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
connect() {
|
||||
|
||||
let settings = {
|
||||
persistent: false,
|
||||
create: true,
|
||||
maxItems: 1,
|
||||
maxOptions: 100,
|
||||
createOnBlur: true,
|
||||
selectOnTab: true,
|
||||
valueField: 'text',
|
||||
searchField: 'text',
|
||||
orderField: 'text',
|
||||
|
||||
//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',
|
||||
plugins: {
|
||||
'autoselect_typed': {},
|
||||
'click_to_edit': {},
|
||||
'clear_button': {},
|
||||
'restore_on_backspace': {}
|
||||
}
|
||||
};
|
||||
|
||||
if (this.element.dataset.url) {
|
||||
const url = this.element.dataset.url;
|
||||
settings.load = (query, callback) => {
|
||||
const self = this;
|
||||
if (self.loading > 1) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.text())
|
||||
.then(text => {
|
||||
// Convert the text file to array
|
||||
let lines = text.split("\n");
|
||||
//Remove all lines beginning with #
|
||||
lines = lines.filter(x => !x.startsWith("#"));
|
||||
|
||||
//Convert the array to an object, where each line is in the text field
|
||||
lines = lines.map(x => {
|
||||
return {text: x};
|
||||
});
|
||||
|
||||
|
||||
//Unset the load function to prevent endless recursion
|
||||
self._tomSelect.settings.load = null;
|
||||
|
||||
callback(lines);
|
||||
}).catch(() => {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
this._tomSelect = new TomSelect(this.element, settings);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
//Destroy the TomSelect instance
|
||||
this._tomSelect.destroy();
|
||||
}
|
||||
|
||||
}
|
|
@ -22,10 +22,6 @@ import '../../css/components/tom-select_extensions.css';
|
|||
import TomSelect from "tom-select";
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
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 {
|
||||
_tomSelect;
|
||||
|
@ -40,20 +36,12 @@ export default class extends Controller {
|
|||
const allowAdd = this.element.getAttribute("data-allow-add") === "true";
|
||||
const addHint = this.element.getAttribute("data-add-hint") ?? "";
|
||||
|
||||
|
||||
|
||||
|
||||
let settings = {
|
||||
allowEmptyOption: true,
|
||||
selectOnTab: true,
|
||||
maxOptions: null,
|
||||
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)
|
||||
maxItems: 1,
|
||||
delimiter: "$$VERY_LONG_DELIMITER_THAT_SHOULD_NEVER_APPEAR$$",
|
||||
splitOn: null,
|
||||
create: allowAdd,
|
||||
createFilter: /\D/, //Must contain a non-digit character, otherwise they would be recognized as DB ID
|
||||
|
||||
searchField: [
|
||||
{field: "text", weight : 2},
|
||||
|
@ -64,108 +52,15 @@ export default class extends Controller {
|
|||
render: {
|
||||
item: this.renderItem.bind(this),
|
||||
option: this.renderOption.bind(this),
|
||||
option_create: (data, escape) => {
|
||||
//If the input starts with "->", we prepend the current selected value, for easier extension of existing values
|
||||
//This here handles the display part, while the createItem function handles the actual creation
|
||||
if (data.input.startsWith("->")) {
|
||||
//Get current selected value
|
||||
const current = this._tomSelect.getItem(this._tomSelect.getValue()).textContent.replaceAll("→", "->").trim();
|
||||
//Prepend it to the input
|
||||
if (current) {
|
||||
data.input = current + " " + data.input;
|
||||
} else {
|
||||
//If there is no current value, we remove the "->"
|
||||
data.input = data.input.substring(2);
|
||||
}
|
||||
}
|
||||
|
||||
option_create: function(data, escape) {
|
||||
return '<div class="create"><i class="fa-solid fa-plus fa-fw"></i> <strong>' + escape(data.input) + '</strong>… ' +
|
||||
'<small class="text-muted float-end">(' + addHint +')</small>' +
|
||||
'</div>';
|
||||
},
|
||||
},
|
||||
|
||||
//Add callbacks to update validity
|
||||
onInitialize: 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);
|
||||
//Do not do a sync here as this breaks the initial rendering of the empty option
|
||||
//this._tomSelect.sync();
|
||||
}
|
||||
|
||||
createItem(input, callback) {
|
||||
|
||||
//If the input starts with "->", we prepend the current selected value, for easier extension of existing values
|
||||
if (input.startsWith("->")) {
|
||||
//Get current selected value
|
||||
let current = this._tomSelect.getItem(this._tomSelect.getValue()).textContent.replaceAll("→", "->").trim();
|
||||
//Replace no break spaces with normal spaces
|
||||
current = current.replaceAll("\u00A0", " ");
|
||||
//Prepend it to the input
|
||||
if (current) {
|
||||
input = current + " " + input;
|
||||
} else {
|
||||
//If there is no current value, we remove the "->"
|
||||
input = input.substring(2);
|
||||
}
|
||||
}
|
||||
|
||||
callback({
|
||||
//$%$ is a special value prefix, that is used to identify items, that are not yet in the DB
|
||||
value: '$%$' + input,
|
||||
text: input,
|
||||
not_in_db_yet: true,
|
||||
});
|
||||
}
|
||||
|
||||
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() {
|
||||
//Mark this input as invalid, if the selected option is disabled
|
||||
|
||||
const input = this.element;
|
||||
const selectedOption = input.options[input.selectedIndex];
|
||||
|
||||
if (selectedOption && selectedOption.disabled) {
|
||||
input.setCustomValidity("This option was disabled. Please select another option.");
|
||||
} else {
|
||||
input.setCustomValidity("");
|
||||
}
|
||||
}
|
||||
|
||||
getTomSelect() {
|
||||
|
@ -183,27 +78,14 @@ export default class extends Controller {
|
|||
}
|
||||
|
||||
if (data.short) {
|
||||
let short = escape(data.short)
|
||||
|
||||
//Make text italic, if the item is not yet in the DB
|
||||
if (data.not_in_db_yet) {
|
||||
short = '<i>' + short + '</i>';
|
||||
}
|
||||
|
||||
return '<div><b>' + short + '</b></div>';
|
||||
return '<div><b>' + escape(data.short) + '</b></div>';
|
||||
}
|
||||
|
||||
let name = "";
|
||||
if (data.parent) {
|
||||
name += escape(data.parent) + " → ";
|
||||
}
|
||||
|
||||
if (data.not_in_db_yet) {
|
||||
//Not yet added items are shown italic and with a badge
|
||||
name += "<i><b>" + escape(data.text) + "</b></i>" + "<span class='ms-3 badge bg-info badge-info'>" + trans(ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB) + "</span>";
|
||||
} else {
|
||||
name += "<b>" + escape(data.text) + "</b>";
|
||||
}
|
||||
name += "<b>" + escape(data.text) + "</b>";
|
||||
|
||||
return '<div>' + (data.image ? "<img class='structural-entity-select-image' style='margin-right: 5px;' ' src='" + data.image + "'/>" : "") + name + '</div>';
|
||||
}
|
||||
|
|
|
@ -23,21 +23,14 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
|
|||
import '../../css/components/tom-select_extensions.css';
|
||||
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 {
|
||||
_tomSelect;
|
||||
|
||||
connect() {
|
||||
let settings = {
|
||||
plugins: {
|
||||
remove_button:{},
|
||||
'autoselect_typed': {},
|
||||
'click_to_edit': {},
|
||||
remove_button:{
|
||||
}
|
||||
},
|
||||
persistent: false,
|
||||
selectOnTab: true,
|
||||
|
|
|
@ -81,71 +81,31 @@ export default class extends Controller {
|
|||
this._tree.remove();
|
||||
}
|
||||
|
||||
const BS53Theme = {
|
||||
getOptions() {
|
||||
return {
|
||||
onhoverColor: 'var(--bs-secondary-bg)',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this._tree = new BSTreeView(this.treeTarget, {
|
||||
levels: 1,
|
||||
showTags: this._showTags,
|
||||
data: data,
|
||||
showIcon: true,
|
||||
preventUnselect: true,
|
||||
allowReselect: true,
|
||||
onNodeSelected: (event) => {
|
||||
const node = event.detail.node;
|
||||
if (node.href) {
|
||||
window.Turbo.visit(node.href, {action: "advance"});
|
||||
this._registerURLWatcher(node);
|
||||
}
|
||||
},
|
||||
}, [BS5Theme, BS53Theme, FAIconTheme]);
|
||||
//onNodeContextmenu: contextmenu_handler,
|
||||
}, [BS5Theme, FAIconTheme]);
|
||||
|
||||
this.treeTarget.addEventListener(EVENT_INITIALIZED, (event) => {
|
||||
/** @type {BSTreeView} */
|
||||
const treeView = event.detail.treeView;
|
||||
treeView.revealNode(treeView.getSelected());
|
||||
|
||||
//Add the url watcher to all selected nodes
|
||||
for (const node of treeView.getSelected()) {
|
||||
this._registerURLWatcher(node);
|
||||
}
|
||||
|
||||
//Add contextmenu event listener to the tree, which allows us to open the links in a new tab with a right click
|
||||
treeView.getTreeElement().addEventListener("contextmenu", this._onContextMenu.bind(this));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
_registerURLWatcher(node)
|
||||
{
|
||||
//Register a watcher for a location change, which will unselect the node, if the location changes
|
||||
const desired_url = node.href;
|
||||
|
||||
//Ensure that the node is unselected, if the location changes
|
||||
const unselectNode = () => {
|
||||
//Parse url so we can properly compare them
|
||||
const desired = new URL(node.href, window.location.origin);
|
||||
|
||||
//We only compare the pathname, because the hash and parameters should not matter
|
||||
if(window.location.pathname !== desired.pathname) {
|
||||
//The ignore parameter is important here, otherwise the node will not be unselected
|
||||
node.setSelected(false, {silent: true, ignorePreventUnselect: true});
|
||||
|
||||
//Unregister the watcher
|
||||
document.removeEventListener('turbo:load', unselectNode);
|
||||
}
|
||||
};
|
||||
|
||||
//Register the watcher via hotwire turbo
|
||||
//We must just load to have the new url in window.location
|
||||
document.addEventListener('turbo:load', unselectNode);
|
||||
}
|
||||
|
||||
_onContextMenu(event)
|
||||
{
|
||||
//Find the node that was clicked and open link in new tab
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = [ "display", "select" ]
|
||||
|
||||
connect()
|
||||
{
|
||||
this.update();
|
||||
this.selectTarget.addEventListener('change', this.update.bind(this));
|
||||
}
|
||||
|
||||
update()
|
||||
{
|
||||
//If the select value is 0, then we show the input field
|
||||
if( this.selectTarget.value === '0')
|
||||
{
|
||||
this.displayTarget.classList.remove('d-none');
|
||||
}
|
||||
else
|
||||
{
|
||||
this.displayTarget.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
import {Controller} from "@hotwired/stimulus";
|
||||
//import * as ZXing from "@zxing/library";
|
||||
|
||||
import {Html5QrcodeScanner, Html5Qrcode} from "@part-db/html5-qrcode";
|
||||
import {Html5QrcodeScanner, Html5Qrcode} from "html5-qrcode";
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
|
@ -50,7 +50,7 @@ export default class extends Controller {
|
|||
});
|
||||
|
||||
this._scanner = new Html5QrcodeScanner(this.element.id, {
|
||||
fps: 10,
|
||||
fps: 2,
|
||||
qrbox: qrboxFunction,
|
||||
experimentalFeatures: {
|
||||
//This option improves reading quality on android chrome
|
||||
|
@ -61,11 +61,6 @@ export default class extends Controller {
|
|||
this._scanner.render(this.onScanSuccess.bind(this));
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this._scanner.pause();
|
||||
this._scanner.clear();
|
||||
}
|
||||
|
||||
onScanSuccess(decodedText, decodedResult) {
|
||||
//Put our decoded Text into the input box
|
||||
document.getElementById('scan_dialog_input').value = decodedText;
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 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";
|
||||
|
||||
/**
|
||||
* This controller is used on a checkbox, which toggles the max value of all number input fields
|
||||
*/
|
||||
export default class extends Controller {
|
||||
|
||||
_checkbox;
|
||||
|
||||
getCheckbox() {
|
||||
if (this._checkbox) {
|
||||
return this._checkbox;
|
||||
}
|
||||
|
||||
//Find the checkbox inside the controller element
|
||||
this._checkbox = this.element.querySelector('input[type="checkbox"]');
|
||||
return this._checkbox;
|
||||
}
|
||||
|
||||
connect() {
|
||||
//Add event listener to the checkbox
|
||||
this.getCheckbox().addEventListener('change', this.toggleInputLimits.bind(this));
|
||||
}
|
||||
|
||||
toggleInputLimits() {
|
||||
//Find all input fields with the data-toggle-input-limits-target="max"
|
||||
const inputFields = document.querySelectorAll("input[type='number']");
|
||||
|
||||
inputFields.forEach((inputField) => {
|
||||
//Ensure that the input field has either a max or a data-max attribute
|
||||
if (!inputField.hasAttribute('max') && !inputField.hasAttribute('data-max')) {
|
||||
return;
|
||||
}
|
||||
|
||||
//If the checkbox is checked, rename the max attribute to data-max
|
||||
if (this.getCheckbox().checked) {
|
||||
inputField.setAttribute('data-max', inputField.getAttribute('max'));
|
||||
inputField.removeAttribute('max');
|
||||
} else {
|
||||
//If the checkbox is not checked, rename the data-max attribute back to max
|
||||
inputField.setAttribute('max', inputField.getAttribute('data-max'));
|
||||
inputField.removeAttribute('data-max');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -25,20 +25,9 @@ import "katex/dist/katex.css";
|
|||
export default class extends Controller {
|
||||
static targets = ["input", "preview"];
|
||||
|
||||
static values = {
|
||||
unit: {type: Boolean, default: false} //Render as upstanding (non-italic) text, useful for units
|
||||
}
|
||||
|
||||
updatePreview()
|
||||
{
|
||||
let value = "";
|
||||
if (this.unitValue) {
|
||||
value = "\\mathrm{" + this.inputTarget.value + "}";
|
||||
} else {
|
||||
value = this.inputTarget.value;
|
||||
}
|
||||
|
||||
katex.render(value, this.previewTarget, {
|
||||
katex.render(this.inputTarget.value, this.previewTarget, {
|
||||
throwOnError: false,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,13 +22,6 @@ import TomSelect from "tom-select";
|
|||
import katex from "katex";
|
||||
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' */
|
||||
export default class extends Controller
|
||||
{
|
||||
|
@ -60,10 +53,7 @@ export default class extends Controller
|
|||
connect() {
|
||||
const settings = {
|
||||
plugins: {
|
||||
'autoselect_typed': {},
|
||||
'click_to_edit': {},
|
||||
'clear_button': {},
|
||||
'restore_on_backspace': {}
|
||||
clear_button:{}
|
||||
},
|
||||
persistent: false,
|
||||
maxItems: 1,
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller
|
||||
{
|
||||
static targets = ['link', 'mode', 'otherSelect'];
|
||||
static values = {
|
||||
targetId: Number,
|
||||
};
|
||||
|
||||
connect() {
|
||||
}
|
||||
|
||||
update() {
|
||||
const link = this.linkTarget;
|
||||
const other_select = this.otherSelectTarget;
|
||||
|
||||
//Extract the mode using the mode radio buttons (we filter the array to get the checked one)
|
||||
const mode = (this.modeTargets.filter((e)=>e.checked))[0].value;
|
||||
|
||||
if (other_select.value === '') {
|
||||
link.classList.add('disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
//Extract href template from data attribute on link target
|
||||
let href = link.getAttribute('data-href-template');
|
||||
|
||||
let target, other;
|
||||
if (mode === '1') {
|
||||
target = this.targetIdValue;
|
||||
other = other_select.value;
|
||||
} else if (mode === '2') {
|
||||
target = other_select.value;
|
||||
other = this.targetIdValue;
|
||||
} else {
|
||||
throw 'Invalid mode';
|
||||
}
|
||||
|
||||
//Replace placeholder with actual target id
|
||||
href = href.replace('__target__', target);
|
||||
//Replace placeholder with selected value of the select (the event sender)
|
||||
href = href.replace('__other__', other);
|
||||
|
||||
//Assign new href to link
|
||||
link.setAttribute('href', href);
|
||||
//Make link clickable
|
||||
link.classList.remove('disabled');
|
||||
}
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
* Copyright (C) 2019 - 2022 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
|
||||
|
@ -18,13 +17,13 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
|
||||
namespace App\Repository\UserSystem;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
class ApiTokenRepository extends EntityRepository
|
||||
export default class extends Controller
|
||||
{
|
||||
}
|
||||
connect() {
|
||||
this.element.onclick = function() {
|
||||
window.u2fauth.register();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -99,25 +99,10 @@ label:not(.form-check-label, .custom-control-label) {
|
|||
|
||||
form .col-form-label.required:after, form label.required:after {
|
||||
bottom: 4px;
|
||||
color: var(--bs-secondary-color);
|
||||
color: var(--bs-dark);
|
||||
content: "\2022";
|
||||
filter: opacity(75%);
|
||||
position: relative;
|
||||
right: -2px;
|
||||
z-index: 700;
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* HTML diff styling
|
||||
****************************************/
|
||||
|
||||
/* Insertations are marked with green background and bold */
|
||||
ins {
|
||||
background-color: #95f095;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
del {
|
||||
background-color: #f09595;
|
||||
font-weight: bold;
|
||||
}
|
|
@ -1,11 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
* Copyright (C) 2019 - 2022 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
|
||||
|
@ -20,12 +16,22 @@ declare(strict_types=1);
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace App\Entity\LabelSystem;
|
||||
|
||||
enum LabelProcessMode: string
|
||||
{
|
||||
/** Use placeholders like [[PLACEHOLDER]] which gets replaced with content */
|
||||
case PLACEHOLDER = 'html';
|
||||
/** Interpret the given lines as twig template */
|
||||
case TWIG = 'twig';
|
||||
.darkmode-layer {
|
||||
z-index: 2020;
|
||||
}
|
||||
|
||||
/** If darkmode is enabled revert the blening for images and videos, as these should be shown not inverted */
|
||||
.darkmode--activated img,
|
||||
.darkmode--activated video,
|
||||
.darkmode--activated object {
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
.darkmode--activated .hoverpic:hover {
|
||||
background: black;
|
||||
}
|
||||
|
||||
.tools-ic-logos img {
|
||||
mix-blend-mode: normal;
|
||||
}
|
|
@ -67,6 +67,7 @@ ul.structural_link {
|
|||
padding-bottom: 7px;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
/* Display list items side by side */
|
||||
|
@ -78,7 +79,7 @@ ul.structural_link li {
|
|||
/* Add a slash symbol (/) before/behind each list item */
|
||||
ul.structural_link li+li:before {
|
||||
padding: 2px;
|
||||
color: var(--bs-tertiary-color);
|
||||
color: grey;
|
||||
/*content: "/\00a0";*/
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 900;
|
||||
|
@ -88,13 +89,13 @@ ul.structural_link li+li:before {
|
|||
|
||||
/* Add a color to all links inside the list */
|
||||
ul.structural_link li a {
|
||||
color: var(--bs-link-color);
|
||||
color: #0275d8;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Add a color on mouse-over */
|
||||
ul.structural_link li a:hover {
|
||||
color: var(--bs-link-hover-color);
|
||||
color: #01447e;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
@ -111,11 +112,4 @@ ul.structural_link li a:hover {
|
|||
.permission-checkbox:checked {
|
||||
background-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;
|
||||
}
|
|
@ -51,6 +51,7 @@
|
|||
.part-table-image {
|
||||
max-height: 40px;
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.part-info-image {
|
||||
|
@ -60,4 +61,4 @@
|
|||
|
||||
.object-fit-cover {
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
|
@ -78,6 +78,8 @@ body {
|
|||
overflow: -moz-scrollbars-none;
|
||||
/* Use standard version for hiding the scrollbar */
|
||||
scrollbar-width: none;
|
||||
|
||||
background-color: var(--light);
|
||||
}
|
||||
|
||||
#sidebar-container {
|
||||
|
@ -108,8 +110,8 @@ body {
|
|||
.back-to-top {
|
||||
cursor: pointer;
|
||||
position: fixed;
|
||||
bottom: 60px;
|
||||
right: 40px;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
display:none;
|
||||
z-index: 1030;
|
||||
}
|
||||
|
|
|
@ -63,6 +63,10 @@ table.dataTable > tbody > tr.selected > td > a {
|
|||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
.card-footer-table {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
table.dataTable {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
@ -80,24 +84,14 @@ th.select-checkbox {
|
|||
* Datatables definitions/overrides
|
||||
********************************************************************/
|
||||
|
||||
.dt-length {
|
||||
.dataTables_length {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/** Fix datatables select-checkbox position */
|
||||
table.dataTable tr.selected td.select-checkbox:after
|
||||
{
|
||||
margin-top: -20px !important;
|
||||
}
|
||||
|
||||
/** Show pagination right aligned */
|
||||
.dt-paging .pagination {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/** Fix table row coloring */
|
||||
table.table.dataTable > :not(caption) > * > * {
|
||||
background-color: var(--bs-table-bg);
|
||||
margin-top: -28px !important;
|
||||
}
|
||||
|
||||
|
||||
|
@ -109,4 +103,43 @@ Classes for Datatables export
|
|||
#export-messageTop,
|
||||
.export-helper{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* Styling for the select all checkbox in the parts table
|
||||
* Should match the styling of the select checkbox
|
||||
******************************************************/
|
||||
table.dataTable > thead > tr > th.select-checkbox {
|
||||
position: relative;
|
||||
}
|
||||
table.dataTable > thead > tr > th.select-checkbox:before,
|
||||
table.dataTable > thead > tr > th.select-checkbox:after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 1.2em;
|
||||
left: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
table.dataTable > thead > tr > th.select-checkbox:before {
|
||||
content: " ";
|
||||
margin-top: -5px;
|
||||
margin-left: -6px;
|
||||
border: 1px solid black;
|
||||
border-radius: 3px;
|
||||
}
|
||||
table.dataTable > thead > tr.selected > th.select-checkbox:after {
|
||||
content: "✓";
|
||||
font-size: 20px;
|
||||
margin-top: -23px;
|
||||
margin-left: -6px;
|
||||
text-align: center;
|
||||
/*text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9; */
|
||||
}
|
||||
table.dataTable.compact > thead > tr > th.select-checkbox:before {
|
||||
margin-top: -12px;
|
||||
}
|
||||
table.dataTable.compact > thead > tr.selected > th.select-checkbox:after {
|
||||
margin-top: -16px;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -24,8 +24,9 @@
|
|||
/** Should be the same settings, as in label_style.css */
|
||||
.ck-html-label .ck-content {
|
||||
font-family: "DejaVu Sans Mono", monospace;
|
||||
font-size: 12pt;
|
||||
font-size: 12px;
|
||||
line-height: 1.0;
|
||||
font-size-adjust: 1.5;
|
||||
}
|
||||
|
||||
.ck-html-label .ck-content p {
|
||||
|
@ -35,42 +36,3 @@
|
|||
.ck-html-label .ck-content hr {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
/***********************************************
|
||||
* Hide CKEditor powered by message
|
||||
***********************************************/
|
||||
.ck-powered-by {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/***********************************************
|
||||
* Use Bootstrap color vars for CKEditor
|
||||
***********************************************/
|
||||
:root {
|
||||
--ck-color-base-foreground: var(--bs-secondary-bg);
|
||||
--ck-color-base-background: var(--bs-body-bg);
|
||||
--ck-color-base-border: var(--bs-border-color);
|
||||
--ck-color-base-action: var(--bs-success);
|
||||
--ck-color-base-focus: var(--bs-primary-border-subtle);
|
||||
--ck-color-base-text: var(--bs-body-color);
|
||||
--ck-color-base-active: var(--bs-primary-bg-subtle);
|
||||
--ck-color-base-active-focus: var(--bs-primary);
|
||||
--ck-color-base-error: var(--bs-danger);
|
||||
|
||||
/* Improve contrast between text and toolbar */
|
||||
--ck-color-toolbar-background: var(--bs-tertiary-bg);
|
||||
|
||||
/* Buttons */
|
||||
--ck-color-button-default-hover-background: var(--bs-secondary-bg);
|
||||
--ck-color-button-default-active-background: var(--bs-secondary-bg);
|
||||
|
||||
--ck-color-button-on-background: var(--bs-body-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-disabled-background: var(--bs-secondary-bg);
|
||||
--ck-color-button-on-color: var(--bs-primary)
|
||||
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
/******************************************************************************************
|
||||
* This styles the checkboxes of the select extension exactly like the ones in bootstrap 5
|
||||
******************************************************************************************/
|
||||
|
||||
table.dataTable > tbody > tr > .selected {
|
||||
background-color: var(--bs-primary-bg-subtle) !important;
|
||||
color: white;
|
||||
}
|
||||
table.dataTable > tbody > tr > .dt-select {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
table.dataTable > thead > tr > .dt-select {
|
||||
text-align: center;
|
||||
}
|
||||
table.dataTable input.dt-select-checkbox {
|
||||
--bs-form-check-bg: var(--bs-body-bg);
|
||||
flex-shrink: 0;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-top: 0.25em;
|
||||
vertical-align: top;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
background-color: var(--bs-form-check-bg);
|
||||
background-image: var(--bs-form-check-bg-image);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
border: var(--bs-border-width) solid var(--bs-border-color);
|
||||
-webkit-print-color-adjust: exact;
|
||||
color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
border-radius: 0.25em;
|
||||
}
|
||||
|
||||
table.dataTable input.dt-select-checkbox:checked {
|
||||
background-color: rgb(var(--bs-secondary-rgb));
|
||||
border-color: rgb(var(--bs-secondary-rgb));
|
||||
--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
|
||||
}
|
||||
|
||||
table.dataTable input.dt-select-checkbox:indeterminate {
|
||||
background-color: rgb(var(--bs-secondary-rgb));
|
||||
border-color: rgb(var(--bs-secondary-rgb));
|
||||
--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
div.dt-container span.select-info,
|
||||
div.dt-container span.select-item {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
div.dt-container span.select-info,
|
||||
div.dt-container span.select-item {
|
||||
margin-left: 0;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
table.dataTable.table-sm tbody td.select-checkbox::before {
|
||||
margin-top: -9px;
|
||||
}
|
||||
|
|
@ -18,29 +18,6 @@
|
|||
*/
|
||||
|
||||
.tagsinput.ts-wrapper.multi .ts-control > div {
|
||||
background: var(--bs-secondary-bg);
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
/*********
|
||||
* BS 5.3 compatible dark mode
|
||||
***************/
|
||||
|
||||
.ts-dropdown .active {
|
||||
background-color: var(--bs-secondary-bg) !important;
|
||||
color: var(--bs-body-color) !important;
|
||||
}
|
||||
|
||||
.ts-dropdown, .ts-control, .ts-control input {
|
||||
color: var(--bs-body-color) !important;
|
||||
}
|
||||
|
||||
.ts-dropdown, .ts-dropdown.form-control, .ts-dropdown.form-select {
|
||||
background: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
.ts-dropdown .optgroup-header {
|
||||
color: var(--bs-tertiary-color);
|
||||
background: var(--bs-body-bg);
|
||||
cursor: default;
|
||||
}
|
||||
background: var(--bs-secondary);
|
||||
color: var(--bs-white);
|
||||
}
|
File diff suppressed because it is too large
Load diff
3
assets/fonts/dompdf/.gitignore
vendored
3
assets/fonts/dompdf/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
# Ignore font files
|
||||
*.otf
|
||||
*.ttf
|
|
@ -1 +0,0 @@
|
|||
Put your font ttf files in this folder to make them available to the label generator.
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
import '../css/app/layout.css';
|
||||
import '../css/app/helpers.css';
|
||||
import '../css/app/darkmode.css';
|
||||
import '../css/app/tables.css';
|
||||
import '../css/app/bs-overrides.css';
|
||||
import '../css/app/treeview.css';
|
||||
|
@ -44,18 +45,4 @@ import "./register_events";
|
|||
import "./tristate_checkboxes";
|
||||
|
||||
//Define jquery globally
|
||||
window.$ = window.jQuery = require("jquery");
|
||||
|
||||
//Use the local WASM file for the ZXing library
|
||||
import {
|
||||
setZXingModuleOverrides,
|
||||
} from "barcode-detector/pure";
|
||||
import wasmFile from "../../node_modules/zxing-wasm/dist/reader/zxing_reader.wasm";
|
||||
setZXingModuleOverrides({
|
||||
locateFile: (path, prefix) => {
|
||||
if (path.endsWith(".wasm")) {
|
||||
return wasmFile;
|
||||
}
|
||||
return prefix + path;
|
||||
},
|
||||
});
|
||||
window.$ = window.jQuery = require("jquery")
|
File diff suppressed because it is too large
Load diff
|
@ -47,8 +47,7 @@
|
|||
method: config.method,
|
||||
data: {
|
||||
_dt: config.name,
|
||||
_init: true,
|
||||
order: config.initial_order ?? undefined,
|
||||
_init: true
|
||||
}
|
||||
}).done(function(data) {
|
||||
var baseState;
|
||||
|
@ -73,17 +72,6 @@
|
|||
}
|
||||
} else {
|
||||
request._dt = config.name;
|
||||
|
||||
//Try to resolve the original column index when the column was reordered (using the ColReorder plugin)
|
||||
//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) {
|
||||
request.order.forEach(function (order) {
|
||||
order.column = settings.aoColumns[order.column]._ColReorder_iOrigCol;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$.ajax(typeof config.url === 'function' ? config.url(dt) : config.url, {
|
||||
method: config.method,
|
||||
data: request
|
||||
|
@ -98,15 +86,6 @@
|
|||
dtOpts = config.options(dtOpts);
|
||||
}
|
||||
|
||||
//Choose the column where the className contains "select-column" and apply the select extension to its render field
|
||||
//Added for Part-DB
|
||||
for (let column of dtOpts.columns) {
|
||||
if (column.className && column.className.includes('dt-select')) {
|
||||
column.render = $.fn.dataTable.render.select();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
root.html(data.template);
|
||||
dt = $('table', root).DataTable(dtOpts);
|
||||
if (config.state !== 'none') {
|
||||
|
|
|
@ -20,8 +20,6 @@
|
|||
'use strict';
|
||||
|
||||
import {Dropdown} from "bootstrap";
|
||||
import ClipboardJS from "clipboard";
|
||||
import {Modal} from "bootstrap";
|
||||
|
||||
class RegisterEventHelper {
|
||||
constructor() {
|
||||
|
@ -29,14 +27,7 @@ class RegisterEventHelper {
|
|||
this.configureDropdowns();
|
||||
this.registerSpecialCharInput();
|
||||
|
||||
//Initialize ClipboardJS
|
||||
this.registerLoadHandler(() => {
|
||||
new ClipboardJS('.btn');
|
||||
});
|
||||
|
||||
this.registerModalDropRemovalOnFormSubmit();
|
||||
|
||||
|
||||
}
|
||||
|
||||
registerModalDropRemovalOnFormSubmit() {
|
||||
|
@ -46,15 +37,6 @@ class RegisterEventHelper {
|
|||
if (back_drop) {
|
||||
back_drop.remove();
|
||||
}
|
||||
|
||||
//Remove scroll-lock if it is still active
|
||||
if (document.body.classList.contains('modal-open')) {
|
||||
document.body.classList.remove('modal-open');
|
||||
|
||||
//Remove the padding-right and overflow:hidden from the body
|
||||
document.body.style.paddingRight = '';
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -77,16 +59,13 @@ class RegisterEventHelper {
|
|||
}
|
||||
|
||||
registerTooltips() {
|
||||
const handler = () => {
|
||||
this.registerLoadHandler(() => {
|
||||
$(".tooltip").remove();
|
||||
//Exclude dropdown buttons from tooltips, otherwise we run into endless errors from bootstrap (bootstrap.esm.js:614 Bootstrap doesn't allow more than one instance per element. Bound instance: bs.dropdown.)
|
||||
$('a[title], label[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i[title], small[title]')
|
||||
$('a[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i.fas[title]')
|
||||
//@ts-ignore
|
||||
.tooltip("hide").tooltip({container: "body", placement: "auto", boundary: 'window'});
|
||||
};
|
||||
|
||||
this.registerLoadHandler(handler);
|
||||
document.addEventListener('dt:loaded', handler);
|
||||
});
|
||||
}
|
||||
|
||||
registerSpecialCharInput() {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
import {Tab, Dropdown, Collapse} from "bootstrap";
|
||||
import {Tab, Dropdown} from "bootstrap";
|
||||
import tab from "bootstrap/js/src/tab";
|
||||
|
||||
/**
|
||||
|
@ -54,7 +54,6 @@ class TabRememberHelper {
|
|||
const first_element = merged[0] ?? null;
|
||||
if(first_element) {
|
||||
this.revealElementOnTab(first_element);
|
||||
this.revealElementInCollapse(first_element);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,20 +62,10 @@ class TabRememberHelper {
|
|||
* @param event
|
||||
*/
|
||||
onInvalid(event) {
|
||||
this.revealElementInCollapse(event.target);
|
||||
this.revealElementOnTab(event.target);
|
||||
this.revealElementInDropdown(event.target);
|
||||
}
|
||||
|
||||
revealElementInCollapse(element) {
|
||||
let collapse = element.closest('.collapse');
|
||||
|
||||
if(collapse) {
|
||||
let bs_collapse = Collapse.getOrCreateInstance(collapse);
|
||||
bs_collapse.show();
|
||||
}
|
||||
}
|
||||
|
||||
revealElementInDropdown(element) {
|
||||
let dropdown = element.closest('.dropdown-menu');
|
||||
|
||||
|
|
|
@ -21,13 +21,8 @@
|
|||
|
||||
class WebauthnTFA {
|
||||
|
||||
_b64UrlSafeEncode = (str) => {
|
||||
const b64 = btoa(str);
|
||||
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
||||
}
|
||||
|
||||
// Decodes a Base64Url string
|
||||
_b64UrlSafeDecode = (input) => {
|
||||
// Decodes a Base64Url string
|
||||
_base64UrlDecode = (input) => {
|
||||
input = input
|
||||
.replace(/-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
|
@ -44,16 +39,13 @@ class WebauthnTFA {
|
|||
};
|
||||
|
||||
// Converts an array of bytes into a Base64Url string
|
||||
_arrayToBase64String = (a) => {
|
||||
const str = String.fromCharCode(...a);
|
||||
return this._b64UrlSafeEncode(str);
|
||||
}
|
||||
_arrayToBase64String = (a) => btoa(String.fromCharCode(...a));
|
||||
|
||||
// Prepares the public key options object returned by the Webauthn Framework
|
||||
_preparePublicKeyOptions = publicKey => {
|
||||
//Convert challenge from Base64Url string to Uint8Array
|
||||
publicKey.challenge = Uint8Array.from(
|
||||
this._b64UrlSafeDecode(publicKey.challenge),
|
||||
this._base64UrlDecode(publicKey.challenge),
|
||||
c => c.charCodeAt(0)
|
||||
);
|
||||
|
||||
|
@ -75,7 +67,7 @@ class WebauthnTFA {
|
|||
return {
|
||||
...data,
|
||||
id: Uint8Array.from(
|
||||
this._b64UrlSafeDecode(data.id),
|
||||
this._base64UrlDecode(data.id),
|
||||
c => c.charCodeAt(0)
|
||||
),
|
||||
};
|
||||
|
@ -89,7 +81,7 @@ class WebauthnTFA {
|
|||
return {
|
||||
...data,
|
||||
id: Uint8Array.from(
|
||||
this._b64UrlSafeDecode(data.id),
|
||||
this._base64UrlDecode(data.id),
|
||||
c => c.charCodeAt(0)
|
||||
),
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -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 };
|
|
@ -1,16 +0,0 @@
|
|||
import { localeFallbacks } from '../var/translations/configuration';
|
||||
import { trans, getLocale, setLocale, setLocaleFallbacks } from '@symfony/ux-translator';
|
||||
/*
|
||||
* This file is part of the Symfony UX Translator package.
|
||||
*
|
||||
* If folder "../var/translations" does not exist, or some translations are missing,
|
||||
* you must warmup your Symfony cache to refresh JavaScript translations.
|
||||
*
|
||||
* If you use TypeScript, you can rename this file to "translator.ts" to take advantage of types checking.
|
||||
*/
|
||||
|
||||
setLocaleFallbacks(localeFallbacks);
|
||||
|
||||
export { trans };
|
||||
|
||||
export * from '../var/translations';
|
11
bin/console
11
bin/console
|
@ -4,17 +4,6 @@
|
|||
use App\Kernel;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
|
||||
if (!is_dir(dirname(__DIR__).'/vendor')) {
|
||||
throw new LogicException('Dependencies are missing. Try running "composer install".');
|
||||
}
|
||||
|
||||
//Increase xdebug.max_nesting_level to 1000 if required (see issue #411)
|
||||
//Check if xdebug extension is active, and xdebug.max_nesting_level is set to 256 or lower
|
||||
if (extension_loaded('xdebug') && ((int) ini_get('xdebug.max_nesting_level')) <= 256) {
|
||||
//Increase xdebug.max_nesting_level to 1000
|
||||
ini_set('xdebug.max_nesting_level', '1000');
|
||||
}
|
||||
|
||||
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
|
||||
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
|
||||
}
|
||||
|
|
10
bin/phpunit
10
bin/phpunit
|
@ -6,13 +6,9 @@ if (!ini_get('date.timezone')) {
|
|||
}
|
||||
|
||||
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||
} else {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
PHPUnit\TextUI\Command::main();
|
||||
}
|
||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
PHPUnit\TextUI\Command::main();
|
||||
} else {
|
||||
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||
|
|
|
@ -5,5 +5,4 @@ coverage:
|
|||
status:
|
||||
project:
|
||||
default:
|
||||
threshold: 10%
|
||||
target: 40%
|
||||
threshold: 5%
|
159
composer.json
159
composer.json
|
@ -1,118 +1,106 @@
|
|||
{
|
||||
"name": "part-db/part-db-server",
|
||||
"type": "project",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-intl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"amphp/http-client": "^5.1",
|
||||
"api-platform/core": "^3.1",
|
||||
"ext-dom": "*",
|
||||
"beberlei/doctrineextensions": "^1.2",
|
||||
"brick/math": "0.12.1 as 0.11.0",
|
||||
"composer/ca-bundle": "^1.5",
|
||||
"composer/package-versions-deprecated": "^1.11.99.5",
|
||||
"doctrine/data-fixtures": "^2.0.0",
|
||||
"doctrine/dbal": "^4.0.0",
|
||||
"brick/math": "^0.8.15",
|
||||
"composer/package-versions-deprecated": "1.11.99.4",
|
||||
"doctrine/annotations": "^1.6",
|
||||
"doctrine/dbal": "^3.4.6",
|
||||
"doctrine/doctrine-bundle": "^2.0",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||
"doctrine/orm": "^3.2.0",
|
||||
"dompdf/dompdf": "^v3.0.0",
|
||||
"doctrine/orm": "^2.9",
|
||||
"dompdf/dompdf": "^2.0.0",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"florianv/swap": "^4.0",
|
||||
"florianv/swap-bundle": "dev-master",
|
||||
"gregwar/captcha-bundle": "^2.1.0",
|
||||
"hshn/base64-encoded-file": "^5.0",
|
||||
"jbtronics/2fa-webauthn": "^v2.2.0",
|
||||
"jbtronics/dompdf-font-loader-bundle": "^1.0.0",
|
||||
"jfcherng/php-diff": "^6.14",
|
||||
"knpuniversity/oauth2-client-bundle": "^2.15",
|
||||
"hslavich/oneloginsaml-bundle": "^2.10",
|
||||
"jbtronics/2fa-webauthn": "^1.0.0",
|
||||
"league/csv": "^9.8.0",
|
||||
"league/html-to-markdown": "^5.0.1",
|
||||
"liip/imagine-bundle": "^2.2",
|
||||
"nbgrp/onelogin-saml-bundle": "^1.3",
|
||||
"nelexa/zip": "^4.0",
|
||||
"nelmio/cors-bundle": "^2.3",
|
||||
"nelmio/security-bundle": "^3.0",
|
||||
"nyholm/psr7": "^1.1",
|
||||
"omines/datatables-bundle": "^0.9.1",
|
||||
"paragonie/sodium_compat": "^1.21",
|
||||
"part-db/label-fonts": "^1.0",
|
||||
"rhukster/dom-sanitizer": "^1.0",
|
||||
"runtime/frankenphp-symfony": "^0.2.0",
|
||||
"ocramius/proxy-manager": "2.2.*",
|
||||
"omines/datatables-bundle": "^0.5.0",
|
||||
"php-translation/symfony-bundle": "^0.13.0",
|
||||
"phpdocumentor/reflection-docblock": "^5.2",
|
||||
"s9e/text-formatter": "^2.1",
|
||||
"scheb/2fa-backup-code": "^6.8.0",
|
||||
"scheb/2fa-bundle": "^6.8.0",
|
||||
"scheb/2fa-google-authenticator": "^6.8.0",
|
||||
"scheb/2fa-trusted-device": "^6.8.0",
|
||||
"scheb/2fa-backup-code": "^5.13",
|
||||
"scheb/2fa-bundle": "^5.13",
|
||||
"scheb/2fa-google-authenticator": "^5.13",
|
||||
"scheb/2fa-trusted-device": "^5.13",
|
||||
"sensio/framework-extra-bundle": "^6.1.1",
|
||||
"shivas/versioning-bundle": "^4.0",
|
||||
"spatie/db-dumper": "^3.3.1",
|
||||
"spatie/db-dumper": "^2.21",
|
||||
"symfony/apache-pack": "^1.0",
|
||||
"symfony/asset": "6.4.*",
|
||||
"symfony/console": "6.4.*",
|
||||
"symfony/css-selector": "6.4.*",
|
||||
"symfony/dom-crawler": "6.4.*",
|
||||
"symfony/dotenv": "6.4.*",
|
||||
"symfony/expression-language": "6.4.*",
|
||||
"symfony/flex": "^v2.3.1",
|
||||
"symfony/form": "6.4.*",
|
||||
"symfony/framework-bundle": "6.4.*",
|
||||
"symfony/http-client": "6.4.*",
|
||||
"symfony/http-kernel": "6.4.*",
|
||||
"symfony/mailer": "6.4.*",
|
||||
"symfony/asset": "5.4.*",
|
||||
"symfony/console": "5.4.*",
|
||||
"symfony/dotenv": "5.4.*",
|
||||
"symfony/expression-language": "5.4.*",
|
||||
"symfony/flex": "^1.1",
|
||||
"symfony/form": "5.4.*",
|
||||
"symfony/framework-bundle": "5.4.*",
|
||||
"symfony/http-client": "5.4.*",
|
||||
"symfony/http-kernel": "5.4.*",
|
||||
"symfony/mailer": "5.4.*",
|
||||
"symfony/monolog-bundle": "^3.1",
|
||||
"symfony/polyfill-php82": "^1.28",
|
||||
"symfony/process": "6.4.*",
|
||||
"symfony/property-access": "6.4.*",
|
||||
"symfony/property-info": "6.4.*",
|
||||
"symfony/rate-limiter": "6.4.*",
|
||||
"symfony/runtime": "6.4.*",
|
||||
"symfony/security-bundle": "6.4.*",
|
||||
"symfony/serializer": "6.4.*",
|
||||
"symfony/string": "6.4.*",
|
||||
"symfony/translation": "6.4.*",
|
||||
"symfony/twig-bundle": "6.4.*",
|
||||
"symfony/ux-translator": "^2.10",
|
||||
"symfony/process": "5.4.*",
|
||||
"symfony/property-access": "5.4.*",
|
||||
"symfony/property-info": "5.4.*",
|
||||
"symfony/proxy-manager-bridge": "5.4.*",
|
||||
"symfony/rate-limiter": "5.4.*",
|
||||
"symfony/runtime": "5.4.*",
|
||||
"symfony/security-bundle": "5.4.*",
|
||||
"symfony/serializer": "5.4.*",
|
||||
"symfony/translation": "5.4.*",
|
||||
"symfony/twig-bundle": "5.4.*",
|
||||
"symfony/ux-turbo": "^2.0",
|
||||
"symfony/validator": "6.4.*",
|
||||
"symfony/web-link": "6.4.*",
|
||||
"symfony/webpack-encore-bundle": "^v2.0.1",
|
||||
"symfony/yaml": "6.4.*",
|
||||
"tecnickcom/tc-lib-barcode": "^2.1.4",
|
||||
"symfony/validator": "5.4.*",
|
||||
"symfony/web-link": "5.4.*",
|
||||
"symfony/webpack-encore-bundle": "^1.1",
|
||||
"symfony/yaml": "5.4.*",
|
||||
"tecnickcom/tc-lib-barcode": "^1.15",
|
||||
"twig/cssinliner-extra": "^3.0",
|
||||
"twig/extra-bundle": "^3.8",
|
||||
"twig/html-extra": "^3.8",
|
||||
"twig/extra-bundle": "^3.0",
|
||||
"twig/html-extra": "^3.0",
|
||||
"twig/inky-extra": "^3.0",
|
||||
"twig/intl-extra": "^3.8",
|
||||
"twig/markdown-extra": "^3.8",
|
||||
"twig/string-extra": "^3.8",
|
||||
"web-auth/webauthn-symfony-bundle": "^4.0.0"
|
||||
"twig/intl-extra": "^3.0",
|
||||
"twig/markdown-extra": "^3.0",
|
||||
"web-auth/webauthn-symfony-bundle": "^3.3",
|
||||
"webmozart/assert": "^1.4",
|
||||
"doctrine/data-fixtures": "^1.6.6"
|
||||
|
||||
},
|
||||
"require-dev": {
|
||||
"dama/doctrine-test-bundle": "^v8.0.0",
|
||||
"doctrine/doctrine-fixtures-bundle": "^4.0.0",
|
||||
"ekino/phpstan-banned-code": "^v3.0.0",
|
||||
"jbtronics/translation-editor-bundle": "^1.0",
|
||||
"dama/doctrine-test-bundle": "^7.0",
|
||||
"ekino/phpstan-banned-code": "^v1.0.0",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^2.0.4",
|
||||
"phpstan/phpstan-doctrine": "^2.0.1",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.1",
|
||||
"phpstan/phpstan-symfony": "^2.0.0",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"rector/rector": "^2.0.4",
|
||||
"phpstan/phpstan": "^1.4.7",
|
||||
"phpstan/phpstan-doctrine": "^1.2.11",
|
||||
"phpstan/phpstan-symfony": "^1.1.7",
|
||||
"psalm/plugin-symfony": "^v5.0.1",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"symfony/browser-kit": "6.4.*",
|
||||
"symfony/debug-bundle": "6.4.*",
|
||||
"symfony/browser-kit": "^5.2",
|
||||
"symfony/css-selector": "^5.2",
|
||||
"symfony/debug-bundle": "^5.2",
|
||||
"symfony/maker-bundle": "^1.13",
|
||||
"symfony/phpunit-bridge": "6.4.*",
|
||||
"symfony/stopwatch": "6.4.*",
|
||||
"symfony/web-profiler-bundle": "6.4.*",
|
||||
"symplify/easy-coding-standard": "^12.0"
|
||||
"symfony/phpunit-bridge": "5.4.*",
|
||||
"symfony/stopwatch": "^5.2",
|
||||
"symfony/web-profiler-bundle": "^5.2",
|
||||
"symplify/easy-coding-standard": "^11.0",
|
||||
"vimeo/psalm": "^5.6.0",
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "Used to improve price calculation performance",
|
||||
|
@ -123,7 +111,7 @@
|
|||
"*": "dist"
|
||||
},
|
||||
"platform": {
|
||||
"php": "8.1.0"
|
||||
"php": "7.4.0"
|
||||
},
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
|
@ -155,7 +143,7 @@
|
|||
"post-update-cmd": [
|
||||
"@auto-scripts"
|
||||
],
|
||||
"phpstan": "vendor/bin/phpstan analyse src --level 5 --memory-limit 1G"
|
||||
"phpstan": "vendor/bin/phpstan analyse src --level 2 --memory-limit 1G"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/symfony": "*"
|
||||
|
@ -163,8 +151,9 @@
|
|||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "6.4.*",
|
||||
"docker": true
|
||||
"require": "5.4.*"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
]
|
||||
}
|
||||
|
|
13811
composer.lock
generated
13811
composer.lock
generated
File diff suppressed because it is too large
Load diff
23
config/bootstrap.php
Normal file
23
config/bootstrap.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
|
||||
if (!class_exists(Dotenv::class)) {
|
||||
throw new LogicException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
|
||||
}
|
||||
|
||||
// Load cached env vars if the .env.local.php file exists
|
||||
// Run "composer dump-env prod" to create it (requires symfony/flex >=1.2)
|
||||
if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && (!isset($env['APP_ENV']) || ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV'])) {
|
||||
(new Dotenv(false))->populate($env);
|
||||
} else {
|
||||
// load all the .env files
|
||||
(new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env');
|
||||
}
|
||||
|
||||
$_SERVER += $_ENV;
|
||||
$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev';
|
||||
$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV'];
|
||||
$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0';
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
return [
|
||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
|
@ -18,18 +19,13 @@ return [
|
|||
DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
|
||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||
Gregwar\CaptchaBundle\GregwarCaptchaBundle::class => ['all' => true],
|
||||
Translation\Bundle\TranslationBundle::class => ['all' => true],
|
||||
Florianv\SwapBundle\FlorianvSwapBundle::class => ['all' => true],
|
||||
Nelmio\SecurityBundle\NelmioSecurityBundle::class => ['all' => true],
|
||||
Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
|
||||
Jbtronics\TFAWebauthn\TFAWebauthnBundle::class => ['all' => true],
|
||||
Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true],
|
||||
SpomkyLabs\CborBundle\SpomkyLabsCborBundle::class => ['all' => true],
|
||||
Webauthn\Bundle\WebauthnBundle::class => ['all' => true],
|
||||
Nbgrp\OneloginSamlBundle\NbgrpOneloginSamlBundle::class => ['all' => true],
|
||||
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
|
||||
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
||||
Jbtronics\DompdfFontLoaderBundle\DompdfFontLoaderBundle::class => ['all' => true],
|
||||
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
|
||||
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
||||
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
|
||||
Jbtronics\TranslationEditorBundle\JbtronicsTranslationEditorBundle::class => ['dev' => true],
|
||||
Hslavich\OneloginSamlBundle\HslavichOneloginSamlBundle::class => ['all' => true],
|
||||
];
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
api_platform:
|
||||
title: 'Part-DB API'
|
||||
description: 'API of Part-DB'
|
||||
version: '0.1.0'
|
||||
|
||||
formats:
|
||||
jsonld: ['application/ld+json']
|
||||
json: ['application/json']
|
||||
jsonapi: ['application/vnd.api+json']
|
||||
|
||||
docs_formats:
|
||||
jsonld: ['application/ld+json']
|
||||
jsonopenapi: ['application/vnd.openapi+json']
|
||||
html: ['text/html']
|
||||
json: ['application/vnd.openapi+json']
|
||||
|
||||
swagger:
|
||||
api_keys:
|
||||
# overridden in OpenApiFactoryDecorator
|
||||
access_token:
|
||||
name: Authorization
|
||||
type: header
|
||||
|
||||
defaults:
|
||||
# TODO: Change this to true later. In the moment it is false, because we use the session in somewhere
|
||||
stateless: false
|
||||
cache_headers:
|
||||
vary: ['Content-Type', 'Authorization', 'Origin']
|
||||
extra_properties:
|
||||
standard_put: true
|
||||
rfc_7807_compliant_errors: true
|
||||
|
||||
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
|
||||
use_symfony_listeners: true
|
||||
|
||||
serializer:
|
||||
# Change this to false later, to remove the hydra prefix on the API
|
||||
hydra_prefix: true
|
|
@ -20,6 +20,3 @@ framework:
|
|||
tree.cache:
|
||||
adapter: cache.app
|
||||
tags: true
|
||||
|
||||
info_provider.cache:
|
||||
adapter: cache.app
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
when@test:
|
||||
dama_doctrine_test:
|
||||
enable_static_connection: true
|
||||
enable_static_meta_data_cache: true
|
||||
enable_static_query_cache: true
|
|
@ -8,14 +8,15 @@ datatables:
|
|||
|
||||
# Set options, as documented at https://datatables.net/reference/option/
|
||||
options:
|
||||
lengthMenu : [[10, 25, 50, 100], [10, 25, 50, 100]] # We add the "All" option, when part tables are generated
|
||||
pageLength: '%partdb.table.default_page_size%' # Set to -1 to disable pagination (i.e. show all rows) by default
|
||||
dom: " <'row' <'col mb-2 input-group flex-nowrap' B l > <'col-auto mb-2' < p >>>
|
||||
<'card'
|
||||
rt
|
||||
<'card-footer card-footer-table text-muted' i >
|
||||
>
|
||||
<'row' <'col mt-2 input-group flex-nowrap' B l > <'col-auto mt-2' < p >>>"
|
||||
lengthMenu : [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]]
|
||||
pageLength: 50
|
||||
#dom: "<'row' <'col-sm-12' tr>><'row' <'col-sm-6'l><'col-sm-6 text-right'pif>>"
|
||||
dom: " <'row'<'col mb-2 input-group' B l> <'col mb-2' <'pull-end' p>>>
|
||||
<'card'
|
||||
rt
|
||||
<'card-footer card-footer-table text-muted' i >
|
||||
>
|
||||
<'row'<'col mt-2 input-group' B l> <'col mt-2' <'pull-right' p>>>"
|
||||
pagingType: 'simple_numbers'
|
||||
searching: true
|
||||
stateSave: true
|
||||
|
|
5
config/packages/dev/php_translation.yaml
Normal file
5
config/packages/dev/php_translation.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
translation:
|
||||
symfony_profiler:
|
||||
enabled: true
|
||||
webui:
|
||||
enabled: true
|
|
@ -2,32 +2,18 @@ doctrine:
|
|||
dbal:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
|
||||
# Required for DAMA doctrine test bundle
|
||||
use_savepoints: true
|
||||
|
||||
# IMPORTANT: You MUST configure your server version,
|
||||
# either here or in the DATABASE_URL env var (see .env file)
|
||||
|
||||
types:
|
||||
# UTC datetimes
|
||||
datetime:
|
||||
class: App\Doctrine\Types\UTCDateTimeType
|
||||
date:
|
||||
class: App\Doctrine\Types\UTCDateTimeType
|
||||
|
||||
datetime_immutable:
|
||||
class: App\Doctrine\Types\UTCDateTimeImmutableType
|
||||
date_immutable:
|
||||
class: App\Doctrine\Types\UTCDateTimeImmutableType
|
||||
|
||||
big_decimal:
|
||||
class: App\Doctrine\Types\BigDecimalType
|
||||
tinyint:
|
||||
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)~
|
||||
# Only enable this when needed
|
||||
|
@ -35,29 +21,20 @@ doctrine:
|
|||
|
||||
orm:
|
||||
auto_generate_proxy_classes: true
|
||||
enable_lazy_ghost_objects: true
|
||||
report_fields_where_declared: true
|
||||
validate_xml_mapping: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
auto_mapping: true
|
||||
controller_resolver:
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
App:
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
type: annotation
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
|
||||
dql:
|
||||
string_functions:
|
||||
regexp: App\Doctrine\Functions\Regexp
|
||||
field: DoctrineExtensions\Query\Mysql\Field
|
||||
field2: App\Doctrine\Functions\Field2
|
||||
natsort: App\Doctrine\Functions\Natsort
|
||||
array_position: App\Doctrine\Functions\ArrayPosition
|
||||
ilike: App\Doctrine\Functions\ILike
|
||||
regexp: DoctrineExtensions\Query\Mysql\Regexp
|
||||
ifnull: DoctrineExtensions\Query\Mysql\IfNull
|
||||
|
||||
when@test:
|
||||
doctrine:
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
dompdf_font_loader:
|
||||
auto_install: true
|
||||
|
||||
fonts:
|
||||
unifont:
|
||||
normal: "%kernel.project_dir%/vendor/part-db/label-fonts/fonts/unifont.ttf"
|
||||
|
||||
# Enable autodiscovery of fonts, so that font installation is much easier
|
||||
autodiscovery:
|
||||
paths:
|
||||
- "%kernel.project_dir%/assets/fonts/dompdf"
|
|
@ -2,13 +2,8 @@
|
|||
framework:
|
||||
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
|
||||
disallow_search_engine_index: false
|
||||
|
||||
# Must be set to true, to enable the change of HTTP method via _method parameter, otherwise our delete routines does not work anymore
|
||||
# Must be set to true, to enable the change of HTTP methhod via _method parameter, otherwise our delete routines does not work anymore
|
||||
# TODO: Rework delete routines to work without _method parameter as it is not recommended anymore (see https://github.com/symfony/symfony/issues/45278)
|
||||
http_method_override: true
|
||||
|
||||
|
@ -27,12 +22,16 @@ framework:
|
|||
handler_id: null
|
||||
cookie_secure: auto
|
||||
cookie_samesite: lax
|
||||
storage_factory_id: session.storage.factory.native
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
php_errors:
|
||||
log: true
|
||||
|
||||
form:
|
||||
legacy_error_messages: false # Enable to use the new Form component validation messages
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
test: true
|
||||
|
|
60
config/packages/hslavich_onelogin_saml.yaml
Normal file
60
config/packages/hslavich_onelogin_saml.yaml
Normal file
|
@ -0,0 +1,60 @@
|
|||
# See https://github.com/SAML-Toolkits/php-saml for more information about the SAML settings
|
||||
|
||||
hslavich_onelogin_saml:
|
||||
# Basic settings
|
||||
idp:
|
||||
entityId: '%env(string:SAML_IDP_ENTITY_ID)%'
|
||||
singleSignOnService:
|
||||
url: '%env(string:SAML_IDP_SINGLE_SIGN_ON_SERVICE)%'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
singleLogoutService:
|
||||
url: '%env(string:SAML_IDP_SINGLE_LOGOUT_SERVICE)%'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
x509cert: '%env(string:SAML_IDP_X509_CERT)%'
|
||||
sp:
|
||||
entityId: '%env(string:SAML_SP_ENTITY_ID)%'
|
||||
assertionConsumerService:
|
||||
url: '%partdb.default_uri%saml/acs'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
|
||||
singleLogoutService:
|
||||
url: '%partdb.default_uri%logout'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
x509cert: '%env(string:SAML_SP_X509_CERT)%'
|
||||
privateKey: '%env(string:SAMLP_SP_PRIVATE_KEY)%'
|
||||
|
||||
# Optional settings
|
||||
#baseurl: 'http://myapp.com'
|
||||
strict: true
|
||||
debug: false
|
||||
security:
|
||||
allowRepeatAttributeName: true
|
||||
# nameIdEncrypted: false
|
||||
authnRequestsSigned: true
|
||||
logoutRequestSigned: true
|
||||
logoutResponseSigned: true
|
||||
# wantMessagesSigned: false
|
||||
# wantAssertionsSigned: true
|
||||
# wantNameIdEncrypted: false
|
||||
# requestedAuthnContext: true
|
||||
# signMetadata: false
|
||||
# wantXMLValidation: true
|
||||
# relaxDestinationValidation: false
|
||||
# destinationStrictlyMatches: true
|
||||
# rejectUnsolicitedResponsesWithInResponseTo: false
|
||||
# signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
|
||||
# digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256'
|
||||
#contactPerson:
|
||||
# technical:
|
||||
# givenName: 'Tech User'
|
||||
# emailAddress: 'techuser@example.com'
|
||||
# support:
|
||||
# givenName: 'Support User'
|
||||
# emailAddress: 'supportuser@example.com'
|
||||
# administrative:
|
||||
# givenName: 'Administrative User'
|
||||
# emailAddress: 'administrativeuser@example.com'
|
||||
#organization:
|
||||
# en:
|
||||
# name: 'Part-DB-name'
|
||||
# displayname: 'Displayname'
|
||||
# url: 'http://example.com'
|
|
@ -1,5 +0,0 @@
|
|||
framework:
|
||||
http_client:
|
||||
default_options:
|
||||
headers:
|
||||
'User-Agent': 'Part-DB'
|
|
@ -1,10 +0,0 @@
|
|||
services:
|
||||
Psr\Http\Message\RequestFactoryInterface: '@http_discovery.psr17_factory'
|
||||
Psr\Http\Message\ResponseFactoryInterface: '@http_discovery.psr17_factory'
|
||||
Psr\Http\Message\ServerRequestFactoryInterface: '@http_discovery.psr17_factory'
|
||||
Psr\Http\Message\StreamFactoryInterface: '@http_discovery.psr17_factory'
|
||||
Psr\Http\Message\UploadedFileFactoryInterface: '@http_discovery.psr17_factory'
|
||||
Psr\Http\Message\UriFactoryInterface: '@http_discovery.psr17_factory'
|
||||
|
||||
http_discovery.psr17_factory:
|
||||
class: Http\Discovery\Psr17Factory
|
|
@ -1,38 +0,0 @@
|
|||
knpu_oauth2_client:
|
||||
clients:
|
||||
# configure your clients as described here: https://github.com/knpuniversity/oauth2-client-bundle#configuration
|
||||
|
||||
ip_digikey_oauth:
|
||||
type: generic
|
||||
provider_class: '\League\OAuth2\Client\Provider\GenericProvider'
|
||||
|
||||
client_id: '%env(PROVIDER_DIGIKEY_CLIENT_ID)%'
|
||||
client_secret: '%env(PROVIDER_DIGIKEY_SECRET)%'
|
||||
|
||||
redirect_route: 'oauth_client_check'
|
||||
redirect_params: {name: 'ip_digikey_oauth'}
|
||||
|
||||
provider_options:
|
||||
urlAuthorize: 'https://api.digikey.com/v1/oauth2/authorize'
|
||||
urlAccessToken: 'https://api.digikey.com/v1/oauth2/token'
|
||||
urlResourceOwnerDetails: ''
|
||||
|
||||
# Sandbox
|
||||
#urlAuthorize: 'https://sandbox-api.digikey.com/v1/oauth2/authorize'
|
||||
#urlAccessToken: 'https://sandbox-api.digikey.com/v1/oauth2/token'
|
||||
#urlResourceOwnerDetails: ''
|
||||
|
||||
ip_octopart_oauth:
|
||||
type: generic
|
||||
provider_class: '\League\OAuth2\Client\Provider\GenericProvider'
|
||||
|
||||
client_id: '%env(PROVIDER_OCTOPART_CLIENT_ID)%'
|
||||
client_secret: '%env(PROVIDER_OCTOPART_SECRET)%'
|
||||
|
||||
redirect_route: 'oauth_client_check'
|
||||
redirect_params: { name: 'ip_octopart_oauth' }
|
||||
|
||||
provider_options:
|
||||
urlAuthorize: 'https://identity.nexar.com/connect/authorize'
|
||||
urlAccessToken: 'https://identity.nexar.com/connect/token'
|
||||
urlResourceOwnerDetails: ''
|
2
config/packages/lock.yaml
Normal file
2
config/packages/lock.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
framework:
|
||||
lock: '%env(LOCK_DSN)%'
|
|
@ -50,6 +50,7 @@ when@prod:
|
|||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
formatter: monolog.formatter.json
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
|
@ -73,6 +74,7 @@ when@docker:
|
|||
type: stream
|
||||
path: "php://stderr"
|
||||
level: debug
|
||||
formatter: monolog.formatter.json
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
# See https://github.com/SAML-Toolkits/php-saml for more information about the SAML settings
|
||||
|
||||
# Define a parameter here, so we can access it later in the default fallback
|
||||
parameters:
|
||||
saml.sp.privateKey: '%env(string:SAML_SP_PRIVATE_KEY)%'
|
||||
|
||||
nbgrp_onelogin_saml:
|
||||
use_proxy_vars: '%env(bool:SAML_BEHIND_PROXY)%'
|
||||
onelogin_settings:
|
||||
default:
|
||||
# Basic settings
|
||||
idp:
|
||||
entityId: '%env(string:SAML_IDP_ENTITY_ID)%'
|
||||
singleSignOnService:
|
||||
url: '%env(string:SAML_IDP_SINGLE_SIGN_ON_SERVICE)%'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
singleLogoutService:
|
||||
url: '%env(string:SAML_IDP_SINGLE_LOGOUT_SERVICE)%'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
x509cert: '%env(string:SAML_IDP_X509_CERT)%'
|
||||
sp:
|
||||
entityId: '%env(string:SAML_SP_ENTITY_ID)%'
|
||||
assertionConsumerService:
|
||||
url: '%partdb.default_uri%saml/acs'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
|
||||
singleLogoutService:
|
||||
url: '%partdb.default_uri%logout'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
x509cert: '%env(string:SAML_SP_X509_CERT)%'
|
||||
# Before the env variable was wrongly named "SAMLP_SP_PRIVATE_KEY".
|
||||
# For compatibility reasons we keep it and only fallback to the new name if the old one is not set. This may be removed in the future.
|
||||
privateKey: '%env(string:default:saml.sp.privateKey:string:SAMLP_SP_PRIVATE_KEY)%'
|
||||
|
||||
# Optional settings
|
||||
baseurl: '%partdb.default_uri%saml/'
|
||||
strict: true
|
||||
debug: false
|
||||
security:
|
||||
allowRepeatAttributeName: true
|
||||
# nameIdEncrypted: false
|
||||
authnRequestsSigned: true
|
||||
logoutRequestSigned: true
|
||||
logoutResponseSigned: true
|
||||
# wantMessagesSigned: false
|
||||
# wantAssertionsSigned: true
|
||||
# wantNameIdEncrypted: false
|
||||
# requestedAuthnContext: true
|
||||
# signMetadata: false
|
||||
# wantXMLValidation: true
|
||||
# relaxDestinationValidation: false
|
||||
# destinationStrictlyMatches: true
|
||||
# rejectUnsolicitedResponsesWithInResponseTo: false
|
||||
# signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
|
||||
# digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256'
|
||||
#contactPerson:
|
||||
# technical:
|
||||
# givenName: 'Tech User'
|
||||
# emailAddress: 'techuser@example.com'
|
||||
# support:
|
||||
# givenName: 'Support User'
|
||||
# emailAddress: 'supportuser@example.com'
|
||||
# administrative:
|
||||
# givenName: 'Administrative User'
|
||||
# emailAddress: 'administrativeuser@example.com'
|
||||
#organization:
|
||||
# en:
|
||||
# name: 'Part-DB-name'
|
||||
# displayname: 'Displayname'
|
||||
# url: 'http://example.com'
|
|
@ -1,10 +0,0 @@
|
|||
nelmio_cors:
|
||||
defaults:
|
||||
origin_regex: true
|
||||
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
|
||||
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
|
||||
allow_headers: ['Content-Type', 'Authorization']
|
||||
expose_headers: ['Link']
|
||||
max_age: 3600
|
||||
paths:
|
||||
'^/': null
|
|
@ -12,13 +12,6 @@ nelmio_security:
|
|||
external_redirects:
|
||||
abort: true
|
||||
log: true
|
||||
allow_list:
|
||||
# Whitelist the domain of the SAML IDP, so we can redirect to it during the SAML login process
|
||||
- '%env(string:key:host:url:SAML_IDP_SINGLE_SIGN_ON_SERVICE)%'
|
||||
|
||||
# Whitelist the info provider APIs (OAuth redirects)
|
||||
- 'digikey.com'
|
||||
- 'nexar.com'
|
||||
|
||||
# forces Microsoft's XSS-Protection with
|
||||
# its block mode
|
||||
|
@ -51,16 +44,12 @@ nelmio_security:
|
|||
img-src:
|
||||
- '*'
|
||||
- 'data:'
|
||||
# Required for be able to load pictures in the QR code scanner
|
||||
- 'blob:'
|
||||
style-src:
|
||||
- 'self'
|
||||
- 'unsafe-inline'
|
||||
- 'data:'
|
||||
script-src:
|
||||
- 'self'
|
||||
# Required for loading the Wasm for the barcode scanner:
|
||||
- 'wasm-unsafe-eval'
|
||||
object-src:
|
||||
- 'self'
|
||||
- 'data:'
|
||||
|
|
11
config/packages/php_translation.yaml
Normal file
11
config/packages/php_translation.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
translation:
|
||||
locales: ["en", "de"]
|
||||
edit_in_place:
|
||||
enabled: false
|
||||
config_name: app
|
||||
configs:
|
||||
app:
|
||||
dirs: ["%kernel.project_dir%/templates", "%kernel.project_dir%/src"]
|
||||
output_dir: "%kernel.project_dir%/translations"
|
||||
excluded_names: ["*TestCase.php", "*Test.php"]
|
||||
excluded_dirs: [cache, data, logs]
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue