Compare commits

..

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

663 changed files with 17960 additions and 59612 deletions

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -1,2 +0,0 @@
opcache.preload_user = root
opcache.preload = /app/config/preload.php

View file

@ -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 "$@"

View file

@ -1,4 +0,0 @@
worker {
file ./public/index.php
env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
}

View file

@ -39,8 +39,8 @@ if [ -d /var/www/html/var/db ]; then
fi fi
fi fi
# Start PHP-FPM (the PHP_VERSION is replaced by the configured version in the Dockerfile) # Start PHP-FPM
service phpPHP_VERSION-fpm start 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) # first arg is `-f` or `--some-option` (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/docker-php-entrypoint)
if [ "${1#-}" != "$1" ]; then if [ "${1#-}" != "$1" ]; then

View file

@ -25,7 +25,7 @@
CustomLog ${APACHE_LOG_DIR}/access.log combined CustomLog ${APACHE_LOG_DIR}/access.log combined
# Pass the configuration from the docker env to the PHP environment (here you should list all .env options) # 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 APP_ENV APP_DEBUG APP_SECRET REDIRECT_TO_HTTPS
PassEnv TRUSTED_PROXIES TRUSTED_HOSTS LOCK_DSN 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 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 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
@ -42,10 +42,6 @@
PassEnv PROVIDER_TME_KEY PROVIDER_TME_SECRET PROVIDER_TME_CURRENCY PROVIDER_TME_LANGUAGE PROVIDER_TME_COUNTRY PROVIDER_TME_GET_GROSS_PRICES 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_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_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 PassEnv EDA_KICAD_CATEGORY_DEPTH
# For most configuration files from conf-available/, which are # For most configuration files from conf-available/, which are

View file

@ -5,8 +5,6 @@ tests/
docs/ docs/
.git .git
/public/media/*
###> symfony/framework-bundle ### ###> symfony/framework-bundle ###
/.env.local /.env.local
/.env.local.php /.env.local.php
@ -44,39 +42,3 @@ yarn-error.log
/phpunit.xml /phpunit.xml
.phpunit.result.cache .phpunit.result.cache
###< phpunit/phpunit ### ###< 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

64
.env
View file

@ -23,10 +23,6 @@ DATABASE_MYSQL_USE_SSL_CA=0
# Only do this, if you know what you are doing! # Only do this, if you know what you are doing!
DATABASE_MYSQL_SSL_VERIFY_CERT=1 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 # General settings
################################################################################### ###################################################################################
@ -143,8 +139,7 @@ PROVIDER_TME_CURRENCY=EUR
PROVIDER_TME_LANGUAGE=en PROVIDER_TME_LANGUAGE=en
# The country to get results for # The country to get results for
PROVIDER_TME_COUNTRY=DE PROVIDER_TME_COUNTRY=DE
# [DEPRECATED] Set this to 1 to get gross prices (including VAT) instead of net prices # 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 PROVIDER_TME_GET_GROSS_PRICES=1
# Octopart / Nexar Provider: # Octopart / Nexar Provider:
@ -182,61 +177,6 @@ PROVIDER_LCSC_ENABLED=0
# The currency to get prices in (e.g. EUR, USD, etc.) # The currency to get prices in (e.g. EUR, USD, etc.)
PROVIDER_LCSC_CURRENCY=EUR 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 # EDA integration related settings
################################################################################## ##################################################################################
@ -308,8 +248,6 @@ BANNER=""
APP_ENV=prod APP_ENV=prod
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849 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 # 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,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16

View file

View file

@ -65,7 +65,7 @@ jobs:
- -
name: Build and push name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v5
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7 platforms: linux/amd64,linux/arm64,linux/arm/v7

View file

@ -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

View file

@ -16,10 +16,9 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
strategy: strategy:
fail-fast: false
matrix: matrix:
php-versions: [ '8.1', '8.2', '8.3', '8.4' ] php-versions: [ '8.1', '8.2', '8.3' ]
db-type: [ 'mysql', 'sqlite', 'postgres' ] db-type: [ 'mysql', 'sqlite' ]
env: env:
# Note that we set DATABASE URL later based on our db-type matrix value # Note that we set DATABASE URL later based on our db-type matrix value
@ -31,17 +30,13 @@ jobs:
steps: steps:
- name: Set Database env for MySQL - 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' if: matrix.db-type == 'mysql'
- name: Set Database env for SQLite - name: Set Database env for SQLite
run: echo "DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"" >> $GITHUB_ENV run: echo "DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"" >> $GITHUB_ENV
if: matrix.db-type == 'sqlite' 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 - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -55,17 +50,8 @@ jobs:
- name: Start MySQL - name: Start MySQL
run: sudo systemctl start mysql.service 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: Setup MySQL
- 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
# uses: mirromutth/mysql-action@v1.1 # uses: mirromutth/mysql-action@v1.1
# with: # with:
# mysql version: 5.7 # mysql version: 5.7
@ -113,7 +99,12 @@ jobs:
- name: Create DB - name: Create DB
run: php bin/console --env test doctrine:database:create --if-not-exists -n 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 - name: Do migrations
run: php bin/console --env test doctrine:migrations:migrate -n run: php bin/console --env test doctrine:migrations:migrate -n
@ -126,7 +117,7 @@ jobs:
run: ./bin/phpunit --coverage-clover=coverage.xml run: ./bin/phpunit --coverage-clover=coverage.xml
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v4
with: with:
env_vars: PHP_VERSION,DB_TYPE env_vars: PHP_VERSION,DB_TYPE
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
@ -144,11 +135,11 @@ jobs:
- name: Test check-requirements command - name: Test check-requirements command
run: php bin/console partdb:check-requirements -n 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 - name: Test legacy Part-DB import
run: bash .github/assets/legacy_import/test_legacy_import.sh run: bash .github/assets/legacy_import/test_legacy_import.sh
if: matrix.db-type == 'mysql' && matrix.php-versions == '8.2' if: matrix.db-type == 'mysql' && matrix.php-versions == '8.2'
env: env:
DATABASE_URL: mysql://root:root@localhost:3306/legacy_db DATABASE_URL: mysql://root:root@localhost:3306/legacy_db

View file

@ -1,64 +1,22 @@
ARG BASE_IMAGE=debian:bookworm-slim FROM debian:bullseye-slim
ARG PHP_VERSION=8.3
FROM ${BASE_IMAGE} AS base
ARG PHP_VERSION
# Install needed dependencies for PHP build # Install needed dependencies for PHP build
#RUN apt-get update && apt-get install -y pkg-config curl libcurl4-openssl-dev libicu-dev \ #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 \ # 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/* # && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get -y install \ RUN apt-get update && apt-get -y install apt-transport-https lsb-release ca-certificates curl zip mariadb-client \
apt-transport-https \
lsb-release \
ca-certificates \
curl \
zip \
mariadb-client \
postgresql-client \
&& curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg \ && 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' \ && 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 update && apt-get upgrade -y \
&& apt-get install -y \ && 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 \
apache2 \ && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*;
php${PHP_VERSION} \
php${PHP_VERSION}-fpm \ ENV APACHE_CONFDIR /etc/apache2
php${PHP_VERSION}-opcache \ ENV APACHE_ENVVARS $APACHE_CONFDIR/envvars
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/* \
# Create workdir and set permissions if directory does not exists # Create workdir and set permissions if directory does not exists
&& mkdir -p /var/www/html \ RUN mkdir -p /var/www/html && chown -R www-data:www-data /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
# Configure apache 2 (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/Dockerfile) # Configure apache 2 (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/Dockerfile)
# generically convert lines like # generically convert lines like
@ -69,6 +27,8 @@ ENV APACHE_ENVVARS=$APACHE_CONFDIR/envvars
# so that they can be overridden at runtime ("-e APACHE_RUN_USER=...") # so that they can be overridden at runtime ("-e APACHE_RUN_USER=...")
RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"; \ RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"; \
set -eux; . "$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 # logs should go to stdout / stderr
ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \ ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \
@ -76,87 +36,82 @@ RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"
ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \ ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \
chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR"; chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR";
# --- # Enable php-fpm
RUN a2enmod proxy_fcgi setenvif && a2enconf php8.1-fpm
FROM scratch AS apache-config
ARG PHP_VERSION
# Configure php-fpm to log to stdout of the container (stdout of PID 1) # 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 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 # 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 RUN { \
[global] echo '[global]'; \
error_log = /proc/1/fd/1 echo 'error_log = /proc/1/fd/1'; \
echo; \
[www] echo '[www]'; \
access.log = /proc/1/fd/1 echo 'access.log = /proc/1/fd/1'; \
catch_workers_output = yes echo 'catch_workers_output = yes'; \
decorate_workers_output = no echo 'decorate_workers_output = no'; \
clear_env = no echo 'clear_env = no'; \
EOF } | tee "/etc/php/8.1/fpm/pool.d/zz-docker.conf"
# PHP files should be handled by PHP, and should be preferred over any other file type # 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 RUN { \
<FilesMatch \\.php$> echo '<FilesMatch \.php$>'; \
SetHandler application/x-httpd-php echo '\tSetHandler application/x-httpd-php'; \
</FilesMatch> echo '</FilesMatch>'; \
echo; \
DirectoryIndex disabled echo 'DirectoryIndex disabled'; \
DirectoryIndex index.php index.html echo 'DirectoryIndex index.php index.html'; \
echo; \
<Directory /var/www/> echo '<Directory /var/www/>'; \
Options -Indexes echo '\tOptions -Indexes'; \
AllowOverride All echo '\tAllowOverride All'; \
</Directory> echo '</Directory>'; \
EOF } | 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) # 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 RUN \
opcache.memory_consumption=256 { \
opcache.max_accelerated_files=20000 echo 'opcache.memory_consumption=256'; \
opcache.validate_timestamp=0 echo 'opcache.max_accelerated_files=20000'; \
# Configure Realpath cache for performance echo 'opcache.validate_timestamp=0'; \
realpath_cache_size=4096K # Configure Realpath cache for performance
realpath_cache_ttl=600 echo 'realpath_cache_size=4096K'; \
EOF echo 'realpath_cache_ttl=600'; \
} > /etc/php/8.1/fpm/conf.d/symfony-recommended.ini
# Increase upload limit and enable preloading # Increase upload limit and enable preloading
COPY <<EOF /etc/php/${PHP_VERSION}/fpm/conf.d/partdb.ini RUN \
upload_max_filesize=256M { \
post_max_size=300M echo 'upload_max_filesize=256M'; \
opcache.preload_user=www-data echo 'post_max_size=300M'; \
opcache.preload=/var/www/html/config/preload.php echo 'opcache.preload_user=www-data'; \
log_limit=8096 echo 'opcache.preload=/var/www/html/config/preload.php'; \
EOF } > /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 # Set working dir
WORKDIR /var/www/html WORKDIR /var/www/html
COPY --from=apache-config / /
COPY --chown=www-data:www-data . . COPY --chown=www-data:www-data . .
# Setup apache2 # Setup apache2
RUN a2dissite 000-default.conf && \ RUN a2dissite 000-default.conf
a2ensite symfony.conf && \ COPY ./.docker/symfony.conf /etc/apache2/sites-available/symfony.conf
# Enable php-fpm RUN a2ensite symfony.conf
a2enmod proxy_fcgi setenvif && \ RUN a2enmod rewrite
a2enconf php${PHP_VERSION}-fpm && \
a2enconf docker-php && \
a2enmod rewrite
# Install composer and yarn dependencies for Part-DB # Install composer and yarn dependencies for Part-DB
USER www-data USER www-data
RUN composer install -a --no-dev && \ RUN composer install -a --no-dev && composer clear-cache
composer clear-cache RUN yarn install --network-timeout 600000 && yarn build && yarn cache clean && rm -rf node_modules/
RUN yarn install --network-timeout 600000 && \
yarn build && \
yarn cache clean && \
rm -rf node_modules/
# Use docker env to output logs to stdout # Use docker env to output logs to stdout
ENV APP_ENV=docker ENV APP_ENV=docker
@ -164,12 +119,10 @@ ENV DATABASE_URL="sqlite:///%kernel.project_dir%/uploads/app.db"
USER root USER root
# Replace the php version placeholder in the entry point, with our php version # Copy entrypoint to /usr/local/bin and make it executable
RUN sed -i "s/PHP_VERSION/${PHP_VERSION}/g" ./.docker/partdb-entrypoint.sh 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
# Copy entrypoint and 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
RUN install ./.docker/partdb-entrypoint.sh /usr/local/bin && \
install ./.docker/apache2-foreground /usr/local/bin
ENTRYPOINT ["partdb-entrypoint.sh"] ENTRYPOINT ["partdb-entrypoint.sh"]
CMD ["apache2-foreground"] CMD ["apache2-foreground"]
@ -177,4 +130,4 @@ CMD ["apache2-foreground"]
STOPSIGNAL SIGWINCH STOPSIGNAL SIGWINCH
EXPOSE 80 EXPOSE 80
VOLUME ["/var/www/html/uploads", "/var/www/html/public/media"] VOLUME ["/var/www/html/uploads", "/var/www/html/public/media"]

View file

@ -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

View file

@ -55,7 +55,7 @@ for the first time.
* Event log: Track what changes happen to your inventory, track which user does what. Revert your parts to older * Event log: Track what changes happen to your inventory, track which user does what. Revert your parts to older
versions. versions.
* Responsive design: You can use Part-DB on your PC, your tablet, and your smartphone using the same interface. * 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 * MySQL and SQLite are supported as database backends
* Support for rich text descriptions and comments in parts * Support for rich text descriptions and comments in parts
* Support for multiple currencies and automatic update of exchange rates supported * 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) * Powerful search and filter function, including parametric search (search for parts according to some specifications)
@ -74,10 +74,10 @@ Part-DB is also used by small companies and universities for managing their inve
## Requirements ## Requirements
* A **web server** (like Apache2 or nginx) that is capable of * A **web server** (like Apache2 or nginx) that is capable of
running [Symfony 6](https://symfony.com/doc/current/reference/requirements.html), running [Symfony 5](https://symfony.com/doc/current/reference/requirements.html),
this includes a minimum PHP version of **PHP 8.1** this includes a minimum PHP version of **PHP 8.1**
* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.4) database server, or **PostgreSQL** 10+ if you do not want to use SQLite. * A **MySQL** (at least 5.7) /**MariaDB** (at least 10.2.2) database server if you do not want to use SQLite.
* Shell access to your server is highly recommended! * Shell access to your server is highly suggested!
* For building the client-side assets **yarn** and **nodejs** (>= 18.0) is needed. * For building the client-side assets **yarn** and **nodejs** (>= 18.0) is needed.
## Installation ## Installation

View file

@ -1 +1 @@
1.16.1 1.11.0-dev

View file

@ -88,8 +88,5 @@ export default class extends Controller {
} else { } else {
this.hideSidebar(); this.hideSidebar();
} }
//Hide the tootip on the button
this._toggle_button.blur();
} }
} }

View file

@ -23,12 +23,6 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
import '../../css/components/tom-select_extensions.css'; import '../../css/components/tom-select_extensions.css';
import TomSelect from "tom-select"; import TomSelect from "tom-select";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
export default class extends Controller { export default class extends Controller {
_tomSelect; _tomSelect;
@ -52,12 +46,6 @@ export default class extends Controller {
} }
return '<div>' + escape(data.label) + '</div>'; return '<div>' + escape(data.label) + '</div>';
} }
},
plugins: {
'autoselect_typed': {},
'click_to_edit': {},
'clear_button': {},
"restore_on_backspace": {}
} }
}; };

View file

@ -53,7 +53,6 @@ export default class extends Controller {
const config = { const config = {
language: language, language: language,
licenseKey: "GPL",
} }
const watchdog = new EditorWatchdog(); const watchdog = new EditorWatchdog();

View file

@ -75,49 +75,13 @@ export default class extends Controller {
//Insert new html after the last child element //Insert new html after the last child element
//If the table has a tbody, insert it there //If the table has a tbody, insert it there
//Afterwards return the newly created row
if(targetTable.tBodies[0]) { if(targetTable.tBodies[0]) {
targetTable.tBodies[0].insertAdjacentHTML('beforeend', newElementStr); targetTable.tBodies[0].insertAdjacentHTML('beforeend', newElementStr);
return targetTable.tBodies[0].lastElementChild;
} else { //Otherwise just insert it } else { //Otherwise just insert it
targetTable.insertAdjacentHTML('beforeend', newElementStr); 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 * Similar to createEvent Pricedetails need some special handling to fill min amount
* @param event * @param event

View file

@ -24,25 +24,18 @@ import 'datatables.net-bs5/css/dataTables.bootstrap5.css'
import 'datatables.net-buttons-bs5/css/buttons.bootstrap5.css' import 'datatables.net-buttons-bs5/css/buttons.bootstrap5.css'
import 'datatables.net-fixedheader-bs5/css/fixedHeader.bootstrap5.css' import 'datatables.net-fixedheader-bs5/css/fixedHeader.bootstrap5.css'
import 'datatables.net-responsive-bs5/css/responsive.bootstrap5.css'; import 'datatables.net-responsive-bs5/css/responsive.bootstrap5.css';
import 'datatables.net-select-bs5/css/select.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';
//JS //JS
import 'datatables.net-bs5'; import 'datatables.net-bs5';
import 'datatables.net-buttons-bs5'; import 'datatables.net-buttons-bs5';
import 'datatables.net-buttons/js/buttons.colVis.js'; import 'datatables.net-buttons/js/buttons.colVis.js';
import 'datatables.net-fixedheader-bs5'; import 'datatables.net-fixedheader-bs5';
import 'datatables.net-select-bs5';
import 'datatables.net-colreorder-bs5'; import 'datatables.net-colreorder-bs5';
import 'datatables.net-responsive-bs5'; import 'datatables.net-responsive-bs5';
import '../../../js/lib/datatables'; 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'; const EVENT_DT_LOADED = 'dt:loaded';
export default class extends Controller { export default class extends Controller {
@ -139,7 +132,7 @@ export default class extends Controller {
if(this.isSelectable()) { if(this.isSelectable()) {
options.select = { options.select = {
style: 'multi+shift', style: 'multi+shift',
selector: 'td.dt-select', selector: 'td.select-checkbox'
}; };
} }
@ -193,6 +186,27 @@ export default class extends Controller {
dt.fixedHeader.headerOffset($("#navbar").outerHeight()); 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 //Allow to further configure the datatable
promise.then(this._afterLoaded.bind(this)); promise.then(this._afterLoaded.bind(this));

View file

@ -24,50 +24,18 @@ import "../../css/components/autocomplete_bootstrap_theme.css";
import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches';
import {marked} from "marked"; 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 { export default class extends Controller {
static targets = ["input"]; static targets = ["input"];
_autocomplete; _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() { initialize() {
// The endpoint for searching parts // The endpoint for searching parts
const base_url = this.element.dataset.autocomplete; const base_url = this.element.dataset.autocomplete;
// The URL template for the part detail pages // The URL template for the part detail pages
const part_detail_uri_template = this.element.dataset.detailUrl; 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 that = this;
const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
@ -77,15 +45,11 @@ export default class extends Controller {
this._autocomplete = autocomplete({ this._autocomplete = autocomplete({
container: this.element, container: this.element,
//Place the panel in the navbar, if the element is in navbar mode panelContainer: document.getElementById("navbar-frame"),
panelContainer: navbar_mode ? document.getElementById("navbar-search-form") : document.body, panelPlacement: 'end',
panelPlacement: this.element.dataset.panelPlacement,
plugins: [recentSearchesPlugin], plugins: [recentSearchesPlugin],
openOnFocus: true, openOnFocus: true,
placeholder: trans(SEARCH_PLACEHOLDER), placeholder: "Search for parts",
translations: {
submitButtonTitle: trans(SEARCH_SUBMIT)
},
// Use a navigator compatible with turbo: // Use a navigator compatible with turbo:
navigator: { navigator: {
@ -113,11 +77,6 @@ export default class extends Controller {
return; return;
} }
//Do not submit the form, if the input is empty
if (state.query === "") {
return;
}
input.value = state.query; input.value = state.query;
input.form.requestSubmit(); input.form.requestSubmit();
}, },
@ -125,56 +84,38 @@ export default class extends Controller {
getSources({ query }) { getSources({ query }) {
return [ return [
// The parts source
{ {
sourceId: 'parts', sourceId: 'parts',
getItems() { getItems() {
const url = base_url.replace('__QUERY__', encodeURIComponent(query)); const url = base_url.replace('__QUERY__', encodeURIComponent(query));
const data = fetch(url) return fetch(url)
.then((response) => response.json()) .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 }) { getItemUrl({ item }) {
return part_detail_uri_template.replace('__ID__', item.id); return part_detail_uri_template.replace('__ID__', item.id);
}, },
templates: { templates: {
header({ html }) { header({ html }) {
return html`<span class="aa-SourceHeaderTitle">${trans(STATISTICS_PARTS)}</span> return html`<span class="aa-SourceHeaderTitle">Parts</span>
<div class="aa-SourceHeaderLine" />`; <div class="aa-SourceHeaderLine" />`;
}, },
item({item, components, html}) { item({item, components, html}) {
const details_url = part_detail_uri_template.replace('__ID__', item.id); const details_url = part_detail_uri_template.replace('__ID__', item.id);
return html` return html`
<a class="aa-ItemLink" href="${details_url}"> <a class="aa-ItemLink" href="${details_url}">
<div class="aa-ItemContent"> <div class="aa-ItemContent">
<div class="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop"> <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"/> <img src="${item.image}" alt="${item.name}" width="40" height="40"/>
</div> </div>
<div class="aa-ItemContentBody"> <div class="aa-ItemContentBody">
<div class="aa-ItemContentTitle"> <div class="aa-ItemContentTitle">
<b> ${components.Highlight({hit: item, attribute: 'name'})}
${components.Highlight({hit: item, attribute: 'name'})}
</b>
</div> </div>
<div class="aa-ItemContentDescription"> <div class="aa-ItemContentDescription">
${components.Highlight({hit: item, attribute: 'description'})} ${components.Snippet({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> </div>
</div> </div>
@ -186,15 +127,5 @@ export default class extends Controller {
]; ];
}, },
}); });
//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);
});
}
} }
} }

View file

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

View file

@ -24,8 +24,6 @@ import {Controller} from "@hotwired/stimulus";
import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js' import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
export default class extends Controller { export default class extends Controller {
_tomSelect; _tomSelect;
@ -40,20 +38,11 @@ export default class extends Controller {
const allowAdd = this.element.getAttribute("data-allow-add") === "true"; const allowAdd = this.element.getAttribute("data-allow-add") === "true";
const addHint = this.element.getAttribute("data-add-hint") ?? ""; const addHint = this.element.getAttribute("data-add-hint") ?? "";
let settings = { let settings = {
allowEmptyOption: true, allowEmptyOption: true,
selectOnTab: true, selectOnTab: true,
maxOptions: null, maxOptions: null,
create: allowAdd ? this.createItem.bind(this) : false, create: allowAdd ? this.createItem.bind(this) : false,
createFilter: this.createFilter.bind(this),
// This three options allow us to paste element names with commas: (see issue #538)
maxItems: 1,
delimiter: "$$VERY_LONG_DELIMITER_THAT_SHOULD_NEVER_APPEAR$$",
splitOn: null,
searchField: [ searchField: [
{field: "text", weight : 2}, {field: "text", weight : 2},
@ -64,21 +53,7 @@ export default class extends Controller {
render: { render: {
item: this.renderItem.bind(this), item: this.renderItem.bind(this),
option: this.renderOption.bind(this), option: this.renderOption.bind(this),
option_create: (data, escape) => { option_create: function(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);
}
}
return '<div class="create"><i class="fa-solid fa-plus fa-fw"></i>&nbsp;<strong>' + escape(data.input) + '</strong>&hellip;&nbsp;' + return '<div class="create"><i class="fa-solid fa-plus fa-fw"></i>&nbsp;<strong>' + escape(data.input) + '</strong>&hellip;&nbsp;' +
'<small class="text-muted float-end">(' + addHint +')</small>' + '<small class="text-muted float-end">(' + addHint +')</small>' +
'</div>'; '</div>';
@ -88,39 +63,14 @@ export default class extends Controller {
//Add callbacks to update validity //Add callbacks to update validity
onInitialize: this.updateValidity.bind(this), onInitialize: this.updateValidity.bind(this),
onChange: this.updateValidity.bind(this), onChange: this.updateValidity.bind(this),
plugins: {
"autoselect_typed": {},
}
}; };
//Add clear button plugin, if an empty option is present
if (this.element.querySelector("option[value='']") !== null) {
settings.plugins["clear_button"] = {};
}
this._tomSelect = new TomSelect(this.element, settings); this._tomSelect = new TomSelect(this.element, settings);
//Do not do a sync here as this breaks the initial rendering of the empty option //Do not do a sync here as this breaks the initial rendering of the empty option
//this._tomSelect.sync(); //this._tomSelect.sync();
} }
createItem(input, callback) { 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({ callback({
//$%$ is a special value prefix, that is used to identify items, that are not yet in the DB //$%$ is a special value prefix, that is used to identify items, that are not yet in the DB
value: '$%$' + input, value: '$%$' + input,
@ -129,31 +79,6 @@ export default class extends Controller {
}); });
} }
createFilter(input) {
//Normalize the input (replace spacing around arrows)
if (input.includes("->")) {
const inputs = input.split("->");
inputs.forEach((value, index) => {
inputs[index] = value.trim();
});
input = inputs.join("->");
} else {
input = input.trim();
}
const options = this._tomSelect.options;
//Iterate over all options and check if the input is already present
for (let index in options) {
const option = options[index];
if (option.path === input) {
return false;
}
}
return true;
}
updateValidity() { updateValidity() {
//Mark this input as invalid, if the selected option is disabled //Mark this input as invalid, if the selected option is disabled

View file

@ -23,21 +23,14 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
import '../../css/components/tom-select_extensions.css'; import '../../css/components/tom-select_extensions.css';
import TomSelect from "tom-select"; import TomSelect from "tom-select";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
export default class extends Controller { export default class extends Controller {
_tomSelect; _tomSelect;
connect() { connect() {
let settings = { let settings = {
plugins: { plugins: {
remove_button:{}, remove_button:{
'autoselect_typed': {}, }
'click_to_edit': {},
}, },
persistent: false, persistent: false,
selectOnTab: true, selectOnTab: true,

View file

@ -20,7 +20,7 @@
import {Controller} from "@hotwired/stimulus"; import {Controller} from "@hotwired/stimulus";
//import * as ZXing from "@zxing/library"; //import * as ZXing from "@zxing/library";
import {Html5QrcodeScanner, Html5Qrcode} from "@part-db/html5-qrcode"; import {Html5QrcodeScanner, Html5Qrcode} from "html5-qrcode";
/* stimulusFetch: 'lazy' */ /* stimulusFetch: 'lazy' */
export default class extends Controller { export default class extends Controller {
@ -50,7 +50,7 @@ export default class extends Controller {
}); });
this._scanner = new Html5QrcodeScanner(this.element.id, { this._scanner = new Html5QrcodeScanner(this.element.id, {
fps: 10, fps: 2,
qrbox: qrboxFunction, qrbox: qrboxFunction,
experimentalFeatures: { experimentalFeatures: {
//This option improves reading quality on android chrome //This option improves reading quality on android chrome
@ -61,11 +61,6 @@ export default class extends Controller {
this._scanner.render(this.onScanSuccess.bind(this)); this._scanner.render(this.onScanSuccess.bind(this));
} }
disconnect() {
this._scanner.pause();
this._scanner.clear();
}
onScanSuccess(decodedText, decodedResult) { onScanSuccess(decodedText, decodedResult) {
//Put our decoded Text into the input box //Put our decoded Text into the input box
document.getElementById('scan_dialog_input').value = decodedText; document.getElementById('scan_dialog_input').value = decodedText;

View file

@ -22,13 +22,6 @@ import TomSelect from "tom-select";
import katex from "katex"; import katex from "katex";
import "katex/dist/katex.css"; import "katex/dist/katex.css";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
/* stimulusFetch: 'lazy' */ /* stimulusFetch: 'lazy' */
export default class extends Controller export default class extends Controller
{ {
@ -60,10 +53,7 @@ export default class extends Controller
connect() { connect() {
const settings = { const settings = {
plugins: { plugins: {
'autoselect_typed': {}, clear_button:{}
'click_to_edit': {},
'clear_button': {},
'restore_on_backspace': {}
}, },
persistent: false, persistent: false,
maxItems: 1, maxItems: 1,

View file

@ -51,6 +51,7 @@
.part-table-image { .part-table-image {
max-height: 40px; max-height: 40px;
object-fit: contain; object-fit: contain;
width: 100%;
} }
.part-info-image { .part-info-image {
@ -60,4 +61,4 @@
.object-fit-cover { .object-fit-cover {
object-fit: cover; object-fit: cover;
} }

View file

@ -108,8 +108,8 @@ body {
.back-to-top { .back-to-top {
cursor: pointer; cursor: pointer;
position: fixed; position: fixed;
bottom: 60px; bottom: 20px;
right: 40px; right: 20px;
display:none; display:none;
z-index: 1030; z-index: 1030;
} }

View file

@ -63,6 +63,10 @@ table.dataTable > tbody > tr.selected > td > a {
margin-block-end: 0; margin-block-end: 0;
} }
.card-footer-table {
padding-top: 0;
}
table.dataTable { table.dataTable {
margin-top: 0 !important; margin-top: 0 !important;
} }
@ -109,4 +113,55 @@ Classes for Datatables export
#export-messageTop, #export-messageTop,
.export-helper{ .export-helper{
display: none; 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: 0.9em;
left: 50%;
width: 1em !important;
height: 1em !important;
box-sizing: border-box;
}
table.dataTable > thead > tr > th.select-checkbox:before {
content: " ";
margin-top: -5px;
margin-left: -6px;
border: 2px solid var(--bs-tertiary-color);
border-radius: 3px;
}
table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:before {
border: 2px solid var(--bs-tertiary-color) !important;
font-weight: bold;
}
table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > td.select-checkbox:after, table.dataTable > tbody > tr > th.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:after {
width: 1em !important;
height: 1em !important;
}
table.dataTable > thead > tr.selected > th.select-checkbox:after {
content: "✓";
font-size: 20px;
font-weight: bold;
margin-top: -20px;
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;
}

View file

@ -629,10 +629,9 @@ body {
display: none; display: none;
} }
.aa-ItemContent mark { .aa-ItemContent mark {
background: var(--bs-highlight-bg); background: none;
color: var(--bs-body-color); color: var(--bs-body-color);
font-style: normal; font-style: normal;
padding: 0;
font-weight: 700; font-weight: 700;
font-weight: var(--aa-font-weight-bold); font-weight: var(--aa-font-weight-bold);
} }

View file

@ -24,8 +24,9 @@
/** Should be the same settings, as in label_style.css */ /** Should be the same settings, as in label_style.css */
.ck-html-label .ck-content { .ck-html-label .ck-content {
font-family: "DejaVu Sans Mono", monospace; font-family: "DejaVu Sans Mono", monospace;
font-size: 12pt; font-size: 12px;
line-height: 1.0; line-height: 1.0;
font-size-adjust: 1.5;
} }
.ck-html-label .ck-content p { .ck-html-label .ck-content p {

View file

@ -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;
}

View file

@ -44,18 +44,4 @@ import "./register_events";
import "./tristate_checkboxes"; import "./tristate_checkboxes";
//Define jquery globally //Define jquery globally
window.$ = window.jQuery = require("jquery"); 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;
},
});

File diff suppressed because it is too large Load diff

View file

@ -98,15 +98,6 @@
dtOpts = config.options(dtOpts); 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); root.html(data.template);
dt = $('table', root).DataTable(dtOpts); dt = $('table', root).DataTable(dtOpts);
if (config.state !== 'none') { if (config.state !== 'none') {

View file

@ -21,7 +21,6 @@
import {Dropdown} from "bootstrap"; import {Dropdown} from "bootstrap";
import ClipboardJS from "clipboard"; import ClipboardJS from "clipboard";
import {Modal} from "bootstrap";
class RegisterEventHelper { class RegisterEventHelper {
constructor() { constructor() {
@ -32,11 +31,9 @@ class RegisterEventHelper {
//Initialize ClipboardJS //Initialize ClipboardJS
this.registerLoadHandler(() => { this.registerLoadHandler(() => {
new ClipboardJS('.btn'); new ClipboardJS('.btn');
}); })
this.registerModalDropRemovalOnFormSubmit(); this.registerModalDropRemovalOnFormSubmit();
} }
registerModalDropRemovalOnFormSubmit() { registerModalDropRemovalOnFormSubmit() {
@ -46,15 +43,6 @@ class RegisterEventHelper {
if (back_drop) { if (back_drop) {
back_drop.remove(); 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 = '';
}
}); });
} }

View file

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

View file

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

View file

@ -4,10 +4,6 @@
use App\Kernel; use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application; 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) //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 //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) { if (extension_loaded('xdebug') && ((int) ini_get('xdebug.max_nesting_level')) <= 256) {

View file

@ -1,5 +1,4 @@
{ {
"name": "part-db/part-db-server",
"type": "project", "type": "project",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"require": { "require": {
@ -11,23 +10,22 @@
"ext-intl": "*", "ext-intl": "*",
"ext-json": "*", "ext-json": "*",
"ext-mbstring": "*", "ext-mbstring": "*",
"amphp/http-client": "^5.1",
"api-platform/core": "^3.1", "api-platform/core": "^3.1",
"beberlei/doctrineextensions": "^1.2", "beberlei/doctrineextensions": "^1.2",
"brick/math": "0.12.1 as 0.11.0", "brick/math": "0.12.1 as 0.11.0",
"composer/ca-bundle": "^1.3", "composer/ca-bundle": "^1.3",
"composer/package-versions-deprecated": "^1.11.99.5", "composer/package-versions-deprecated": "^1.11.99.5",
"doctrine/data-fixtures": "^2.0.0", "doctrine/annotations": "1.14.3",
"doctrine/dbal": "^4.0.0", "doctrine/data-fixtures": "^1.6.6",
"doctrine/dbal": "^3.4.6",
"doctrine/doctrine-bundle": "^2.0", "doctrine/doctrine-bundle": "^2.0",
"doctrine/doctrine-migrations-bundle": "^3.0", "doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^3.2.0", "doctrine/orm": "^2.16",
"dompdf/dompdf": "^v3.0.0", "dompdf/dompdf": "dev-master#c9cf4be933e2406a51990bd4eb9e70612e790cc0 as v2.0.4",
"erusev/parsedown": "^1.7", "erusev/parsedown": "^1.7",
"florianv/swap": "^4.0", "florianv/swap": "^4.0",
"florianv/swap-bundle": "dev-master", "florianv/swap-bundle": "dev-master",
"gregwar/captcha-bundle": "^2.1.0", "gregwar/captcha-bundle": "^2.1.0",
"hshn/base64-encoded-file": "^5.0",
"jbtronics/2fa-webauthn": "^v2.2.0", "jbtronics/2fa-webauthn": "^v2.2.0",
"jbtronics/dompdf-font-loader-bundle": "^1.0.0", "jbtronics/dompdf-font-loader-bundle": "^1.0.0",
"jfcherng/php-diff": "^6.14", "jfcherng/php-diff": "^6.14",
@ -40,10 +38,12 @@
"nelmio/cors-bundle": "^2.3", "nelmio/cors-bundle": "^2.3",
"nelmio/security-bundle": "^3.0", "nelmio/security-bundle": "^3.0",
"nyholm/psr7": "^1.1", "nyholm/psr7": "^1.1",
"omines/datatables-bundle": "^0.9.1", "ocramius/proxy-manager": "2.2.*",
"paragonie/sodium_compat": "^1.21", "omines/datatables-bundle": "^0.8.0",
"part-db/label-fonts": "^1.0", "part-db/label-fonts": "^1.0",
"runtime/frankenphp-symfony": "^0.2.0", "php-translation/symfony-bundle": "^0.14.0",
"phpdocumentor/reflection-docblock": "^5.2",
"phpstan/phpdoc-parser": "^1.23",
"s9e/text-formatter": "^2.1", "s9e/text-formatter": "^2.1",
"scheb/2fa-backup-code": "^6.8.0", "scheb/2fa-backup-code": "^6.8.0",
"scheb/2fa-bundle": "^6.8.0", "scheb/2fa-bundle": "^6.8.0",
@ -54,8 +54,6 @@
"symfony/apache-pack": "^1.0", "symfony/apache-pack": "^1.0",
"symfony/asset": "6.4.*", "symfony/asset": "6.4.*",
"symfony/console": "6.4.*", "symfony/console": "6.4.*",
"symfony/css-selector": "6.4.*",
"symfony/dom-crawler": "6.4.*",
"symfony/dotenv": "6.4.*", "symfony/dotenv": "6.4.*",
"symfony/expression-language": "6.4.*", "symfony/expression-language": "6.4.*",
"symfony/flex": "^v2.3.1", "symfony/flex": "^v2.3.1",
@ -69,6 +67,7 @@
"symfony/process": "6.4.*", "symfony/process": "6.4.*",
"symfony/property-access": "6.4.*", "symfony/property-access": "6.4.*",
"symfony/property-info": "6.4.*", "symfony/property-info": "6.4.*",
"symfony/proxy-manager-bridge": "6.4.*",
"symfony/rate-limiter": "6.4.*", "symfony/rate-limiter": "6.4.*",
"symfony/runtime": "6.4.*", "symfony/runtime": "6.4.*",
"symfony/security-bundle": "6.4.*", "symfony/security-bundle": "6.4.*",
@ -84,34 +83,36 @@
"symfony/yaml": "6.4.*", "symfony/yaml": "6.4.*",
"tecnickcom/tc-lib-barcode": "^2.1.4", "tecnickcom/tc-lib-barcode": "^2.1.4",
"twig/cssinliner-extra": "^3.0", "twig/cssinliner-extra": "^3.0",
"twig/extra-bundle": "^3.8", "twig/extra-bundle": "^3.0",
"twig/html-extra": "^3.8", "twig/html-extra": "^3.0",
"twig/inky-extra": "^3.0", "twig/inky-extra": "^3.0",
"twig/intl-extra": "^3.8", "twig/intl-extra": "^3.0",
"twig/markdown-extra": "^3.8", "twig/markdown-extra": "^3.0",
"twig/string-extra": "^3.8", "web-auth/webauthn-symfony-bundle": "^4.0.0",
"web-auth/webauthn-symfony-bundle": "^4.0.0" "webmozart/assert": "^1.4"
}, },
"require-dev": { "require-dev": {
"dama/doctrine-test-bundle": "^v8.0.0", "dama/doctrine-test-bundle": "^v8.0.0",
"doctrine/doctrine-fixtures-bundle": "^4.0.0", "doctrine/doctrine-fixtures-bundle": "^3.2",
"ekino/phpstan-banned-code": "^v3.0.0", "ekino/phpstan-banned-code": "^v1.0.0",
"jbtronics/translation-editor-bundle": "^1.0",
"phpstan/extension-installer": "^1.0", "phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^2.0.4", "phpstan/phpstan": "^1.4.7",
"phpstan/phpstan-doctrine": "^2.0.1", "phpstan/phpstan-doctrine": "^1.2.11",
"phpstan/phpstan-strict-rules": "^2.0.1", "phpstan/phpstan-strict-rules": "^1.5",
"phpstan/phpstan-symfony": "^2.0.0", "phpstan/phpstan-symfony": "^1.1.7",
"phpunit/phpunit": "^9.5", "phpunit/phpunit": "^9.5",
"rector/rector": "^2.0.4", "psalm/plugin-symfony": "^v5.0.1",
"rector/rector": "^0.18.0",
"roave/security-advisories": "dev-latest", "roave/security-advisories": "dev-latest",
"symfony/browser-kit": "6.4.*", "symfony/browser-kit": "6.4.*",
"symfony/css-selector": "6.4.*",
"symfony/debug-bundle": "6.4.*", "symfony/debug-bundle": "6.4.*",
"symfony/maker-bundle": "^1.13", "symfony/maker-bundle": "^1.13",
"symfony/phpunit-bridge": "6.4.*", "symfony/phpunit-bridge": "6.4.*",
"symfony/stopwatch": "6.4.*", "symfony/stopwatch": "6.4.*",
"symfony/web-profiler-bundle": "6.4.*", "symfony/web-profiler-bundle": "6.4.*",
"symplify/easy-coding-standard": "^12.0" "symplify/easy-coding-standard": "^12.0",
"vimeo/psalm": "^5.6.0"
}, },
"suggest": { "suggest": {
"ext-bcmath": "Used to improve price calculation performance", "ext-bcmath": "Used to improve price calculation performance",
@ -162,8 +163,7 @@
"extra": { "extra": {
"symfony": { "symfony": {
"allow-contrib": false, "allow-contrib": false,
"require": "6.4.*", "require": "6.4.*"
"docker": true
} }
} }
} }

7348
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -18,6 +18,7 @@ return [
DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true], DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Gregwar\CaptchaBundle\GregwarCaptchaBundle::class => ['all' => true], Gregwar\CaptchaBundle\GregwarCaptchaBundle::class => ['all' => true],
Translation\Bundle\TranslationBundle::class => ['all' => true],
Florianv\SwapBundle\FlorianvSwapBundle::class => ['all' => true], Florianv\SwapBundle\FlorianvSwapBundle::class => ['all' => true],
Nelmio\SecurityBundle\NelmioSecurityBundle::class => ['all' => true], Nelmio\SecurityBundle\NelmioSecurityBundle::class => ['all' => true],
Symfony\UX\Turbo\TurboBundle::class => ['all' => true], Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
@ -31,5 +32,4 @@ return [
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true], KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
Jbtronics\TranslationEditorBundle\JbtronicsTranslationEditorBundle::class => ['dev' => true],
]; ];

View file

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

View file

@ -1,5 +0,0 @@
when@test:
dama_doctrine_test:
enable_static_connection: true
enable_static_meta_data_cache: true
enable_static_query_cache: true

View file

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

View file

@ -0,0 +1,5 @@
translation:
symfony_profiler:
enabled: true
webui:
enabled: true

View file

@ -9,25 +9,14 @@ doctrine:
# either here or in the DATABASE_URL env var (see .env file) # either here or in the DATABASE_URL env var (see .env file)
types: types:
# UTC datetimes
datetime: datetime:
class: App\Doctrine\Types\UTCDateTimeType class: App\Doctrine\Types\UTCDateTimeType
date: date:
class: App\Doctrine\Types\UTCDateTimeType class: App\Doctrine\Types\UTCDateTimeType
datetime_immutable:
class: App\Doctrine\Types\UTCDateTimeImmutableType
date_immutable:
class: App\Doctrine\Types\UTCDateTimeImmutableType
big_decimal: big_decimal:
class: App\Doctrine\Types\BigDecimalType class: App\Doctrine\Types\BigDecimalType
tinyint: tinyint:
class: App\Doctrine\Types\TinyIntType class: App\Doctrine\Types\TinyIntType
# This was removed in doctrine/orm 4.0 but we need it for the WebauthnKey entity
array:
class: App\Doctrine\Types\ArrayType
schema_filter: ~^(?!internal)~ schema_filter: ~^(?!internal)~
# Only enable this when needed # Only enable this when needed
@ -40,8 +29,6 @@ doctrine:
validate_xml_mapping: true validate_xml_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true auto_mapping: true
controller_resolver:
auto_mapping: true
mappings: mappings:
App: App:
type: attribute type: attribute
@ -52,12 +39,10 @@ doctrine:
dql: dql:
string_functions: string_functions:
regexp: App\Doctrine\Functions\Regexp regexp: DoctrineExtensions\Query\Mysql\Regexp
ifnull: DoctrineExtensions\Query\Mysql\IfNull
field: DoctrineExtensions\Query\Mysql\Field field: DoctrineExtensions\Query\Mysql\Field
field2: App\Doctrine\Functions\Field2 field2: App\Doctrine\Functions\Field2
natsort: App\Doctrine\Functions\Natsort
array_position: App\Doctrine\Functions\ArrayPosition
ilike: App\Doctrine\Functions\ILike
when@test: when@test:
doctrine: doctrine:

View file

@ -50,6 +50,7 @@ when@prod:
type: stream type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log" path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug level: debug
formatter: monolog.formatter.json
console: console:
type: console type: console
process_psr_3_messages: false process_psr_3_messages: false
@ -73,6 +74,7 @@ when@docker:
type: stream type: stream
path: "php://stderr" path: "php://stderr"
level: debug level: debug
formatter: monolog.formatter.json
console: console:
type: console type: console
process_psr_3_messages: false process_psr_3_messages: false

View file

@ -51,16 +51,12 @@ nelmio_security:
img-src: img-src:
- '*' - '*'
- 'data:' - 'data:'
# Required for be able to load pictures in the QR code scanner
- 'blob:'
style-src: style-src:
- 'self' - 'self'
- 'unsafe-inline' - 'unsafe-inline'
- 'data:' - 'data:'
script-src: script-src:
- 'self' - 'self'
# Required for loading the Wasm for the barcode scanner:
- 'wasm-unsafe-eval'
object-src: object-src:
- 'self' - 'self'
- 'data:' - 'data:'

View 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]

View file

@ -11,13 +11,11 @@ parameters:
partdb.banner: '%env(trim:string:BANNER)%' # The info text shown in the homepage, if empty config/banner.md is used partdb.banner: '%env(trim:string:BANNER)%' # The info text shown in the homepage, if empty config/banner.md is used
partdb.default_currency: '%env(string:BASE_CURRENCY)%' # The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country partdb.default_currency: '%env(string:BASE_CURRENCY)%' # The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country
partdb.global_theme: '' # The theme to use globally (see public/build/themes/ for choices, use name without .css). Set to '' for default bootstrap theme partdb.global_theme: '' # The theme to use globally (see public/build/themes/ for choices, use name without .css). Set to '' for default bootstrap theme
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] # The languages that are shown in user drop down menu partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh'] # The languages that are shown in user drop down menu
partdb.enforce_change_comments_for: '%env(csv:ENFORCE_CHANGE_COMMENTS_FOR)%' # The actions for which a change comment is required (e.g. "part_edit", "part_create", etc.). If this is empty, change comments are not required at all. partdb.enforce_change_comments_for: '%env(csv:ENFORCE_CHANGE_COMMENTS_FOR)%' # The actions for which a change comment is required (e.g. "part_edit", "part_create", etc.). If this is empty, change comments are not required at all.
partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails
partdb.db.emulate_natural_sort: '%env(bool:DATABASE_EMULATE_NATURAL_SORT)%' # If this is set to true, natural sorting is emulated on platforms that do not support it natively. This can be slow on large datasets.
###################################################################################################################### ######################################################################################################################
# Users and Privacy # Users and Privacy
###################################################################################################################### ######################################################################################################################
@ -147,5 +145,3 @@ parameters:
env(HISTORY_SAVE_NEW_DATA): 1 env(HISTORY_SAVE_NEW_DATA): 1
env(EDA_KICAD_CATEGORY_DEPTH): 0 env(EDA_KICAD_CATEGORY_DEPTH): 0
env(DATABASE_EMULATE_NATURAL_SORT): 0

View file

@ -0,0 +1,6 @@
_translation_webui:
resource: '@TranslationBundle/Resources/config/routing_webui.yaml'
prefix: /admin
_translation_profiler:
resource: '@TranslationBundle/Resources/config/routing_symfony_profiler.yaml'

View file

@ -1,3 +0,0 @@
when@dev:
translation_editor:
resource: '@JbtronicsTranslationEditorBundle/config/routes.php'

View file

@ -0,0 +1,3 @@
_translation_edit_in_place:
resource: '@TranslationBundle/Resources/config/routing_edit_in_place.yaml'
prefix: /admin

View file

@ -76,12 +76,18 @@ services:
# Only the event classes specified here are saved to DB (set to []) to log all events # Only the event classes specified here are saved to DB (set to []) to log all events
$whitelist: [] $whitelist: []
App\EventListener\LogSystem\EventLoggerListener: App\EventSubscriber\LogSystem\EventLoggerSubscriber:
arguments: arguments:
$save_changed_fields: '%env(bool:HISTORY_SAVE_CHANGED_FIELDS)%' $save_changed_fields: '%env(bool:HISTORY_SAVE_CHANGED_FIELDS)%'
$save_changed_data: '%env(bool:HISTORY_SAVE_CHANGED_DATA)%' $save_changed_data: '%env(bool:HISTORY_SAVE_CHANGED_DATA)%'
$save_removed_data: '%env(bool:HISTORY_SAVE_REMOVED_DATA)%' $save_removed_data: '%env(bool:HISTORY_SAVE_REMOVED_DATA)%'
$save_new_data: '%env(bool:HISTORY_SAVE_NEW_DATA)%' $save_new_data: '%env(bool:HISTORY_SAVE_NEW_DATA)%'
tags:
- { name: 'doctrine.event_subscriber' }
App\EventSubscriber\LogSystem\LogDBMigrationSubscriber:
tags:
- { name: 'doctrine.event_subscriber' }
App\Form\AttachmentFormType: App\Form\AttachmentFormType:
arguments: arguments:
@ -306,16 +312,6 @@ services:
$enabled: '%env(bool:PROVIDER_LCSC_ENABLED)%' $enabled: '%env(bool:PROVIDER_LCSC_ENABLED)%'
$currency: '%env(string:PROVIDER_LCSC_CURRENCY)%' $currency: '%env(string:PROVIDER_LCSC_CURRENCY)%'
App\Services\InfoProviderSystem\Providers\OEMSecretsProvider:
arguments:
$api_key: '%env(string:PROVIDER_OEMSECRETS_KEY)%'
$country_code: '%env(string:PROVIDER_OEMSECRETS_COUNTRY_CODE)%'
$currency: '%env(PROVIDER_OEMSECRETS_CURRENCY)%'
$zero_price: '%env(PROVIDER_OEMSECRETS_ZERO_PRICE)%'
$set_param: '%env(PROVIDER_OEMSECRETS_SET_PARAM)%'
$sort_criteria: '%env(PROVIDER_OEMSECRETS_SORT_CRITERIA)%'
#################################################################################################################### ####################################################################################################################
# API system # API system
#################################################################################################################### ####################################################################################################################
@ -410,4 +406,4 @@ when@test:
arguments: arguments:
- '@doctrine.fixtures.loader' - '@doctrine.fixtures.loader'
- '@doctrine' - '@doctrine'
- { default: '@App\Doctrine\Purger\DoNotUsePurgerFactory' } - { default: '@App\Doctrine\Purger\DoNotUsePurgerFactory' }

View file

@ -172,58 +172,25 @@ example `/api/parts/123?_comment=This%20is%20a%20change%20comment`.
## Creating attachments and parameters ## Creating attachments and parameters
To create attachments and parameters, use the POST endpoint. Internally there are different types of attachments and {: .warning }
parameters, for each entity type, where the attachments or parameters are used (e.g. PartAttachment for parts, etc.). > The way described below is more a workaround than a proper solution. This might break in future versions of Part-DB!
The type of the attachment or parameter is automatically determined by the `element` property of the request data if a
IRI is passed. You can use the `_type` property to explicitly set the type of the attachment or parameter (the value must
be the value of the `@type` property of the owning entity. e.g. `Part` for parts).
For example, to create an attachment on a part, you can use the following request: Currently, it is not possible to create attachments or parameters via a `POST` operation on the entity endpoint.
The workaround for this is to send a patch request to the owning entity endpoint (e.g. parts `/api/parts/123`):
``` ```
POST /api/attachments PATCH /api/parts/123
{ {
"name": "front68", "attachments": [
"attachment_type": "/api/attachment_types/1", {"name": "front68", "attachment_type": "/api/attachment_types/1", "url": "https://invalid.invalid/test.url"}
"url": "https://invalid.invalid/test.url", ],
"element": "/api/parts/123" "parameters": [
{"name": "value", "unit": "Ohm", "value": 100}
]
} }
``` ```
## Uploading files to attachments The limitation of this is, that this will override/delete all existing attachments/parameters of the entity.
To upload files to the attachments you can use the special `upload` property of the attachment entity during write operations (POST, PUT, PATCH). See [issue #502](https://github.com/Part-DB/Part-DB-server/issues/502) for more details on this topic.
Under `data` you can pass a base64 encoded string of the file content, and under `filename` the name of the file.
Using the `private` property you can control if the file is the attachment should be stored privately or public.
For example, to upload a file to an attachment, you can use the following request:
```
PATCH /api/attachments/123
{
"upload": {
"data": "data:@file/octet-stream;base64,LS0gcGhwTXlB[...]",
"filename": "test.csv",
"private": false
},
"name": "Rename attachment"
}
```
This also works for creating new attachments, by including the `upload` property in the request data along with the other properties.
Using the `downloadUrl` property of `upload` you can say Part-DB to upload the file specified at the URL set on the attachment.
```
PATCH /api/attachments/123
{
"upload": {
"downloadUrl": true
},
"url": "https://host.invalid/myfile.pdf"
}
```

View file

@ -23,7 +23,7 @@ each other so that it does not matter which one of your 1000 things of Part you
A part entity has many fields, which can be used to describe it better. Most of the fields are optional: A part entity has many fields, which can be used to describe it better. Most of the fields are optional:
* **Name** (Required): The name of the part or how you want to call it. This could be a manufacturer-provided name, or a * **Name** (Required): The name of the part or how you want to call it. This could be a manufacturer-provided name, or a
name you thought of yourself. Each name needs to be unique and must exist in a single category. name you thought of yourself. The name have to be unique in a single category.
* **Description**: A short (single-line) description of what this part is/does. For longer information, you should use * **Description**: A short (single-line) description of what this part is/does. For longer information, you should use
the comment field or the specifications the comment field or the specifications
* **Category** (Required): The category (see there) to which this part belongs to. * **Category** (Required): The category (see there) to which this part belongs to.
@ -152,7 +152,7 @@ Footprints are hierarchically which allows you to build logically sorted trees.
### Measurement Unit ### Measurement Unit
By default, part in stock is counted in number of individual parts, which is fine for things like electronic components, By default, part instock is counted in number of individual parts, which is fine for things like electronic components,
which exist only in integer quantities. However, if you have things with fractional units like the length of a wire or which exist only in integer quantities. However, if you have things with fractional units like the length of a wire or
the volume of a liquid, you have to define a measurement unit. the volume of a liquid, you have to define a measurement unit.
The measurement unit represents a physical quantity like mass, volume, or length. The measurement unit represents a physical quantity like mass, volume, or length.
@ -160,9 +160,6 @@ The measurement unit represents a physical quantity like mass, volume, or length
You can define a short unit for it (like m for Meters, or g for grams) which will be shown when a quantity of a part You can define a short unit for it (like m for Meters, or g for grams) which will be shown when a quantity of a part
with this unit is shown. with this unit is shown.
In order to cover wider use cases and allow you to define measurement units further, it is possible to define parameters
associated to a measurement unit. These parameters are distinct from a part's parameters and are not inherited.
### Currency ### Currency
By default, all prices are set in the base currency configured for the instance (by default euros). If you want to use By default, all prices are set in the base currency configured for the instance (by default euros). If you want to use
@ -239,4 +236,4 @@ replaced with data for the actual thing.
You do not have to define a label profile to generate labels (you can just set the settings on the fly in the label You do not have to define a label profile to generate labels (you can just set the settings on the fly in the label
dialog), however, if you want to generate many labels, it is recommended to save the settings as a label profile, to save dialog), however, if you want to generate many labels, it is recommended to save the settings as a label profile, to save
it for later usage. This ensures that all generated labels look the same. it for later usage. This ensures that all generated labels look the same.

View file

@ -32,22 +32,14 @@ options listed, see `.env` file for the full list of possible env variables.
### General options ### General options
* `DATABASE_URL`: Configures the database which Part-DB uses: * `DATABASE_URL`: Configures the database which Part-DB uses. For mysql use a string in the form
* For MySQL (or MariaDB) use a string in the form of `mysql://<USERNAME>:<PASSWORD>@<HOST>:<PORT>/<TABLE_NAME>` here of `mysql://<USERNAME>:<PASSWORD>@<HOST>:<PORT>/<TABLE_NAME>` here
(e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`). (e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`). For SQLite use the following format to specify the
* For SQLite use the following format to specify the
absolute path where it should be located `sqlite:///path/part/app.db`. You can use `%kernel.project_dir%` as absolute path where it should be located `sqlite:///path/part/app.db`. You can use `%kernel.project_dir%` as
placeholder for the Part-DB root folder (e.g. `sqlite:///%kernel.project_dir%/var/app.db`) placeholder for the Part-DB root folder (e.g. `sqlite:///%kernel.project_dir%/var/app.db`)
* For Postgresql use a string in the form of `DATABASE_URL=postgresql://user:password@127.0.0.1:5432/part-db?serverVersion=x.y`.
Please note that **`serverVersion=x.y`** variable is required due to dependency of Symfony framework.
* `DATABASE_MYSQL_USE_SSL_CA`: If this value is set to `1` or `true` and a MySQL connection is used, then the connection * `DATABASE_MYSQL_USE_SSL_CA`: If this value is set to `1` or `true` and a MySQL connection is used, then the connection
is encrypted by SSL/TLS and the server certificate is verified against the system CA certificates or the CA certificate is encrypted by SSL/TLS and the server certificate is verified against the system CA certificates or the CA certificate
bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept all certificates. bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept all certificates.
* `DATABASE_EMULATE_NATURAL_SORT` (default 0): If set to 1, Part-DB will emulate natural sorting, even if the database
does not support it natively. However this is much slower than the native sorting, and contain bugs or quirks, so use
it only, if you have to.
* `DEFAULT_LANG`: The default language to use server-wide (when no language is explicitly specified by a user or via * `DEFAULT_LANG`: The default language to use server-wide (when no language is explicitly specified by a user or via
language chooser). Must be something like `en`, `de`, `fr`, etc. language chooser). Must be something like `en`, `de`, `fr`, etc.
* `DEFAULT_TIMEZONE`: The default timezone to use globally, when a user has no timezone specified. Must be something * `DEFAULT_TIMEZONE`: The default timezone to use globally, when a user has no timezone specified. Must be something
@ -91,10 +83,6 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
* `datastructure_create`: Creation of a new datastructure (e.g. category, manufacturer, ...) * `datastructure_create`: Creation of a new datastructure (e.g. category, manufacturer, ...)
* `CHECK_FOR_UPDATES` (default `1`): Set this to 0, if you do not want Part-DB to connect to GitHub to check for new * `CHECK_FOR_UPDATES` (default `1`): Set this to 0, if you do not want Part-DB to connect to GitHub to check for new
versions, or if your server can not connect to the internet. versions, or if your server can not connect to the internet.
* `APP_SECRET`: This variable is a configuration parameter used for various security-related purposes,
particularly for securing and protecting various aspects of your application. It's a secret key that is used for
cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this
value should be handled as confidential data and not shared publicly.
### E-Mail settings ### E-Mail settings
@ -218,10 +206,6 @@ See the [information providers]({% link usage/information_provider_system.md %})
mode. (**You should not do this on a publicly accessible server, as it will leak sensitive information!**) mode. (**You should not do this on a publicly accessible server, as it will leak sensitive information!**)
* `BANNER`: You can configure the text that should be shown as the banner on the homepage. Useful especially for docker * `BANNER`: You can configure the text that should be shown as the banner on the homepage. Useful especially for docker
containers. In all other applications you can just change the `config/banner.md` file. containers. In all other applications you can just change the `config/banner.md` file.
* `DISABLE_YEAR2038_BUG_CHECK`: If set to `1`, the year 2038 bug check is disabled on 32-bit systems, and dates after
2038 are no longer forbidden. However this will lead to 500 error messages when rendering dates after 2038 as all current
32-bit PHP versions can not format these dates correctly. This setting is for the case that future PHP versions will
handle this correctly on 32-bit systems. 64-bit systems are not affected by this bug, and the check is always disabled.
## Banner ## Banner
@ -252,4 +236,4 @@ The following options are available:
number of sidebar panels by changing the number of items in this list. number of sidebar panels by changing the number of items in this list.
* `partdb.sidebar.root_node_enable`: Show a root node in the sidebar trees, of which all nodes are children of * `partdb.sidebar.root_node_enable`: Show a root node in the sidebar trees, of which all nodes are children of
* `partdb.sidebar.root_expanded`: Expand the root node in the sidebar trees by default * `partdb.sidebar.root_expanded`: Expand the root node in the sidebar trees by default
* `partdb.available_themes`: The list of available themes a user can choose from. * `partdb.available_themes`: The list of available themes a user can choose from.

View file

@ -41,7 +41,7 @@ It is installed on a web server and so can be accessed with any browser without
* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older * Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older
versions. versions.
* Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface. * 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 * MySQL and SQLite supported as database backends
* Support for rich text descriptions and comments in parts * Support for rich text descriptions and comments in parts
* Support for multiple currencies and automatic update of exchange rates supported * 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) * Powerful search and filter function, including parametric search (search for parts according to some specifications)

View file

@ -7,18 +7,10 @@ nav_order: 1
# Choosing database: SQLite or MySQL # Choosing database: SQLite or MySQL
Part-DB saves its data in a [relational (SQL) database](https://en.wikipedia.org/wiki/Relational_database). Part-DB saves its data in a [relational (SQL) database](https://en.wikipedia.org/wiki/Relational_database). Part-DB
supports either the use of [SQLite](https://www.sqlite.org/index.html)
For this multiple database types are supported, currently these are: or [MySQL](https://www.mysql.com/) / [MariaDB](https://mariadb.org/) (which are mostly the same, except for some minor
differences).
* [SQLite](https://www.sqlite.org/index.html)
* [MySQL](https://www.mysql.com/) / [MariaDB](https://mariadb.org/) (which are mostly the same, except for some minor
differences)
* [PostgreSQL](https://www.postgresql.org/)
All these database types allow for the same basic functionality and allow Part-DB to run. However, there are some minor
differences between them, which might be important for you. Therefore the pros and cons of the different database types
are listed here.
{: .important } {: .important }
You have to choose between the database types before you start using Part-DB and **you can not change it (easily) after You have to choose between the database types before you start using Part-DB and **you can not change it (easily) after
@ -26,157 +18,29 @@ you have started creating data**. So you should choose the database type for you
## Comparison ## Comparison
### SQLite **SQLite** is the default database type which is configured out of the box. All data is saved in a single file (
normally `var/app.db` in the Part-DB folder) and no additional installation or configuration besides Part-DB is needed.
To use **MySQL/MariaDB** as database, you have to install and configure the MySQL server, configure it and create a
database and user for Part-DB, which needs some additional work. When using docker you need an additional docker
container, and volume for the data
#### Pros When using **SQLite** The database can be backuped easily by just copying the SQLite file to a safe place. Ideally, the *
*MySQL** database has to be dumped to a SQL file (using `mysqldump`). The `console partdb:backup` command can do this
automatically
* **Easy to use**: No additional installation or configuration is needed, just start Part-DB and it will work out of the box However, SQLite does not support certain operations like regex search, which has to be emulated by PHP and therefore is
* **Easy backup**: Just copy the SQLite file to a safe place, and you have a backup, which you can restore by copying it pretty slow compared to the same operation at MySQL. In the future, there might be features that may only be available, when
back. No need to work with SQL dumps using MySQL. Also, SQLite has limitations in comparisons and sorting of Unicode characters, which might lead to unexpected
behavior when using non-ASCII characters in your data. For example `µ` (micro sign) is not seen as equal to `μ(greek minuscule mu),
therefore searching for `µ` (micro sign) will not find parts containing `μ` (mu) and vice versa. In MySQL identical-looking characters are seen as equal, which is more intuitive in most cases.
#### Cons In general MySQL might perform better for big Part-DB instances with many entries, lots of users and high activity, than
SQLite.
* **Performance**: SQLite is not as fast as MySQL or PostgreSQL, especially when using complex queries or many users. ## Conclusion and Suggestion
* **Emulated RegEx search**: SQLite does not support RegEx search natively. Part-DB can emulate it, however that is pretty slow.
* **Emualted natural sorting**: SQLite does not support natural sorting natively. Part-DB can emulate it, but it is pretty slow.
* **Limitations with Unicode**: SQLite has limitations in comparisons and sorting of Unicode characters, which might lead to
unexpected behavior when using non-ASCII characters in your data. For example `µ` (micro sign) is not seen as equal to
`μ` (greek minuscule mu), therefore searching for `µ` (micro sign) will not find parts containing `μ` (mu) and vice versa.
The other databases behave more intuitive in this case.
* **No advanced features**: SQLite do no support many of the advanced features of MySQL or PostgreSQL, which might be utilized
in future versions of Part-DB
### MySQL/MariaDB
**If possible, it is recommended to use MariaDB 10.7+ (instead of MySQL), as it supports natural sorting of columns natively.**
#### Pros
* **Performance**: Compared to SQLite, MySQL/MariaDB will probably perform better, especially in large databases with many
users and high activity.
* **Natural Sorting**: MariaDB 10.7+ supports natural sorting of columns. On other databases it has to be emulated, which is pretty
slow.
* **Native RegEx search**: MySQL supports RegEx search natively, which is faster than emulating it in PHP.
* **Advanced features**: MySQL/MariaDB supports many advanced features, which might be utilized in future versions of Part-DB.
* **Full Unicode support**: MySQL/MariaDB has better support for Unicode characters, which makes it more intuitive to use
non-ASCII characters in your data.
#### Cons
* **Additional installation and configuration**: You have to install and configure the MySQL server, create a database and
user for Part-DB, which needs some additional work compared to SQLite.
* **Backup**: The MySQL database has to be dumped to a SQL file (using `mysqldump`). The `console partdb:backup` command can automate this.
### PostgreSQL
#### Pros
* **Performance**: PostgreSQL is known for its performance, especially in large databases with many users and high activity.
* **Advanced features**: PostgreSQL supports many advanced features, which might be utilized in future versions of Part-DB.
* **Full Unicode support**: PostgreSQL has better support for Unicode characters, which makes it more intuitive to use
non-ASCII characters in your data.
* **Native RegEx search**: PostgreSQL supports RegEx search natively, which is faster than emulating it in PHP.
* **Native Natural Sorting**: PostgreSQL supports natural sorting of columns natively in all versions and in general the support for it
is better than on MariaDB.
* **Support of transactional DDL**: PostgreSQL supports transactional DDL, which means that if you encounter a problem during a schema change,
the database will automatically rollback the changes. On MySQL/MariaDB you have to manually rollback the changes, by restoring from a database backup.
#### Cons
* **New backend**: The support of postgresql is new, and it was not tested as much as the other backends. There might be some bugs caused by this.
* **Additional installation and configuration**: You have to install and configure the PostgreSQL server, create a database and
user for Part-DB, which needs some additional work compared to SQLite.
* **Backup**: The PostgreSQL database has to be dumped to a SQL file (using `pg_dump`). The `console partdb:backup` command can automate this.
## Recommendation
When you are a hobbyist and use Part-DB for your own small inventory management with only you as user (or maybe sometimes When you are a hobbyist and use Part-DB for your own small inventory management with only you as user (or maybe sometimes
a few other people), then the easy-to-use SQLite database will be fine, as long as you can live with the limitations, stated above. a few other people), then the easy-to-use SQLite database will be fine.
However using MariaDB (or PostgreSQL), has no disadvantages in that situation (besides the initial setup requirements), so you might
want to use it, to be prepared for future use cases.
When you are planning to have a very big database, with a lot of entries and many users which regularly using Part-DB, then you should When you are planning to have a very big database, with a lot of entries and many users which regularly (and
use MariaDB or PostgreSQL, as they will perform better in that situation and allow for more advanced features. concurrently) using Part-DB you should maybe use MySQL as this will scale better.
If you should use MariaDB or PostgreSQL depends on your personal preference and what you already have installed on your servers and
what you are familiar with.
## Using the different databases
The only difference in using the different databases, is a different value in the `DATABASE_URL` environment variable in the `.env.local` file
or in the `DATABASE_URL` environment variable in your server or container configuration. It has the shape of a URL, where the scheme (the part before `://`)
is the database type, and the rest is connection information.
**The env var format below is for the `env.local` file. It might work differently for other env configuration. E.g. in a docker-compose file you have to remove the quotes!**
### SQLite
```shell
DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"
```
Here you just need to configure the path to the SQLite file, which is created by Part-DB when performing the database migrations.
The `%kernel.project_dir%` is a placeholder for the path to the project directory, which is replaced by the actual path by Symfony, so that you do not
need to specify the path manually. In the example the database will be created as `app.db` in the `var` directory of your Part-DB installation folder.
### MySQL/MariaDB
```shell
DATABASE_URL="mysql://user:password@127.0.0.1:3306/database?serverVersion=8.0.37"
```
Here you have to replace `user`, `password` and `database` with the credentials of the MySQL/MariaDB user and the database name you want to use.
The host (here 127.0.0.1) and port should also be specified according to your MySQL/MariaDB server configuration.
In the `serverVersion` parameter you can specify the version of the MySQL/MariaDB server you are using, in the way the server returns it
(e.g. `8.0.37` for MySQL and `10.4.14-MariaDB`). If you do not know it, you can leave the default value.
If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `unix_socket` parameter.
```shell
DATABASE_URL="mysql://user:password@localhost/database?serverVersion=8.0.37&unix_socket=/var/run/mysqld/mysqld.sock"
```
### PostgreSQL
```shell
DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=12.19&charset=utf8"
```
Here you have to replace `db_user`, `db_password` and `db_name` with the credentials of the PostgreSQL user and the database name you want to use.
The host (here 127.0.0.1) and port should also be specified according to your PostgreSQL server configuration.
In the `serverVersion` parameter you can specify the version of the PostgreSQL server you are using, in the way the server returns it
(e.g. `12.19 (Debian 12.19-1.pgdg120+1)`). If you do not know it, you can leave the default value.
The `charset` parameter specify the character set of the database. It should be set to `utf8` to ensure that all characters are stored correctly.
If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `host` parameter.
```shell
DATABASE_URL="postgresql://db_user@localhost/db_name?serverVersion=16.6&charset=utf8&host=/var/run/postgresql"
```
## Natural Sorting
Natural sorting is the sorting of strings in a way that numbers are sorted by their numerical value, not by their ASCII value.
For example in the classical binary sorting the string `DIP-4`, `DIP-8`, `DIP-16`, `DIP-28` would be sorted as following:
* `DIP-16`
* `DIP-28`
* `DIP-4`
* `DIP-8`
In natural sorting, it would be sorted as:
* `DIP-4`
* `DIP-8`
* `DIP-16`
* `DIP-28`
Part-DB can sort names in part tables and tree views naturally. PostgreSQL and MariaDB 10.7+ support natural sorting natively,
and it is automatically used if available.
For SQLite and MySQL < 10.7 it has to be emulated if wanted, which is pretty slow. Therefore it has to be explicity enabled by setting the
`DATABASE_EMULATE_NATURAL_SORT` environment variable to `1`. If it is 0 the classical binary sorting is used, on these databases. The emulations
might have some quirks and issues, so it is recommended to use a database which supports natural sorting natively, if you want to use it.

View file

@ -6,6 +6,4 @@ has_children: true
--- ---
# Installation # Installation
Below you can find some guides to install Part-DB. Below you can find some guides to install Part-DB.
For the hobbyists without much experience, we recommend the docker installation or direct installation on debian.

View file

@ -16,9 +16,6 @@ where docker is available (especially recommended for Windows and macOS).
> network. > network.
> If you want to expose Part-DB to the internet, you have to configure a reverse proxy with an SSL certificate! > If you want to expose Part-DB to the internet, you have to configure a reverse proxy with an SSL certificate!
It is recommended to install Part-DB on a 64-bit system, as the 32-bit version of PHP is affected by the
[Year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) and can not handle dates after 2038 correctly.
## Docker-compose ## Docker-compose
Docker-compose configures the needed images and automatically creates the needed containers and volumes. Docker-compose configures the needed images and automatically creates the needed containers and volumes.
@ -158,7 +155,7 @@ services:
container_name: partdb_database container_name: partdb_database
image: mysql:8.0 image: mysql:8.0
restart: unless-stopped restart: unless-stopped
command: --default-authentication-plugin=mysql_native_password --log-bin-trust-function-creators=1 command: --default-authentication-plugin=mysql_native_password
environment: environment:
# Change this Password # Change this Password
MYSQL_ROOT_PASSWORD: SECRET_ROOT_PASSWORD MYSQL_ROOT_PASSWORD: SECRET_ROOT_PASSWORD
@ -201,10 +198,6 @@ You also have to create the database as described above in step 4.
You can run the console commands described in README by You can run the console commands described in README by
executing `docker exec --user=www-data -it partdb bin/console [command]` executing `docker exec --user=www-data -it partdb bin/console [command]`
{: .warning }
> If you run a root console inside the container, and wanna execute commands on the webserver behalf, be sure to use `sudo -E` command (with the `-E` flag) to preserve env variables from the current shell.
> Otherwise Part-DB console might use the wrong configuration to execute commands.
## Troubleshooting ## Troubleshooting
*Login is not possible. Login page is just reloading and no error message is shown or something like "CSFR token invalid"*: *Login is not possible. Login page is just reloading and no error message is shown or something like "CSFR token invalid"*:

View file

@ -18,9 +18,6 @@ installation.
> network. > network.
> If you want to expose Part-DB to the internet, you HAVE to configure an SSL connection! > If you want to expose Part-DB to the internet, you HAVE to configure an SSL connection!
It is recommended to install Part-DB on a 64-bit system, as the 32-bit version of PHP is affected by the
[Year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) and can not handle dates after 2038 correctly.
## Installation with SQLite database ## Installation with SQLite database
### Install prerequisites ### Install prerequisites
@ -342,7 +339,7 @@ exit
Change it to the following (you have to replace `YOUR_SECRET_PASSWORD` with the password you have chosen in step 3): Change it to the following (you have to replace `YOUR_SECRET_PASSWORD` with the password you have chosen in step 3):
``` ```
DATABASE_URL=mysql://partdb:YOUR_SECRET_PASSWORD@127.0.0.1:3306/partdb DATABASE_URL=DATABASE_URL=mysql://partdb:YOUR_SECRET_PASSWORD@127.0.0.1:3306/partdb
``` ```
5. Create the database schema with: 5. Create the database schema with:

View file

@ -1,42 +0,0 @@
---
title: Kubernetes / Helm
layout: default
parent: Installation
nav_order: 5
---
# Kubernetes / Helm Charts
If you are using Kubernetes, you can use the [helm charts](https://helm.sh/) provided in this [repository](https://github.com/Part-DB/helm-charts).
## Usage
[Helm](https://helm.sh) must be installed to use the charts. Please refer to
Helm's [documentation](https://helm.sh/docs) to get started.
Once Helm has been set up correctly, add the repo as follows:
`helm repo add part-db https://part-db.github.io/helm-charts`
If you had already added this repo earlier, run `helm repo update` to retrieve
the latest versions of the packages. You can then run `helm search repo
part-db` to see the charts.
To install the part-db chart:
helm install my-part-db part-db/part-db
To uninstall the chart:
helm delete my-part-db
This repository is also available at [ArtifactHUB](https://artifacthub.io/packages/search?repo=part-db).
## Configuration
See the README in the [chart directory](https://github.com/Part-DB/helm-charts/tree/main/charts/part-db) for more
information on the available configuration options.
## Bugreports
If you find issues related to the helm charts, please open an issue in the [helm-charts repository](https://github.com/Part-DB/helm-charts).

View file

@ -1,31 +0,0 @@
---
title: Proxmox VE LXC
layout: default
parent: Installation
nav_order: 6
---
# Proxmox VE LXC
{: .warning }
> The proxmox VE LXC script for Part-DB is developed and maintained by [Proxmox VE Helper-Scripts](https://community-scripts.github.io/ProxmoxVE/)
> and not by the Part-DB developers. Keep in mind that the script is not officially supported by the Part-DB developers.
If you are using Proxmox VE you can use the scripts provided by [Proxmox VE Helper-Scripts community](https://community-scripts.github.io/ProxmoxVE/scripts?id=part-db)
to easily install Part-DB in a LXC container.
## Usage
To create a new LXC container with Part-DB, you can use the following command in the Proxmox VE shell:
```bash
bash -c "$(wget -qLO - https://github.com/community-scripts/ProxmoxVE/raw/main/ct/part-db.sh)"
```
The same command can be used to update an existing Part-DB container.
See the [helper script website](https://community-scripts.github.io/ProxmoxVE/scripts?id=part-db) for more information.
## Bugreports
If you find issues related to the proxmox VE LXC script, please open an issue in the [Proxmox VE Helper-Scripts repository](https://github.com/community-scripts/ProxmoxVE).

View file

@ -25,12 +25,6 @@ is named `partdb`, you can execute the command `php bin/console cache:clear` wit
docker exec --user=www-data partdb php bin/console cache:clear docker exec --user=www-data partdb php bin/console cache:clear
``` ```
{: .warning }
> If you run a root console inside the docker container, and wanna execute commands on the webserver behalf, be sure to use `sudo -E` command (with the `-E` flag) to preserve env variables from the current shell.
> Otherwise Part-DB console might use the wrong configuration to execute commands.
## Troubleshooting
## User management commands ## User management commands
* `php bin/console partdb:users:list`: List all users of this Part-DB instance * `php bin/console partdb:users:list`: List all users of this Part-DB instance
@ -70,4 +64,4 @@ docker exec --user=www-data partdb php bin/console cache:clear
## Database commands ## Database commands
* `php bin/console doctrine:migrations:migrate`: Migrate the database to the latest version * `php bin/console doctrine:migrations:migrate`: Migrate the database to the latest version
* `php bin/console doctrine:migrations:up-to-date`: Check if the database is up-to-date * `php bin/console doctrine:migrations:up-to-date`: Check if the database is up-to-date

View file

@ -107,7 +107,7 @@ The following env configuration options are available:
default: `EUR`). If an offer is only available in a certain currency, default: `EUR`). If an offer is only available in a certain currency,
Part-DB will save the prices in their native currency, and you can use Part-DB currency conversion feature to convert Part-DB will save the prices in their native currency, and you can use Part-DB currency conversion feature to convert
it to your preferred currency. it to your preferred currency.
* `PROVIDER_OCTOPART_COUNTRY`: The country you want to get prices in if available (optional, 2 letter ISO-code, * `PROVIDER_OCOTPART_COUNTRY`: The country you want to get prices in if available (optional, 2 letter ISO-code,
default: `DE`). To get the correct prices, you have to set this and the currency setting to the correct value. default: `DE`). To get the correct prices, you have to set this and the currency setting to the correct value.
* `PROVIDER_OCTOPART_SEARCH_LIMIT`: The maximum number of results to return per search (optional, default: `10`). This * `PROVIDER_OCTOPART_SEARCH_LIMIT`: The maximum number of results to return per search (optional, default: `10`). This
affects how quickly your monthly limit is used up. affects how quickly your monthly limit is used up.
@ -127,9 +127,6 @@ You must create an organization there and create a "Production app". Most settin
grant access to the "Product Information" API. grant access to the "Product Information" API.
You will get a Client ID and a Client Secret, which you have to put in the Part-DB env configuration (see below). You will get a Client ID and a Client Secret, which you have to put in the Part-DB env configuration (see below).
**Attention**: Currently only the "Product Information V3 (Deprecated)" is supported by Part-DB.
Using "Product Information V4" will not work.
The following env configuration options are available: The following env configuration options are available:
* `PROVIDER_DIGIKEY_CLIENT_ID`: The client ID you got from Digi-Key (mandatory) * `PROVIDER_DIGIKEY_CLIENT_ID`: The client ID you got from Digi-Key (mandatory)
@ -215,46 +212,6 @@ An API key is not required, it is enough to enable the provider using the follow
* `PROVIDER_LCSC_ENABLED`: Set this to `1` to enable the LCSC provider * `PROVIDER_LCSC_ENABLED`: Set this to `1` to enable the LCSC provider
* `PROVIDER_LCSC_CURRENCY`: The currency you want to get prices in (see LCSC webshop for available currencies, default: `EUR`) * `PROVIDER_LCSC_CURRENCY`: The currency you want to get prices in (see LCSC webshop for available currencies, default: `EUR`)
### OEMsecrets
The oemsecrets provider uses the [oemsecrets API](https://www.oemsecrets.com/) to search for parts and getting shopping
information from them. Similar to octopart it aggregates offers from different distributors.
You can apply for a free API key on the [oemsecrets API page](https://www.oemsecrets.com/api/) and put the key you get
in the Part-DB env configuration (see below).
The following env configuration options are available:
* `PROVIDER_OEMSECRETS_KEY`: The API key you got from oemsecrets (mandatory)
* `PROVIDER_OEMSECRETS_COUNTRY_CODE`: The two-letter code of the country you want to get the prices for
* `PROVIDER_OEMSECRETS_CURRENCY`: The currency you want to get prices in (optional, default: `EUR`)
* `PROVIDER_OEMSECRETS_ZERO_PRICE`: If set to `1`, parts with a price of 0 will be included in the search results, otherwise
they will be excluded (optional, default: `0`)
* `PROVIDER_OEMSECRETS_SET_PARAM`: If set to `1`, the provider will try to extract parameters from the part description
* `PROVIDER_OEMSECRETS_SORT_CRITERIA`: The criteria to sort the search results by. 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 set to any other value, no sorting is performed.
### Reichelt
The reichelt provider uses webscraping from [reichelt.com](https://reichelt.com/) to get part information.
This is not an official API and could break at any time. So use it at your own risk.
The following env configuration options are available:
* `PROVIDER_REICHELT_ENABLED`: Set this to `1` to enable the Reichelt provider
* `PROVIDER_REICHELT_CURRENCY`: The currency you want to get prices in. Only possible for countries which use Non-EUR (optional, default: `EUR`)
* `PROVIDER_REICHELT_COUNTRY`: The country you want to get the prices for (optional, default: `DE`)
* `PROVIDER_REICHELT_LANGUAGE`: The language you want to get the descriptions in (optional, default: `en`)
* `PROVIDER_REICHELT_INCLUDE_VAT`: If set to `1`, the prices will be gross prices (including tax), otherwise net prices (optional, default: `1`)
### Pollin
The pollin provider uses webscraping from [pollin.de](https://www.pollin.de/) to get part information.
This is not an official API and could break at any time. So use it at your own risk.
The following env configuration options are available:
* `PROVIDER_POLLIN_ENABLED`: Set this to `1` to enable the Pollin provider
### Custom provider ### Custom provider
To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long

View file

@ -117,6 +117,6 @@ For a German keyboard layout, replace `[` with `0`, and `]` with `´`.
| Key | Character | | Key | Character |
|--------------------------------|--------------------| |--------------------------------|--------------------|
| **Alt + [** (code 219) | © (Copyright char) | | **Alt + [** (code 219) | © (Copyright char) |
| **Alt + Shift + [** (code 219) | ® (Registered char) | | **Alt + Shift + [** (code 219) | (Registered char) |
| **Alt + ]** (code 221) | ™ (Trademark char) | | **Alt + ]** (code 221) | ™ (Trademark char) |
| **Alt + Shift + ]** (code 221) | ° (Degree char) | | **Alt + Shift + ]** (code 221) | (Degree char) |

View file

@ -89,169 +89,15 @@ If you select "Twig" in parser mode under advanced settings, you can input a twi
source mode). You can use most of the twig tags and filters listed source mode). You can use most of the twig tags and filters listed
in [official documentation](https://twig.symfony.com/doc/3.x/). in [official documentation](https://twig.symfony.com/doc/3.x/).
Twig allows you for much more complex and dynamic label generation. You can use loops, conditions, and functions to create The following variables are in injected into Twig and can be accessed using `{% raw %}{{ variable }}` (
the label content and you can access almost all data Part-DB offers. The label templates are evaluated in a special sandboxed environment,
where only certain operations are allowed. Only read access to entities is allowed. However as it circumvents Part-DB normal permission system,
the twig mode is only available to users with the "Twig mode" permission.
The following variables are in injected into Twig and can be accessed using `{% raw %}{{ variable }}{% endraw %}` (
or `{% raw %}{{ variable.property }}{% endraw %}`): or `{% raw %}{{ variable.property }}{% endraw %}`):
| Variable name | Description | | Variable name | Description |
|--------------------------------------------|--------------------------------------------------------------------------------------| |--------------------------------------------|-------------------------------------------------------------------------------------|
| `{% raw %}{{ element }}{% endraw %}` | The target element, selected in label dialog. | | `{% raw %}{{ element }}{% endraw %}` | The target element, selected in label dialog |
| `{% raw %}{{ user }}{% endraw %}` | The current logged in user. Null if you are not logged in | | `{% raw %}{{ user }}{% endraw %}` | The current logged in user. Null if you are not logged in |
| `{% raw %}{{ install_title }}{% endraw %}` | The name of the current Part-DB instance (similar to [[INSTALL_NAME]] placeholder). | | `{% raw %}{{ install_title }}{% endraw %}` | The name of the current Part-DB instance (similar to [[INSTALL_NAME]] placeholder). |
| `{% raw %}{{ page }}{% endraw %}` | The page number (the nth-element for which the label is generated | | `{% raw %}{{ page }}{% endraw %}` | The page number (the nth-element for which the label is generated |
| `{% raw %}{{ last_page }}{% endraw %}` | The page number of the last element. Equals the number of all pages / element labels |
| `{% raw %}{{ paper_width }}{% endraw %}` | The width of the label paper in mm |
| `{% raw %}{{ paper_height }}{% endraw %}` | The height of the label paper in mm |
### Use the placeholders in twig mode
You can use the placeholders described above in the twig mode on `element` using the `{% raw %}{{ placeholder('PLACEHOLDER', element) }}{% endraw %}`
function or the ``{{ "[[PLACEHOLDER]]"|placeholders(element) }}`` filter:
```twig
{% raw %}
{# The function can be used to get the a single placeholder value of an element, if the placeholder does not exist, null is returned #}
{{ placeholder('[[NAME]]', element) }}
{# The filter can be used to replace all placeholders in a string with the values of the element #}
{{ "[[NAME]]: [[DESCRIPTION]]"|placeholders(element) }}
{# Using the apply environment every placeholder in the apply block will be replaced automatically #}
{% apply placeholders(element) %}
[[NAME]]: [[DESCRIPTION]]
{% endapply %}
{# If the block contains HTML use placeholders(element)|raw to prevent escaping of the HTML #}
{% apply placeholders(element)|raw %}
<b>[[NAME]]</b>: [[DESCRIPTION]]
{% endapply %}
{% endraw %}
```
### Important entity fields in twig mode
In twig mode you have access to many fields of the entity you are generating the label for and their associated entities.
Following are some important fields of the entities listed. See the [SandboxedTwigFactory service](https://github.com/Part-DB/Part-DB-server/blob/master/src/Services/LabelSystem/SandboxedTwigFactory.php) for the full list of allowed class methods.
Please not that the field names might change in the future.
#### Part
| Field name | Description |
|---------------------|-----------------------------------------------------------------------------------------------|
| `id` | The internal ID of the part |
| `name` | The name of the part |
| `category` | The category of the part |
| `manufacturer` | The manufacturer of the part |
| `footprint` | The footprint of the part |
| `mass` | The mass of the part |
| `ManufacturerProductNumber` | The manufacturer product number of the part |
| `tags` | The tags of the part |
| `description` | The rich text (markdown) description of the part |
| `comment` | The rich text (markdown) comment of the part |
| `lastModified` | The datetime object when the part was last modified |
| `creationDate` | The datetime object when the part was created |
| `ipn` | The internal part number of the part |
| `partUnit` | The unit of the part |
| `amountSum` | The sum of the amount of all part lots of this part |
| `amountUnknwon` | Bool: True if there is at least one part lot with unknown amount |
| `partLots` | The part lots of the part |
| `parameters` | The parameters of the part |
| `orderdetails` | The order details of the part as array of Orderdetails |
#### Part lot
| Field name | Description |
|---------------------|-----------------------------------------------------------------------------------------------|
| `id` | The internal ID of the part lot |
| `name` | The name of the part lot |
| `comment` | The rich text (markdown) comment of the part lot |
| `expirationDate` | The expiration date of the part lot (as Datetime object) |
| `amount` | The amount of parts in this lot |
| `storageLocation` | The storage location of this part lot |
| `part` | The part of this part lot |
| `needsRefill` | Bool: True if the part lot needs a refill |
| `expired` | Bool: True if the part lot is expired |
| `vendorBarcode` | The vendor barcode field of the lot |
#### Structural entities like categories, manufacturers, footprints, and storage locations
| Field name | Description |
|---------------------|-----------------------------------------------------------------------------------------------|
| `id` | The internal ID of the entity |
| `name` | The name of the entity |
| `comment` | The rich text (markdown) comment of the entity |
| `parent` | The parent entity of the entity |
| `children` | The children entities of the entity |
| `lastModified` | The datetime object when the entity was last modified |
| `creationDate` | The datetime object when the entity was created |
| `level` | The level of the entity in the hierarchy |
| `fullPath` | The full path of the entity (you can pass the delimiter as parameter) |
| `pathArray` | The path of the entity as array of strings |
#### Orderdetails
| Field name | Description |
|---------------------|-----------------------------------------------------------------------------------------------|
| `id` | The internal ID of the order detail |
| `part` | The part of the order detail |
| `supplier` | The supplier/distributor of the order detail |
| `obsolete` | Bool: True if the order detail is obsolete |
| `pricedetails` | The price details of the order detail as array of Pricedetails |
#### Pricedetails
| Field name | Description |
|---------------------|-----------------------------------------------------------------------------------------------|
| `id` | The internal ID of the price detail |
| `price` | The price of the price detail |
| `currency` | The currency of the price detail |
| `currencyIsoCode` | The ISO code of the used currency |
| `pricePerUnit` | The price per unit of the price detail |
| `priceRelatedQuantity` | The related quantity of the price detail |
| `minDiscountQuantity` | The minimum discount quantity of the price detail |
#### User
| Field name | Description |
|---------------------|-----------------------------------------------------------------------------------------------|
| `id` | The internal ID of the user |
| `username` | The username of the user |
| `email` | The email of the user |
| `fullName` | The full name of the user |
| `lastName` | The last name of the user |
| `firstName` | The first name of the user |
| `department` | The department of the user |
### Part-DB specific twig functions and filters
Part-DB offers some custom twig functions and filters, which can be used in the twig mode and ease the rendering of
certain data:
#### Functions
| Function name | Description |
|----------------------------------------------|-----------------------------------------------------------------------------------------------|
| `placeholder(placeholder, element)` | Get the value of a placeholder of an element |
| `entity_type(element)` | Get the type of an entity as string |
| `entity_url(element, type)` | Get the URL to a specific entity type page (e.g. `info`, `edit`, etc.) |
| `barcode_svg(content, type)` | Generate a barcode SVG from the content and type (e.g. `QRCODE`, `CODE128` etc.). A svg string is returned, which you need to data uri encode to inline it. |
### Filters
| Filter name | Description |
|----------------------------------------------|-----------------------------------------------------------------------------------------------|
| `format_bytes` | Format a byte count to a human readable string |
| `format_money(price, currency)` | Format a price to a human readable string with the currency |
| `format_amount(amount, unit)` | Format an amount to a human readable string with the unit object |
| `format_si(value, unit_str)` | Format a value using SI prefixes and the given unit string |
| `placeholders(element)` | Replace all placeholders in a string with the values of the element |
## Use custom fonts for PDF labels ## Use custom fonts for PDF labels

View file

@ -235,14 +235,4 @@ EOD;
{ {
$this->warnIf(true, "Migration not needed for SQLite. Skipping..."); $this->warnIf(true, "Migration not needed for SQLite. Skipping...");
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -380,14 +380,4 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration
{ {
$this->warnIf(true, "Migration not needed for SQLite. Skipping..."); $this->warnIf(true, "Migration not needed for SQLite. Skipping...");
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -88,14 +88,4 @@ final class Version20190913141126 extends AbstractMultiPlatformMigration
{ {
$this->warnIf(true, "Migration not needed for SQLite. Skipping..."); $this->warnIf(true, "Migration not needed for SQLite. Skipping...");
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -179,14 +179,4 @@ final class Version20190924113252 extends AbstractMultiPlatformMigration
{ {
$this->warnIf(true, "Migration not needed for SQLite. Skipping..."); $this->warnIf(true, "Migration not needed for SQLite. Skipping...");
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -65,14 +65,4 @@ final class Version20191214153125 extends AbstractMultiPlatformMigration
{ {
$this->warnIf(true, "Migration not needed for SQLite. Skipping..."); $this->warnIf(true, "Migration not needed for SQLite. Skipping...");
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -68,14 +68,4 @@ final class Version20200126191823 extends AbstractMultiPlatformMigration
{ {
$this->warnIf(true, "Migration not needed for SQLite. Skipping..."); $this->warnIf(true, "Migration not needed for SQLite. Skipping...");
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -56,14 +56,4 @@ final class Version20200311204104 extends AbstractMultiPlatformMigration
{ {
$this->warnIf(true, "Migration not needed for SQLite. Skipping..."); $this->warnIf(true, "Migration not needed for SQLite. Skipping...");
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -42,14 +42,4 @@ final class Version20200409130946 extends AbstractMultiPlatformMigration
{ {
$this->warnIf(true, "Migration not needed for SQLite. Skipping..."); $this->warnIf(true, "Migration not needed for SQLite. Skipping...");
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -163,14 +163,4 @@ EOD;
$this->addSql('DROP TABLE u2f_keys'); $this->addSql('DROP TABLE u2f_keys');
$this->addSql('DROP TABLE "users"'); $this->addSql('DROP TABLE "users"');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -535,14 +535,4 @@ final class Version20220925162725 extends AbstractMultiPlatformMigration
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)'); $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)');
$this->addSql('CREATE INDEX IDX_1483A5E96DEDCEC2 ON "users" (id_preview_attachement)'); $this->addSql('CREATE INDEX IDX_1483A5E96DEDCEC2 ON "users" (id_preview_attachement)');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -47,14 +47,4 @@ final class Version20221003212851 extends AbstractMultiPlatformMigration
{ {
$this->addSql('DROP TABLE webauthn_keys'); $this->addSql('DROP TABLE webauthn_keys');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -4,20 +4,24 @@ declare(strict_types=1);
namespace DoctrineMigrations; namespace DoctrineMigrations;
use App\Entity\UserSystem\PermissionData;
use App\Migration\AbstractMultiPlatformMigration; use App\Migration\AbstractMultiPlatformMigration;
use App\Migration\WithPermPresetsTrait; use App\Security\Interfaces\HasPermissionsInterface;
use App\Services\UserSystem\PermissionPresetsHelper; use App\Services\UserSystem\PermissionPresetsHelper;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/** /**
* Auto-generated Migration: Please modify to your needs! * Auto-generated Migration: Please modify to your needs!
*/ */
final class Version20221114193325 extends AbstractMultiPlatformMigration implements ContainerAwareInterface final class Version20221114193325 extends AbstractMultiPlatformMigration implements ContainerAwareInterface
{ {
use WithPermPresetsTrait; private ?ContainerInterface $container = null;
private ?PermissionPresetsHelper $permission_presets_helper = null;
public function __construct(Connection $connection, LoggerInterface $logger) public function __construct(Connection $connection, LoggerInterface $logger)
{ {
@ -29,6 +33,34 @@ final class Version20221114193325 extends AbstractMultiPlatformMigration impleme
return 'Update the permission system to the new system. Please note that all permissions will be reset!'; return 'Update the permission system to the new system. Please note that all permissions will be reset!';
} }
private function getJSONPermDataFromPreset(string $preset): string
{
if ($this->permission_presets_helper === null) {
throw new \RuntimeException('PermissionPresetsHelper not set! There seems to be some issue with the dependency injection!');
}
//Create a virtual user on which we can apply the preset
$user = new class implements HasPermissionsInterface {
public PermissionData $perm_data;
public function __construct()
{
$this->perm_data = new PermissionData();
}
public function getPermissions(): PermissionData
{
return $this->perm_data;
}
};
//Apply the preset to the virtual user
$this->permission_presets_helper->applyPreset($user, $preset);
//And return the json data
return json_encode($user->getPermissions());
}
private function addDataMigrationAndWarning(): void private function addDataMigrationAndWarning(): void
{ {
@ -132,15 +164,11 @@ final class Version20221114193325 extends AbstractMultiPlatformMigration impleme
$this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); $this->addSql('CREATE INDEX user_idx_username ON "users" (name)');
} }
public function setContainer(ContainerInterface $container = null)
public function postgreSQLUp(Schema $schema): void
{ {
$this->warnIf(true, "Migration not needed for Postgres. Skipping..."); if ($container) {
} $this->container = $container;
$this->permission_presets_helper = $container->get(PermissionPresetsHelper::class);
public function postgreSQLDown(Schema $schema): void }
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
} }
} }

View file

@ -55,14 +55,4 @@ final class Version20221204004815 extends AbstractMultiPlatformMigration
$this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)'); $this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)');
$this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -67,15 +67,6 @@ final class Version20221216224745 extends AbstractMultiPlatformMigration
$this->addSql('CREATE INDEX log_idx_type ON log (type)'); $this->addSql('CREATE INDEX log_idx_type ON log (type)');
$this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)');
$this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)');
}
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
} }
} }

View file

@ -320,14 +320,4 @@ final class Version20230108165410 extends AbstractMultiPlatformMigration
$this->addSql('ALTER TABLE projects RENAME TO devices'); $this->addSql('ALTER TABLE projects RENAME TO devices');
$this->addSql('ALTER TABLE project_bom_entries RENAME TO device_parts'); $this->addSql('ALTER TABLE project_bom_entries RENAME TO device_parts');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -521,14 +521,4 @@ final class Version20230219225340 extends AbstractMultiPlatformMigration
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -37,14 +37,4 @@ final class Version20230220221024 extends AbstractMultiPlatformMigration
{ {
$this->addSql('ALTER TABLE `users` DROP saml_user'); $this->addSql('ALTER TABLE `users` DROP saml_user');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -297,14 +297,4 @@ final class Version20230402170923 extends AbstractMultiPlatformMigration
$this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -49,14 +49,4 @@ final class Version20230408170059 extends AbstractMultiPlatformMigration
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)'); $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)');
$this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); $this->addSql('CREATE INDEX user_idx_username ON "users" (name)');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -434,14 +434,4 @@ final class Version20230408213957 extends AbstractMultiPlatformMigration
$this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -45,14 +45,4 @@ final class Version20230417211732 extends AbstractMultiPlatformMigration
{ {
//As we done nothing, we don't need to implement this method. //As we done nothing, we don't need to implement this method.
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -62,14 +62,4 @@ final class Version20230528000149 extends AbstractMultiPlatformMigration
$this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -348,14 +348,4 @@ final class Version20230716184033 extends AbstractMultiPlatformMigration
$this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -99,14 +99,4 @@ final class Version20230730131708 extends AbstractMultiPlatformMigration
$this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)');
$this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)'); $this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -41,14 +41,4 @@ final class Version20230816213201 extends AbstractMultiPlatformMigration
{ {
$this->addSql('DROP TABLE api_tokens'); $this->addSql('DROP TABLE api_tokens');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -68,14 +68,4 @@ final class Version20231114223101 extends AbstractMultiPlatformMigration
$this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)'); $this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)');
$this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)'); $this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -85,14 +85,4 @@ final class Version20231130180903 extends AbstractMultiPlatformMigration
$this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)');
$this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)'); $this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)');
} }
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
} }

View file

@ -1,75 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
final class Version20240427222442 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Updated webauthn_keys table with new columns for additional info.';
}
public function mySQLUp(Schema $schema): void
{
$this->addSql('ALTER TABLE webauthn_keys ADD backup_eligible TINYINT(1) DEFAULT NULL, ADD backup_status TINYINT(1) DEFAULT NULL, ADD uv_initialized TINYINT(1) DEFAULT NULL, ADD last_time_used DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
}
public function mySQLDown(Schema $schema): void
{
$this->addSql('ALTER TABLE webauthn_keys DROP backup_eligible, DROP backup_status, DROP uv_initialized, DROP last_time_used');
}
public function sqLiteUp(Schema $schema): void
{
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui FROM webauthn_keys');
$this->addSql('DROP TABLE webauthn_keys');
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --(DC2Type:base64)
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array)
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path)
, aaguid CLOB NOT NULL --(DC2Type:aaguid)
, credential_public_key CLOB NOT NULL --(DC2Type:base64)
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, other_ui CLOB DEFAULT NULL --(DC2Type:array)
, backup_eligible BOOLEAN DEFAULT NULL, backup_status BOOLEAN DEFAULT NULL, uv_initialized BOOLEAN DEFAULT NULL, last_time_used DATETIME DEFAULT NULL --(DC2Type:datetime_immutable)
, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui FROM __temp__webauthn_keys');
$this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
}
public function sqLiteDown(Schema $schema): void
{
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, name, last_modified, datetime_added FROM webauthn_keys');
$this->addSql('DROP TABLE webauthn_keys');
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --
(DC2Type:base64)
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --
(DC2Type:array)
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --
(DC2Type:trust_path)
, aaguid CLOB NOT NULL --
(DC2Type:aaguid)
, credential_public_key CLOB NOT NULL --
(DC2Type:base64)
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, other_ui CLOB DEFAULT NULL --
(DC2Type:array)
, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, name, last_modified, datetime_added FROM __temp__webauthn_keys');
$this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
}
public function postgreSQLUp(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
public function postgreSQLDown(Schema $schema): void
{
$this->warnIf(true, "Migration not needed for Postgres. Skipping...");
}
}

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