mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-01-13 13:49:33 +00:00
Compare commits
522 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a8199d81f | ||
|
|
3f6a6cc767 | ||
|
|
33a3dc6203 | ||
|
|
1cd0b459be | ||
|
|
6828ce5803 | ||
|
|
644a44e8e9 | ||
|
|
6c3e4d7880 | ||
|
|
aefb69c51e | ||
|
|
300ee33be2 | ||
|
|
64efca4786 | ||
|
|
ddbfc87ce1 | ||
|
|
3454fa51de | ||
|
|
343ad6beff | ||
|
|
d385303a52 | ||
|
|
00b35e3306 | ||
|
|
e0a25009d9 | ||
|
|
3f0e4b09ce | ||
|
|
96a37a0cb0 | ||
|
|
2157916e9b | ||
|
|
be35c36c58 | ||
|
|
7116c2ceb9 | ||
|
|
c1d4ce77db | ||
|
|
bba3bd90a9 | ||
|
|
eaaf3ac75c | ||
|
|
8957e55a9e | ||
|
|
a232671302 | ||
|
|
5a53423594 | ||
|
|
390206f529 | ||
|
|
74862c7bb8 | ||
|
|
0e61a84ea6 | ||
|
|
3e380f82d2 | ||
|
|
a5d7a5f1d3 | ||
|
|
876cfc0375 | ||
|
|
641c8388c1 | ||
|
|
2f580c92d1 | ||
|
|
402edf096d | ||
|
|
f467002619 | ||
|
|
98b8c5b788 | ||
|
|
e0feda4e46 | ||
|
|
9565a9d548 | ||
|
|
b457298152 | ||
|
|
319ac406a8 | ||
|
|
065396d1e9 | ||
|
|
15243dbcc8 | ||
|
|
e1090d46e3 | ||
|
|
8d903c9586 | ||
|
|
39ff4f81c0 | ||
|
|
c60b406157 | ||
|
|
a66a1b1c33 | ||
|
|
b1bf70c531 | ||
|
|
5ab31a84e4 | ||
|
|
fb51548ecc | ||
|
|
061bd9fd10 | ||
|
|
0ac23cdf21 | ||
|
|
6fcdc0b0c3 | ||
|
|
60ff727896 | ||
|
|
225e347c24 | ||
|
|
a356eecd74 | ||
|
|
443cb57ce8 | ||
|
|
9dc17ad9d0 | ||
|
|
02b1f7aa16 | ||
|
|
d244400f4f | ||
|
|
fb805e2e0a | ||
|
|
8548237522 | ||
|
|
77819af9a8 | ||
|
|
0000cd7a02 | ||
|
|
9a1dbe06dc | ||
|
|
fd7106af28 | ||
|
|
cb6da36954 | ||
|
|
a5275f7be7 | ||
|
|
17f9755b86 | ||
|
|
a3d6f77fda | ||
|
|
36e1fcfbed | ||
|
|
68217f50c4 | ||
|
|
1925a71f30 | ||
|
|
d42f728fad | ||
|
|
023d38d170 | ||
|
|
9a1961bcd7 | ||
|
|
f27f0ab12d | ||
|
|
9f2989444a | ||
|
|
8efc1ab07d | ||
|
|
1596b4d7ea | ||
|
|
d8ec65461e | ||
|
|
d89a76bdc3 | ||
|
|
b1210bc3b5 | ||
|
|
3b4a099285 | ||
|
|
161a9e930d | ||
|
|
3649fffde1 | ||
|
|
d55d9b43f9 | ||
|
|
8e11e06077 | ||
|
|
e112a8dbe0 | ||
|
|
5d843ec8eb | ||
|
|
3518321f0e | ||
|
|
4750a50fb9 | ||
|
|
52a9975769 | ||
|
|
bb650c2218 | ||
|
|
045362de0e | ||
|
|
3dc66542b2 | ||
|
|
070ce800d5 | ||
|
|
57dc4242b2 | ||
|
|
5fa2167755 | ||
|
|
4eac63b683 | ||
|
|
171508fcad | ||
|
|
e4f8252e0f | ||
|
|
e513960401 | ||
|
|
84e35603b1 | ||
|
|
3459731ca8 | ||
|
|
819a8cc56d | ||
|
|
6a5039326c | ||
|
|
bee1542cce | ||
|
|
95c5ab7b8b | ||
|
|
f184afc918 | ||
|
|
54f318ecac | ||
|
|
5e3bd26e27 | ||
|
|
e53b72a8d1 | ||
|
|
e8ff15ad0f | ||
|
|
ec6b3ae414 | ||
|
|
07e4521c30 | ||
|
|
771857e014 | ||
|
|
14a4f1f437 | ||
|
|
600686c32b | ||
|
|
2e3ff05d83 | ||
|
|
e9dcdbc30d | ||
|
|
6cbb482c0f | ||
|
|
68aafc4d2e | ||
|
|
70354c8599 | ||
|
|
43601e060c | ||
|
|
56f82a7587 | ||
|
|
4c30cab7c1 | ||
|
|
1c8ca6c0a2 | ||
|
|
5dbe4ba00b | ||
|
|
377feaf566 | ||
|
|
05839a549c | ||
|
|
7a1a458abe | ||
|
|
c71e4cd063 | ||
|
|
d8e093e0c5 | ||
|
|
351e084ab1 | ||
|
|
bba6fff4a5 | ||
|
|
a8e92b5f46 | ||
|
|
8315e33258 | ||
|
|
3881c26ee0 | ||
|
|
028c64f6ec | ||
|
|
4088b141a6 | ||
|
|
445881bae9 | ||
|
|
b3f7e445fe | ||
|
|
1f2a7b86e5 | ||
|
|
ae787530ff | ||
|
|
6e4ae15438 | ||
|
|
e06d9da186 | ||
|
|
b035014867 | ||
|
|
746aa53bc3 | ||
|
|
7b61a00f21 | ||
|
|
aa24888ee5 | ||
|
|
c735bfdb1d | ||
|
|
41dbc27e27 | ||
|
|
4d98605e93 | ||
|
|
07166037b9 | ||
|
|
e1418dfdc1 | ||
|
|
ab92620f56 | ||
|
|
0a4b873b77 | ||
|
|
23bafa4471 | ||
|
|
436d3df83f | ||
|
|
37393dd6c9 | ||
|
|
8c15af3105 | ||
|
|
0ac1d19415 | ||
|
|
63a33d1057 | ||
|
|
a9d0caad5f | ||
|
|
6ed4ad4c8c | ||
|
|
71946afd75 | ||
|
|
919bf49ec1 | ||
|
|
001f2e97ea | ||
|
|
d2d5490aab | ||
|
|
c788fa99e3 | ||
|
|
34d284b1c4 | ||
|
|
67c736f979 | ||
|
|
6b1e7b3544 | ||
|
|
da30a6657e | ||
|
|
2e0b5edd95 | ||
|
|
1d6f0b403a | ||
|
|
df65f39d5e | ||
|
|
1bfea3c48a | ||
|
|
07db1554c7 | ||
|
|
ed1e51f694 | ||
|
|
5b71d68179 | ||
|
|
b94e28a961 | ||
|
|
1d52b7c464 | ||
|
|
0d49632b92 | ||
|
|
702e5c8732 | ||
|
|
d2b605edc0 | ||
|
|
4c28871283 | ||
|
|
1d38c50abc | ||
|
|
710569daaf | ||
|
|
92cd645945 | ||
|
|
16126c4000 | ||
|
|
eda6deff47 | ||
|
|
27a18bdc1e | ||
|
|
98b62cc81e | ||
|
|
2c195d9767 | ||
|
|
bb49c67108 | ||
|
|
f0dc80aac9 | ||
|
|
8998b006e0 | ||
|
|
b4b758c356 | ||
|
|
a399b629d1 | ||
|
|
41a7238ab7 | ||
|
|
0e99faee0a | ||
|
|
13e75808f8 | ||
|
|
1a0fab0615 | ||
|
|
fcdeb0479a | ||
|
|
79ac318d0f | ||
|
|
6765c110c6 | ||
|
|
f6f83cc111 | ||
|
|
c6d5fb3f57 | ||
|
|
4b8ef4b0fa | ||
|
|
46d8c86e0c | ||
|
|
c7102bcd8c | ||
|
|
d6ac16ede0 | ||
|
|
23cad8261b | ||
|
|
65d840c444 | ||
|
|
c52126ccf8 | ||
|
|
8eec606589 | ||
|
|
cdc58507db | ||
|
|
03f7ad66d2 | ||
|
|
3b01af1247 | ||
|
|
8d2ff6f5d7 | ||
|
|
6ff7f64384 | ||
|
|
c2cbbee0df | ||
|
|
e81c8470be | ||
|
|
ecd2abe00e | ||
|
|
0d1ae030be | ||
|
|
1f669a9c53 | ||
|
|
8ff2fc5a82 | ||
|
|
c7ec8adc31 | ||
|
|
cee6d355e8 | ||
|
|
4b00697f02 | ||
|
|
617ae03b48 | ||
|
|
71629a696c | ||
|
|
14cc0b9e9a | ||
|
|
c5a1df37b9 | ||
|
|
46d1a0cb1b | ||
|
|
a18ec373d2 | ||
|
|
ced16620ec | ||
|
|
890621b651 | ||
|
|
5a5691a8c4 | ||
|
|
fb92db8c05 | ||
|
|
2b28aa8ba9 | ||
|
|
90f83273da | ||
|
|
76f3c379b5 | ||
|
|
1d33d95c57 | ||
|
|
72e3766be5 | ||
|
|
7c1ab6460d | ||
|
|
d0f2422e0d | ||
|
|
4277f42285 | ||
|
|
0e9558e331 | ||
|
|
4e9e82d9f1 | ||
|
|
411ac500ba | ||
|
|
b1443a817b | ||
|
|
3e8ca06177 | ||
|
|
c1b7272ab1 | ||
|
|
b093866d15 | ||
|
|
065ef9f8ae | ||
|
|
9b17efc12c | ||
|
|
fe7910a2f2 | ||
|
|
eb4258053e | ||
|
|
117ff4484d | ||
|
|
ba7d139f8a | ||
|
|
d657b2ff04 | ||
|
|
0637c05053 | ||
|
|
88fbc46325 | ||
|
|
379155e839 | ||
|
|
0717239296 | ||
|
|
d3e3c4e3f8 | ||
|
|
c9a1febc56 | ||
|
|
7f099972e1 | ||
|
|
52444e05e4 | ||
|
|
4fcd55748f | ||
|
|
d57107ed3e | ||
|
|
0c7aa5e92a | ||
|
|
17f123ba8a | ||
|
|
1156bb52af | ||
|
|
71be75b3e7 | ||
|
|
5a4f151ca3 | ||
|
|
9729a43f2b | ||
|
|
4da403569c | ||
|
|
74be016b68 | ||
|
|
3896d3d9ab | ||
|
|
ed396765c8 | ||
|
|
cc9d50a8fe | ||
|
|
9b4d5e9c27 | ||
|
|
ccb837e4b4 | ||
|
|
2bc39e7791 | ||
|
|
fa7f3a1da1 | ||
|
|
c91d37d2a4 | ||
|
|
5ab7ac4d4b | ||
|
|
4c8940f9c3 | ||
|
|
aa29f10d51 | ||
|
|
78885ec3c5 | ||
|
|
1fb137e89f | ||
|
|
facfb37383 | ||
|
|
c5751b2aa6 | ||
|
|
aa4299041b | ||
|
|
c27f2246a3 | ||
|
|
a6be786d5d | ||
|
|
578a030175 | ||
|
|
f858e68f12 | ||
|
|
bdd88700d4 | ||
|
|
87cf75f67d | ||
|
|
c3cc7cb0d6 | ||
|
|
e1600cdec9 | ||
|
|
431cf23600 | ||
|
|
08ce1795fc | ||
|
|
e369ce6db9 | ||
|
|
af4ea17faa | ||
|
|
bb13ebc0ec | ||
|
|
3b42d7a2c8 | ||
|
|
e98d988c45 | ||
|
|
cc70e77dee | ||
|
|
7a86109d66 | ||
|
|
5238be1460 | ||
|
|
6edc8056ec | ||
|
|
b19cc13897 | ||
|
|
50f478f7ef | ||
|
|
80482f7294 | ||
|
|
dc864fad04 | ||
|
|
6d495b38b4 | ||
|
|
1c838d1e42 | ||
|
|
652c7abbce | ||
|
|
d925fd8913 | ||
|
|
9a8e34cbe3 | ||
|
|
34ae83cc8c | ||
|
|
e26e6da15d | ||
|
|
d45cd23c0f | ||
|
|
b60a1070e9 | ||
|
|
7f04827a0b | ||
|
|
da11c9b793 | ||
|
|
93b04fbf94 | ||
|
|
a2d94b54b1 | ||
|
|
1aedcc056f | ||
|
|
2019f44395 | ||
|
|
b217d3e4ef | ||
|
|
e3125e3afb | ||
|
|
d5c5c7c772 | ||
|
|
f945118827 | ||
|
|
ab811b1b7b | ||
|
|
1cd5d7b073 | ||
|
|
c9576f0b08 | ||
|
|
cbedb377ac | ||
|
|
6340bdd677 | ||
|
|
f41722c7eb | ||
|
|
b645c674bc | ||
|
|
e45a56c66a | ||
|
|
b4a7d18ace | ||
|
|
d70fe0fbaa | ||
|
|
48ff2494f6 | ||
|
|
ee33d743e6 | ||
|
|
eaaf44b391 | ||
|
|
a48490ac1a | ||
|
|
cb63b3bde7 | ||
|
|
f48791e961 | ||
|
|
a75a60fecd | ||
|
|
ac89489202 | ||
|
|
ae08d9539d | ||
|
|
275b695d6b | ||
|
|
d4d80de10f | ||
|
|
49ddb2938f | ||
|
|
7054c51490 | ||
|
|
808af0d3cd | ||
|
|
b14fc0e22a | ||
|
|
f7259a118b | ||
|
|
be60c4363c | ||
|
|
631db7df31 | ||
|
|
781ea45633 | ||
|
|
0eee161630 | ||
|
|
7a1b9b8ce1 | ||
|
|
3fcb5ce82e | ||
|
|
eb7aefb8c0 | ||
|
|
475cfe60f9 | ||
|
|
1e9a2e5382 | ||
|
|
9eaf5042ec | ||
|
|
d9ef9cd7b7 | ||
|
|
128b428644 | ||
|
|
23cd51c1ca | ||
|
|
d370f976a7 | ||
|
|
247fed7d74 | ||
|
|
c834058678 | ||
|
|
6c229ccb3a | ||
|
|
696eb8092a | ||
|
|
f9b85c3b85 | ||
|
|
496df89e0b | ||
|
|
58d4207b49 | ||
|
|
390736cf5a | ||
|
|
fd090b8701 | ||
|
|
286cd009de | ||
|
|
1f384a6ab8 | ||
|
|
883d38ecfc | ||
|
|
a691be6584 | ||
|
|
833a8c34f4 | ||
|
|
9fde748fbe | ||
|
|
8c22ec46bc | ||
|
|
74cc8629f6 | ||
|
|
fc3857aa67 | ||
|
|
db1b91fc32 | ||
|
|
e87720a838 | ||
|
|
74fef78120 | ||
|
|
9eb8e33e09 | ||
|
|
8bcebf57c4 | ||
|
|
e2735823a0 | ||
|
|
f3ad3c1ffe | ||
|
|
f215bd11cd | ||
|
|
dc480f755c | ||
|
|
f1d34bbc24 | ||
|
|
6665203f2a | ||
|
|
ce86863095 | ||
|
|
e2bad9e9da | ||
|
|
2cd2a481d9 | ||
|
|
78de2c5e03 | ||
|
|
dc25397469 | ||
|
|
db810445fb | ||
|
|
a3db52b184 | ||
|
|
7d96b2a611 | ||
|
|
50f4c01e99 | ||
|
|
0bc6d9986b | ||
|
|
d3c3fedac2 | ||
|
|
6137065b4e | ||
|
|
a58fcd94dd | ||
|
|
daec5aa4b1 | ||
|
|
2819b457fa | ||
|
|
1933234ed4 | ||
|
|
4e1bd486e8 | ||
|
|
53889c7813 | ||
|
|
9a3794bc83 | ||
|
|
e5cf8550ee | ||
|
|
5c4aa11b4b | ||
|
|
1b86257836 | ||
|
|
0f5fb992ef | ||
|
|
392740d79c | ||
|
|
41108bd969 | ||
|
|
40a837b165 | ||
|
|
7e6b931db4 | ||
|
|
9eb825f89a | ||
|
|
af87c1ae1d | ||
|
|
18bf68cb6a | ||
|
|
38c826713f | ||
|
|
b9c3358f7f | ||
|
|
2f2f4b2b3e | ||
|
|
423cdf6ca2 | ||
|
|
dc81a5b361 | ||
|
|
a4f2a7a7df | ||
|
|
c0c847c5be | ||
|
|
1dbcff66d1 | ||
|
|
6e28f2a74e | ||
|
|
9812671a89 | ||
|
|
8b417d6441 | ||
|
|
ff57b5b270 | ||
|
|
c00edef69c | ||
|
|
a235f05794 | ||
|
|
bd411ba13b | ||
|
|
f8bdbf1fde | ||
|
|
beea572c47 | ||
|
|
442a7aa235 | ||
|
|
2226b72d1c | ||
|
|
00a74ed96a | ||
|
|
699a5c935f | ||
|
|
b2946aee0d | ||
|
|
beb079b825 | ||
|
|
64497c4c0d | ||
|
|
70bbb47850 | ||
|
|
73253dd03c | ||
|
|
442457f11b | ||
|
|
c44535990b | ||
|
|
b8d5b83eee | ||
|
|
00da2dedc3 | ||
|
|
4ce1de079e | ||
|
|
6b9c125de4 | ||
|
|
2c4f44e808 | ||
|
|
2b694731ad | ||
|
|
7e34535e62 | ||
|
|
cf791cff1d | ||
|
|
0bb831fe88 | ||
|
|
42a32ce142 | ||
|
|
23f58b7bf4 | ||
|
|
4e9101fded | ||
|
|
9c700c77a8 | ||
|
|
cb1f674332 | ||
|
|
6823d94ffb | ||
|
|
3d4e91fc69 | ||
|
|
97aed847b6 | ||
|
|
8750573724 | ||
|
|
1ac9641a04 | ||
|
|
ad47c8d8ed | ||
|
|
0dbf417866 | ||
|
|
a45bf22ac5 | ||
|
|
b5a6ba921c | ||
|
|
f6a2467eae | ||
|
|
79da0518c2 | ||
|
|
5e512f8935 | ||
|
|
99c10ffe85 | ||
|
|
47830dcd08 | ||
|
|
947cce78d7 | ||
|
|
74e555d25d | ||
|
|
e9973af8f4 | ||
|
|
2ab2b7f77d | ||
|
|
463812fb3d | ||
|
|
d2406726c6 | ||
|
|
1f04d1b993 | ||
|
|
2ef46cdd34 | ||
|
|
2bc50b2888 | ||
|
|
9e0f86788d | ||
|
|
2681c7ded3 | ||
|
|
5ab6a63492 | ||
|
|
6df7bc5f2a | ||
|
|
a0a7ca3c9c | ||
|
|
f88584e1ca | ||
|
|
3e657a7cac | ||
|
|
7cc67f8bb1 | ||
|
|
0772d85918 | ||
|
|
26d83af298 | ||
|
|
4876068cce | ||
|
|
08ae313dfe | ||
|
|
3967c53468 | ||
|
|
7ad077862c | ||
|
|
5a563e4f8f | ||
|
|
5a4b7c525b |
594 changed files with 61166 additions and 13308 deletions
|
|
@ -26,6 +26,28 @@ if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
|
||||||
composer install --prefer-dist --no-progress --no-interaction
|
composer install --prefer-dist --no-progress --no-interaction
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Install additional composer packages if COMPOSER_EXTRA_PACKAGES is set
|
||||||
|
if [ -n "$COMPOSER_EXTRA_PACKAGES" ]; then
|
||||||
|
echo "Installing additional composer packages: $COMPOSER_EXTRA_PACKAGES"
|
||||||
|
# Note: COMPOSER_EXTRA_PACKAGES is intentionally not quoted to allow word splitting
|
||||||
|
# This enables passing multiple package names separated by spaces
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
composer require $COMPOSER_EXTRA_PACKAGES --no-install --no-interaction --no-progress
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Running composer install to install packages without dev dependencies..."
|
||||||
|
composer install --no-dev --no-interaction --no-progress --optimize-autoloader
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Successfully installed additional composer packages"
|
||||||
|
else
|
||||||
|
echo "Failed to install composer dependencies"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Failed to add additional composer packages to composer.json"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if grep -q ^DATABASE_URL= .env; then
|
if grep -q ^DATABASE_URL= .env; then
|
||||||
echo "Waiting for database to be ready..."
|
echo "Waiting for database to be ready..."
|
||||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
|
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,30 @@ if [ -d /var/www/html/var/db ]; then
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Install additional composer packages if COMPOSER_EXTRA_PACKAGES is set
|
||||||
|
if [ -n "$COMPOSER_EXTRA_PACKAGES" ]; then
|
||||||
|
echo "Installing additional composer packages: $COMPOSER_EXTRA_PACKAGES"
|
||||||
|
# Note: COMPOSER_EXTRA_PACKAGES is intentionally not quoted to allow word splitting
|
||||||
|
# This enables passing multiple package names separated by spaces
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
sudo -E -u www-data composer require $COMPOSER_EXTRA_PACKAGES --no-install --no-interaction --no-progress
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Running composer install to install packages without dev dependencies..."
|
||||||
|
sudo -E -u www-data composer install --no-dev --no-interaction --no-progress --optimize-autoloader
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Successfully installed additional composer packages"
|
||||||
|
else
|
||||||
|
echo "Failed to install composer dependencies"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Failed to add additional composer packages to composer.json"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Start PHP-FPM (the PHP_VERSION is replaced by the configured version in the Dockerfile)
|
# Start PHP-FPM (the PHP_VERSION is replaced by the configured version in the Dockerfile)
|
||||||
service phpPHP_VERSION-fpm start
|
php-fpmPHP_VERSION -F &
|
||||||
|
|
||||||
|
|
||||||
# Run migrations if automigration is enabled via env variable DB_AUTOMIGRATE
|
# Run migrations if automigration is enabled via env variable DB_AUTOMIGRATE
|
||||||
|
|
@ -90,4 +112,4 @@ if [ "${1#-}" != "$1" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Pass to the original entrypoint
|
# Pass to the original entrypoint
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|
|
||||||
|
|
@ -24,34 +24,10 @@
|
||||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||||
|
|
||||||
# Pass the configuration from the docker env to the PHP environment (here you should list all .env options)
|
|
||||||
PassEnv APP_ENV APP_DEBUG APP_SECRET REDIRECT_TO_HTTPS DISABLE_YEAR2038_BUG_CHECK
|
|
||||||
PassEnv TRUSTED_PROXIES TRUSTED_HOSTS LOCK_DSN
|
|
||||||
PassEnv DATABASE_URL ENFORCE_CHANGE_COMMENTS_FOR DATABASE_MYSQL_USE_SSL_CA DATABASE_MYSQL_SSL_VERIFY_CERT
|
|
||||||
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI CHECK_FOR_UPDATES ATTACHMENT_DOWNLOAD_BY_DEFAULT
|
|
||||||
PassEnv MAILER_DSN ALLOW_EMAIL_PW_RESET EMAIL_SENDER_EMAIL EMAIL_SENDER_NAME
|
|
||||||
PassEnv HISTORY_SAVE_CHANGED_FIELDS HISTORY_SAVE_CHANGED_DATA HISTORY_SAVE_REMOVED_DATA HISTORY_SAVE_NEW_DATA
|
|
||||||
PassEnv ERROR_PAGE_ADMIN_EMAIL ERROR_PAGE_SHOW_HELP
|
|
||||||
PassEnv DEMO_MODE NO_URL_REWRITE_AVAILABLE FIXER_API_KEY BANNER
|
|
||||||
# In old version the SAML sp private key env, was wrongly named SAMLP_SP_PRIVATE_KEY, keep it for backward compatibility
|
|
||||||
PassEnv SAML_ENABLED SAML_BEHIND_PROXY SAML_ROLE_MAPPING SAML_UPDATE_GROUP_ON_LOGIN SAML_IDP_ENTITY_ID SAML_IDP_SINGLE_SIGN_ON_SERVICE SAML_IDP_SINGLE_LOGOUT_SERVICE SAML_IDP_X509_CERT SAML_SP_ENTITY_ID SAML_SP_X509_CERT SAML_SP_PRIVATE_KEY SAMLP_SP_PRIVATE_KEY
|
|
||||||
PassEnv TABLE_DEFAULT_PAGE_SIZE TABLE_PARTS_DEFAULT_COLUMNS
|
|
||||||
|
|
||||||
PassEnv PROVIDER_DIGIKEY_CLIENT_ID PROVIDER_DIGIKEY_SECRET PROVIDER_DIGIKEY_CURRENCY PROVIDER_DIGIKEY_LANGUAGE PROVIDER_DIGIKEY_COUNTRY
|
|
||||||
PassEnv PROVIDER_ELEMENT14_KEY PROVIDER_ELEMENT14_STORE_ID
|
|
||||||
PassEnv PROVIDER_TME_KEY PROVIDER_TME_SECRET PROVIDER_TME_CURRENCY PROVIDER_TME_LANGUAGE PROVIDER_TME_COUNTRY PROVIDER_TME_GET_GROSS_PRICES
|
|
||||||
PassEnv PROVIDER_OCTOPART_CLIENT_ID PROVIDER_OCTOPART_SECRET PROVIDER_OCTOPART_CURRENCY PROVIDER_OCTOPART_COUNTRY PROVIDER_OCTOPART_SEARCH_LIMIT PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS
|
|
||||||
PassEnv PROVIDER_MOUSER_KEY PROVIDER_MOUSER_SEARCH_OPTION PROVIDER_MOUSER_SEARCH_LIMIT PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE
|
|
||||||
PassEnv PROVIDER_LCSC_ENABLED PROVIDER_LCSC_CURRENCY
|
|
||||||
PassEnv PROVIDER_OEMSECRETS_KEY PROVIDER_OEMSECRETS_COUNTRY_CODE PROVIDER_OEMSECRETS_CURRENCY PROVIDER_OEMSECRETS_ZERO_PRICE PROVIDER_OEMSECRETS_SET_PARAM PROVIDER_OEMSECRETS_SORT_CRITERIA
|
|
||||||
PassEnv PROVIDER_REICHELT_ENABLED PROVIDER_REICHELT_CURRENCY PROVIDER_REICHELT_COUNTRY PROVIDER_REICHELT_LANGUAGE PROVIDER_REICHELT_INCLUDE_VAT
|
|
||||||
PassEnv PROVIDER_POLLIN_ENABLED
|
|
||||||
PassEnv EDA_KICAD_CATEGORY_DEPTH
|
|
||||||
|
|
||||||
# For most configuration files from conf-available/, which are
|
# For most configuration files from conf-available/, which are
|
||||||
# enabled or disabled at a global level, it is possible to
|
# enabled or disabled at a global level, it is possible to
|
||||||
# include a line for only one particular virtual host. For example the
|
# include a line for only one particular virtual host. For example the
|
||||||
# following line enables the CGI configuration for this host only
|
# following line enables the CGI configuration for this host only
|
||||||
# after it has been globally disabled with "a2disconf".
|
# after it has been globally disabled with "a2disconf".
|
||||||
#Include conf-available/serve-cgi-bin.conf
|
#Include conf-available/serve-cgi-bin.conf
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
|
|
|
||||||
17
.editorconfig
Normal file
17
.editorconfig
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[{compose.yaml,compose.*.yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
204
.env
204
.env
|
|
@ -31,37 +31,9 @@ DATABASE_EMULATE_NATURAL_SORT=0
|
||||||
# General settings
|
# General settings
|
||||||
###################################################################################
|
###################################################################################
|
||||||
|
|
||||||
# The language to use serverwide as default (en, de, ru, etc.)
|
# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates or when no request context is available.
|
||||||
DEFAULT_LANG="en"
|
|
||||||
# The default timezone to use serverwide (e.g. Europe/Berlin)
|
|
||||||
DEFAULT_TIMEZONE="Europe/Berlin"
|
|
||||||
# The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country
|
|
||||||
BASE_CURRENCY="EUR"
|
|
||||||
# The name of this installation. This will be shown as title in the browser and in the header of the website
|
|
||||||
INSTANCE_NAME="Part-DB"
|
|
||||||
# Allow users to download attachments to the server by providing an URL
|
|
||||||
# This could be a potential security issue, as the user can retrieve any file the server has access to (via internet)
|
|
||||||
ALLOW_ATTACHMENT_DOWNLOADS=0
|
|
||||||
# Set this to 1, if the "download external files" checkbox should be checked by default for new attachments
|
|
||||||
ATTACHMENT_DOWNLOAD_BY_DEFAULT=0
|
|
||||||
# Use gravatars for user avatars, when user has no own avatar defined
|
|
||||||
USE_GRAVATAR=0
|
|
||||||
# The maximum allowed size for attachment files in bytes (you can use M for megabytes and G for gigabytes)
|
|
||||||
# Please note that the php.ini setting upload_max_filesize also limits the maximum size of uploaded files
|
|
||||||
MAX_ATTACHMENT_FILE_SIZE="100M"
|
|
||||||
|
|
||||||
# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates
|
|
||||||
# This must end with a slash!
|
|
||||||
DEFAULT_URI="https://partdb.changeme.invalid/"
|
DEFAULT_URI="https://partdb.changeme.invalid/"
|
||||||
|
|
||||||
# With this option you can configure, where users are enforced to give a change reason, which will be logged
|
|
||||||
# This is a comma separated list of values, see documentation for available values
|
|
||||||
# Leave this empty, to make all change reasons optional
|
|
||||||
ENFORCE_CHANGE_COMMENTS_FOR=""
|
|
||||||
|
|
||||||
# Disable that if you do not want that Part-DB connects to GitHub to check for available updates, or if your server can not connect to the internet
|
|
||||||
CHECK_FOR_UPDATES=1
|
|
||||||
|
|
||||||
###################################################################################
|
###################################################################################
|
||||||
# Email settings
|
# Email settings
|
||||||
###################################################################################
|
###################################################################################
|
||||||
|
|
@ -78,21 +50,6 @@ EMAIL_SENDER_NAME="Part-DB Mailer"
|
||||||
# Set this to 1 to allow reset of a password per email
|
# Set this to 1 to allow reset of a password per email
|
||||||
ALLOW_EMAIL_PW_RESET=0
|
ALLOW_EMAIL_PW_RESET=0
|
||||||
|
|
||||||
######################################################################################
|
|
||||||
# History/Eventlog settings
|
|
||||||
######################################################################################
|
|
||||||
# If you want to use full timetrave functionality all values below have to be set to 1
|
|
||||||
|
|
||||||
# Save which fields were changed in a ElementEdited log entry
|
|
||||||
HISTORY_SAVE_CHANGED_FIELDS=1
|
|
||||||
# Save the old data in the ElementEdited log entry (warning this could increase the database size in short time)
|
|
||||||
HISTORY_SAVE_CHANGED_DATA=1
|
|
||||||
# Save the data of an element that gets removed into log entry. This allows to undelete an element
|
|
||||||
HISTORY_SAVE_REMOVED_DATA=1
|
|
||||||
# Save the new data of an element that gets changed or added. This allows an easy comparison of the old and new data on the detail page
|
|
||||||
# This option only becomes active when HISTORY_SAVE_CHANGED_DATA is set to 1
|
|
||||||
HISTORY_SAVE_NEW_DATA=1
|
|
||||||
|
|
||||||
###################################################################################
|
###################################################################################
|
||||||
# Error pages settings
|
# Error pages settings
|
||||||
###################################################################################
|
###################################################################################
|
||||||
|
|
@ -102,149 +59,6 @@ ERROR_PAGE_ADMIN_EMAIL=''
|
||||||
# If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them...
|
# If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them...
|
||||||
ERROR_PAGE_SHOW_HELP=1
|
ERROR_PAGE_SHOW_HELP=1
|
||||||
|
|
||||||
##################################################################################
|
|
||||||
# Part table settings
|
|
||||||
##################################################################################
|
|
||||||
|
|
||||||
# The default page size for the part table (set to -1 to show all parts on one page)
|
|
||||||
TABLE_DEFAULT_PAGE_SIZE=50
|
|
||||||
# Configure which columns will be visible by default in the parts table (and in which order).
|
|
||||||
# This is a comma separated list of column names. See documentation for available values.
|
|
||||||
TABLE_PARTS_DEFAULT_COLUMNS=name,description,category,footprint,manufacturer,storage_location,amount
|
|
||||||
|
|
||||||
##################################################################################
|
|
||||||
# Info provider settings
|
|
||||||
##################################################################################
|
|
||||||
|
|
||||||
# Digikey Provider:
|
|
||||||
# You can get your client id and secret from https://developer.digikey.com/
|
|
||||||
PROVIDER_DIGIKEY_CLIENT_ID=
|
|
||||||
PROVIDER_DIGIKEY_SECRET=
|
|
||||||
# The currency to get prices in
|
|
||||||
PROVIDER_DIGIKEY_CURRENCY=EUR
|
|
||||||
# The language to get results in (en, de, fr, it, es, zh, ja, ko)
|
|
||||||
PROVIDER_DIGIKEY_LANGUAGE=en
|
|
||||||
# The country to get results for
|
|
||||||
PROVIDER_DIGIKEY_COUNTRY=DE
|
|
||||||
|
|
||||||
# Farnell Provider:
|
|
||||||
# You can get your API key from https://partner.element14.com/
|
|
||||||
PROVIDER_ELEMENT14_KEY=
|
|
||||||
# Configure the store domain you want to use. This decides the language and currency of results. You can get a list of available stores from https://partner.element14.com/docs/Product_Search_API_REST__Description
|
|
||||||
PROVIDER_ELEMENT14_STORE_ID=de.farnell.com
|
|
||||||
|
|
||||||
# TME Provider:
|
|
||||||
# You can get your API key from https://developers.tme.eu/en/
|
|
||||||
PROVIDER_TME_KEY=
|
|
||||||
PROVIDER_TME_SECRET=
|
|
||||||
# The currency to get prices in
|
|
||||||
PROVIDER_TME_CURRENCY=EUR
|
|
||||||
# The language to get results in (en, de, pl)
|
|
||||||
PROVIDER_TME_LANGUAGE=en
|
|
||||||
# The country to get results for
|
|
||||||
PROVIDER_TME_COUNTRY=DE
|
|
||||||
# [DEPRECATED] Set this to 1 to get gross prices (including VAT) instead of net prices
|
|
||||||
# With private API keys, this option cannot be used anymore is ignored by Part-DB. The VAT inclusion depends on your TME account settings.
|
|
||||||
PROVIDER_TME_GET_GROSS_PRICES=1
|
|
||||||
|
|
||||||
# Octopart / Nexar Provider:
|
|
||||||
# You can get your API key from https://nexar.com/api
|
|
||||||
PROVIDER_OCTOPART_CLIENT_ID=
|
|
||||||
PROVIDER_OCTOPART_SECRET=
|
|
||||||
# The currency and country to get prices for (you have to set both to get meaningful results)
|
|
||||||
# 3 letter ISO currency code (e.g. EUR, USD, GBP)
|
|
||||||
PROVIDER_OCTOPART_CURRENCY=EUR
|
|
||||||
# 2 letter ISO country code (e.g. DE, US, GB)
|
|
||||||
PROVIDER_OCTOPART_COUNTRY=DE
|
|
||||||
# The number of results to get from Octopart while searching (please note that this counts towards your API limits)
|
|
||||||
PROVIDER_OCTOPART_SEARCH_LIMIT=10
|
|
||||||
# Set to false to include non authorized offers in the results
|
|
||||||
PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS=1
|
|
||||||
|
|
||||||
# Mouser Provider API V2:
|
|
||||||
# You can get your API key from https://www.mouser.it/api-hub/
|
|
||||||
PROVIDER_MOUSER_KEY=
|
|
||||||
# Filter search results by RoHS compliance and stock availability:
|
|
||||||
# Available options: None | Rohs | InStock | RohsAndInStock
|
|
||||||
PROVIDER_MOUSER_SEARCH_OPTION='None'
|
|
||||||
# The number of results to get from Mouser while searching (please note that this value is max 50)
|
|
||||||
PROVIDER_MOUSER_SEARCH_LIMIT=50
|
|
||||||
# It is recommended to leave this set to 'true'. The option is not really good doumented by Mouser:
|
|
||||||
# Used when searching for keywords in the language specified when you signed up for Search API.
|
|
||||||
PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE='true'
|
|
||||||
|
|
||||||
# LCSC Provider:
|
|
||||||
# LCSC does not provide an offical API, so this used the API LCSC uses to render their webshop.
|
|
||||||
# LCSC did not intended the use of this API and it could break any time, so use it at your own risk.
|
|
||||||
|
|
||||||
# We dont require an API key for LCSC, just set this to 1 to enable LCSC support
|
|
||||||
PROVIDER_LCSC_ENABLED=0
|
|
||||||
# The currency to get prices in (e.g. EUR, USD, etc.)
|
|
||||||
PROVIDER_LCSC_CURRENCY=EUR
|
|
||||||
|
|
||||||
# Oemsecrets Provider API 3.0.1:
|
|
||||||
# You can get your API key from https://www.oemsecrets.com/api
|
|
||||||
PROVIDER_OEMSECRETS_KEY=
|
|
||||||
# The country you want the output for
|
|
||||||
PROVIDER_OEMSECRETS_COUNTRY_CODE=DE
|
|
||||||
# Available country code are:
|
|
||||||
# AD, AE, AQ, AR, AT, AU, BE, BO, BR, BV, BY, CA, CH, CL, CN, CO, CZ, DE, DK, EC, EE, EH,
|
|
||||||
# ES, FI, FK, FO, FR, GB, GE, GF, GG, GI, GL, GR, GS, GY, HK, HM, HR, HU, IE, IM, IN, IS,
|
|
||||||
# IT, JM, JP, KP, KR, KZ, LI, LK, LT, LU, MC, MD, ME, MK, MT, NL, NO, NZ, PE, PH, PL, PT,
|
|
||||||
# PY, RO, RS, RU, SB, SD, SE, SG, SI, SJ, SK, SM, SO, SR, SY, SZ, TC, TF, TG, TH, TJ, TK,
|
|
||||||
# TM, TN, TO, TR, TT, TV, TW, TZ, UA, UG, UM, US, UY, UZ, VA, VE, VG, VI, VN, VU, WF, YE,
|
|
||||||
# ZA, ZM, ZW
|
|
||||||
#
|
|
||||||
# The currency you want the prices to be displayed in
|
|
||||||
PROVIDER_OEMSECRETS_CURRENCY=EUR
|
|
||||||
# Available currency are:AUD, CAD, CHF, CNY, DKK, EUR, GBP, HKD, ILS, INR, JPY, KRW, NOK,
|
|
||||||
# NZD, RUB, SEK, SGD, TWD, USD
|
|
||||||
#
|
|
||||||
# If PROVIDER_OEMSECRETS_ZERO_PRICE is set to 0, distributors with zero prices
|
|
||||||
# will be discarded from the creation of a new part (set to 1 otherwise)
|
|
||||||
PROVIDER_OEMSECRETS_ZERO_PRICE=0
|
|
||||||
#
|
|
||||||
# When PROVIDER_OEMSECRETS_SET_PARAM is set to 1 the parameters for the part are generated
|
|
||||||
# from the description transforming unstructured descriptions into structured parameters;
|
|
||||||
# each parameter in description should have the form: "...;name1:value1;name2:value2"
|
|
||||||
PROVIDER_OEMSECRETS_SET_PARAM=1
|
|
||||||
#
|
|
||||||
# This environment variable determines the sorting criteria for product results.
|
|
||||||
# The sorting process first arranges items based on the provided keyword.
|
|
||||||
# Then, if set to 'C', it further sorts by completeness (prioritizing items with the most
|
|
||||||
# detailed information). If set to 'M', it further sorts by manufacturer name.
|
|
||||||
#If unset or set to any other value, no sorting is performed.
|
|
||||||
PROVIDER_OEMSECRETS_SORT_CRITERIA=C
|
|
||||||
|
|
||||||
|
|
||||||
# Reichelt provider:
|
|
||||||
# Reichelt.com offers no official API, so this info provider webscrapes the website to extract info
|
|
||||||
# It could break at any time, use it at your own risk
|
|
||||||
# We dont require an API key for Reichelt, just set this to 1 to enable Reichelt support
|
|
||||||
PROVIDER_REICHELT_ENABLED=0
|
|
||||||
# The country to get prices for
|
|
||||||
PROVIDER_REICHELT_COUNTRY=DE
|
|
||||||
# The language to get results in (en, de, fr, nl, pl, it, es)
|
|
||||||
PROVIDER_REICHELT_LANGUAGE=en
|
|
||||||
# Include VAT in prices (set to 1 to include VAT, 0 to exclude VAT)
|
|
||||||
PROVIDER_REICHELT_INCLUDE_VAT=1
|
|
||||||
# The currency to get prices in (only for countries with countries other than EUR)
|
|
||||||
PROVIDER_REICHELT_CURRENCY=EUR
|
|
||||||
|
|
||||||
# Pollin provider:
|
|
||||||
# Pollin.de offers no official API, so this info provider webscrapes the website to extract info
|
|
||||||
# It could break at any time, use it at your own risk
|
|
||||||
# We dont require an API key for Pollin, just set this to 1 to enable Pollin support
|
|
||||||
PROVIDER_POLLIN_ENABLED=0
|
|
||||||
|
|
||||||
##################################################################################
|
|
||||||
# EDA integration related settings
|
|
||||||
##################################################################################
|
|
||||||
|
|
||||||
# This value determines the depth of the category tree, that is visible inside KiCad
|
|
||||||
# 0 means that only the top level categories are visible. Set to a value > 0 to show more levels.
|
|
||||||
# Set to -1, to show all parts of Part-DB inside a single category in KiCad
|
|
||||||
EDA_KICAD_CATEGORY_DEPTH=0
|
|
||||||
|
|
||||||
###################################################################################
|
###################################################################################
|
||||||
# SAML Single sign on-settings
|
# SAML Single sign on-settings
|
||||||
|
|
@ -298,16 +112,6 @@ NO_URL_REWRITE_AVAILABLE=0
|
||||||
# Set to 1, if Part-DB should redirect all HTTP requests to HTTPS. You dont need to configure this, if your webserver already does this.
|
# Set to 1, if Part-DB should redirect all HTTP requests to HTTPS. You dont need to configure this, if your webserver already does this.
|
||||||
REDIRECT_TO_HTTPS=0
|
REDIRECT_TO_HTTPS=0
|
||||||
|
|
||||||
# If you want to use fixer.io for currency conversion, you have to set this to your API key
|
|
||||||
FIXER_API_KEY=CHANGEME
|
|
||||||
|
|
||||||
# Override value if you want to show to show a given text on homepage.
|
|
||||||
# When this is empty the content of config/banner.md is used as banner
|
|
||||||
BANNER=""
|
|
||||||
|
|
||||||
APP_ENV=prod
|
|
||||||
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
|
|
||||||
|
|
||||||
# Set this to zero, if you want to disable the year 2038 bug check on 32-bit systems (it will cause errors with current 32-bit PHP versions)
|
# Set this to zero, if you want to disable the year 2038 bug check on 32-bit systems (it will cause errors with current 32-bit PHP versions)
|
||||||
DISABLE_YEAR2038_BUG_CHECK=0
|
DISABLE_YEAR2038_BUG_CHECK=0
|
||||||
|
|
||||||
|
|
@ -325,3 +129,9 @@ LOCK_DSN=flock
|
||||||
###> nelmio/cors-bundle ###
|
###> nelmio/cors-bundle ###
|
||||||
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
||||||
###< nelmio/cors-bundle ###
|
###< nelmio/cors-bundle ###
|
||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
APP_ENV=prod
|
||||||
|
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
|
||||||
|
APP_SHARE_DIR=var/share
|
||||||
|
###< symfony/framework-bundle ###
|
||||||
|
|
|
||||||
4
.env.dev
4
.env.dev
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
APP_SECRET=318b5d659e07a0b3f96d9b3a83b254ca
|
||||||
|
###< symfony/framework-bundle ###
|
||||||
|
|
@ -10,4 +10,6 @@ DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"
|
||||||
#DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db
|
#DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db
|
||||||
|
|
||||||
# Disable update checks, as tests would fail, when github is not reachable
|
# Disable update checks, as tests would fail, when github is not reachable
|
||||||
CHECK_FOR_UPDATES=0
|
CHECK_FOR_UPDATES=0
|
||||||
|
|
||||||
|
INSTANCE_NAME="Part-DB"
|
||||||
186
.github/copilot-instructions.md
vendored
Normal file
186
.github/copilot-instructions.md
vendored
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
# Copilot Instructions for Part-DB
|
||||||
|
|
||||||
|
Part-DB is an Open-Source inventory management system for electronic components built with Symfony 7.4 and modern web technologies.
|
||||||
|
|
||||||
|
## Technology Stack
|
||||||
|
|
||||||
|
- **Backend**: PHP 8.2+, Symfony 7.4, Doctrine ORM
|
||||||
|
- **Frontend**: Bootstrap 5, Hotwire Stimulus/Turbo, TypeScript, Webpack Encore
|
||||||
|
- **Database**: MySQL 5.7+/MariaDB 10.4+/PostgreSQL 10+/SQLite
|
||||||
|
- **Testing**: PHPUnit with DAMA Doctrine Test Bundle
|
||||||
|
- **Code Quality**: Easy Coding Standard (ECS), PHPStan (level 5)
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
- `src/`: PHP application code organized by purpose (Controller, Entity, Service, Form, etc.)
|
||||||
|
- `assets/`: Frontend TypeScript/JavaScript and CSS files
|
||||||
|
- `templates/`: Twig templates for views
|
||||||
|
- `tests/`: PHPUnit tests mirroring the `src/` structure
|
||||||
|
- `config/`: Symfony configuration files
|
||||||
|
- `public/`: Web-accessible files
|
||||||
|
- `translations/`: Translation files for multi-language support
|
||||||
|
|
||||||
|
## Coding Standards
|
||||||
|
|
||||||
|
### PHP Code
|
||||||
|
|
||||||
|
- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) and [Symfony coding standards](https://symfony.com/doc/current/contributing/code/standards.html)
|
||||||
|
- Use type hints for all parameters and return types
|
||||||
|
- Always declare strict types: `declare(strict_types=1);` at the top of PHP files
|
||||||
|
- Use PHPDoc blocks for complex logic or when type information is needed
|
||||||
|
|
||||||
|
### TypeScript/JavaScript
|
||||||
|
|
||||||
|
- Use TypeScript for new frontend code
|
||||||
|
- Follow existing Stimulus controller patterns in `assets/controllers/`
|
||||||
|
- Use Bootstrap 5 components and utilities
|
||||||
|
- Leverage Hotwire Turbo for dynamic page updates
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
|
||||||
|
- Entities: Use descriptive names that reflect database models (e.g., `Part`, `StorageLocation`)
|
||||||
|
- Controllers: Suffix with `Controller` (e.g., `PartController`)
|
||||||
|
- Services: Descriptive names reflecting their purpose (e.g., `PartService`, `LabelGenerator`)
|
||||||
|
- Tests: Match the class being tested with `Test` suffix (e.g., `PartTest`, `PartControllerTest`)
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Install PHP dependencies: `composer install`
|
||||||
|
- Install JS dependencies: `yarn install`
|
||||||
|
- Build frontend assets: `yarn build` (production) or `yarn watch` (development)
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
- Create database: `php bin/console doctrine:database:create --env=dev`
|
||||||
|
- Run migrations: `php bin/console doctrine:migrations:migrate --env=dev`
|
||||||
|
- Load fixtures: `php bin/console partdb:fixtures:load -n --env=dev`
|
||||||
|
|
||||||
|
Or use Makefile shortcuts:
|
||||||
|
- `make dev-setup`: Complete development environment setup
|
||||||
|
- `make dev-reset`: Reset development environment (cache clear + migrate)
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
- Set up test environment: `make test-setup`
|
||||||
|
- Run all tests: `php bin/phpunit`
|
||||||
|
- Run specific test: `php bin/phpunit tests/Path/To/SpecificTest.php`
|
||||||
|
- Run tests with coverage: `php bin/phpunit --coverage-html var/coverage`
|
||||||
|
- Test environment uses SQLite by default for speed
|
||||||
|
|
||||||
|
### Static Analysis
|
||||||
|
|
||||||
|
- Run PHPStan: `composer phpstan` or `COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G vendor/bin/phpstan analyse src --level 5`
|
||||||
|
- PHPStan configuration is in `phpstan.dist.neon`
|
||||||
|
|
||||||
|
### Running the Application
|
||||||
|
|
||||||
|
- Development server: `symfony serve` (requires Symfony CLI)
|
||||||
|
- Or configure Apache/nginx to serve from `public/` directory
|
||||||
|
- Set `APP_ENV=dev` in `.env.local` for development mode
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Always sanitize user input
|
||||||
|
- Use Symfony's security component for authentication/authorization
|
||||||
|
- Check permissions using the permission system before allowing actions
|
||||||
|
- Never expose sensitive data in logs or error messages
|
||||||
|
- Use parameterized queries (Doctrine handles this automatically)
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- Use Doctrine query builder for complex queries instead of DQL when possible
|
||||||
|
- Lazy load relationships to avoid N+1 queries
|
||||||
|
- Cache results when appropriate using Symfony's cache component
|
||||||
|
- Use pagination for large result sets (DataTables integration available)
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
- Always create migrations for schema changes: `php bin/console make:migration`
|
||||||
|
- Review migration files before running them
|
||||||
|
- Use Doctrine annotations or attributes for entity mapping
|
||||||
|
- Follow existing entity patterns for relationships and lifecycle callbacks
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
- Use Stimulus controllers for interactive components
|
||||||
|
- Leverage Turbo for dynamic page updates without full page reloads
|
||||||
|
- Use Bootstrap 5 classes for styling
|
||||||
|
- Keep JavaScript modular and organized in controllers
|
||||||
|
- Use the translation system for user-facing strings
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
|
||||||
|
- Use translation keys, not hardcoded strings: `{{ 'part.info.title'|trans }}`
|
||||||
|
- Add new translation keys to `translations/` files
|
||||||
|
- Primary language is English (en)
|
||||||
|
- Translations are managed via Crowdin, but can be edited locally if needed
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
- Write unit tests for services and helpers
|
||||||
|
- Write functional tests for controllers
|
||||||
|
- Use fixtures for test data
|
||||||
|
- Tests should be isolated and not depend on execution order
|
||||||
|
- Mock external dependencies when appropriate
|
||||||
|
- Follow existing test patterns in the repository
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Creating an Entity
|
||||||
|
|
||||||
|
1. Create entity class in `src/Entity/` with Doctrine attributes
|
||||||
|
2. Generate migration: `php bin/console make:migration`
|
||||||
|
3. Review and run migration: `php bin/console doctrine:migrations:migrate`
|
||||||
|
4. Create repository if needed in `src/Repository/`
|
||||||
|
5. Add fixtures in `src/DataFixtures/` for testing
|
||||||
|
|
||||||
|
### Adding a Form
|
||||||
|
|
||||||
|
1. Create form type in `src/Form/`
|
||||||
|
2. Extend `AbstractType` and implement `buildForm()` and `configureOptions()`
|
||||||
|
3. Use in controller and render in Twig template
|
||||||
|
4. Follow existing form patterns for consistency
|
||||||
|
|
||||||
|
### Creating a Controller Action
|
||||||
|
|
||||||
|
1. Add method to appropriate controller in `src/Controller/`
|
||||||
|
2. Use route attributes for routing
|
||||||
|
3. Check permissions using security voters
|
||||||
|
4. Return Response or render Twig template
|
||||||
|
5. Add corresponding template in `templates/`
|
||||||
|
|
||||||
|
### Adding a Service
|
||||||
|
|
||||||
|
1. Create service class in `src/Services/`
|
||||||
|
2. Use dependency injection via constructor
|
||||||
|
3. Tag service in `config/services.yaml` if needed
|
||||||
|
4. Services are autowired by default
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- Part-DB uses fine-grained permissions - always check user permissions before actions
|
||||||
|
- Multi-language support is critical - use translation keys everywhere
|
||||||
|
- The application supports multiple database backends - write portable code
|
||||||
|
- Responsive design is important - test on mobile/tablet viewports
|
||||||
|
- Event system is used for logging changes - emit events when appropriate
|
||||||
|
- API Platform is integrated for REST API endpoints
|
||||||
|
|
||||||
|
## Multi-tenancy Considerations
|
||||||
|
|
||||||
|
- Part-DB is designed as a single-tenant application with multiple users
|
||||||
|
- User groups have different permission levels
|
||||||
|
- Always scope queries to respect user permissions
|
||||||
|
- Use the security context to get current user information
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Documentation](https://docs.part-db.de/)
|
||||||
|
- [Contributing Guide](CONTRIBUTING.md)
|
||||||
|
- [Symfony Documentation](https://symfony.com/doc/current/index.html)
|
||||||
|
- [Doctrine Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/current/)
|
||||||
|
- [Bootstrap 5 Documentation](https://getbootstrap.com/docs/5.1/)
|
||||||
|
- [Hotwire Documentation](https://hotwired.dev/)
|
||||||
19
.github/workflows/assets_artifact_build.yml
vendored
19
.github/workflows/assets_artifact_build.yml
vendored
|
|
@ -1,5 +1,8 @@
|
||||||
name: Build assets artifact
|
name: Build assets artifact
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
|
@ -19,7 +22,7 @@ jobs:
|
||||||
APP_ENV: prod
|
APP_ENV: prod
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
|
|
@ -34,12 +37,12 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-composer-
|
${{ runner.os }}-composer-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: composer install --prefer-dist --no-progress --no-dev -a
|
run: composer install --prefer-dist --no-progress --no-dev -a
|
||||||
|
|
@ -48,7 +51,7 @@ jobs:
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v5
|
||||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
|
@ -57,9 +60,9 @@ jobs:
|
||||||
${{ runner.os }}-yarn-
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '20'
|
||||||
|
|
||||||
- name: Install yarn dependencies
|
- name: Install yarn dependencies
|
||||||
run: yarn install
|
run: yarn install
|
||||||
|
|
@ -77,13 +80,13 @@ jobs:
|
||||||
run: zip -r /tmp/partdb_assets.zip public/build/ vendor/
|
run: zip -r /tmp/partdb_assets.zip public/build/ vendor/
|
||||||
|
|
||||||
- name: Upload assets artifact
|
- name: Upload assets artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: Only dependencies and built assets
|
name: Only dependencies and built assets
|
||||||
path: /tmp/partdb_assets.zip
|
path: /tmp/partdb_assets.zip
|
||||||
|
|
||||||
- name: Upload full artifact
|
- name: Upload full artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: Full Part-DB including dependencies and built assets
|
name: Full Part-DB including dependencies and built assets
|
||||||
path: /tmp/partdb_with_assets.zip
|
path: /tmp/partdb_with_assets.zip
|
||||||
|
|
|
||||||
7
.github/workflows/docker_build.yml
vendored
7
.github/workflows/docker_build.yml
vendored
|
|
@ -1,5 +1,8 @@
|
||||||
name: Docker Image Build
|
name: Docker Image Build
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
on:
|
on:
|
||||||
#schedule:
|
#schedule:
|
||||||
# - cron: '0 10 * * *' # everyday at 10am
|
# - cron: '0 10 * * *' # everyday at 10am
|
||||||
|
|
@ -17,7 +20,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
-
|
-
|
||||||
name: Docker meta
|
name: Docker meta
|
||||||
id: docker_meta
|
id: docker_meta
|
||||||
|
|
@ -73,4 +76,4 @@ jobs:
|
||||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
|
|
||||||
7
.github/workflows/docker_frankenphp.yml
vendored
7
.github/workflows/docker_frankenphp.yml
vendored
|
|
@ -1,5 +1,8 @@
|
||||||
name: Docker Image Build (FrankenPHP)
|
name: Docker Image Build (FrankenPHP)
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
on:
|
on:
|
||||||
#schedule:
|
#schedule:
|
||||||
# - cron: '0 10 * * *' # everyday at 10am
|
# - cron: '0 10 * * *' # everyday at 10am
|
||||||
|
|
@ -17,7 +20,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
-
|
-
|
||||||
name: Docker meta
|
name: Docker meta
|
||||||
id: docker_meta
|
id: docker_meta
|
||||||
|
|
@ -74,4 +77,4 @@ jobs:
|
||||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
|
|
||||||
17
.github/workflows/static_analysis.yml
vendored
17
.github/workflows/static_analysis.yml
vendored
|
|
@ -1,5 +1,8 @@
|
||||||
name: Static analysis
|
name: Static analysis
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
|
@ -16,7 +19,7 @@ jobs:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
|
|
@ -30,20 +33,20 @@ jobs:
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-composer-
|
${{ runner.os }}-composer-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: composer install --prefer-dist --no-progress
|
run: composer install --prefer-dist --no-progress
|
||||||
|
|
||||||
- name: Lint config files
|
- name: Lint config files
|
||||||
run: ./bin/console lint:yaml config --parse-tags
|
run: ./bin/console lint:yaml config --parse-tags
|
||||||
|
|
||||||
- name: Lint twig templates
|
- name: Lint twig templates
|
||||||
run: ./bin/console lint:twig templates --env=prod
|
run: ./bin/console lint:twig templates --env=prod
|
||||||
|
|
||||||
|
|
@ -53,13 +56,13 @@ jobs:
|
||||||
|
|
||||||
- name: Check dependencies for security
|
- name: Check dependencies for security
|
||||||
uses: symfonycorp/security-checker-action@v5
|
uses: symfonycorp/security-checker-action@v5
|
||||||
|
|
||||||
- name: Check doctrine mapping
|
- name: Check doctrine mapping
|
||||||
run: ./bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction
|
run: ./bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction
|
||||||
|
|
||||||
# Use the -d option to raise the max nesting level
|
# Use the -d option to raise the max nesting level
|
||||||
- name: Generate dev container
|
- name: Generate dev container
|
||||||
run: php -d xdebug.max_nesting_level=1000 ./bin/console cache:clear --env dev
|
run: php -d xdebug.max_nesting_level=1000 ./bin/console cache:clear --env dev
|
||||||
|
|
||||||
- name: Run PHPstan
|
- name: Run PHPstan
|
||||||
run: composer phpstan
|
run: composer phpstan
|
||||||
|
|
|
||||||
49
.github/workflows/tests.yml
vendored
49
.github/workflows/tests.yml
vendored
|
|
@ -1,5 +1,8 @@
|
||||||
name: PHPUnit Tests
|
name: PHPUnit Tests
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
|
@ -9,7 +12,7 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- '*'
|
- '*'
|
||||||
- "!l10n_*"
|
- "!l10n_*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
phpunit:
|
phpunit:
|
||||||
name: PHPUnit and coverage Test (PHP ${{ matrix.php-versions }}, ${{ matrix.db-type }})
|
name: PHPUnit and coverage Test (PHP ${{ matrix.php-versions }}, ${{ matrix.db-type }})
|
||||||
|
|
@ -18,7 +21,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
php-versions: [ '8.1', '8.2', '8.3', '8.4' ]
|
php-versions: ['8.2', '8.3', '8.4', '8.5' ]
|
||||||
db-type: [ 'mysql', 'sqlite', 'postgres' ]
|
db-type: [ 'mysql', 'sqlite', 'postgres' ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|
@ -43,7 +46,7 @@ jobs:
|
||||||
if: matrix.db-type == 'postgres'
|
if: matrix.db-type == 'postgres'
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
|
|
@ -52,7 +55,7 @@ jobs:
|
||||||
coverage: pcov
|
coverage: pcov
|
||||||
ini-values: xdebug.max_nesting_level=1000
|
ini-values: xdebug.max_nesting_level=1000
|
||||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
||||||
|
|
||||||
- name: Start MySQL
|
- name: Start MySQL
|
||||||
run: sudo systemctl start mysql.service
|
run: sudo systemctl start mysql.service
|
||||||
if: matrix.db-type == 'mysql'
|
if: matrix.db-type == 'mysql'
|
||||||
|
|
@ -71,73 +74,73 @@ jobs:
|
||||||
# mysql version: 5.7
|
# mysql version: 5.7
|
||||||
# mysql database: 'part-db'
|
# mysql database: 'part-db'
|
||||||
# mysql root password: '1234'
|
# mysql root password: '1234'
|
||||||
|
|
||||||
## Setup caches
|
## Setup caches
|
||||||
|
|
||||||
- name: Get Composer Cache Directory
|
- name: Get Composer Cache Directory
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-composer-
|
${{ runner.os }}-composer-
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
- name: Get yarn cache directory path
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v5
|
||||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-yarn-
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
- name: Install composer dependencies
|
- name: Install composer dependencies
|
||||||
run: composer install --prefer-dist --no-progress
|
run: composer install --prefer-dist --no-progress
|
||||||
|
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '20'
|
||||||
|
|
||||||
- name: Install yarn dependencies
|
- name: Install yarn dependencies
|
||||||
run: yarn install
|
run: yarn install
|
||||||
|
|
||||||
- name: Build frontend
|
- name: Build frontend
|
||||||
run: yarn build
|
run: yarn build
|
||||||
|
|
||||||
- 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' || matrix.db-type == 'postgres'
|
||||||
|
|
||||||
- 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
|
||||||
|
|
||||||
# Use our own custom fixtures loading command to circumvent some problems with reset the autoincrement values
|
# Use our own custom fixtures loading command to circumvent some problems with reset the autoincrement values
|
||||||
- name: Load fixtures
|
- name: Load fixtures
|
||||||
run: php bin/console --env test partdb:fixtures:load -n
|
run: php bin/console --env test partdb:fixtures:load -n
|
||||||
|
|
||||||
- name: Run PHPunit and generate coverage
|
- name: Run PHPunit and generate coverage
|
||||||
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@v5
|
||||||
with:
|
with:
|
||||||
env_vars: PHP_VERSION,DB_TYPE
|
env_vars: PHP_VERSION,DB_TYPE
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
|
|
||||||
- name: Test app:clean-attachments
|
- name: Test app:clean-attachments
|
||||||
run: php bin/console partdb:attachments:clean-unused -n
|
run: php bin/console partdb:attachments:clean-unused -n
|
||||||
|
|
||||||
- name: Test app:convert-bbcode
|
- name: Test app:convert-bbcode
|
||||||
run: php bin/console app:convert-bbcode -n
|
run: php bin/console app:convert-bbcode -n
|
||||||
|
|
||||||
- name: Test app:show-logs
|
- name: Test app:show-logs
|
||||||
run: php bin/console app:show-logs -n
|
run: php bin/console app:show-logs -n
|
||||||
|
|
||||||
|
|
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -2,6 +2,7 @@
|
||||||
/.env.local
|
/.env.local
|
||||||
/.env.local.php
|
/.env.local.php
|
||||||
/.env.*.local
|
/.env.*.local
|
||||||
|
/.env.local.bak
|
||||||
/config/secrets/prod/prod.decrypt.private.php
|
/config/secrets/prod/prod.decrypt.private.php
|
||||||
/public/bundles/
|
/public/bundles/
|
||||||
/var/
|
/var/
|
||||||
|
|
@ -41,9 +42,12 @@ yarn-error.log
|
||||||
|
|
||||||
###> phpunit/phpunit ###
|
###> phpunit/phpunit ###
|
||||||
/phpunit.xml
|
/phpunit.xml
|
||||||
.phpunit.result.cache
|
/.phpunit.cache/
|
||||||
###< phpunit/phpunit ###
|
###< phpunit/phpunit ###
|
||||||
|
|
||||||
###> phpstan/phpstan ###
|
###> phpstan/phpstan ###
|
||||||
phpstan.neon
|
phpstan.neon
|
||||||
###< phpstan/phpstan ###
|
###< phpstan/phpstan ###
|
||||||
|
|
||||||
|
.claude/
|
||||||
|
CLAUDE.md
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# How to contribute
|
# How to contribute
|
||||||
|
|
||||||
Thank you for consider to contribute to Part-DB!
|
Thank you for considering contributing to Part-DB!
|
||||||
Please read the text below, so your contributed content can be contributed easily to Part-DB.
|
Please read the text below, so your contributed content can be incorporated into Part-DB easily.
|
||||||
|
|
||||||
You can contribute to Part-DB in various ways:
|
You can contribute to Part-DB in various ways:
|
||||||
* Report bugs and request new features via [issues](https://github.com/Part-DB/Part-DB-server/issues)
|
* Report bugs and request new features via [issues](https://github.com/Part-DB/Part-DB-server/issues)
|
||||||
|
|
@ -18,38 +18,38 @@ Part-DB uses translation keys (e.g. part.info.title) that are sorted by their us
|
||||||
was translated in other languages (this is possible via the "Other languages" dropdown in the translation editor).
|
was translated in other languages (this is possible via the "Other languages" dropdown in the translation editor).
|
||||||
|
|
||||||
## Project structure
|
## Project structure
|
||||||
Part-DB uses symfony's recommended [project structure](https://symfony.com/doc/current/best_practices.html).
|
Part-DB uses Symfony's recommended [project structure](https://symfony.com/doc/current/best_practices.html).
|
||||||
Interesting folders are:
|
Interesting folders are:
|
||||||
* `public`: Everything in this directory will be publicy accessible via web. Use this folder to serve static images.
|
* `public`: Everything in this directory will be publicly accessible via web. Use this folder to serve static images.
|
||||||
* `assets`: The frontend assets are saved here. You can find the javascript and CSS code here.
|
* `assets`: The frontend assets are saved here. You can find the JavaScript and CSS code here.
|
||||||
* `src`: Part-DB's PHP code is saved here. Note that the sub directories are structured by the classes purposes (so use `Controller` Controllers, `Entities` for Database models, etc.)
|
* `src`: Part-DB's PHP code is saved here. Note that the subdirectories are structured by the classes' purposes (so use `Controller` for Controllers, `Entity` for Database models, etc.)
|
||||||
* `translations`: The translations used in Part-DB are saved here
|
* `translations`: The translations used in Part-DB are saved here.
|
||||||
* `templates`: The templates (HTML) that are used by Twig to render the different pages. Email templates are also saved here.
|
* `templates`: The templates (HTML) that are used by Twig to render the different pages. Email templates are also saved here.
|
||||||
* `tests/`: Tests that can be run by PHPunit.
|
* `tests/`: Tests that can be run by PHPUnit.
|
||||||
|
|
||||||
## Development environment
|
## Development environment
|
||||||
For setting up an development you will need to install PHP, composer, a database server (MySQL or MariaDB) and yarn (which needs an nodejs environment).
|
For setting up a development environment, you will need to install PHP, Composer, a database server (MySQL or MariaDB) and yarn (which needs a Node.js environment).
|
||||||
* Copy `.env` to `.env.local` and change `APP_ENV` to `APP_ENV=dev`. That way you will get development tools (symfony profiler) and other features that
|
* Copy `.env` to `.env.local` and change `APP_ENV` to `APP_ENV=dev`. That way you will get development tools (Symfony profiler) and other features that
|
||||||
will simplify development.
|
will simplify development.
|
||||||
* Run `composer install` (without -o) to install PHP dependencies and `yarn install` to install frontend dependencies
|
* Run `composer install` (without -o) to install PHP dependencies and `yarn install` to install frontend dependencies.
|
||||||
* Run `yarn watch`. The program will run in the background and compile the frontend files whenever you change something in the CSS or TypeScript files
|
* Run `yarn watch`. The program will run in the background and compile the frontend files whenever you change something in the CSS or TypeScript files.
|
||||||
* For running Part-DB it is recommended to use [Symfony CLI](https://symfony.com/download).
|
* For running Part-DB, it is recommended to use [Symfony CLI](https://symfony.com/download).
|
||||||
That way you can run a correct configured webserver with `symfony serve`
|
That way you can run a correctly configured webserver with `symfony serve`.
|
||||||
|
|
||||||
## Coding style
|
## Coding style
|
||||||
Code should follow the [PSR12-Standard](https://www.php-fig.org/psr/psr-12/) and symfony's [coding standards](https://symfony.com/doc/current/contributing/code/standards.html).
|
Code should follow the [PSR-12 Standard](https://www.php-fig.org/psr/psr-12/) and Symfony's [coding standards](https://symfony.com/doc/current/contributing/code/standards.html).
|
||||||
|
|
||||||
Part-DB uses [Easy Coding Standard](https://github.com/symplify/easy-coding-standard) to check and fix coding style violations:
|
Part-DB uses [Easy Coding Standard](https://github.com/symplify/easy-coding-standard) to check and fix coding style violations:
|
||||||
* To check your code for valid code style run `vendor/bin/ecs check src/`
|
* To check your code for valid code style, run `vendor/bin/ecs check src/`
|
||||||
* To fix violations run `vendor/bin/ecs check src/` (please checks afterwards if the code is valid afterwards)
|
* To fix violations, run `vendor/bin/ecs check src/ --fix` (please check afterwards if the code is still valid)
|
||||||
|
|
||||||
## GitHub actions
|
## GitHub actions
|
||||||
Part-DB uses GitHub actions to run various tests and checks on the code:
|
Part-DB uses GitHub Actions to run various tests and checks on the code:
|
||||||
* Yarn dependencies can compile
|
* Yarn dependencies can compile
|
||||||
* PHPunit tests run successful
|
* PHPUnit tests run successfully
|
||||||
* Config files, translations and templates has valid syntax
|
* Config files, translations, and templates have valid syntax
|
||||||
* Doctrine schema valid
|
* Doctrine schema is valid
|
||||||
* No known vulnerable dependecies are used
|
* No known vulnerable dependencies are used
|
||||||
* Static analysis successful (phpstan with `--level=2`)
|
* Static analysis is successful (phpstan with `--level=2`)
|
||||||
|
|
||||||
Further the code coverage of the PHPunit tests is determined and uploaded to [CodeCov](https://codecov.io/gh/Part-DB/Part-DB-server).
|
Further, the code coverage of the PHPUnit tests is determined and uploaded to [CodeCov](https://codecov.io/gh/Part-DB/Part-DB-server).
|
||||||
|
|
|
||||||
10
Dockerfile
10
Dockerfile
|
|
@ -1,5 +1,5 @@
|
||||||
ARG BASE_IMAGE=debian:bookworm-slim
|
ARG BASE_IMAGE=debian:bookworm-slim
|
||||||
ARG PHP_VERSION=8.3
|
ARG PHP_VERSION=8.4
|
||||||
|
|
||||||
FROM ${BASE_IMAGE} AS base
|
FROM ${BASE_IMAGE} AS base
|
||||||
ARG PHP_VERSION
|
ARG PHP_VERSION
|
||||||
|
|
@ -48,7 +48,7 @@ RUN apt-get update && apt-get -y install \
|
||||||
# Install node and yarn
|
# Install node and yarn
|
||||||
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
|
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
|
||||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
|
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
|
||||||
curl -sL https://deb.nodesource.com/setup_20.x | bash - && \
|
curl -sL https://deb.nodesource.com/setup_22.x | bash - && \
|
||||||
apt-get update && apt-get install -y \
|
apt-get update && apt-get install -y \
|
||||||
nodejs \
|
nodejs \
|
||||||
yarn \
|
yarn \
|
||||||
|
|
@ -119,12 +119,12 @@ realpath_cache_size=4096K
|
||||||
realpath_cache_ttl=600
|
realpath_cache_ttl=600
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Increase upload limit and enable preloading
|
# Increase upload limit and enable preloading (disabled for now, as it does not seem to work properly, and require prod env anyway)
|
||||||
COPY <<EOF /etc/php/${PHP_VERSION}/fpm/conf.d/partdb.ini
|
COPY <<EOF /etc/php/${PHP_VERSION}/fpm/conf.d/partdb.ini
|
||||||
upload_max_filesize=256M
|
upload_max_filesize=256M
|
||||||
post_max_size=300M
|
post_max_size=300M
|
||||||
opcache.preload_user=www-data
|
;opcache.preload_user=www-data
|
||||||
opcache.preload=/var/www/html/config/preload.php
|
;opcache.preload=/var/www/html/config/preload.php
|
||||||
log_limit=8096
|
log_limit=8096
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
FROM dunglas/frankenphp:1-php8.3 AS frankenphp_upstream
|
FROM dunglas/frankenphp:1-php8.4 AS frankenphp_upstream
|
||||||
|
|
||||||
RUN apt-get update && apt-get -y install \
|
RUN apt-get update && apt-get -y install \
|
||||||
curl \
|
curl \
|
||||||
|
|
@ -13,13 +13,33 @@ RUN apt-get update && apt-get -y install \
|
||||||
zip \
|
zip \
|
||||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*;
|
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*;
|
||||||
|
|
||||||
# Install node and yarn
|
RUN set -eux; \
|
||||||
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
|
# Prepare keyrings directory
|
||||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
|
mkdir -p /etc/apt/keyrings; \
|
||||||
curl -sL https://deb.nodesource.com/setup_20.x | bash - && \
|
\
|
||||||
apt-get update && apt-get install -y \
|
# Import Yarn GPG key
|
||||||
nodejs yarn \
|
curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg \
|
||||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
|
| tee /etc/apt/keyrings/yarn.gpg >/dev/null; \
|
||||||
|
chmod 644 /etc/apt/keyrings/yarn.gpg; \
|
||||||
|
\
|
||||||
|
# Add Yarn repo with signed-by
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian stable main" \
|
||||||
|
| tee /etc/apt/sources.list.d/yarn.list; \
|
||||||
|
\
|
||||||
|
# Run NodeSource setup script (unchanged)
|
||||||
|
curl -sL https://deb.nodesource.com/setup_22.x | bash -; \
|
||||||
|
\
|
||||||
|
# Install Node.js + Yarn
|
||||||
|
apt-get update; \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
nodejs \
|
||||||
|
yarn; \
|
||||||
|
\
|
||||||
|
# Cleanup
|
||||||
|
apt-get -y autoremove; \
|
||||||
|
apt-get clean autoclean; \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
||||||
# Install PHP
|
# Install PHP
|
||||||
RUN set -eux; \
|
RUN set -eux; \
|
||||||
|
|
|
||||||
91
Makefile
Normal file
91
Makefile
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# PartDB Makefile for Test Environment Management
|
||||||
|
|
||||||
|
.PHONY: help deps-install lint format format-check test coverage pre-commit all test-typecheck \
|
||||||
|
test-setup test-clean test-db-create test-db-migrate test-cache-clear test-fixtures test-run test-reset \
|
||||||
|
section-dev dev-setup dev-clean dev-db-create dev-db-migrate dev-cache-clear dev-warmup dev-reset
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
help: ## Show this help
|
||||||
|
@awk 'BEGIN {FS = ":.*##"}; /^[a-zA-Z0-9][a-zA-Z0-9_-]+:.*##/ {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
deps-install: ## Install PHP dependencies with unlimited memory
|
||||||
|
@echo "📦 Installing PHP dependencies..."
|
||||||
|
COMPOSER_MEMORY_LIMIT=-1 composer install
|
||||||
|
yarn install
|
||||||
|
@echo "✅ Dependencies installed"
|
||||||
|
|
||||||
|
# Complete test environment setup
|
||||||
|
test-setup: test-clean test-db-create test-db-migrate test-fixtures ## Complete test setup (clean, create DB, migrate, fixtures)
|
||||||
|
@echo "✅ Test environment setup complete!"
|
||||||
|
|
||||||
|
# Clean test environment
|
||||||
|
test-clean: ## Clean test cache and database files
|
||||||
|
@echo "🧹 Cleaning test environment..."
|
||||||
|
rm -rf var/cache/test
|
||||||
|
rm -f var/app_test.db
|
||||||
|
@echo "✅ Test environment cleaned"
|
||||||
|
|
||||||
|
# Create test database
|
||||||
|
test-db-create: ## Create test database (if not exists)
|
||||||
|
@echo "🗄️ Creating test database..."
|
||||||
|
-php bin/console doctrine:database:create --if-not-exists --env test || echo "⚠️ Database creation failed (expected for SQLite) - continuing..."
|
||||||
|
|
||||||
|
# Run database migrations for test environment
|
||||||
|
test-db-migrate: ## Run database migrations for test environment
|
||||||
|
@echo "🔄 Running database migrations..."
|
||||||
|
COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env test
|
||||||
|
|
||||||
|
# Clear test cache
|
||||||
|
test-cache-clear: ## Clear test cache
|
||||||
|
@echo "🗑️ Clearing test cache..."
|
||||||
|
rm -rf var/cache/test
|
||||||
|
@echo "✅ Test cache cleared"
|
||||||
|
|
||||||
|
# Load test fixtures
|
||||||
|
test-fixtures: ## Load test fixtures
|
||||||
|
@echo "📦 Loading test fixtures..."
|
||||||
|
php bin/console partdb:fixtures:load -n --env test
|
||||||
|
|
||||||
|
# Run PHPUnit tests
|
||||||
|
test-run: ## Run PHPUnit tests
|
||||||
|
@echo "🧪 Running tests..."
|
||||||
|
php bin/phpunit
|
||||||
|
|
||||||
|
# Quick test reset (clean + migrate + fixtures, skip DB creation)
|
||||||
|
test-reset: test-cache-clear test-db-migrate test-fixtures
|
||||||
|
@echo "✅ Test environment reset complete!"
|
||||||
|
|
||||||
|
test-typecheck: ## Run static analysis (PHPStan)
|
||||||
|
@echo "🧪 Running type checks..."
|
||||||
|
COMPOSER_MEMORY_LIMIT=-1 composer phpstan
|
||||||
|
|
||||||
|
# Development helpers
|
||||||
|
dev-setup: dev-clean dev-db-create dev-db-migrate dev-warmup ## Complete development setup (clean, create DB, migrate, warmup)
|
||||||
|
@echo "✅ Development environment setup complete!"
|
||||||
|
|
||||||
|
dev-clean: ## Clean development cache and database files
|
||||||
|
@echo "🧹 Cleaning development environment..."
|
||||||
|
rm -rf var/cache/dev
|
||||||
|
rm -f var/app_dev.db
|
||||||
|
@echo "✅ Development environment cleaned"
|
||||||
|
|
||||||
|
dev-db-create: ## Create development database (if not exists)
|
||||||
|
@echo "🗄️ Creating development database..."
|
||||||
|
-php bin/console doctrine:database:create --if-not-exists --env dev || echo "⚠️ Database creation failed (expected for SQLite) - continuing..."
|
||||||
|
|
||||||
|
dev-db-migrate: ## Run database migrations for development environment
|
||||||
|
@echo "🔄 Running database migrations..."
|
||||||
|
COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env dev
|
||||||
|
|
||||||
|
dev-cache-clear: ## Clear development cache
|
||||||
|
@echo "🗑️ Clearing development cache..."
|
||||||
|
rm -rf var/cache/dev
|
||||||
|
@echo "✅ Development cache cleared"
|
||||||
|
|
||||||
|
dev-warmup: ## Warm up development cache
|
||||||
|
@echo "🔥 Warming up development cache..."
|
||||||
|
COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G bin/console cache:warmup --env dev -n
|
||||||
|
|
||||||
|
dev-reset: dev-cache-clear dev-db-migrate ## Quick development reset (cache clear + migrate)
|
||||||
|
@echo "✅ Development environment reset complete!"
|
||||||
12
README.md
12
README.md
|
|
@ -3,7 +3,7 @@
|
||||||

|

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

|

|
||||||

|

|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
@ -29,8 +29,8 @@ If you want to test Part-DB without installing it, you can use [this](https://de
|
||||||
|
|
||||||
You can log in with username: *user* and password: *user*.
|
You can log in with username: *user* and password: *user*.
|
||||||
|
|
||||||
Every change to the master branch gets automatically deployed, so it represents the current development progress and is
|
Every change to the master branch gets automatically deployed, so it represents the current development progress and
|
||||||
may not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading
|
may not be completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading
|
||||||
the page
|
the page
|
||||||
for the first time.
|
for the first time.
|
||||||
|
|
||||||
|
|
@ -75,10 +75,10 @@ Part-DB is also used by small companies and universities for managing their inve
|
||||||
|
|
||||||
* A **web server** (like Apache2 or nginx) that is capable of
|
* A **web server** (like Apache2 or nginx) that is capable of
|
||||||
running [Symfony 6](https://symfony.com/doc/current/reference/requirements.html),
|
running [Symfony 6](https://symfony.com/doc/current/reference/requirements.html),
|
||||||
this includes a minimum PHP version of **PHP 8.1**
|
this includes a minimum PHP version of **PHP 8.2**
|
||||||
* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.4) database server, or **PostgreSQL** 10+ if you do not want to use SQLite.
|
* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.4) database server, or **PostgreSQL** 10+ if you do not want to use SQLite.
|
||||||
* Shell access to your server is highly recommended!
|
* Shell access to your server is highly recommended!
|
||||||
* For building the client-side assets **yarn** and **nodejs** (>= 18.0) is needed.
|
* For building the client-side assets **yarn** and **nodejs** (>= 20.0) is needed.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
@ -142,7 +142,7 @@ There you will find various methods to support development on a monthly or a one
|
||||||
|
|
||||||
## Built with
|
## Built with
|
||||||
|
|
||||||
* [Symfony 5](https://symfony.com/): The main framework used for the serverside PHP
|
* [Symfony 6](https://symfony.com/): The main framework used for the serverside PHP
|
||||||
* [Bootstrap 5](https://getbootstrap.com/) and [Bootswatch](https://bootswatch.com/): Used as website theme
|
* [Bootstrap 5](https://getbootstrap.com/) and [Bootswatch](https://bootswatch.com/): Used as website theme
|
||||||
* [Fontawesome](https://fontawesome.com/): Used as icon set
|
* [Fontawesome](https://fontawesome.com/): Used as icon set
|
||||||
* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend
|
* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
1.17.0
|
2.4.0
|
||||||
|
|
|
||||||
1
assets/ckeditor/emojis.json
Normal file
1
assets/ckeditor/emojis.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,66 +1,63 @@
|
||||||
/**
|
import {ClassicEditor} from 'ckeditor5'
|
||||||
* @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved.
|
import {Alignment} from 'ckeditor5';
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
import {Autoformat} from 'ckeditor5';
|
||||||
*/
|
import {Base64UploadAdapter} from 'ckeditor5';
|
||||||
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js';
|
import {BlockQuote} from 'ckeditor5';
|
||||||
import Alignment from '@ckeditor/ckeditor5-alignment/src/alignment.js';
|
import {Bold} from 'ckeditor5';
|
||||||
import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat.js';
|
import {Code} from 'ckeditor5';
|
||||||
import Base64UploadAdapter from '@ckeditor/ckeditor5-upload/src/adapters/base64uploadadapter.js';
|
import {CodeBlock} from 'ckeditor5';
|
||||||
import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote.js';
|
import {Essentials} from 'ckeditor5';
|
||||||
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js';
|
import {FindAndReplace} from 'ckeditor5';
|
||||||
import Code from '@ckeditor/ckeditor5-basic-styles/src/code.js';
|
import {FontBackgroundColor} from 'ckeditor5';
|
||||||
import CodeBlock from '@ckeditor/ckeditor5-code-block/src/codeblock.js';
|
import {FontColor} from 'ckeditor5';
|
||||||
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials.js';
|
import {FontFamily} from 'ckeditor5';
|
||||||
import FindAndReplace from '@ckeditor/ckeditor5-find-and-replace/src/findandreplace.js';
|
import {FontSize} from 'ckeditor5';
|
||||||
import FontBackgroundColor from '@ckeditor/ckeditor5-font/src/fontbackgroundcolor.js';
|
import {GeneralHtmlSupport} from 'ckeditor5';
|
||||||
import FontColor from '@ckeditor/ckeditor5-font/src/fontcolor.js';
|
import {Heading} from 'ckeditor5';
|
||||||
import FontFamily from '@ckeditor/ckeditor5-font/src/fontfamily.js';
|
import {Highlight} from 'ckeditor5';
|
||||||
import FontSize from '@ckeditor/ckeditor5-font/src/fontsize.js';
|
import {HorizontalLine} from 'ckeditor5';
|
||||||
import GeneralHtmlSupport from '@ckeditor/ckeditor5-html-support/src/generalhtmlsupport.js';
|
import {HtmlComment} from 'ckeditor5';
|
||||||
import Heading from '@ckeditor/ckeditor5-heading/src/heading.js';
|
import {HtmlEmbed} from 'ckeditor5';
|
||||||
import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight.js';
|
import {Image} from 'ckeditor5';
|
||||||
import HorizontalLine from '@ckeditor/ckeditor5-horizontal-line/src/horizontalline.js';
|
import {ImageResize} from 'ckeditor5';
|
||||||
import HtmlComment from '@ckeditor/ckeditor5-html-support/src/htmlcomment.js';
|
import {ImageStyle} from 'ckeditor5';
|
||||||
import HtmlEmbed from '@ckeditor/ckeditor5-html-embed/src/htmlembed.js';
|
import {ImageToolbar} from 'ckeditor5';
|
||||||
import Image from '@ckeditor/ckeditor5-image/src/image.js';
|
import {ImageUpload} from 'ckeditor5';
|
||||||
import ImageResize from '@ckeditor/ckeditor5-image/src/imageresize.js';
|
import {Indent} from 'ckeditor5';
|
||||||
import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle.js';
|
import {IndentBlock} from 'ckeditor5';
|
||||||
import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar.js';
|
import {Italic} from 'ckeditor5';
|
||||||
import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload.js';
|
import {Link} from 'ckeditor5';
|
||||||
import Indent from '@ckeditor/ckeditor5-indent/src/indent.js';
|
import {LinkImage} from 'ckeditor5';
|
||||||
import IndentBlock from '@ckeditor/ckeditor5-indent/src/indentblock.js';
|
import {List} from 'ckeditor5';
|
||||||
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic.js';
|
import {ListProperties} from 'ckeditor5';
|
||||||
import Link from '@ckeditor/ckeditor5-link/src/link.js';
|
import {Markdown} from 'ckeditor5';
|
||||||
import LinkImage from '@ckeditor/ckeditor5-link/src/linkimage.js';
|
import {MediaEmbed} from 'ckeditor5';
|
||||||
import List from '@ckeditor/ckeditor5-list/src/list.js';
|
import {MediaEmbedToolbar} from 'ckeditor5';
|
||||||
import ListProperties from '@ckeditor/ckeditor5-list/src/listproperties.js';
|
import {Paragraph} from 'ckeditor5';
|
||||||
import Markdown from '@ckeditor/ckeditor5-markdown-gfm/src/markdown.js';
|
import {PasteFromOffice} from 'ckeditor5';
|
||||||
import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed.js';
|
import {RemoveFormat} from 'ckeditor5';
|
||||||
import MediaEmbedToolbar from '@ckeditor/ckeditor5-media-embed/src/mediaembedtoolbar.js';
|
import {SourceEditing} from 'ckeditor5';
|
||||||
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph.js';
|
import {SpecialCharacters} from 'ckeditor5';
|
||||||
import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice.js';
|
import {SpecialCharactersArrows} from 'ckeditor5';
|
||||||
import RemoveFormat from '@ckeditor/ckeditor5-remove-format/src/removeformat.js';
|
import {SpecialCharactersCurrency} from 'ckeditor5';
|
||||||
import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting.js';
|
import {SpecialCharactersEssentials} from 'ckeditor5';
|
||||||
import SpecialCharacters from '@ckeditor/ckeditor5-special-characters/src/specialcharacters.js';
|
import {SpecialCharactersLatin} from 'ckeditor5';
|
||||||
import SpecialCharactersArrows from '@ckeditor/ckeditor5-special-characters/src/specialcharactersarrows.js';
|
import {SpecialCharactersMathematical} from 'ckeditor5';
|
||||||
import SpecialCharactersCurrency from '@ckeditor/ckeditor5-special-characters/src/specialcharacterscurrency.js';
|
import {SpecialCharactersText} from 'ckeditor5';
|
||||||
import SpecialCharactersEssentials from '@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials.js';
|
import {Strikethrough} from 'ckeditor5';
|
||||||
import SpecialCharactersLatin from '@ckeditor/ckeditor5-special-characters/src/specialcharacterslatin.js';
|
import {Subscript} from 'ckeditor5';
|
||||||
import SpecialCharactersMathematical from '@ckeditor/ckeditor5-special-characters/src/specialcharactersmathematical.js';
|
import {Superscript} from 'ckeditor5';
|
||||||
import SpecialCharactersText from '@ckeditor/ckeditor5-special-characters/src/specialcharacterstext.js';
|
import {Table} from 'ckeditor5';
|
||||||
import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough.js';
|
import {TableCaption} from 'ckeditor5';
|
||||||
import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js';
|
import {TableCellProperties} from 'ckeditor5';
|
||||||
import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js';
|
import {TableColumnResize} from 'ckeditor5';
|
||||||
import Table from '@ckeditor/ckeditor5-table/src/table.js';
|
import {TableProperties} from 'ckeditor5';
|
||||||
import TableCaption from '@ckeditor/ckeditor5-table/src/tablecaption.js';
|
import {TableToolbar} from 'ckeditor5';
|
||||||
import TableCellProperties from '@ckeditor/ckeditor5-table/src/tablecellproperties';
|
import {Underline} from 'ckeditor5';
|
||||||
import TableColumnResize from '@ckeditor/ckeditor5-table/src/tablecolumnresize.js';
|
import {WordCount} from 'ckeditor5';
|
||||||
import TableProperties from '@ckeditor/ckeditor5-table/src/tableproperties';
|
import {EditorWatchdog} from 'ckeditor5';
|
||||||
import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar.js';
|
|
||||||
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline.js';
|
|
||||||
import WordCount from '@ckeditor/ckeditor5-word-count/src/wordcount.js';
|
|
||||||
import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog.js';
|
|
||||||
import PartDBLabel from "./plugins/PartDBLabel/PartDBLabel";
|
import PartDBLabel from "./plugins/PartDBLabel/PartDBLabel";
|
||||||
|
import SpecialCharactersGreek from "./plugins/special_characters_emoji";
|
||||||
|
|
||||||
class Editor extends ClassicEditor {}
|
class Editor extends ClassicEditor {}
|
||||||
|
|
||||||
|
|
@ -122,7 +119,8 @@ Editor.builtinPlugins = [
|
||||||
Underline,
|
Underline,
|
||||||
WordCount,
|
WordCount,
|
||||||
|
|
||||||
PartDBLabel
|
PartDBLabel,
|
||||||
|
SpecialCharactersGreek
|
||||||
];
|
];
|
||||||
|
|
||||||
// Editor configuration.
|
// Editor configuration.
|
||||||
|
|
|
||||||
|
|
@ -2,68 +2,69 @@
|
||||||
* @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js';
|
import {ClassicEditor} from 'ckeditor5';
|
||||||
import Alignment from '@ckeditor/ckeditor5-alignment/src/alignment.js';
|
import {Alignment} from 'ckeditor5';
|
||||||
import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat.js';
|
import {Autoformat} from 'ckeditor5';
|
||||||
import Base64UploadAdapter from '@ckeditor/ckeditor5-upload/src/adapters/base64uploadadapter.js';
|
import {Base64UploadAdapter} from 'ckeditor5';
|
||||||
import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote.js';
|
import {BlockQuote} from 'ckeditor5';
|
||||||
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js';
|
import {Bold} from 'ckeditor5';
|
||||||
import Code from '@ckeditor/ckeditor5-basic-styles/src/code.js';
|
import {Code} from 'ckeditor5';
|
||||||
import CodeBlock from '@ckeditor/ckeditor5-code-block/src/codeblock.js';
|
import {CodeBlock} from 'ckeditor5';
|
||||||
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials.js';
|
import {Essentials} from 'ckeditor5';
|
||||||
import FindAndReplace from '@ckeditor/ckeditor5-find-and-replace/src/findandreplace.js';
|
import {FindAndReplace} from 'ckeditor5';
|
||||||
import FontBackgroundColor from '@ckeditor/ckeditor5-font/src/fontbackgroundcolor.js';
|
import {FontBackgroundColor} from 'ckeditor5';
|
||||||
import FontColor from '@ckeditor/ckeditor5-font/src/fontcolor.js';
|
import {FontColor} from 'ckeditor5';
|
||||||
import FontFamily from '@ckeditor/ckeditor5-font/src/fontfamily.js';
|
import {FontFamily} from 'ckeditor5';
|
||||||
import FontSize from '@ckeditor/ckeditor5-font/src/fontsize.js';
|
import {FontSize} from 'ckeditor5';
|
||||||
import GeneralHtmlSupport from '@ckeditor/ckeditor5-html-support/src/generalhtmlsupport.js';
|
import {GeneralHtmlSupport} from 'ckeditor5';
|
||||||
import Heading from '@ckeditor/ckeditor5-heading/src/heading.js';
|
import {Heading} from 'ckeditor5';
|
||||||
import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight.js';
|
import {Highlight} from 'ckeditor5';
|
||||||
import HorizontalLine from '@ckeditor/ckeditor5-horizontal-line/src/horizontalline.js';
|
import {HorizontalLine} from 'ckeditor5';
|
||||||
import HtmlComment from '@ckeditor/ckeditor5-html-support/src/htmlcomment.js';
|
import {HtmlComment} from 'ckeditor5';
|
||||||
import HtmlEmbed from '@ckeditor/ckeditor5-html-embed/src/htmlembed.js';
|
import {HtmlEmbed} from 'ckeditor5';
|
||||||
import Image from '@ckeditor/ckeditor5-image/src/image.js';
|
import {Image} from 'ckeditor5';
|
||||||
import ImageResize from '@ckeditor/ckeditor5-image/src/imageresize.js';
|
import {ImageResize} from 'ckeditor5';
|
||||||
import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle.js';
|
import {ImageStyle} from 'ckeditor5';
|
||||||
import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar.js';
|
import {ImageToolbar} from 'ckeditor5';
|
||||||
import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload.js';
|
import {ImageUpload} from 'ckeditor5';
|
||||||
import Indent from '@ckeditor/ckeditor5-indent/src/indent.js';
|
import {Indent} from 'ckeditor5';
|
||||||
import IndentBlock from '@ckeditor/ckeditor5-indent/src/indentblock.js';
|
import {IndentBlock} from 'ckeditor5';
|
||||||
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic.js';
|
import {Italic} from 'ckeditor5';
|
||||||
import Link from '@ckeditor/ckeditor5-link/src/link.js';
|
import {Link} from 'ckeditor5';
|
||||||
import LinkImage from '@ckeditor/ckeditor5-link/src/linkimage.js';
|
import {LinkImage} from 'ckeditor5';
|
||||||
import List from '@ckeditor/ckeditor5-list/src/list.js';
|
import {List} from 'ckeditor5';
|
||||||
import ListProperties from '@ckeditor/ckeditor5-list/src/listproperties.js';
|
import {ListProperties} from 'ckeditor5';
|
||||||
import Markdown from '@ckeditor/ckeditor5-markdown-gfm/src/markdown.js';
|
import {Markdown} from 'ckeditor5';
|
||||||
import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed.js';
|
import {MediaEmbed} from 'ckeditor5';
|
||||||
import MediaEmbedToolbar from '@ckeditor/ckeditor5-media-embed/src/mediaembedtoolbar.js';
|
import {MediaEmbedToolbar} from 'ckeditor5';
|
||||||
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph.js';
|
import {Paragraph} from 'ckeditor5';
|
||||||
import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice.js';
|
import {PasteFromOffice} from 'ckeditor5';
|
||||||
import RemoveFormat from '@ckeditor/ckeditor5-remove-format/src/removeformat.js';
|
import {RemoveFormat} from 'ckeditor5';
|
||||||
import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting.js';
|
import {SourceEditing} from 'ckeditor5';
|
||||||
import SpecialCharacters from '@ckeditor/ckeditor5-special-characters/src/specialcharacters.js';
|
import {SpecialCharacters} from 'ckeditor5';
|
||||||
import SpecialCharactersArrows from '@ckeditor/ckeditor5-special-characters/src/specialcharactersarrows.js';
|
import {SpecialCharactersArrows} from 'ckeditor5';
|
||||||
import SpecialCharactersCurrency from '@ckeditor/ckeditor5-special-characters/src/specialcharacterscurrency.js';
|
import {SpecialCharactersCurrency} from 'ckeditor5';
|
||||||
import SpecialCharactersEssentials from '@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials.js';
|
import {SpecialCharactersEssentials} from 'ckeditor5';
|
||||||
import SpecialCharactersLatin from '@ckeditor/ckeditor5-special-characters/src/specialcharacterslatin.js';
|
import {SpecialCharactersLatin} from 'ckeditor5';
|
||||||
import SpecialCharactersMathematical from '@ckeditor/ckeditor5-special-characters/src/specialcharactersmathematical.js';
|
import {SpecialCharactersMathematical} from 'ckeditor5';
|
||||||
import SpecialCharactersText from '@ckeditor/ckeditor5-special-characters/src/specialcharacterstext.js';
|
import {SpecialCharactersText} from 'ckeditor5';
|
||||||
import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough.js';
|
import {Strikethrough} from 'ckeditor5';
|
||||||
import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js';
|
import {Subscript} from 'ckeditor5';
|
||||||
import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js';
|
import {Superscript} from 'ckeditor5';
|
||||||
import Table from '@ckeditor/ckeditor5-table/src/table.js';
|
import {Table} from 'ckeditor5';
|
||||||
import TableCaption from '@ckeditor/ckeditor5-table/src/tablecaption.js';
|
import {TableCaption} from 'ckeditor5';
|
||||||
import TableCellProperties from '@ckeditor/ckeditor5-table/src/tablecellproperties';
|
import {TableCellProperties} from 'ckeditor5';
|
||||||
import TableColumnResize from '@ckeditor/ckeditor5-table/src/tablecolumnresize.js';
|
import {TableColumnResize} from 'ckeditor5';
|
||||||
import TableProperties from '@ckeditor/ckeditor5-table/src/tableproperties';
|
import {TableProperties} from 'ckeditor5';
|
||||||
import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar.js';
|
import {TableToolbar} from 'ckeditor5';
|
||||||
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline.js';
|
import {Underline} from 'ckeditor5';
|
||||||
import WordCount from '@ckeditor/ckeditor5-word-count/src/wordcount.js';
|
import {WordCount} from 'ckeditor5';
|
||||||
import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog.js';
|
import {EditorWatchdog} from 'ckeditor5';
|
||||||
import TodoList from '@ckeditor/ckeditor5-list/src/todolist';
|
import {TodoList} from 'ckeditor5';
|
||||||
|
|
||||||
import ExtendedMarkdown from "./plugins/extendedMarkdown.js";
|
import ExtendedMarkdown from "./plugins/extendedMarkdown.js";
|
||||||
import SpecialCharactersEmoji from "./plugins/special_characters_emoji";
|
import SpecialCharactersGreek from "./plugins/special_characters_emoji";
|
||||||
|
import {Mention, Emoji} from "ckeditor5";
|
||||||
|
|
||||||
class Editor extends ClassicEditor {}
|
class Editor extends ClassicEditor {}
|
||||||
|
|
||||||
|
|
@ -117,9 +118,11 @@ Editor.builtinPlugins = [
|
||||||
Underline,
|
Underline,
|
||||||
TodoList,
|
TodoList,
|
||||||
|
|
||||||
|
Mention, Emoji,
|
||||||
|
|
||||||
//Our own extensions
|
//Our own extensions
|
||||||
ExtendedMarkdown,
|
ExtendedMarkdown,
|
||||||
SpecialCharactersEmoji
|
SpecialCharactersGreek
|
||||||
];
|
];
|
||||||
|
|
||||||
// Editor configuration.
|
// Editor configuration.
|
||||||
|
|
@ -148,6 +151,7 @@ Editor.defaultConfig = {
|
||||||
'indent',
|
'indent',
|
||||||
'|',
|
'|',
|
||||||
'specialCharacters',
|
'specialCharacters',
|
||||||
|
"emoji",
|
||||||
'horizontalLine',
|
'horizontalLine',
|
||||||
'|',
|
'|',
|
||||||
'imageUpload',
|
'imageUpload',
|
||||||
|
|
|
||||||
|
|
@ -2,35 +2,36 @@
|
||||||
* @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js';
|
import {ClassicEditor} from 'ckeditor5';
|
||||||
import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat.js';
|
import {Autoformat} from 'ckeditor5';
|
||||||
import AutoLink from '@ckeditor/ckeditor5-link/src/autolink.js';
|
import {AutoLink} from 'ckeditor5';
|
||||||
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js';
|
import {Bold} from 'ckeditor5';
|
||||||
import Code from '@ckeditor/ckeditor5-basic-styles/src/code.js';
|
import {Code} from 'ckeditor5';
|
||||||
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials.js';
|
import {Essentials} from 'ckeditor5';
|
||||||
import FindAndReplace from '@ckeditor/ckeditor5-find-and-replace/src/findandreplace.js';
|
import {FindAndReplace} from 'ckeditor5';
|
||||||
import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight.js';
|
import {Highlight} from 'ckeditor5';
|
||||||
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic.js';
|
import {Italic} from 'ckeditor5';
|
||||||
import Link from '@ckeditor/ckeditor5-link/src/link.js';
|
import {Link} from 'ckeditor5';
|
||||||
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph.js';
|
import {Paragraph} from 'ckeditor5';
|
||||||
import RemoveFormat from '@ckeditor/ckeditor5-remove-format/src/removeformat.js';
|
import {RemoveFormat} from 'ckeditor5';
|
||||||
import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting.js';
|
import {SourceEditing} from 'ckeditor5';
|
||||||
import SpecialCharacters from '@ckeditor/ckeditor5-special-characters/src/specialcharacters.js';
|
import {SpecialCharacters} from 'ckeditor5';
|
||||||
import SpecialCharactersArrows from '@ckeditor/ckeditor5-special-characters/src/specialcharactersarrows.js';
|
import {SpecialCharactersArrows} from 'ckeditor5';
|
||||||
import SpecialCharactersCurrency from '@ckeditor/ckeditor5-special-characters/src/specialcharacterscurrency.js';
|
import {SpecialCharactersCurrency} from 'ckeditor5';
|
||||||
import SpecialCharactersEssentials from '@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials.js';
|
import {SpecialCharactersEssentials} from 'ckeditor5';
|
||||||
import SpecialCharactersLatin from '@ckeditor/ckeditor5-special-characters/src/specialcharacterslatin.js';
|
import {SpecialCharactersLatin} from 'ckeditor5';
|
||||||
import SpecialCharactersMathematical from '@ckeditor/ckeditor5-special-characters/src/specialcharactersmathematical.js';
|
import {SpecialCharactersMathematical} from 'ckeditor5';
|
||||||
import SpecialCharactersText from '@ckeditor/ckeditor5-special-characters/src/specialcharacterstext.js';
|
import {SpecialCharactersText} from 'ckeditor5';
|
||||||
import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough.js';
|
import {Strikethrough} from 'ckeditor5';
|
||||||
import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js';
|
import {Subscript} from 'ckeditor5';
|
||||||
import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js';
|
import {Superscript} from 'ckeditor5';
|
||||||
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline.js';
|
import {Underline} from 'ckeditor5';
|
||||||
import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog.js';
|
import {EditorWatchdog} from 'ckeditor5';
|
||||||
|
import {Mention, Emoji} from "ckeditor5";
|
||||||
|
|
||||||
import ExtendedMarkdownInline from "./plugins/extendedMarkdownInline";
|
import ExtendedMarkdownInline from "./plugins/extendedMarkdownInline";
|
||||||
import SingleLinePlugin from "./plugins/singleLine";
|
import SingleLinePlugin from "./plugins/singleLine";
|
||||||
import SpecialCharactersEmoji from "./plugins/special_characters_emoji";
|
import SpecialCharactersGreek from "./plugins/special_characters_emoji";
|
||||||
|
|
||||||
class Editor extends ClassicEditor {}
|
class Editor extends ClassicEditor {}
|
||||||
|
|
||||||
|
|
@ -62,7 +63,8 @@ Editor.builtinPlugins = [
|
||||||
|
|
||||||
ExtendedMarkdownInline,
|
ExtendedMarkdownInline,
|
||||||
SingleLinePlugin,
|
SingleLinePlugin,
|
||||||
SpecialCharactersEmoji
|
SpecialCharactersGreek,
|
||||||
|
Mention, Emoji
|
||||||
];
|
];
|
||||||
|
|
||||||
// Editor configuration.
|
// Editor configuration.
|
||||||
|
|
@ -81,6 +83,7 @@ Editor.defaultConfig = {
|
||||||
'link',
|
'link',
|
||||||
'code',
|
'code',
|
||||||
'specialCharacters',
|
'specialCharacters',
|
||||||
|
'emoji',
|
||||||
'|',
|
'|',
|
||||||
'undo',
|
'undo',
|
||||||
'redo',
|
'redo',
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import PartDBLabelEditing from "./PartDBLabelEditing";
|
||||||
|
|
||||||
import "./PartDBLabel.css";
|
import "./PartDBLabel.css";
|
||||||
|
|
||||||
import Plugin from "@ckeditor/ckeditor5-core/src/plugin";
|
import {Plugin} from "ckeditor5";
|
||||||
|
|
||||||
export default class PartDBLabel extends Plugin {
|
export default class PartDBLabel extends Plugin {
|
||||||
static get requires() {
|
static get requires() {
|
||||||
|
|
@ -32,4 +32,4 @@ export default class PartDBLabel extends Plugin {
|
||||||
static get pluginName() {
|
static get pluginName() {
|
||||||
return 'PartDBLabel';
|
return 'PartDBLabel';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Command from '@ckeditor/ckeditor5-core/src/command';
|
import {Command} from 'ckeditor5';
|
||||||
|
|
||||||
export default class PartDBLabelCommand extends Command {
|
export default class PartDBLabelCommand extends Command {
|
||||||
execute( { value } ) {
|
execute( { value } ) {
|
||||||
|
|
@ -47,4 +47,4 @@ export default class PartDBLabelCommand extends Command {
|
||||||
|
|
||||||
this.isEnabled = isAllowed;
|
this.isEnabled = isAllowed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
import {Plugin} from 'ckeditor5';
|
||||||
import PartDBLabelCommand from "./PartDBLabelCommand";
|
import PartDBLabelCommand from "./PartDBLabelCommand";
|
||||||
|
|
||||||
import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
|
import { toWidget } from 'ckeditor5';
|
||||||
import Widget from '@ckeditor/ckeditor5-widget/src/widget';
|
import {Widget} from 'ckeditor5';
|
||||||
|
|
||||||
export default class PartDBLabelEditing extends Plugin {
|
export default class PartDBLabelEditing extends Plugin {
|
||||||
static get requires() { // ADDED
|
static get requires() { // ADDED
|
||||||
|
|
@ -102,4 +102,4 @@ export default class PartDBLabelEditing extends Plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,15 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
import {Plugin} from 'ckeditor5';
|
||||||
|
|
||||||
require('./lang/de.js');
|
require('./lang/de.js');
|
||||||
|
require('./lang/en.js');
|
||||||
|
|
||||||
import { addListToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
|
import { addListToDropdown, createDropdown } from 'ckeditor5';
|
||||||
|
|
||||||
import Collection from '@ckeditor/ckeditor5-utils/src/collection';
|
import {Collection} from 'ckeditor5';
|
||||||
import Model from '@ckeditor/ckeditor5-ui/src/model';
|
import {UIModel} from 'ckeditor5';
|
||||||
|
|
||||||
export default class PartDBLabelUI extends Plugin {
|
export default class PartDBLabelUI extends Plugin {
|
||||||
init() {
|
init() {
|
||||||
|
|
@ -128,6 +129,8 @@ const PLACEHOLDERS = [
|
||||||
['[[BARCODE_QR]]', 'QR code linking to this element'],
|
['[[BARCODE_QR]]', 'QR code linking to this element'],
|
||||||
['[[BARCODE_C128]]', 'Code 128 barcode linking to this element'],
|
['[[BARCODE_C128]]', 'Code 128 barcode linking to this element'],
|
||||||
['[[BARCODE_C39]]', 'Code 39 barcode linking to this element'],
|
['[[BARCODE_C39]]', 'Code 39 barcode linking to this element'],
|
||||||
|
['[[BARCODE_C93]]', 'Code 93 barcode linking to this element'],
|
||||||
|
['[[BARCODE_DATAMATRIX]]', 'Datamatrix code linking to this element'],
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -149,18 +152,28 @@ const PLACEHOLDERS = [
|
||||||
function getDropdownItemsDefinitions(t) {
|
function getDropdownItemsDefinitions(t) {
|
||||||
const itemDefinitions = new Collection();
|
const itemDefinitions = new Collection();
|
||||||
|
|
||||||
|
let first = true;
|
||||||
|
|
||||||
for ( const group of PLACEHOLDERS) {
|
for ( const group of PLACEHOLDERS) {
|
||||||
|
|
||||||
//Add group header
|
//Add group header
|
||||||
itemDefinitions.add({
|
|
||||||
'type': 'separator',
|
//Skip separator for first group
|
||||||
model: new Model( {
|
if (!first) {
|
||||||
withText: true,
|
|
||||||
})
|
itemDefinitions.add({
|
||||||
});
|
'type': 'separator',
|
||||||
|
model: new UIModel( {
|
||||||
|
withText: true,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
itemDefinitions.add({
|
itemDefinitions.add({
|
||||||
type: 'button',
|
type: 'button',
|
||||||
model: new Model( {
|
model: new UIModel( {
|
||||||
label: t(group.label),
|
label: t(group.label),
|
||||||
withText: true,
|
withText: true,
|
||||||
isEnabled: false,
|
isEnabled: false,
|
||||||
|
|
@ -171,7 +184,7 @@ function getDropdownItemsDefinitions(t) {
|
||||||
for ( const entry of group.entries) {
|
for ( const entry of group.entries) {
|
||||||
const definition = {
|
const definition = {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
model: new Model( {
|
model: new UIModel( {
|
||||||
commandParam: entry[0],
|
commandParam: entry[0],
|
||||||
label: t(entry[1]),
|
label: t(entry[1]),
|
||||||
tooltip: entry[0],
|
tooltip: entry[0],
|
||||||
|
|
@ -185,4 +198,4 @@ function getDropdownItemsDefinitions(t) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return itemDefinitions;
|
return itemDefinitions;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,9 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Make sure that the global object is defined. If not, define it.
|
import {add} from "ckeditor5";
|
||||||
window.CKEDITOR_TRANSLATIONS = window.CKEDITOR_TRANSLATIONS || {};
|
|
||||||
|
|
||||||
// Make sure that the dictionary for Polish translations exist.
|
add( "de", {
|
||||||
window.CKEDITOR_TRANSLATIONS[ 'de' ] = window.CKEDITOR_TRANSLATIONS[ 'de' ] || {};
|
|
||||||
window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary = window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary || {};
|
|
||||||
|
|
||||||
// Extend the dictionary for Polish translations with your translations:
|
|
||||||
Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
|
|
||||||
'Label Placeholder': 'Label Platzhalter',
|
'Label Placeholder': 'Label Platzhalter',
|
||||||
'Part': 'Bauteil',
|
'Part': 'Bauteil',
|
||||||
|
|
||||||
|
|
@ -69,6 +63,8 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
|
||||||
'QR code linking to this element': 'QR Code verknüpft mit diesem Element',
|
'QR code linking to this element': 'QR Code verknüpft mit diesem Element',
|
||||||
'Code 128 barcode linking to this element': 'Code 128 Barcode verknüpft mit diesem Element',
|
'Code 128 barcode linking to this element': 'Code 128 Barcode verknüpft mit diesem Element',
|
||||||
'Code 39 barcode linking to this element': 'Code 39 Barcode verknüpft mit diesem Element',
|
'Code 39 barcode linking to this element': 'Code 39 Barcode verknüpft mit diesem Element',
|
||||||
|
'Code 93 barcode linking to this element': 'Code 93 Barcode verknüpft mit diesem Element',
|
||||||
|
'Datamatrix code linking to this element': 'Datamatrix Code verknüpft mit diesem Element',
|
||||||
|
|
||||||
'Location ID': 'Lagerort ID',
|
'Location ID': 'Lagerort ID',
|
||||||
'Name': 'Name',
|
'Name': 'Name',
|
||||||
|
|
@ -86,5 +82,4 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
|
||||||
'Instance name': 'Instanzname',
|
'Instance name': 'Instanzname',
|
||||||
'Target type': 'Zieltyp',
|
'Target type': 'Zieltyp',
|
||||||
'URL of this Part-DB instance': 'URL dieser Part-DB Instanz',
|
'URL of this Part-DB instance': 'URL dieser Part-DB Instanz',
|
||||||
|
});
|
||||||
} );
|
|
||||||
|
|
|
||||||
84
assets/ckeditor/plugins/PartDBLabel/lang/en.js
Normal file
84
assets/ckeditor/plugins/PartDBLabel/lang/en.js
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {add} from "ckeditor5";
|
||||||
|
|
||||||
|
add( "en", {
|
||||||
|
'Label Placeholder': 'Label placeholder',
|
||||||
|
'Part': 'Part',
|
||||||
|
|
||||||
|
'Database ID': 'Database ID',
|
||||||
|
'Part name': 'Part name',
|
||||||
|
'Category': 'Category',
|
||||||
|
'Category (Full path)': 'Category (full path)',
|
||||||
|
'Manufacturer': 'Manufacturer',
|
||||||
|
'Manufacturer (Full path)': 'Manufacturer (full path)',
|
||||||
|
'Footprint': 'Footprint',
|
||||||
|
'Footprint (Full path)': 'Footprint (full path)',
|
||||||
|
'Mass': 'Mass',
|
||||||
|
'Manufacturer Product Number (MPN)': 'Manufacturer Product Number (MPN)',
|
||||||
|
'Internal Part Number (IPN)': 'Internal Part Number (IPN)',
|
||||||
|
'Tags': 'Tags',
|
||||||
|
'Manufacturing status': 'Manufacturing status',
|
||||||
|
'Description': 'Description',
|
||||||
|
'Description (plain text)': 'Description (plain text)',
|
||||||
|
'Comment': 'Comment',
|
||||||
|
'Comment (plain text)': 'Comment (plain text)',
|
||||||
|
'Last modified datetime': 'Last modified datetime',
|
||||||
|
'Creation datetime': 'Creation datetime',
|
||||||
|
'IPN as QR code': 'IPN as QR code',
|
||||||
|
'IPN as Code 128 barcode': 'IPN as Code 128 barcode',
|
||||||
|
'IPN as Code 39 barcode': 'IPN as Code 39 barcode',
|
||||||
|
|
||||||
|
'Lot ID': 'Lot ID',
|
||||||
|
'Lot name': 'Lot name',
|
||||||
|
'Lot comment': 'Lot comment',
|
||||||
|
'Lot expiration date': 'Lot expiration date',
|
||||||
|
'Lot amount': 'Lot amount',
|
||||||
|
'Storage location': 'Storage location',
|
||||||
|
'Storage location (Full path)': 'Storage location (full path)',
|
||||||
|
'Full name of the lot owner': 'Full name of the lot owner',
|
||||||
|
'Username of the lot owner': 'Username of the lot owner',
|
||||||
|
|
||||||
|
'Barcodes': 'Barcodes',
|
||||||
|
'Content of the 1D barcodes (like Code 39)': 'Content of the 1D barcodes (like Code 39)',
|
||||||
|
'Content of the 2D barcodes (QR codes)': 'Content of the 2D barcodes (QR codes)',
|
||||||
|
'QR code linking to this element': 'QR code linking to this element',
|
||||||
|
'Code 128 barcode linking to this element': 'Code 128 barcode linking to this element',
|
||||||
|
'Code 39 barcode linking to this element': 'Code 39 barcode linking to this element',
|
||||||
|
'Code 93 barcode linking to this element': 'Code 93 barcode linking to this element',
|
||||||
|
'Datamatrix code linking to this element': 'Datamatrix code linking to this element',
|
||||||
|
|
||||||
|
'Location ID': 'Location ID',
|
||||||
|
'Name': 'Name',
|
||||||
|
'Full path': 'Full path',
|
||||||
|
'Parent name': 'Parent name',
|
||||||
|
'Parent full path': 'Parent full path',
|
||||||
|
'Full name of the location owner': 'Full name of the location owner',
|
||||||
|
'Username of the location owner': 'Username of the location owner',
|
||||||
|
|
||||||
|
'Username': 'Username',
|
||||||
|
'Username (including name)': 'Username (including name)',
|
||||||
|
'Current datetime': 'Current datetime',
|
||||||
|
'Current date': 'Current date',
|
||||||
|
'Current time': 'Current time',
|
||||||
|
'Instance name': 'Instance name',
|
||||||
|
'Target type': 'Target type',
|
||||||
|
'URL of this Part-DB instance': 'URL of this Part-DB instance',
|
||||||
|
} );
|
||||||
|
|
@ -17,8 +17,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Plugin } from 'ckeditor5/src/core';
|
import { Plugin, MarkdownGfmDataProcessor } from 'ckeditor5';
|
||||||
import GFMDataProcessor from '@ckeditor/ckeditor5-markdown-gfm/src/gfmdataprocessor';
|
|
||||||
|
|
||||||
const ALLOWED_TAGS = [
|
const ALLOWED_TAGS = [
|
||||||
//Common elements
|
//Common elements
|
||||||
|
|
@ -34,7 +33,6 @@ const ALLOWED_TAGS = [
|
||||||
|
|
||||||
//Block elements
|
//Block elements
|
||||||
'span',
|
'span',
|
||||||
'p',
|
|
||||||
'img',
|
'img',
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -57,7 +55,7 @@ export default class ExtendedMarkdown extends Plugin {
|
||||||
constructor( editor ) {
|
constructor( editor ) {
|
||||||
super( editor );
|
super( editor );
|
||||||
|
|
||||||
editor.data.processor = new GFMDataProcessor( editor.data.viewDocument );
|
editor.data.processor = new MarkdownGfmDataProcessor( editor.data.viewDocument );
|
||||||
for (const tag of ALLOWED_TAGS) {
|
for (const tag of ALLOWED_TAGS) {
|
||||||
editor.data.processor.keepHtml(tag);
|
editor.data.processor.keepHtml(tag);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Plugin } from 'ckeditor5/src/core';
|
import {Plugin} from 'ckeditor5';
|
||||||
import GFMDataProcessor from '@ckeditor/ckeditor5-markdown-gfm/src/gfmdataprocessor';
|
import {MarkdownGfmDataProcessor} from 'ckeditor5';
|
||||||
|
|
||||||
const ALLOWED_TAGS = [
|
const ALLOWED_TAGS = [
|
||||||
//Common elements
|
//Common elements
|
||||||
|
|
@ -46,7 +46,7 @@ export default class ExtendedMarkdownInline extends Plugin {
|
||||||
constructor( editor ) {
|
constructor( editor ) {
|
||||||
super( editor );
|
super( editor );
|
||||||
|
|
||||||
editor.data.processor = new GFMDataProcessor( editor.data.viewDocument );
|
editor.data.processor = new MarkdownGfmDataProcessor( editor.data.viewDocument );
|
||||||
for (const tag of ALLOWED_TAGS) {
|
for (const tag of ALLOWED_TAGS) {
|
||||||
editor.data.processor.keepHtml(tag);
|
editor.data.processor.keepHtml(tag);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
import {Plugin} from 'ckeditor5';
|
||||||
|
|
||||||
export default class SingleLinePlugin extends Plugin {
|
export default class SingleLinePlugin extends Plugin {
|
||||||
init() {
|
init() {
|
||||||
|
|
@ -42,7 +42,7 @@ export default class SingleLinePlugin extends Plugin {
|
||||||
//We can not use the dataTransfer.setData method because the old object is somehow protected
|
//We can not use the dataTransfer.setData method because the old object is somehow protected
|
||||||
data.dataTransfer = new DataTransfer();
|
data.dataTransfer = new DataTransfer();
|
||||||
data.dataTransfer.setData("text", cleaned);
|
data.dataTransfer.setData("text", cleaned);
|
||||||
|
|
||||||
}, { priority: 'high' } );
|
}, { priority: 'high' } );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,12 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import SpecialCharacters from '@ckeditor/ckeditor5-special-characters/src/specialcharacters';
|
import SpecialCharacters from 'ckeditor5';
|
||||||
import SpecialCharactersEssentials from '@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials';
|
import SpecialCharactersEssentials from 'ckeditor5';
|
||||||
|
|
||||||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
import {Plugin} from 'ckeditor5';
|
||||||
|
|
||||||
const emoji = require('emoji.json');
|
export default class SpecialCharactersGreek extends Plugin {
|
||||||
|
|
||||||
export default class SpecialCharactersEmoji extends Plugin {
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
const editor = this.editor;
|
const editor = this.editor;
|
||||||
|
|
@ -32,9 +30,6 @@ export default class SpecialCharactersEmoji extends Plugin {
|
||||||
|
|
||||||
//Add greek characters to special characters
|
//Add greek characters to special characters
|
||||||
specialCharsPlugin.addItems('Greek', this.getGreek());
|
specialCharsPlugin.addItems('Greek', this.getGreek());
|
||||||
|
|
||||||
//Add Emojis to special characters
|
|
||||||
specialCharsPlugin.addItems('Emoji', this.getEmojis());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getGreek() {
|
getGreek() {
|
||||||
|
|
@ -96,14 +91,4 @@ export default class SpecialCharactersEmoji extends Plugin {
|
||||||
{ title: 'san', character: 'Ϻ' },
|
{ title: 'san', character: 'Ϻ' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
getEmojis() {
|
|
||||||
//Map our emoji data to the format the plugin expects
|
|
||||||
return emoji.map(emoji => {
|
|
||||||
return {
|
|
||||||
title: emoji.name,
|
|
||||||
character: emoji.char
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
359
assets/controllers/bulk_import_controller.js
Normal file
359
assets/controllers/bulk_import_controller.js
Normal file
|
|
@ -0,0 +1,359 @@
|
||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
import { generateCsrfHeaders } from "./csrf_protection_controller"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["progressBar", "progressText"]
|
||||||
|
static values = {
|
||||||
|
jobId: Number,
|
||||||
|
partId: Number,
|
||||||
|
researchUrl: String,
|
||||||
|
researchAllUrl: String,
|
||||||
|
markCompletedUrl: String,
|
||||||
|
markSkippedUrl: String,
|
||||||
|
markPendingUrl: String
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
// Auto-refresh progress if job is in progress
|
||||||
|
if (this.hasProgressBarTarget) {
|
||||||
|
this.startProgressUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore scroll position after page reload (if any)
|
||||||
|
this.restoreScrollPosition()
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeaders() {
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add CSRF headers if available
|
||||||
|
const form = document.querySelector('form')
|
||||||
|
if (form) {
|
||||||
|
const csrfHeaders = generateCsrfHeaders(form)
|
||||||
|
Object.assign(headers, csrfHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchWithErrorHandling(url, options = {}, timeout = 30000) {
|
||||||
|
const controller = new AbortController()
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
...options,
|
||||||
|
headers: { ...this.getHeaders(), ...options.headers },
|
||||||
|
signal: controller.signal
|
||||||
|
})
|
||||||
|
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text()
|
||||||
|
throw new Error(`Server error (${response.status}): ${errorText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json()
|
||||||
|
} catch (error) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error('Request timed out. Please try again.')
|
||||||
|
} else if (error.message.includes('Failed to fetch')) {
|
||||||
|
throw new Error('Network error. Please check your connection and try again.')
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
if (this.progressInterval) {
|
||||||
|
clearInterval(this.progressInterval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startProgressUpdates() {
|
||||||
|
// Progress updates are handled via page reload for better reliability
|
||||||
|
// No need for periodic updates since state changes trigger page refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreScrollPosition() {
|
||||||
|
const savedPosition = sessionStorage.getItem('bulkImportScrollPosition')
|
||||||
|
if (savedPosition) {
|
||||||
|
// Restore scroll position after a small delay to ensure page is fully loaded
|
||||||
|
setTimeout(() => {
|
||||||
|
window.scrollTo(0, parseInt(savedPosition))
|
||||||
|
// Clear the saved position so it doesn't interfere with normal navigation
|
||||||
|
sessionStorage.removeItem('bulkImportScrollPosition')
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async markCompleted(event) {
|
||||||
|
const partId = event.currentTarget.dataset.partId
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = this.markCompletedUrlValue.replace('__PART_ID__', partId)
|
||||||
|
const data = await this.fetchWithErrorHandling(url, { method: 'POST' })
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
this.updateProgressDisplay(data)
|
||||||
|
this.markRowAsCompleted(partId)
|
||||||
|
|
||||||
|
if (data.job_completed) {
|
||||||
|
this.showJobCompletedMessage()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.showErrorMessage(data.error || 'Failed to mark part as completed')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error marking part as completed:', error)
|
||||||
|
this.showErrorMessage(error.message || 'Failed to mark part as completed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async markSkipped(event) {
|
||||||
|
const partId = event.currentTarget.dataset.partId
|
||||||
|
const reason = prompt('Reason for skipping (optional):') || ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = this.markSkippedUrlValue.replace('__PART_ID__', partId)
|
||||||
|
const data = await this.fetchWithErrorHandling(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ reason })
|
||||||
|
})
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
this.updateProgressDisplay(data)
|
||||||
|
this.markRowAsSkipped(partId)
|
||||||
|
} else {
|
||||||
|
this.showErrorMessage(data.error || 'Failed to mark part as skipped')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error marking part as skipped:', error)
|
||||||
|
this.showErrorMessage(error.message || 'Failed to mark part as skipped')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async markPending(event) {
|
||||||
|
const partId = event.currentTarget.dataset.partId
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = this.markPendingUrlValue.replace('__PART_ID__', partId)
|
||||||
|
const data = await this.fetchWithErrorHandling(url, { method: 'POST' })
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
this.updateProgressDisplay(data)
|
||||||
|
this.markRowAsPending(partId)
|
||||||
|
} else {
|
||||||
|
this.showErrorMessage(data.error || 'Failed to mark part as pending')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error marking part as pending:', error)
|
||||||
|
this.showErrorMessage(error.message || 'Failed to mark part as pending')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgressDisplay(data) {
|
||||||
|
if (this.hasProgressBarTarget) {
|
||||||
|
this.progressBarTarget.style.width = `${data.progress}%`
|
||||||
|
this.progressBarTarget.setAttribute('aria-valuenow', data.progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasProgressTextTarget) {
|
||||||
|
this.progressTextTarget.textContent = `${data.completed_count} / ${data.total_count} completed`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
markRowAsCompleted(partId) {
|
||||||
|
// Save scroll position and refresh page to show updated state
|
||||||
|
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
markRowAsSkipped(partId) {
|
||||||
|
// Save scroll position and refresh page to show updated state
|
||||||
|
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
markRowAsPending(partId) {
|
||||||
|
// Save scroll position and refresh page to show updated state
|
||||||
|
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
showJobCompletedMessage() {
|
||||||
|
const alert = document.createElement('div')
|
||||||
|
alert.className = 'alert alert-success alert-dismissible fade show'
|
||||||
|
alert.innerHTML = `
|
||||||
|
<i class="fas fa-check-circle"></i>
|
||||||
|
Job completed! All parts have been processed.
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
`
|
||||||
|
|
||||||
|
const container = document.querySelector('.card-body')
|
||||||
|
container.insertBefore(alert, container.firstChild)
|
||||||
|
}
|
||||||
|
|
||||||
|
async researchPart(event) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
const partId = event.currentTarget.dataset.partId
|
||||||
|
const spinner = event.currentTarget.querySelector(`[data-research-spinner="${partId}"]`)
|
||||||
|
const button = event.currentTarget
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
if (spinner) {
|
||||||
|
spinner.style.display = 'inline-block'
|
||||||
|
}
|
||||||
|
button.disabled = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = this.researchUrlValue.replace('__PART_ID__', partId)
|
||||||
|
const controller = new AbortController()
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 30000) // 30 second timeout
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this.getHeaders(),
|
||||||
|
signal: controller.signal
|
||||||
|
})
|
||||||
|
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text()
|
||||||
|
throw new Error(`Server error (${response.status}): ${errorText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
this.showSuccessMessage(`Research completed for part. Found ${data.results_count} results.`)
|
||||||
|
// Save scroll position and reload to show updated results
|
||||||
|
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
|
||||||
|
window.location.reload()
|
||||||
|
} else {
|
||||||
|
this.showErrorMessage(data.error || 'Research failed')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error researching part:', error)
|
||||||
|
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
this.showErrorMessage('Research timed out. Please try again.')
|
||||||
|
} else if (error.message.includes('Failed to fetch')) {
|
||||||
|
this.showErrorMessage('Network error. Please check your connection and try again.')
|
||||||
|
} else {
|
||||||
|
this.showErrorMessage(error.message || 'Research failed due to an unexpected error')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// Hide loading state
|
||||||
|
if (spinner) {
|
||||||
|
spinner.style.display = 'none'
|
||||||
|
}
|
||||||
|
button.disabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async researchAllParts(event) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
const spinner = document.getElementById('research-all-spinner')
|
||||||
|
const button = event.currentTarget
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
if (spinner) {
|
||||||
|
spinner.style.display = 'inline-block'
|
||||||
|
}
|
||||||
|
button.disabled = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const controller = new AbortController()
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 120000) // 2 minute timeout for bulk operations
|
||||||
|
|
||||||
|
const response = await fetch(this.researchAllUrlValue, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this.getHeaders(),
|
||||||
|
signal: controller.signal
|
||||||
|
})
|
||||||
|
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text()
|
||||||
|
throw new Error(`Server error (${response.status}): ${errorText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
this.showSuccessMessage(`Research completed for ${data.researched_count} parts.`)
|
||||||
|
// Save scroll position and reload to show updated results
|
||||||
|
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
|
||||||
|
window.location.reload()
|
||||||
|
} else {
|
||||||
|
this.showErrorMessage(data.error || 'Bulk research failed')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error researching all parts:', error)
|
||||||
|
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
this.showErrorMessage('Bulk research timed out. This may happen with large batches. Please try again or process smaller batches.')
|
||||||
|
} else if (error.message.includes('Failed to fetch')) {
|
||||||
|
this.showErrorMessage('Network error. Please check your connection and try again.')
|
||||||
|
} else {
|
||||||
|
this.showErrorMessage(error.message || 'Bulk research failed due to an unexpected error')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// Hide loading state
|
||||||
|
if (spinner) {
|
||||||
|
spinner.style.display = 'none'
|
||||||
|
}
|
||||||
|
button.disabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showSuccessMessage(message) {
|
||||||
|
this.showToast('success', message)
|
||||||
|
}
|
||||||
|
|
||||||
|
showErrorMessage(message) {
|
||||||
|
this.showToast('error', message)
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast(type, message) {
|
||||||
|
// Create a simple alert that doesn't disrupt layout
|
||||||
|
const alertId = 'alert-' + Date.now()
|
||||||
|
const iconClass = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-triangle'
|
||||||
|
const alertClass = type === 'success' ? 'alert-success' : 'alert-danger'
|
||||||
|
|
||||||
|
const alertHTML = `
|
||||||
|
<div class="alert ${alertClass} alert-dismissible fade show position-fixed"
|
||||||
|
style="top: 20px; right: 20px; z-index: 9999; max-width: 400px;"
|
||||||
|
id="${alertId}">
|
||||||
|
<i class="fas ${iconClass} me-2"></i>
|
||||||
|
${message}
|
||||||
|
<button type="button" class="btn-close" onclick="this.parentElement.remove()" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
// Add alert to body
|
||||||
|
document.body.insertAdjacentHTML('beforeend', alertHTML)
|
||||||
|
|
||||||
|
// Auto-remove after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
const alertElement = document.getElementById(alertId)
|
||||||
|
if (alertElement) {
|
||||||
|
alertElement.remove()
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
92
assets/controllers/bulk_job_manage_controller.js
Normal file
92
assets/controllers/bulk_job_manage_controller.js
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
import { generateCsrfHeaders } from "./csrf_protection_controller"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static values = {
|
||||||
|
deleteUrl: String,
|
||||||
|
stopUrl: String,
|
||||||
|
deleteConfirmMessage: String,
|
||||||
|
stopConfirmMessage: String
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
// Controller initialized
|
||||||
|
}
|
||||||
|
getHeaders() {
|
||||||
|
const headers = {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add CSRF headers if available
|
||||||
|
const form = document.querySelector('form')
|
||||||
|
if (form) {
|
||||||
|
const csrfHeaders = generateCsrfHeaders(form)
|
||||||
|
Object.assign(headers, csrfHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
async deleteJob(event) {
|
||||||
|
const jobId = event.currentTarget.dataset.jobId
|
||||||
|
const confirmMessage = this.deleteConfirmMessageValue || 'Are you sure you want to delete this job?'
|
||||||
|
|
||||||
|
if (confirm(confirmMessage)) {
|
||||||
|
try {
|
||||||
|
const deleteUrl = this.deleteUrlValue.replace('__JOB_ID__', jobId)
|
||||||
|
|
||||||
|
const response = await fetch(deleteUrl, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: this.getHeaders()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text()
|
||||||
|
throw new Error(`HTTP ${response.status}: ${errorText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
location.reload()
|
||||||
|
} else {
|
||||||
|
alert('Error deleting job: ' + (data.error || 'Unknown error'))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting job:', error)
|
||||||
|
alert('Error deleting job: ' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopJob(event) {
|
||||||
|
const jobId = event.currentTarget.dataset.jobId
|
||||||
|
const confirmMessage = this.stopConfirmMessageValue || 'Are you sure you want to stop this job?'
|
||||||
|
|
||||||
|
if (confirm(confirmMessage)) {
|
||||||
|
try {
|
||||||
|
const stopUrl = this.stopUrlValue.replace('__JOB_ID__', jobId)
|
||||||
|
|
||||||
|
const response = await fetch(stopUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this.getHeaders()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text()
|
||||||
|
throw new Error(`HTTP ${response.status}: ${errorText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
location.reload()
|
||||||
|
} else {
|
||||||
|
alert('Error stopping job: ' + (data.error || 'Unknown error'))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error stopping job:', error)
|
||||||
|
alert('Error stopping job: ' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -56,12 +56,16 @@ export default class MarkdownController extends Controller {
|
||||||
this.element.innerHTML = DOMPurify.sanitize(MarkdownController._marked.parse(this.unescapeHTML(raw)));
|
this.element.innerHTML = DOMPurify.sanitize(MarkdownController._marked.parse(this.unescapeHTML(raw)));
|
||||||
|
|
||||||
for(let a of this.element.querySelectorAll('a')) {
|
for(let a of this.element.querySelectorAll('a')) {
|
||||||
//Mark all links as external
|
// test if link is absolute
|
||||||
a.classList.add('link-external');
|
var r = new RegExp('^(?:[a-z+]+:)?//', 'i');
|
||||||
//Open links in new tag
|
if (r.test(a.getAttribute('href'))) {
|
||||||
a.setAttribute('target', '_blank');
|
//Mark all links as external
|
||||||
//Dont track
|
a.classList.add('link-external');
|
||||||
a.setAttribute('rel', 'noopener');
|
//Open links in new tag
|
||||||
|
a.setAttribute('target', '_blank');
|
||||||
|
//Dont track
|
||||||
|
a.setAttribute('rel', 'noopener');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Apply bootstrap styles to tables
|
//Apply bootstrap styles to tables
|
||||||
|
|
@ -108,4 +112,4 @@ export default class MarkdownController extends Controller {
|
||||||
gfm: true,
|
gfm: true,
|
||||||
});
|
});
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
|
|
||||||
81
assets/controllers/csrf_protection_controller.js
Normal file
81
assets/controllers/csrf_protection_controller.js
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/;
|
||||||
|
const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/;
|
||||||
|
|
||||||
|
// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager
|
||||||
|
// Use `form.requestSubmit()` to ensure that the submit event is triggered. Using `form.submit()` will not trigger the event
|
||||||
|
// and thus this event-listener will not be executed.
|
||||||
|
document.addEventListener('submit', function (event) {
|
||||||
|
generateCsrfToken(event.target);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie
|
||||||
|
// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked
|
||||||
|
document.addEventListener('turbo:submit-start', function (event) {
|
||||||
|
const h = generateCsrfHeaders(event.detail.formSubmission.formElement);
|
||||||
|
Object.keys(h).map(function (k) {
|
||||||
|
event.detail.formSubmission.fetchRequest.headers[k] = h[k];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted
|
||||||
|
document.addEventListener('turbo:submit-end', function (event) {
|
||||||
|
removeCsrfToken(event.detail.formSubmission.formElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
export function generateCsrfToken (formElement) {
|
||||||
|
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
|
||||||
|
|
||||||
|
if (!csrfField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
|
||||||
|
let csrfToken = csrfField.value;
|
||||||
|
|
||||||
|
if (!csrfCookie && nameCheck.test(csrfToken)) {
|
||||||
|
csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken);
|
||||||
|
csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
|
||||||
|
}
|
||||||
|
csrfField.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
|
||||||
|
if (csrfCookie && tokenCheck.test(csrfToken)) {
|
||||||
|
const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict';
|
||||||
|
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateCsrfHeaders (formElement) {
|
||||||
|
const headers = {};
|
||||||
|
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
|
||||||
|
|
||||||
|
if (!csrfField) {
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
|
||||||
|
|
||||||
|
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
|
||||||
|
headers[csrfCookie] = csrfField.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeCsrfToken (formElement) {
|
||||||
|
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
|
||||||
|
|
||||||
|
if (!csrfField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
|
||||||
|
|
||||||
|
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
|
||||||
|
const cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0';
|
||||||
|
|
||||||
|
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stimulusFetch: 'lazy' */
|
||||||
|
export default 'csrf-protection-controller';
|
||||||
|
|
@ -34,6 +34,11 @@ export default class extends Controller {
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
|
|
||||||
|
let dropdownParent = "body";
|
||||||
|
if (this.element.closest('.modal')) {
|
||||||
|
dropdownParent = null
|
||||||
|
}
|
||||||
|
|
||||||
let settings = {
|
let settings = {
|
||||||
persistent: false,
|
persistent: false,
|
||||||
create: true,
|
create: true,
|
||||||
|
|
@ -42,6 +47,7 @@ export default class extends Controller {
|
||||||
selectOnTab: true,
|
selectOnTab: true,
|
||||||
//This a an ugly solution to disable the delimiter parsing of the TomSelect plugin
|
//This a an ugly solution to disable the delimiter parsing of the TomSelect plugin
|
||||||
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING',
|
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING',
|
||||||
|
dropdownParent: dropdownParent,
|
||||||
render: {
|
render: {
|
||||||
item: (data, escape) => {
|
item: (data, escape) => {
|
||||||
return '<span>' + escape(data.label) + '</span>';
|
return '<span>' + escape(data.label) + '</span>';
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,32 @@ import { default as FullEditor } from "../../ckeditor/markdown_full";
|
||||||
import { default as SingleLineEditor} from "../../ckeditor/markdown_single_line";
|
import { default as SingleLineEditor} from "../../ckeditor/markdown_single_line";
|
||||||
import { default as HTMLLabelEditor } from "../../ckeditor/html_label";
|
import { default as HTMLLabelEditor } from "../../ckeditor/html_label";
|
||||||
|
|
||||||
import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog';
|
import {EditorWatchdog} from 'ckeditor5';
|
||||||
|
|
||||||
|
import "ckeditor5/ckeditor5.css";;
|
||||||
import "../../css/components/ckeditor.css";
|
import "../../css/components/ckeditor.css";
|
||||||
|
|
||||||
|
const translationContext = require.context(
|
||||||
|
'ckeditor5/translations',
|
||||||
|
false,
|
||||||
|
//Only load the translation files we will really need
|
||||||
|
/(de|it|fr|ru|ja|cs|da|zh|pl|hu)\.js$/
|
||||||
|
);
|
||||||
|
|
||||||
|
function loadTranslation(language) {
|
||||||
|
if (!language || language === 'en') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const lang = language.slice(0, 2);
|
||||||
|
const path = `./${lang}.js`;
|
||||||
|
if (translationContext.keys().includes(path)) {
|
||||||
|
const module = translationContext(path);
|
||||||
|
return module.default;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* stimulusFetch: 'lazy' */
|
/* stimulusFetch: 'lazy' */
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
connect() {
|
connect() {
|
||||||
|
|
@ -51,9 +73,22 @@ export default class extends Controller {
|
||||||
|
|
||||||
const language = document.body.dataset.locale ?? "en";
|
const language = document.body.dataset.locale ?? "en";
|
||||||
|
|
||||||
|
const emojiURL = new URL('../../ckeditor/emojis.json', import.meta.url).href;
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
language: language,
|
language: language,
|
||||||
licenseKey: "GPL",
|
licenseKey: "GPL",
|
||||||
|
|
||||||
|
emoji: {
|
||||||
|
definitionsUrl: emojiURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load translations if not english
|
||||||
|
let translations = loadTranslation(language);
|
||||||
|
if (translations) {
|
||||||
|
//Keep existing translations (e.g. from other plugins), if any
|
||||||
|
config.translations = [window.CKEDITOR_TRANSLATIONS, translations];
|
||||||
}
|
}
|
||||||
|
|
||||||
const watchdog = new EditorWatchdog();
|
const watchdog = new EditorWatchdog();
|
||||||
|
|
@ -71,6 +106,15 @@ export default class extends Controller {
|
||||||
editor_div.classList.add(...new_classes.split(","));
|
editor_div.classList.add(...new_classes.split(","));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Automatic synchronization of source input
|
||||||
|
editor.model.document.on("change:data", () => {
|
||||||
|
editor.updateSourceElement();
|
||||||
|
|
||||||
|
// Dispatch the input event for further treatment
|
||||||
|
const event = new Event("input");
|
||||||
|
this.element.dispatchEvent(event);
|
||||||
|
});
|
||||||
|
|
||||||
//This return is important! Otherwise we get mysterious errors in the console
|
//This return is important! Otherwise we get mysterious errors in the console
|
||||||
//See: https://github.com/ckeditor/ckeditor5/issues/5897#issuecomment-628471302
|
//See: https://github.com/ckeditor/ckeditor5/issues/5897#issuecomment-628471302
|
||||||
return editor;
|
return editor;
|
||||||
|
|
@ -84,4 +128,4 @@ export default class extends Controller {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,10 @@ export default class extends DatatablesController {
|
||||||
//Hide/Unhide panel with the selection tools
|
//Hide/Unhide panel with the selection tools
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
selectPanel.classList.remove('d-none');
|
selectPanel.classList.remove('d-none');
|
||||||
|
selectPanel.classList.add('sticky-select-bar');
|
||||||
} else {
|
} else {
|
||||||
selectPanel.classList.add('d-none');
|
selectPanel.classList.add('d-none');
|
||||||
|
selectPanel.classList.remove('sticky-select-bar');
|
||||||
}
|
}
|
||||||
|
|
||||||
//Update selection count text
|
//Update selection count text
|
||||||
|
|
|
||||||
250
assets/controllers/elements/ipn_suggestion_controller.js
Normal file
250
assets/controllers/elements/ipn_suggestion_controller.js
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
import { Controller } from "@hotwired/stimulus";
|
||||||
|
import "../../css/components/autocomplete_bootstrap_theme.css";
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["input"];
|
||||||
|
static values = {
|
||||||
|
partId: Number,
|
||||||
|
partCategoryId: Number,
|
||||||
|
partDescription: String,
|
||||||
|
suggestions: Object,
|
||||||
|
commonSectionHeader: String, // Dynamic header for common Prefixes
|
||||||
|
partIncrementHeader: String, // Dynamic header for new possible part increment
|
||||||
|
suggestUrl: String,
|
||||||
|
};
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.configureAutocomplete();
|
||||||
|
this.watchCategoryChanges();
|
||||||
|
this.watchDescriptionChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
templates = {
|
||||||
|
commonSectionHeader({ title, html }) {
|
||||||
|
return html`
|
||||||
|
<section class="aa-Source">
|
||||||
|
<div class="aa-SourceHeader">
|
||||||
|
<span class="aa-SourceHeaderTitle">${title}</span>
|
||||||
|
<div class="aa-SourceHeaderLine"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
partIncrementHeader({ title, html }) {
|
||||||
|
return html`
|
||||||
|
<section class="aa-Source">
|
||||||
|
<div class="aa-SourceHeader">
|
||||||
|
<span class="aa-SourceHeaderTitle">${title}</span>
|
||||||
|
<div class="aa-SourceHeaderLine"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
list({ html }) {
|
||||||
|
return html`
|
||||||
|
<ul class="aa-List" role="listbox"></ul>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
item({ suggestion, description, html }) {
|
||||||
|
return html`
|
||||||
|
<li class="aa-Item" role="option" data-suggestion="${suggestion}" aria-selected="false">
|
||||||
|
<div class="aa-ItemWrapper">
|
||||||
|
<div class="aa-ItemContent">
|
||||||
|
<div class="aa-ItemIcon aa-ItemIcon--noBorder">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M12 21c4.971 0 9-4.029 9-9s-4.029-9-9-9-9 4.029-9 9 4.029 9 9 9z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="aa-ItemContentBody">
|
||||||
|
<div class="aa-ItemContentTitle">${suggestion}</div>
|
||||||
|
<div class="aa-ItemContentDescription">${description}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
configureAutocomplete() {
|
||||||
|
const inputField = this.inputTarget;
|
||||||
|
const commonPrefixes = this.suggestionsValue.commonPrefixes || [];
|
||||||
|
const prefixesPartIncrement = this.suggestionsValue.prefixesPartIncrement || [];
|
||||||
|
const commonHeader = this.commonSectionHeaderValue;
|
||||||
|
const partIncrementHeader = this.partIncrementHeaderValue;
|
||||||
|
|
||||||
|
if (!inputField || (!commonPrefixes.length && !prefixesPartIncrement.length)) return;
|
||||||
|
|
||||||
|
// Check whether the panel should be created at the update
|
||||||
|
if (this.isPanelInitialized) {
|
||||||
|
const existingPanel = inputField.parentNode.querySelector(".aa-Panel");
|
||||||
|
if (existingPanel) {
|
||||||
|
// Only remove the panel in the update phase
|
||||||
|
|
||||||
|
existingPanel.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create panel
|
||||||
|
const panel = document.createElement("div");
|
||||||
|
panel.classList.add("aa-Panel");
|
||||||
|
panel.style.display = "none";
|
||||||
|
|
||||||
|
// Create panel layout
|
||||||
|
const panelLayout = document.createElement("div");
|
||||||
|
panelLayout.classList.add("aa-PanelLayout", "aa-Panel--scrollable");
|
||||||
|
|
||||||
|
// Section for prefixes part increment
|
||||||
|
if (prefixesPartIncrement.length) {
|
||||||
|
const partIncrementSection = document.createElement("section");
|
||||||
|
partIncrementSection.classList.add("aa-Source");
|
||||||
|
|
||||||
|
const partIncrementHeaderHtml = this.templates.partIncrementHeader({
|
||||||
|
title: partIncrementHeader,
|
||||||
|
html: String.raw,
|
||||||
|
});
|
||||||
|
partIncrementSection.innerHTML += partIncrementHeaderHtml;
|
||||||
|
|
||||||
|
const partIncrementList = document.createElement("ul");
|
||||||
|
partIncrementList.classList.add("aa-List");
|
||||||
|
partIncrementList.setAttribute("role", "listbox");
|
||||||
|
|
||||||
|
prefixesPartIncrement.forEach((prefix) => {
|
||||||
|
const itemHTML = this.templates.item({
|
||||||
|
suggestion: prefix.title,
|
||||||
|
description: prefix.description,
|
||||||
|
html: String.raw,
|
||||||
|
});
|
||||||
|
partIncrementList.innerHTML += itemHTML;
|
||||||
|
});
|
||||||
|
|
||||||
|
partIncrementSection.appendChild(partIncrementList);
|
||||||
|
panelLayout.appendChild(partIncrementSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section for common prefixes
|
||||||
|
if (commonPrefixes.length) {
|
||||||
|
const commonSection = document.createElement("section");
|
||||||
|
commonSection.classList.add("aa-Source");
|
||||||
|
|
||||||
|
const commonSectionHeader = this.templates.commonSectionHeader({
|
||||||
|
title: commonHeader,
|
||||||
|
html: String.raw,
|
||||||
|
});
|
||||||
|
commonSection.innerHTML += commonSectionHeader;
|
||||||
|
|
||||||
|
const commonList = document.createElement("ul");
|
||||||
|
commonList.classList.add("aa-List");
|
||||||
|
commonList.setAttribute("role", "listbox");
|
||||||
|
|
||||||
|
commonPrefixes.forEach((prefix) => {
|
||||||
|
const itemHTML = this.templates.item({
|
||||||
|
suggestion: prefix.title,
|
||||||
|
description: prefix.description,
|
||||||
|
html: String.raw,
|
||||||
|
});
|
||||||
|
commonList.innerHTML += itemHTML;
|
||||||
|
});
|
||||||
|
|
||||||
|
commonSection.appendChild(commonList);
|
||||||
|
panelLayout.appendChild(commonSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.appendChild(panelLayout);
|
||||||
|
inputField.parentNode.appendChild(panel);
|
||||||
|
|
||||||
|
inputField.addEventListener("focus", () => {
|
||||||
|
panel.style.display = "block";
|
||||||
|
});
|
||||||
|
|
||||||
|
inputField.addEventListener("blur", () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
panel.style.display = "none";
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Selection of an item
|
||||||
|
panelLayout.addEventListener("mousedown", (event) => {
|
||||||
|
const target = event.target.closest("li");
|
||||||
|
|
||||||
|
if (target) {
|
||||||
|
inputField.value = target.dataset.suggestion;
|
||||||
|
panel.style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.isPanelInitialized = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
watchCategoryChanges() {
|
||||||
|
const categoryField = document.querySelector('[data-ipn-suggestion="categoryField"]');
|
||||||
|
const descriptionField = document.querySelector('[data-ipn-suggestion="descriptionField"]');
|
||||||
|
this.previousCategoryId = Number(this.partCategoryIdValue);
|
||||||
|
|
||||||
|
if (categoryField) {
|
||||||
|
categoryField.addEventListener("change", () => {
|
||||||
|
const categoryId = Number(categoryField.value);
|
||||||
|
const description = String(descriptionField?.value ?? '');
|
||||||
|
|
||||||
|
// Check whether the category has changed compared to the previous ID
|
||||||
|
if (categoryId !== this.previousCategoryId) {
|
||||||
|
this.fetchNewSuggestions(categoryId, description);
|
||||||
|
this.previousCategoryId = categoryId;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchDescriptionChanges() {
|
||||||
|
const categoryField = document.querySelector('[data-ipn-suggestion="categoryField"]');
|
||||||
|
const descriptionField = document.querySelector('[data-ipn-suggestion="descriptionField"]');
|
||||||
|
this.previousDescription = String(this.partDescriptionValue);
|
||||||
|
|
||||||
|
if (descriptionField) {
|
||||||
|
descriptionField.addEventListener("input", () => {
|
||||||
|
const categoryId = Number(categoryField.value);
|
||||||
|
const description = String(descriptionField?.value ?? '');
|
||||||
|
|
||||||
|
// Check whether the description has changed compared to the previous one
|
||||||
|
if (description !== this.previousDescription) {
|
||||||
|
this.fetchNewSuggestions(categoryId, description);
|
||||||
|
this.previousDescription = description;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchNewSuggestions(categoryId, description) {
|
||||||
|
const baseUrl = this.suggestUrlValue;
|
||||||
|
const partId = this.partIdValue;
|
||||||
|
const truncatedDescription = description.length > 150 ? description.substring(0, 150) : description;
|
||||||
|
const encodedDescription = this.base64EncodeUtf8(truncatedDescription);
|
||||||
|
const url = `${baseUrl}?partId=${partId}&categoryId=${categoryId}` + (description !== '' ? `&description=${encodedDescription}` : '');
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error when calling up the IPN-suggestions: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
this.suggestionsValue = data;
|
||||||
|
this.configureAutocomplete();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Errors when loading the new IPN-suggestions:", error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
base64EncodeUtf8(text) {
|
||||||
|
const utf8Bytes = new TextEncoder().encode(text);
|
||||||
|
return btoa(String.fromCharCode(...utf8Bytes));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -26,9 +26,6 @@ import {marked} from "marked";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
trans,
|
trans,
|
||||||
SEARCH_PLACEHOLDER,
|
|
||||||
SEARCH_SUBMIT,
|
|
||||||
STATISTICS_PARTS
|
|
||||||
} from '../../translator';
|
} from '../../translator';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -82,9 +79,9 @@ export default class extends Controller {
|
||||||
panelPlacement: this.element.dataset.panelPlacement,
|
panelPlacement: this.element.dataset.panelPlacement,
|
||||||
plugins: [recentSearchesPlugin],
|
plugins: [recentSearchesPlugin],
|
||||||
openOnFocus: true,
|
openOnFocus: true,
|
||||||
placeholder: trans(SEARCH_PLACEHOLDER),
|
placeholder: trans("search.placeholder"),
|
||||||
translations: {
|
translations: {
|
||||||
submitButtonTitle: trans(SEARCH_SUBMIT)
|
submitButtonTitle: trans("search.submit")
|
||||||
},
|
},
|
||||||
|
|
||||||
// Use a navigator compatible with turbo:
|
// Use a navigator compatible with turbo:
|
||||||
|
|
@ -153,7 +150,7 @@ export default class extends Controller {
|
||||||
},
|
},
|
||||||
templates: {
|
templates: {
|
||||||
header({ html }) {
|
header({ html }) {
|
||||||
return html`<span class="aa-SourceHeaderTitle">${trans(STATISTICS_PARTS)}</span>
|
return html`<span class="aa-SourceHeaderTitle">${trans("part.labelp")}</span>
|
||||||
<div class="aa-SourceHeaderLine" />`;
|
<div class="aa-SourceHeaderLine" />`;
|
||||||
},
|
},
|
||||||
item({item, components, html}) {
|
item({item, components, html}) {
|
||||||
|
|
@ -197,4 +194,4 @@ export default class extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,19 @@ export default class extends Controller {
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
|
|
||||||
|
//Check if tomselect is inside an modal and do not attach the dropdown to body in that case (as it breaks the modal)
|
||||||
|
let dropdownParent = "body";
|
||||||
|
if (this.element.closest('.modal')) {
|
||||||
|
dropdownParent = null
|
||||||
|
}
|
||||||
|
|
||||||
let settings = {
|
let settings = {
|
||||||
allowEmptyOption: true,
|
allowEmptyOption: true,
|
||||||
plugins: ['dropdown_input'],
|
plugins: ['dropdown_input'],
|
||||||
searchField: ["name", "description", "category", "footprint"],
|
searchField: ["name", "description", "category", "footprint"],
|
||||||
valueField: "id",
|
valueField: "id",
|
||||||
labelField: "name",
|
labelField: "name",
|
||||||
|
dropdownParent: dropdownParent,
|
||||||
preload: "focus",
|
preload: "focus",
|
||||||
render: {
|
render: {
|
||||||
item: (data, escape) => {
|
item: (data, escape) => {
|
||||||
|
|
@ -71,4 +78,4 @@ export default class extends Controller {
|
||||||
//Destroy the TomSelect instance
|
//Destroy the TomSelect instance
|
||||||
this._tomSelect.destroy();
|
this._tomSelect.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,7 @@ import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en';
|
||||||
import * as zxcvbnDePackage from '@zxcvbn-ts/language-de';
|
import * as zxcvbnDePackage from '@zxcvbn-ts/language-de';
|
||||||
import * as zxcvbnFrPackage from '@zxcvbn-ts/language-fr';
|
import * as zxcvbnFrPackage from '@zxcvbn-ts/language-fr';
|
||||||
import * as zxcvbnJaPackage from '@zxcvbn-ts/language-ja';
|
import * as zxcvbnJaPackage from '@zxcvbn-ts/language-ja';
|
||||||
import {trans, USER_PASSWORD_STRENGTH_VERY_WEAK, USER_PASSWORD_STRENGTH_WEAK, USER_PASSWORD_STRENGTH_MEDIUM,
|
import {trans} from '../../translator.js';
|
||||||
USER_PASSWORD_STRENGTH_STRONG, USER_PASSWORD_STRENGTH_VERY_STRONG} from '../../translator.js';
|
|
||||||
|
|
||||||
/* stimulusFetch: 'lazy' */
|
/* stimulusFetch: 'lazy' */
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
|
|
@ -89,23 +88,23 @@ export default class extends Controller {
|
||||||
|
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case 0:
|
case 0:
|
||||||
text = trans(USER_PASSWORD_STRENGTH_VERY_WEAK);
|
text = trans("user.password_strength.very_weak");
|
||||||
classes = "bg-danger badge-danger";
|
classes = "bg-danger badge-danger";
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
text = trans(USER_PASSWORD_STRENGTH_WEAK);
|
text = trans("user.password_strength.weak");
|
||||||
classes = "bg-warning badge-warning";
|
classes = "bg-warning badge-warning";
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
text = trans(USER_PASSWORD_STRENGTH_MEDIUM)
|
text = trans("user.password_strength.medium")
|
||||||
classes = "bg-info badge-info";
|
classes = "bg-info badge-info";
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
text = trans(USER_PASSWORD_STRENGTH_STRONG);
|
text = trans("user.password_strength.strong");
|
||||||
classes = "bg-primary badge-primary";
|
classes = "bg-primary badge-primary";
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
text = trans(USER_PASSWORD_STRENGTH_VERY_STRONG);
|
text = trans("user.password_strength.very_strong");
|
||||||
classes = "bg-success badge-success";
|
classes = "bg-success badge-success";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -120,4 +119,4 @@ export default class extends Controller {
|
||||||
this.badgeTarget.classList.add("badge");
|
this.badgeTarget.classList.add("badge");
|
||||||
this.badgeTarget.classList.add(...classes.split(" "));
|
this.badgeTarget.classList.add(...classes.split(" "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,17 @@ export default class extends Controller {
|
||||||
this._emptyMessage = this.element.getAttribute('title');
|
this._emptyMessage = this.element.getAttribute('title');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dropdownParent = "body";
|
||||||
|
if (this.element.closest('.modal')) {
|
||||||
|
dropdownParent = null
|
||||||
|
}
|
||||||
|
|
||||||
let settings = {
|
let settings = {
|
||||||
|
plugins: ["clear_button"],
|
||||||
allowEmptyOption: true,
|
allowEmptyOption: true,
|
||||||
selectOnTab: true,
|
selectOnTab: true,
|
||||||
maxOptions: null,
|
maxOptions: null,
|
||||||
|
dropdownParent: dropdownParent,
|
||||||
|
|
||||||
render: {
|
render: {
|
||||||
item: this.renderItem.bind(this),
|
item: this.renderItem.bind(this),
|
||||||
|
|
@ -50,7 +56,24 @@ export default class extends Controller {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Load the drag_drop plugin if the select is ordered
|
||||||
|
if (this.element.dataset.orderedValue) {
|
||||||
|
settings.plugins.push('drag_drop');
|
||||||
|
settings.plugins.push("caret_position");
|
||||||
|
}
|
||||||
|
|
||||||
|
//If multiple items can be selected, enable the remove_button plugin
|
||||||
|
if (this.element.multiple) {
|
||||||
|
settings.plugins.push('remove_button');
|
||||||
|
}
|
||||||
|
|
||||||
this._tomSelect = new TomSelect(this.element, settings);
|
this._tomSelect = new TomSelect(this.element, settings);
|
||||||
|
|
||||||
|
//If the select is ordered, we need to update the value field (with the decoded value from the orderedValue field)
|
||||||
|
if (this.element.dataset.orderedValue) {
|
||||||
|
const data = JSON.parse(this.element.dataset.orderedValue);
|
||||||
|
this._tomSelect.setValue(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getTomSelect() {
|
getTomSelect() {
|
||||||
|
|
@ -90,4 +113,4 @@ export default class extends Controller {
|
||||||
//Destroy the TomSelect instance
|
//Destroy the TomSelect instance
|
||||||
this._tomSelect.destroy();
|
this._tomSelect.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,21 @@
|
||||||
import {Controller} from "@hotwired/stimulus";
|
import {Controller} from "@hotwired/stimulus";
|
||||||
import TomSelect from "tom-select";
|
import TomSelect from "tom-select";
|
||||||
|
|
||||||
|
// TODO: Merge with select_controller.js
|
||||||
|
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
_tomSelect;
|
_tomSelect;
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
|
let dropdownParent = "body";
|
||||||
|
if (this.element.closest('.modal')) {
|
||||||
|
dropdownParent = null
|
||||||
|
}
|
||||||
|
|
||||||
this._tomSelect = new TomSelect(this.element, {
|
this._tomSelect = new TomSelect(this.element, {
|
||||||
maxItems: 1000,
|
maxItems: 1000,
|
||||||
allowEmptyOption: true,
|
allowEmptyOption: true,
|
||||||
|
dropdownParent: dropdownParent,
|
||||||
plugins: ['remove_button'],
|
plugins: ['remove_button'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -37,4 +45,4 @@ export default class extends Controller {
|
||||||
this._tomSelect.destroy();
|
this._tomSelect.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,11 @@ export default class extends Controller {
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
|
|
||||||
|
let dropdownParent = "body";
|
||||||
|
if (this.element.closest('.modal')) {
|
||||||
|
dropdownParent = null
|
||||||
|
}
|
||||||
|
|
||||||
let settings = {
|
let settings = {
|
||||||
persistent: false,
|
persistent: false,
|
||||||
create: true,
|
create: true,
|
||||||
|
|
@ -50,6 +55,7 @@ export default class extends Controller {
|
||||||
valueField: 'text',
|
valueField: 'text',
|
||||||
searchField: 'text',
|
searchField: 'text',
|
||||||
orderField: 'text',
|
orderField: 'text',
|
||||||
|
dropdownParent: dropdownParent,
|
||||||
|
|
||||||
//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',
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import '../../css/components/tom-select_extensions.css';
|
||||||
import TomSelect from "tom-select";
|
import TomSelect from "tom-select";
|
||||||
import {Controller} from "@hotwired/stimulus";
|
import {Controller} from "@hotwired/stimulus";
|
||||||
|
|
||||||
import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js'
|
import {trans} from '../../translator.js'
|
||||||
|
|
||||||
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
|
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
|
||||||
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
|
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
|
||||||
|
|
@ -40,7 +40,10 @@ 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 dropdownParent = "body";
|
||||||
|
if (this.element.closest('.modal')) {
|
||||||
|
dropdownParent = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let settings = {
|
let settings = {
|
||||||
|
|
@ -54,6 +57,7 @@ export default class extends Controller {
|
||||||
maxItems: 1,
|
maxItems: 1,
|
||||||
delimiter: "$$VERY_LONG_DELIMITER_THAT_SHOULD_NEVER_APPEAR$$",
|
delimiter: "$$VERY_LONG_DELIMITER_THAT_SHOULD_NEVER_APPEAR$$",
|
||||||
splitOn: null,
|
splitOn: null,
|
||||||
|
dropdownParent: dropdownParent,
|
||||||
|
|
||||||
searchField: [
|
searchField: [
|
||||||
{field: "text", weight : 2},
|
{field: "text", weight : 2},
|
||||||
|
|
@ -200,7 +204,7 @@ export default class extends Controller {
|
||||||
|
|
||||||
if (data.not_in_db_yet) {
|
if (data.not_in_db_yet) {
|
||||||
//Not yet added items are shown italic and with a badge
|
//Not yet added items are shown italic and with a badge
|
||||||
name += "<i><b>" + escape(data.text) + "</b></i>" + "<span class='ms-3 badge bg-info badge-info'>" + trans(ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB) + "</span>";
|
name += "<i><b>" + escape(data.text) + "</b></i>" + "<span class='ms-3 badge bg-info badge-info'>" + trans("entity.select.group.new_not_added_to_DB") + "</span>";
|
||||||
} else {
|
} else {
|
||||||
name += "<b>" + escape(data.text) + "</b>";
|
name += "<b>" + escape(data.text) + "</b>";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,11 @@ export default class extends Controller {
|
||||||
_tomSelect;
|
_tomSelect;
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
|
let dropdownParent = "body";
|
||||||
|
if (this.element.closest('.modal')) {
|
||||||
|
dropdownParent = null
|
||||||
|
}
|
||||||
|
|
||||||
let settings = {
|
let settings = {
|
||||||
plugins: {
|
plugins: {
|
||||||
remove_button:{},
|
remove_button:{},
|
||||||
|
|
@ -43,6 +48,7 @@ export default class extends Controller {
|
||||||
selectOnTab: true,
|
selectOnTab: true,
|
||||||
createOnBlur: true,
|
createOnBlur: true,
|
||||||
create: true,
|
create: true,
|
||||||
|
dropdownParent: dropdownParent,
|
||||||
};
|
};
|
||||||
|
|
||||||
if(this.element.dataset.autocomplete) {
|
if(this.element.dataset.autocomplete) {
|
||||||
|
|
@ -73,4 +79,4 @@ export default class extends Controller {
|
||||||
//Destroy the TomSelect instance
|
//Destroy the TomSelect instance
|
||||||
this._tomSelect.destroy();
|
this._tomSelect.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
136
assets/controllers/field_mapping_controller.js
Normal file
136
assets/controllers/field_mapping_controller.js
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["tbody", "addButton", "submitButton"]
|
||||||
|
static values = {
|
||||||
|
mappingIndex: Number,
|
||||||
|
maxMappings: Number,
|
||||||
|
prototype: String,
|
||||||
|
maxMappingsReachedMessage: String
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.updateAddButtonState()
|
||||||
|
this.updateFieldOptions()
|
||||||
|
this.attachEventListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
attachEventListeners() {
|
||||||
|
// Add event listeners to existing field selects
|
||||||
|
const fieldSelects = this.tbodyTarget.querySelectorAll('select[name*="[field]"]')
|
||||||
|
fieldSelects.forEach(select => {
|
||||||
|
select.addEventListener('change', this.updateFieldOptions.bind(this))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Note: Add button click is handled by Stimulus action in template (data-action="click->field-mapping#addMapping")
|
||||||
|
// No manual event listener needed
|
||||||
|
|
||||||
|
// Form submit handler
|
||||||
|
const form = this.element.querySelector('form')
|
||||||
|
if (form && this.hasSubmitButtonTarget) {
|
||||||
|
form.addEventListener('submit', this.handleFormSubmit.bind(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addMapping() {
|
||||||
|
const currentMappings = this.tbodyTarget.querySelectorAll('.mapping-row').length
|
||||||
|
|
||||||
|
if (currentMappings >= this.maxMappingsValue) {
|
||||||
|
alert(this.maxMappingsReachedMessageValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRowHtml = this.prototypeValue.replace(/__name__/g, this.mappingIndexValue)
|
||||||
|
const tempDiv = document.createElement('div')
|
||||||
|
tempDiv.innerHTML = newRowHtml
|
||||||
|
|
||||||
|
const fieldWidget = tempDiv.querySelector('select[name*="[field]"]') || tempDiv.children[0]
|
||||||
|
const providerWidget = tempDiv.querySelector('select[name*="[providers]"]') || tempDiv.children[1]
|
||||||
|
const priorityWidget = tempDiv.querySelector('input[name*="[priority]"]') || tempDiv.children[2]
|
||||||
|
|
||||||
|
const newRow = document.createElement('tr')
|
||||||
|
newRow.className = 'mapping-row'
|
||||||
|
newRow.innerHTML = `
|
||||||
|
<td>${fieldWidget ? fieldWidget.outerHTML : ''}</td>
|
||||||
|
<td>${providerWidget ? providerWidget.outerHTML : ''}</td>
|
||||||
|
<td>${priorityWidget ? priorityWidget.outerHTML : ''}</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-danger btn-sm" data-action="click->field-mapping#removeMapping">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
`
|
||||||
|
|
||||||
|
this.tbodyTarget.appendChild(newRow)
|
||||||
|
this.mappingIndexValue++
|
||||||
|
|
||||||
|
const newFieldSelect = newRow.querySelector('select[name*="[field]"]')
|
||||||
|
if (newFieldSelect) {
|
||||||
|
newFieldSelect.value = ''
|
||||||
|
newFieldSelect.addEventListener('change', this.updateFieldOptions.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateFieldOptions()
|
||||||
|
this.updateAddButtonState()
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMapping(event) {
|
||||||
|
const row = event.target.closest('tr')
|
||||||
|
row.remove()
|
||||||
|
this.updateFieldOptions()
|
||||||
|
this.updateAddButtonState()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFieldOptions() {
|
||||||
|
const fieldSelects = this.tbodyTarget.querySelectorAll('select[name*="[field]"]')
|
||||||
|
|
||||||
|
const selectedFields = Array.from(fieldSelects)
|
||||||
|
.map(select => select.value)
|
||||||
|
.filter(value => value && value !== '')
|
||||||
|
|
||||||
|
fieldSelects.forEach(select => {
|
||||||
|
Array.from(select.options).forEach(option => {
|
||||||
|
const isCurrentValue = option.value === select.value
|
||||||
|
const isEmptyOption = !option.value || option.value === ''
|
||||||
|
const isAlreadySelected = selectedFields.includes(option.value)
|
||||||
|
|
||||||
|
if (!isEmptyOption && isAlreadySelected && !isCurrentValue) {
|
||||||
|
option.disabled = true
|
||||||
|
option.style.display = 'none'
|
||||||
|
} else {
|
||||||
|
option.disabled = false
|
||||||
|
option.style.display = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAddButtonState() {
|
||||||
|
const currentMappings = this.tbodyTarget.querySelectorAll('.mapping-row').length
|
||||||
|
|
||||||
|
if (this.hasAddButtonTarget) {
|
||||||
|
if (currentMappings >= this.maxMappingsValue) {
|
||||||
|
this.addButtonTarget.disabled = true
|
||||||
|
this.addButtonTarget.title = this.maxMappingsReachedMessageValue
|
||||||
|
} else {
|
||||||
|
this.addButtonTarget.disabled = false
|
||||||
|
this.addButtonTarget.title = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFormSubmit(event) {
|
||||||
|
if (this.hasSubmitButtonTarget) {
|
||||||
|
this.submitButtonTarget.disabled = true
|
||||||
|
|
||||||
|
// Disable the entire form to prevent changes during processing
|
||||||
|
const form = event.target
|
||||||
|
const formElements = form.querySelectorAll('input, select, textarea, button')
|
||||||
|
formElements.forEach(element => {
|
||||||
|
if (element !== this.submitButtonTarget) {
|
||||||
|
element.disabled = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,10 @@ export default class extends Controller {
|
||||||
{
|
{
|
||||||
let value = "";
|
let value = "";
|
||||||
if (this.unitValue) {
|
if (this.unitValue) {
|
||||||
value = "\\mathrm{" + this.inputTarget.value + "}";
|
//Escape percentage signs
|
||||||
|
value = this.inputTarget.value.replace(/%/g, '\\%');
|
||||||
|
|
||||||
|
value = "\\mathrm{" + value + "}";
|
||||||
} else {
|
} else {
|
||||||
value = this.inputTarget.value;
|
value = this.inputTarget.value;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,9 @@ export default class extends Controller
|
||||||
tmp += '<span>' + katex.renderToString(data.symbol) + '</span>'
|
tmp += '<span>' + katex.renderToString(data.symbol) + '</span>'
|
||||||
}
|
}
|
||||||
if (data.unit) {
|
if (data.unit) {
|
||||||
tmp += '<span class="ms-2">' + katex.renderToString('[' + data.unit + ']') + '</span>'
|
let unit = data.unit.replace(/%/g, '\\%');
|
||||||
|
unit = "\\mathrm{" + unit + "}";
|
||||||
|
tmp += '<span class="ms-2">' + katex.renderToString('[' + unit + ']') + '</span>'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
68
assets/controllers/pages/synonyms_collection_controller.js
Normal file
68
assets/controllers/pages/synonyms_collection_controller.js
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Controller } from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ['items'];
|
||||||
|
static values = {
|
||||||
|
prototype: String,
|
||||||
|
prototypeName: { type: String, default: '__name__' },
|
||||||
|
index: { type: Number, default: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
if (!this.hasIndexValue || Number.isNaN(this.indexValue)) {
|
||||||
|
this.indexValue = this.itemsTarget?.children.length || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const encodedProto = this.prototypeValue || '';
|
||||||
|
const placeholder = this.prototypeNameValue || '__name__';
|
||||||
|
if (!encodedProto || !this.itemsTarget) return;
|
||||||
|
|
||||||
|
const protoHtml = this._decodeHtmlAttribute(encodedProto);
|
||||||
|
|
||||||
|
const idx = this.indexValue;
|
||||||
|
const html = protoHtml.replace(new RegExp(placeholder, 'g'), String(idx));
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.innerHTML = html;
|
||||||
|
const newItem = wrapper.firstElementChild;
|
||||||
|
if (newItem) {
|
||||||
|
this.itemsTarget.appendChild(newItem);
|
||||||
|
this.indexValue = idx + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const row = event.currentTarget.closest('.tc-item');
|
||||||
|
if (row) row.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
_decodeHtmlAttribute(str) {
|
||||||
|
const tmp = document.createElement('textarea');
|
||||||
|
tmp.innerHTML = str;
|
||||||
|
return tmp.value || tmp.textContent || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
86
assets/controllers/toggle_password_controller.js
Normal file
86
assets/controllers/toggle_password_controller.js
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Controller } from '@hotwired/stimulus';
|
||||||
|
import '../css/components/toggle_password.css';
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static values = {
|
||||||
|
visibleLabel: { type: String, default: 'Show' },
|
||||||
|
visibleIcon: { type: String, default: 'Default' },
|
||||||
|
hiddenLabel: { type: String, default: 'Hide' },
|
||||||
|
hiddenIcon: { type: String, default: 'Default' },
|
||||||
|
buttonClasses: Array,
|
||||||
|
};
|
||||||
|
|
||||||
|
isDisplayed = false;
|
||||||
|
visibleIcon = `<svg xmlns="http://www.w3.org/2000/svg" class="toggle-password-icon" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
|
||||||
|
<path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd" />
|
||||||
|
</svg>`;
|
||||||
|
hiddenIcon = `<svg xmlns="http://www.w3.org/2000/svg" class="toggle-password-icon" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z" clip-rule="evenodd" />
|
||||||
|
<path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z" />
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
if (this.visibleIconValue !== 'Default') {
|
||||||
|
this.visibleIcon = this.visibleIconValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hiddenIconValue !== 'Default') {
|
||||||
|
this.hiddenIcon = this.hiddenIconValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = this.createButton();
|
||||||
|
|
||||||
|
this.element.insertAdjacentElement('afterend', button);
|
||||||
|
this.dispatchEvent('connect', { element: this.element, button });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {HTMLButtonElement}
|
||||||
|
*/
|
||||||
|
createButton() {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.type = 'button';
|
||||||
|
button.classList.add(...this.buttonClassesValue);
|
||||||
|
button.setAttribute('tabindex', '-1');
|
||||||
|
button.addEventListener('click', this.toggle.bind(this));
|
||||||
|
button.innerHTML = `${this.visibleIcon} ${this.visibleLabelValue}`;
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle input type between "text" or "password" and update label accordingly
|
||||||
|
*/
|
||||||
|
toggle(event) {
|
||||||
|
this.isDisplayed = !this.isDisplayed;
|
||||||
|
const toggleButtonElement = event.currentTarget;
|
||||||
|
toggleButtonElement.innerHTML = this.isDisplayed
|
||||||
|
? `${this.hiddenIcon} ${this.hiddenLabelValue}`
|
||||||
|
: `${this.visibleIcon} ${this.visibleLabelValue}`;
|
||||||
|
this.element.setAttribute('type', this.isDisplayed ? 'text' : 'password');
|
||||||
|
this.dispatchEvent(this.isDisplayed ? 'show' : 'hide', { element: this.element, button: toggleButtonElement });
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchEvent(name, payload) {
|
||||||
|
this.dispatch(name, { detail: payload, prefix: 'toggle-password' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -120,4 +120,11 @@ ins {
|
||||||
del {
|
del {
|
||||||
background-color: #f09595;
|
background-color: #f09595;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/****************************************
|
||||||
|
* Password toggle
|
||||||
|
****************************************/
|
||||||
|
.toggle-password-button {
|
||||||
|
top: 0.7rem !important;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.hoverpic {
|
.hoverpic {
|
||||||
min-width: 10px;
|
min-width: var(--table-image-preview-min-size, 20px);
|
||||||
max-width: 30px;
|
max-width: var(--table-image-preview-max-size, 35px);
|
||||||
display: block;
|
display: block;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.part-table-image {
|
.part-table-image {
|
||||||
max-height: 40px;
|
max-height: calc(1.2*var(--table-image-preview-max-size, 35px)); /** Aspect ratio of maximum 1.2 */
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ showing the sidebar (on devices with md or higher)
|
||||||
*/
|
*/
|
||||||
#sidebar-toggle-button {
|
#sidebar-toggle-button {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 3px;
|
left: 2px;
|
||||||
bottom: 50%;
|
bottom: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,16 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/****************************************
|
||||||
|
* Action bar
|
||||||
|
****************************************/
|
||||||
|
|
||||||
|
.sticky-select-bar {
|
||||||
|
position: sticky;
|
||||||
|
top: 120px;
|
||||||
|
z-index: 1000; /* Ensure the bar is above other content */
|
||||||
|
}
|
||||||
|
|
||||||
/****************************************
|
/****************************************
|
||||||
* Tables
|
* Tables
|
||||||
****************************************/
|
****************************************/
|
||||||
|
|
@ -84,6 +94,11 @@ th.select-checkbox {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Add spacing between column visibility button and length menu */
|
||||||
|
.buttons-colvis {
|
||||||
|
margin-right: 0.2em !important;
|
||||||
|
}
|
||||||
|
|
||||||
/** Fix datatables select-checkbox position */
|
/** Fix datatables select-checkbox position */
|
||||||
table.dataTable tr.selected td.select-checkbox:after
|
table.dataTable tr.selected td.select-checkbox:after
|
||||||
{
|
{
|
||||||
|
|
@ -109,4 +124,4 @@ Classes for Datatables export
|
||||||
#export-messageTop,
|
#export-messageTop,
|
||||||
.export-helper{
|
.export-helper{
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,8 @@
|
||||||
--ck-color-button-on-hover-background: var(--bs-secondary-bg);
|
--ck-color-button-on-hover-background: var(--bs-secondary-bg);
|
||||||
--ck-color-button-on-active-background: var(--bs-secondary-bg);
|
--ck-color-button-on-active-background: var(--bs-secondary-bg);
|
||||||
--ck-color-button-on-disabled-background: var(--bs-secondary-bg);
|
--ck-color-button-on-disabled-background: var(--bs-secondary-bg);
|
||||||
--ck-color-button-on-color: var(--bs-primary)
|
--ck-color-button-on-color: var(--bs-primary);
|
||||||
|
|
||||||
}
|
--ck-content-font-color: var(--ck-color-base-text);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
41
assets/css/components/toggle_password.css
Normal file
41
assets/css/components/toggle_password.css
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.toggle-password-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.toggle-password-icon {
|
||||||
|
height: 1rem;
|
||||||
|
width: 1rem;
|
||||||
|
}
|
||||||
|
.toggle-password-button {
|
||||||
|
align-items: center;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
column-gap: 0.25rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
justify-items: center;
|
||||||
|
height: 1rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
position: absolute;
|
||||||
|
right: 0.5rem;
|
||||||
|
top: -1.25rem;
|
||||||
|
}
|
||||||
|
|
@ -28,7 +28,7 @@ import '../css/app/treeview.css';
|
||||||
import '../css/app/images.css';
|
import '../css/app/images.css';
|
||||||
|
|
||||||
// start the Stimulus application
|
// start the Stimulus application
|
||||||
import '../bootstrap';
|
import '../stimulus_bootstrap';
|
||||||
|
|
||||||
// Need jQuery? Install it with "yarn add jquery", then uncomment to require it.
|
// Need jQuery? Install it with "yarn add jquery", then uncomment to require it.
|
||||||
const $ = require('jquery');
|
const $ = require('jquery');
|
||||||
|
|
@ -49,7 +49,7 @@ window.$ = window.jQuery = require("jquery");
|
||||||
//Use the local WASM file for the ZXing library
|
//Use the local WASM file for the ZXing library
|
||||||
import {
|
import {
|
||||||
setZXingModuleOverrides,
|
setZXingModuleOverrides,
|
||||||
} from "barcode-detector/pure";
|
} from "barcode-detector/ponyfill";
|
||||||
import wasmFile from "../../node_modules/zxing-wasm/dist/reader/zxing_reader.wasm";
|
import wasmFile from "../../node_modules/zxing-wasm/dist/reader/zxing_reader.wasm";
|
||||||
setZXingModuleOverrides({
|
setZXingModuleOverrides({
|
||||||
locateFile: (path, prefix) => {
|
locateFile: (path, prefix) => {
|
||||||
|
|
@ -58,4 +58,4 @@ setZXingModuleOverrides({
|
||||||
}
|
}
|
||||||
return prefix + path;
|
return prefix + path;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -75,11 +75,10 @@
|
||||||
request._dt = config.name;
|
request._dt = config.name;
|
||||||
|
|
||||||
//Try to resolve the original column index when the column was reordered (using the ColReorder plugin)
|
//Try to resolve the original column index when the column was reordered (using the ColReorder plugin)
|
||||||
//Only do this when _ColReorder_iOrigCol is available
|
if (dt.colReorder && dt.colReorder.transpose) {
|
||||||
if (settings.aoColumns && settings.aoColumns.length && settings.aoColumns[0]._ColReorder_iOrigCol !== undefined) {
|
|
||||||
if (request.order && request.order.length) {
|
if (request.order && request.order.length) {
|
||||||
request.order.forEach(function (order) {
|
request.order.forEach(function (order) {
|
||||||
order.column = settings.aoColumns[order.column]._ColReorder_iOrigCol;
|
order.column = dt.colReorder.transpose(order.column, "toOriginal");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { localeFallbacks } from '../var/translations/configuration';
|
import { createTranslator } from '@symfony/ux-translator';
|
||||||
import { trans, getLocale, setLocale, setLocaleFallbacks } from '@symfony/ux-translator';
|
import { messages, localeFallbacks } from '../var/translations/index.js';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This file is part of the Symfony UX Translator package.
|
* This file is part of the Symfony UX Translator package.
|
||||||
*
|
*
|
||||||
|
|
@ -9,8 +10,9 @@ import { trans, getLocale, setLocale, setLocaleFallbacks } from '@symfony/ux-tra
|
||||||
* If you use TypeScript, you can rename this file to "translator.ts" to take advantage of types checking.
|
* If you use TypeScript, you can rename this file to "translator.ts" to take advantage of types checking.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
setLocaleFallbacks(localeFallbacks);
|
const translator = createTranslator({
|
||||||
|
messages,
|
||||||
|
localeFallbacks,
|
||||||
|
});
|
||||||
|
|
||||||
export { trans };
|
export const { trans } = translator;
|
||||||
|
|
||||||
export * from '../var/translations';
|
|
||||||
|
|
|
||||||
21
bin/phpunit
21
bin/phpunit
|
|
@ -1,23 +1,4 @@
|
||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
if (!ini_get('date.timezone')) {
|
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||||
ini_set('date.timezone', 'UTC');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
|
||||||
if (PHP_VERSION_ID >= 80000) {
|
|
||||||
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
|
||||||
} else {
|
|
||||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
|
||||||
require PHPUNIT_COMPOSER_INSTALL;
|
|
||||||
PHPUnit\TextUI\Command::main();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
|
||||||
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
|
||||||
}
|
|
||||||
|
|
|
||||||
115
composer.json
115
composer.json
|
|
@ -3,7 +3,7 @@
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.1",
|
"php": "^8.2",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"ext-gd": "*",
|
"ext-gd": "*",
|
||||||
|
|
@ -12,9 +12,11 @@
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
"amphp/http-client": "^5.1",
|
"amphp/http-client": "^5.1",
|
||||||
"api-platform/core": "^3.1",
|
"api-platform/doctrine-orm": "^4.1",
|
||||||
|
"api-platform/json-api": "^4.0.0",
|
||||||
|
"api-platform/symfony": "^4.0.0",
|
||||||
"beberlei/doctrineextensions": "^1.2",
|
"beberlei/doctrineextensions": "^1.2",
|
||||||
"brick/math": "0.12.1 as 0.11.0",
|
"brick/math": "^0.13.1",
|
||||||
"composer/ca-bundle": "^1.5",
|
"composer/ca-bundle": "^1.5",
|
||||||
"composer/package-versions-deprecated": "^1.11.99.5",
|
"composer/package-versions-deprecated": "^1.11.99.5",
|
||||||
"doctrine/data-fixtures": "^2.0.0",
|
"doctrine/data-fixtures": "^2.0.0",
|
||||||
|
|
@ -22,66 +24,69 @@
|
||||||
"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": "^3.2.0",
|
||||||
"dompdf/dompdf": "^v3.0.0",
|
"dompdf/dompdf": "^3.1.2",
|
||||||
"erusev/parsedown": "^1.7",
|
|
||||||
"florianv/swap": "^4.0",
|
|
||||||
"florianv/swap-bundle": "dev-master",
|
|
||||||
"gregwar/captcha-bundle": "^2.1.0",
|
"gregwar/captcha-bundle": "^2.1.0",
|
||||||
"hshn/base64-encoded-file": "^5.0",
|
"hshn/base64-encoded-file": "^5.0",
|
||||||
"jbtronics/2fa-webauthn": "^v2.2.0",
|
"jbtronics/2fa-webauthn": "^3.0.0",
|
||||||
"jbtronics/dompdf-font-loader-bundle": "^1.0.0",
|
"jbtronics/dompdf-font-loader-bundle": "^1.0.0",
|
||||||
|
"jbtronics/settings-bundle": "^3.0.0",
|
||||||
"jfcherng/php-diff": "^6.14",
|
"jfcherng/php-diff": "^6.14",
|
||||||
"knpuniversity/oauth2-client-bundle": "^2.15",
|
"knpuniversity/oauth2-client-bundle": "^2.15",
|
||||||
|
"league/commonmark": "^2.7",
|
||||||
"league/csv": "^9.8.0",
|
"league/csv": "^9.8.0",
|
||||||
"league/html-to-markdown": "^5.0.1",
|
"league/html-to-markdown": "^5.0.1",
|
||||||
"liip/imagine-bundle": "^2.2",
|
"liip/imagine-bundle": "^2.2",
|
||||||
"nbgrp/onelogin-saml-bundle": "^1.3",
|
"maennchen/zipstream-php": "2.1",
|
||||||
|
"nbgrp/onelogin-saml-bundle": "^v2.0.2",
|
||||||
"nelexa/zip": "^4.0",
|
"nelexa/zip": "^4.0",
|
||||||
"nelmio/cors-bundle": "^2.3",
|
"nelmio/cors-bundle": "^2.3",
|
||||||
"nelmio/security-bundle": "^3.0",
|
"nelmio/security-bundle": "^3.0",
|
||||||
"nyholm/psr7": "^1.1",
|
"nyholm/psr7": "^1.1",
|
||||||
"omines/datatables-bundle": "^0.9.1",
|
"omines/datatables-bundle": "^0.10.0",
|
||||||
"paragonie/sodium_compat": "^1.21",
|
"paragonie/sodium_compat": "^1.21",
|
||||||
"part-db/label-fonts": "^1.0",
|
"part-db/label-fonts": "^1.0",
|
||||||
"runtime/frankenphp-symfony": "^0.2.0",
|
"part-db/swap-bundle": "^6.0.0",
|
||||||
|
"phpoffice/phpspreadsheet": "^5.0.0",
|
||||||
|
"rhukster/dom-sanitizer": "^1.0",
|
||||||
"s9e/text-formatter": "^2.1",
|
"s9e/text-formatter": "^2.1",
|
||||||
"scheb/2fa-backup-code": "^6.8.0",
|
"scheb/2fa-backup-code": "^v7.11.0",
|
||||||
"scheb/2fa-bundle": "^6.8.0",
|
"scheb/2fa-bundle": "^v7.11.0",
|
||||||
"scheb/2fa-google-authenticator": "^6.8.0",
|
"scheb/2fa-google-authenticator": "^v7.11.0",
|
||||||
"scheb/2fa-trusted-device": "^6.8.0",
|
"scheb/2fa-trusted-device": "^v7.11.0",
|
||||||
"shivas/versioning-bundle": "^4.0",
|
"shivas/versioning-bundle": "^4.0",
|
||||||
"spatie/db-dumper": "^3.3.1",
|
"spatie/db-dumper": "^3.3.1",
|
||||||
"symfony/apache-pack": "^1.0",
|
"symfony/apache-pack": "^1.0",
|
||||||
"symfony/asset": "6.4.*",
|
"symfony/asset": "7.4.*",
|
||||||
"symfony/console": "6.4.*",
|
"symfony/console": "7.4.*",
|
||||||
"symfony/css-selector": "6.4.*",
|
"symfony/css-selector": "7.4.*",
|
||||||
"symfony/dom-crawler": "6.4.*",
|
"symfony/dom-crawler": "7.4.*",
|
||||||
"symfony/dotenv": "6.4.*",
|
"symfony/dotenv": "7.4.*",
|
||||||
"symfony/expression-language": "6.4.*",
|
"symfony/expression-language": "7.4.*",
|
||||||
"symfony/flex": "^v2.3.1",
|
"symfony/flex": "^v2.3.1",
|
||||||
"symfony/form": "6.4.*",
|
"symfony/form": "7.4.*",
|
||||||
"symfony/framework-bundle": "6.4.*",
|
"symfony/framework-bundle": "7.4.*",
|
||||||
"symfony/http-client": "6.4.*",
|
"symfony/http-client": "7.4.*",
|
||||||
"symfony/http-kernel": "6.4.*",
|
"symfony/http-kernel": "7.4.*",
|
||||||
"symfony/mailer": "6.4.*",
|
"symfony/mailer": "7.4.*",
|
||||||
"symfony/monolog-bundle": "^3.1",
|
"symfony/monolog-bundle": "^3.1",
|
||||||
"symfony/polyfill-php82": "^1.28",
|
"symfony/process": "7.4.*",
|
||||||
"symfony/process": "6.4.*",
|
"symfony/property-access": "7.4.*",
|
||||||
"symfony/property-access": "6.4.*",
|
"symfony/property-info": "7.4.*",
|
||||||
"symfony/property-info": "6.4.*",
|
"symfony/rate-limiter": "7.4.*",
|
||||||
"symfony/rate-limiter": "6.4.*",
|
"symfony/runtime": "7.4.*",
|
||||||
"symfony/runtime": "6.4.*",
|
"symfony/security-bundle": "7.4.*",
|
||||||
"symfony/security-bundle": "6.4.*",
|
"symfony/serializer": "7.4.*",
|
||||||
"symfony/serializer": "6.4.*",
|
"symfony/string": "7.4.*",
|
||||||
"symfony/string": "6.4.*",
|
"symfony/translation": "7.4.*",
|
||||||
"symfony/translation": "6.4.*",
|
"symfony/twig-bundle": "7.4.*",
|
||||||
"symfony/twig-bundle": "6.4.*",
|
"symfony/type-info": "7.4.0",
|
||||||
"symfony/ux-translator": "^2.10",
|
"symfony/ux-translator": "^2.32.0",
|
||||||
"symfony/ux-turbo": "^2.0",
|
"symfony/ux-turbo": "^2.0",
|
||||||
"symfony/validator": "6.4.*",
|
"symfony/validator": "7.4.*",
|
||||||
"symfony/web-link": "6.4.*",
|
"symfony/web-link": "7.4.*",
|
||||||
"symfony/webpack-encore-bundle": "^v2.0.1",
|
"symfony/webpack-encore-bundle": "^v2.0.1",
|
||||||
"symfony/yaml": "6.4.*",
|
"symfony/yaml": "7.4.*",
|
||||||
|
"symplify/easy-coding-standard": "^12.5.20",
|
||||||
"tecnickcom/tc-lib-barcode": "^2.1.4",
|
"tecnickcom/tc-lib-barcode": "^2.1.4",
|
||||||
"twig/cssinliner-extra": "^3.0",
|
"twig/cssinliner-extra": "^3.0",
|
||||||
"twig/extra-bundle": "^3.8",
|
"twig/extra-bundle": "^3.8",
|
||||||
|
|
@ -90,7 +95,7 @@
|
||||||
"twig/intl-extra": "^3.8",
|
"twig/intl-extra": "^3.8",
|
||||||
"twig/markdown-extra": "^3.8",
|
"twig/markdown-extra": "^3.8",
|
||||||
"twig/string-extra": "^3.8",
|
"twig/string-extra": "^3.8",
|
||||||
"web-auth/webauthn-symfony-bundle": "^4.0.0"
|
"web-auth/webauthn-symfony-bundle": "^5.0.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"dama/doctrine-test-bundle": "^v8.0.0",
|
"dama/doctrine-test-bundle": "^v8.0.0",
|
||||||
|
|
@ -102,16 +107,22 @@
|
||||||
"phpstan/phpstan-doctrine": "^2.0.1",
|
"phpstan/phpstan-doctrine": "^2.0.1",
|
||||||
"phpstan/phpstan-strict-rules": "^2.0.1",
|
"phpstan/phpstan-strict-rules": "^2.0.1",
|
||||||
"phpstan/phpstan-symfony": "^2.0.0",
|
"phpstan/phpstan-symfony": "^2.0.0",
|
||||||
"phpunit/phpunit": "^9.5",
|
"phpunit/phpunit": "^11.5.0",
|
||||||
"rector/rector": "^2.0.4",
|
"rector/rector": "^2.0.4",
|
||||||
"roave/security-advisories": "dev-latest",
|
"roave/security-advisories": "dev-latest",
|
||||||
"symfony/browser-kit": "6.4.*",
|
"symfony/browser-kit": "7.4.*",
|
||||||
"symfony/debug-bundle": "6.4.*",
|
"symfony/debug-bundle": "7.4.*",
|
||||||
"symfony/maker-bundle": "^1.13",
|
"symfony/maker-bundle": "^1.13",
|
||||||
"symfony/phpunit-bridge": "6.4.*",
|
"symfony/phpunit-bridge": "7.4.*",
|
||||||
"symfony/stopwatch": "6.4.*",
|
"symfony/stopwatch": "7.4.*",
|
||||||
"symfony/web-profiler-bundle": "6.4.*",
|
"symfony/web-profiler-bundle": "7.4.*"
|
||||||
"symplify/easy-coding-standard": "^12.0"
|
},
|
||||||
|
"replace": {
|
||||||
|
"symfony/polyfill-mbstring": "*",
|
||||||
|
"symfony/polyfill-php74": "*",
|
||||||
|
"symfony/polyfill-php80": "*",
|
||||||
|
"symfony/polyfill-php81": "*",
|
||||||
|
"symfony/polyfill-php82": "*"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-bcmath": "Used to improve price calculation performance",
|
"ext-bcmath": "Used to improve price calculation performance",
|
||||||
|
|
@ -122,7 +133,7 @@
|
||||||
"*": "dist"
|
"*": "dist"
|
||||||
},
|
},
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "8.1.0"
|
"php": "8.2.0"
|
||||||
},
|
},
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
|
|
@ -154,7 +165,7 @@
|
||||||
"post-update-cmd": [
|
"post-update-cmd": [
|
||||||
"@auto-scripts"
|
"@auto-scripts"
|
||||||
],
|
],
|
||||||
"phpstan": "vendor/bin/phpstan analyse src --level 5 --memory-limit 1G"
|
"phpstan": "php -d memory_limit=1G vendor/bin/phpstan analyse src --level 5"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"symfony/symfony": "*"
|
"symfony/symfony": "*"
|
||||||
|
|
@ -162,7 +173,7 @@
|
||||||
"extra": {
|
"extra": {
|
||||||
"symfony": {
|
"symfony": {
|
||||||
"allow-contrib": false,
|
"allow-contrib": false,
|
||||||
"require": "6.4.*",
|
"require": "7.4.*",
|
||||||
"docker": true
|
"docker": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9127
composer.lock
generated
9127
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,4 @@
|
||||||
Welcome to Part-DB.
|
**Attention**:
|
||||||
|
Since Version 2.0.0 this file is no longer used.
|
||||||
<small>If you want to change this banner, edit `config/banner.md` file or set the `BANNER` environment variable.</small>
|
|
||||||
|
|
||||||
<blockquote class="pb-0">
|
You can now set the banner text directly in the admin interface, or by setting the `BANNER` environment variable.
|
||||||
<p style="font-size: 12px">
|
|
||||||
And God said <br>
|
|
||||||
$\nabla \cdot \vec{D} = \rho$,
|
|
||||||
$\nabla \cdot \vec{B} = 0$,
|
|
||||||
$\nabla \times \vec{E} = -\frac{\partial \vec{B}}{\partial t}$,
|
|
||||||
$\nabla \times \vec{H} = \vec{j} + \frac{\partial \vec{D}}{\partial t}$, <br>
|
|
||||||
and then there was light.
|
|
||||||
</p>
|
|
||||||
</blockquote>
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ return [
|
||||||
Jbtronics\DompdfFontLoaderBundle\DompdfFontLoaderBundle::class => ['all' => true],
|
Jbtronics\DompdfFontLoaderBundle\DompdfFontLoaderBundle::class => ['all' => true],
|
||||||
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
|
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
|
||||||
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
||||||
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
|
Jbtronics\SettingsBundle\JbtronicsSettingsBundle::class => ['all' => true],
|
||||||
Jbtronics\TranslationEditorBundle\JbtronicsTranslationEditorBundle::class => ['dev' => true],
|
Jbtronics\TranslationEditorBundle\JbtronicsTranslationEditorBundle::class => ['dev' => true],
|
||||||
|
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,9 @@ api_platform:
|
||||||
|
|
||||||
pagination_client_items_per_page: true # Allow clients to override the default items per page
|
pagination_client_items_per_page: true # Allow clients to override the default items per page
|
||||||
|
|
||||||
keep_legacy_inflector: false
|
|
||||||
# Need to be true, or some tests will fail
|
# Need to be true, or some tests will fail
|
||||||
use_symfony_listeners: true
|
use_symfony_listeners: true
|
||||||
|
|
||||||
serializer:
|
serializer:
|
||||||
# Change this to false later, to remove the hydra prefix on the API
|
# Change this to false later, to remove the hydra prefix on the API
|
||||||
hydra_prefix: true
|
hydra_prefix: true
|
||||||
|
|
|
||||||
12
config/packages/csrf.yaml
Normal file
12
config/packages/csrf.yaml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Enable stateless CSRF protection for forms and logins/logouts
|
||||||
|
framework:
|
||||||
|
form:
|
||||||
|
csrf_protection:
|
||||||
|
token_id: submit
|
||||||
|
|
||||||
|
csrf_protection:
|
||||||
|
check_header: true
|
||||||
|
stateless_token_ids:
|
||||||
|
- submit
|
||||||
|
- authenticate
|
||||||
|
- logout
|
||||||
|
|
@ -9,7 +9,8 @@ datatables:
|
||||||
# Set options, as documented at https://datatables.net/reference/option/
|
# Set options, as documented at https://datatables.net/reference/option/
|
||||||
options:
|
options:
|
||||||
lengthMenu : [[10, 25, 50, 100], [10, 25, 50, 100]] # We add the "All" option, when part tables are generated
|
lengthMenu : [[10, 25, 50, 100], [10, 25, 50, 100]] # We add the "All" option, when part tables are generated
|
||||||
pageLength: '%partdb.table.default_page_size%' # Set to -1 to disable pagination (i.e. show all rows) by default
|
#pageLength: '%partdb.table.default_page_size%' # Set to -1 to disable pagination (i.e. show all rows) by default
|
||||||
|
pageLength: 50 #TODO
|
||||||
dom: " <'row' <'col mb-2 input-group flex-nowrap' B l > <'col-auto mb-2' < p >>>
|
dom: " <'row' <'col mb-2 input-group flex-nowrap' B l > <'col-auto mb-2' < p >>>
|
||||||
<'card'
|
<'card'
|
||||||
rt
|
rt
|
||||||
|
|
@ -17,7 +18,7 @@ datatables:
|
||||||
>
|
>
|
||||||
<'row' <'col mt-2 input-group flex-nowrap' B l > <'col-auto mt-2' < p >>>"
|
<'row' <'col mt-2 input-group flex-nowrap' B l > <'col-auto mt-2' < p >>>"
|
||||||
pagingType: 'simple_numbers'
|
pagingType: 'simple_numbers'
|
||||||
searching: true
|
searching: false
|
||||||
stateSave: true
|
stateSave: true
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
33
config/packages/doctrine.php
Normal file
33
config/packages/doctrine.php
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class extends the default doctrine ORM configuration to enable native lazy objects on PHP 8.4+.
|
||||||
|
* We have to do this in a PHP file, because the yaml file does not support conditionals on PHP version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return static function(\Symfony\Config\DoctrineConfig $doctrine) {
|
||||||
|
//On PHP 8.4+ we can use native lazy objects, which are much more efficient than proxies.
|
||||||
|
if (PHP_VERSION_ID >= 80400) {
|
||||||
|
$doctrine->orm()->enableNativeLazyObjects(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -25,10 +25,6 @@ doctrine:
|
||||||
tinyint:
|
tinyint:
|
||||||
class: App\Doctrine\Types\TinyIntType
|
class: App\Doctrine\Types\TinyIntType
|
||||||
|
|
||||||
# This was removed in doctrine/orm 4.0 but we need it for the WebauthnKey entity
|
|
||||||
array:
|
|
||||||
class: App\Doctrine\Types\ArrayType
|
|
||||||
|
|
||||||
schema_filter: ~^(?!internal)~
|
schema_filter: ~^(?!internal)~
|
||||||
# Only enable this when needed
|
# Only enable this when needed
|
||||||
profiling_collect_backtrace: false
|
profiling_collect_backtrace: false
|
||||||
|
|
@ -39,6 +35,8 @@ doctrine:
|
||||||
report_fields_where_declared: true
|
report_fields_where_declared: true
|
||||||
validate_xml_mapping: true
|
validate_xml_mapping: true
|
||||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||||
|
identity_generation_preferences:
|
||||||
|
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
|
||||||
auto_mapping: true
|
auto_mapping: true
|
||||||
controller_resolver:
|
controller_resolver:
|
||||||
auto_mapping: true
|
auto_mapping: true
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||||
framework:
|
framework:
|
||||||
secret: '%env(APP_SECRET)%'
|
secret: '%env(APP_SECRET)%'
|
||||||
csrf_protection: true
|
|
||||||
annotations: false
|
|
||||||
handle_all_throwables: true
|
|
||||||
|
|
||||||
# We set this header by ourselves, so we can disable it here
|
# We set this header by ourselves, so we can disable it here
|
||||||
disallow_search_engine_index: false
|
disallow_search_engine_index: false
|
||||||
|
|
@ -30,8 +27,11 @@ framework:
|
||||||
|
|
||||||
#esi: true
|
#esi: true
|
||||||
#fragments: true
|
#fragments: true
|
||||||
php_errors:
|
|
||||||
log: true
|
|
||||||
|
form: { csrf_protection: { token_id: 'submit' } }
|
||||||
|
csrf_protection:
|
||||||
|
stateless_token_ids: ['submit', 'authenticate', 'logout']
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
framework:
|
framework:
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ knpu_oauth2_client:
|
||||||
type: generic
|
type: generic
|
||||||
provider_class: '\League\OAuth2\Client\Provider\GenericProvider'
|
provider_class: '\League\OAuth2\Client\Provider\GenericProvider'
|
||||||
|
|
||||||
client_id: '%env(PROVIDER_DIGIKEY_CLIENT_ID)%'
|
client_id: '%env(settings:digikey:clientId)%'
|
||||||
client_secret: '%env(PROVIDER_DIGIKEY_SECRET)%'
|
client_secret: '%env(settings:digikey:secret)%'
|
||||||
|
|
||||||
redirect_route: 'oauth_client_check'
|
redirect_route: 'oauth_client_check'
|
||||||
redirect_params: {name: 'ip_digikey_oauth'}
|
redirect_params: {name: 'ip_digikey_oauth'}
|
||||||
|
|
@ -26,8 +26,8 @@ knpu_oauth2_client:
|
||||||
type: generic
|
type: generic
|
||||||
provider_class: '\League\OAuth2\Client\Provider\GenericProvider'
|
provider_class: '\League\OAuth2\Client\Provider\GenericProvider'
|
||||||
|
|
||||||
client_id: '%env(PROVIDER_OCTOPART_CLIENT_ID)%'
|
client_id: '%env(settings:octopart:clientId)%'
|
||||||
client_secret: '%env(PROVIDER_OCTOPART_SECRET)%'
|
client_secret: '%env(settings:octopart:secret)%'
|
||||||
|
|
||||||
redirect_route: 'oauth_client_check'
|
redirect_route: 'oauth_client_check'
|
||||||
redirect_params: { name: 'ip_octopart_oauth' }
|
redirect_params: { name: 'ip_octopart_oauth' }
|
||||||
|
|
@ -35,4 +35,4 @@ knpu_oauth2_client:
|
||||||
provider_options:
|
provider_options:
|
||||||
urlAuthorize: 'https://identity.nexar.com/connect/authorize'
|
urlAuthorize: 'https://identity.nexar.com/connect/authorize'
|
||||||
urlAccessToken: 'https://identity.nexar.com/connect/token'
|
urlAccessToken: 'https://identity.nexar.com/connect/token'
|
||||||
urlResourceOwnerDetails: ''
|
urlResourceOwnerDetails: ''
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,6 @@ when@dev:
|
||||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
level: debug
|
level: debug
|
||||||
channels: ["!event"]
|
channels: ["!event"]
|
||||||
# uncomment to get logging in your browser
|
|
||||||
# you may have to allow bigger header sizes in your Web server configuration
|
|
||||||
#firephp:
|
|
||||||
# type: firephp
|
|
||||||
# level: info
|
|
||||||
#chromephp:
|
|
||||||
# type: chromephp
|
|
||||||
# level: info
|
|
||||||
console:
|
console:
|
||||||
type: console
|
type: console
|
||||||
process_psr_3_messages: false
|
process_psr_3_messages: false
|
||||||
|
|
@ -45,6 +37,7 @@ when@prod:
|
||||||
action_level: error
|
action_level: error
|
||||||
handler: nested
|
handler: nested
|
||||||
excluded_http_codes: [404, 405]
|
excluded_http_codes: [404, 405]
|
||||||
|
channels: ["!deprecation"]
|
||||||
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
||||||
nested:
|
nested:
|
||||||
type: stream
|
type: stream
|
||||||
|
|
@ -69,6 +62,7 @@ when@docker:
|
||||||
excluded_http_codes: [404, 405]
|
excluded_http_codes: [404, 405]
|
||||||
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
||||||
include_stacktraces: true
|
include_stacktraces: true
|
||||||
|
channels: ["!deprecation"]
|
||||||
nested:
|
nested:
|
||||||
type: stream
|
type: stream
|
||||||
path: "php://stderr"
|
path: "php://stderr"
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,6 @@ nelmio_security:
|
||||||
- 'digikey.com'
|
- 'digikey.com'
|
||||||
- 'nexar.com'
|
- 'nexar.com'
|
||||||
|
|
||||||
# forces Microsoft's XSS-Protection with
|
|
||||||
# its block mode
|
|
||||||
xss_protection:
|
|
||||||
enabled: true
|
|
||||||
mode_block: true
|
|
||||||
|
|
||||||
# Send a full URL in the `Referer` header when performing a same-origin request,
|
# Send a full URL in the `Referer` header when performing a same-origin request,
|
||||||
# only send the origin of the document to secure destination (HTTPS->HTTPS),
|
# only send the origin of the document to secure destination (HTTPS->HTTPS),
|
||||||
# and send no header to a less secure destination (HTTPS->HTTP).
|
# and send no header to a less secure destination (HTTPS->HTTP).
|
||||||
|
|
@ -69,9 +63,3 @@ nelmio_security:
|
||||||
- 'data:'
|
- 'data:'
|
||||||
block-all-mixed-content: true # defaults to false, blocks HTTP content over HTTPS transport
|
block-all-mixed-content: true # defaults to false, blocks HTTP content over HTTPS transport
|
||||||
# upgrade-insecure-requests: true # defaults to false, upgrades HTTP requests to HTTPS transport
|
# upgrade-insecure-requests: true # defaults to false, upgrades HTTP requests to HTTPS transport
|
||||||
|
|
||||||
when@dev:
|
|
||||||
# disables the Content-Security-Policy header
|
|
||||||
nelmio_security:
|
|
||||||
csp:
|
|
||||||
enabled: false
|
|
||||||
3
config/packages/property_info.yaml
Normal file
3
config/packages/property_info.yaml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
framework:
|
||||||
|
property_info:
|
||||||
|
with_constructor_extractor: true
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
framework:
|
framework:
|
||||||
router:
|
router:
|
||||||
utf8: true
|
|
||||||
|
|
||||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||||
default_uri: '%env(DEFAULT_URI)%'
|
default_uri: '%env(DEFAULT_URI)%'
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ security:
|
||||||
|
|
||||||
firewalls:
|
firewalls:
|
||||||
dev:
|
dev:
|
||||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
pattern: ^/(_(profiler|wdt)|css|images|js|\.well-known)/
|
||||||
security: false
|
security: false
|
||||||
main:
|
main:
|
||||||
provider: app_user_provider
|
provider: app_user_provider
|
||||||
|
|
|
||||||
15
config/packages/settings.yaml
Normal file
15
config/packages/settings.yaml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
jbtronics_settings:
|
||||||
|
default_storage_adapter: Jbtronics\SettingsBundle\Storage\ORMStorageAdapter
|
||||||
|
|
||||||
|
cache:
|
||||||
|
default_cacheable: true
|
||||||
|
|
||||||
|
orm_storage:
|
||||||
|
default_entity_class: App\Entity\SettingsEntry
|
||||||
|
|
||||||
|
|
||||||
|
# Disable caching for development environment
|
||||||
|
when@dev:
|
||||||
|
jbtronics_settings:
|
||||||
|
cache:
|
||||||
|
default_cacheable: false
|
||||||
|
|
@ -5,6 +5,12 @@ florianv_swap:
|
||||||
|
|
||||||
providers:
|
providers:
|
||||||
european_central_bank: ~ # European Central Bank (only works for EUR base currency)
|
european_central_bank: ~ # European Central Bank (only works for EUR base currency)
|
||||||
fixer: # Fixer.io (needs an API key)
|
central_bank_of_czech_republic: ~
|
||||||
access_key: "%env(FIXER_API_KEY)%"
|
central_bank_of_republic_turkey: ~
|
||||||
#exchange_rates_api: ~
|
national_bank_of_romania: ~
|
||||||
|
|
||||||
|
fixer: # Fixer.io (needs an API key)
|
||||||
|
access_key: "%env(string:settings:exchange_rate:fixerApiKey)%"
|
||||||
|
|
||||||
|
frankfurter: ~
|
||||||
|
fawazahmed_currency_api: ~
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
framework:
|
framework:
|
||||||
default_locale: '%partdb.locale%'
|
default_locale: 'en'
|
||||||
# Just enable the locales we need for performance reasons.
|
# Just enable the locales we need for performance reasons.
|
||||||
enabled_locale: '%partdb.locale_menu%'
|
enabled_locale: '%partdb.locale_menu%'
|
||||||
translator:
|
translator:
|
||||||
default_path: '%kernel.project_dir%/translations'
|
default_path: '%kernel.project_dir%/translations'
|
||||||
fallbacks:
|
fallbacks:
|
||||||
- '%partdb.locale%'
|
|
||||||
- 'en'
|
- 'en'
|
||||||
providers:
|
providers:
|
||||||
# crowdin:
|
# crowdin:
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,17 @@
|
||||||
twig:
|
twig:
|
||||||
default_path: '%kernel.project_dir%/templates'
|
default_path: '%kernel.project_dir%/templates'
|
||||||
form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'form/extended_bootstrap_layout.html.twig', 'form/permission_layout.html.twig', 'form/filter_types_layout.html.twig']
|
form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'form/extended_bootstrap_layout.html.twig', 'form/permission_layout.html.twig', 'form/filter_types_layout.html.twig', 'form/synonyms_collection.html.twig']
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
'%kernel.project_dir%/assets/css': css
|
'%kernel.project_dir%/assets/css': css
|
||||||
|
|
||||||
globals:
|
globals:
|
||||||
partdb_title: '%partdb.title%'
|
|
||||||
default_currency: '%partdb.default_currency%'
|
|
||||||
global_theme: '%partdb.global_theme%'
|
|
||||||
allow_email_pw_reset: '%partdb.users.email_pw_reset%'
|
allow_email_pw_reset: '%partdb.users.email_pw_reset%'
|
||||||
locale_menu: '%partdb.locale_menu%'
|
locale_menu: '%partdb.locale_menu%'
|
||||||
attachment_manager: '@App\Services\Attachments\AttachmentManager'
|
attachment_manager: '@App\Services\Attachments\AttachmentManager'
|
||||||
label_profile_dropdown_helper: '@App\Services\LabelSystem\LabelProfileDropdownHelper'
|
label_profile_dropdown_helper: '@App\Services\LabelSystem\LabelProfileDropdownHelper'
|
||||||
error_page_admin_email: '%partdb.error_pages.admin_email%'
|
error_page_admin_email: '%partdb.error_pages.admin_email%'
|
||||||
error_page_show_help: '%partdb.error_pages.show_help%'
|
error_page_show_help: '%partdb.error_pages.show_help%'
|
||||||
sidebar_items: '%partdb.sidebar.items%'
|
|
||||||
sidebar_tree_updater: '@App\Services\Trees\SidebarTreeUpdater'
|
sidebar_tree_updater: '@App\Services\Trees\SidebarTreeUpdater'
|
||||||
avatar_helper: '@App\Services\UserSystem\UserAvatarHelper'
|
avatar_helper: '@App\Services\UserSystem\UserAvatarHelper'
|
||||||
available_themes: '%partdb.available_themes%'
|
available_themes: '%partdb.available_themes%'
|
||||||
|
|
@ -24,4 +20,4 @@ twig:
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
twig:
|
twig:
|
||||||
strict_variables: true
|
strict_variables: true
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
framework:
|
|
||||||
uid:
|
|
||||||
default_uuid_version: 7
|
|
||||||
time_based_uuid_version: 7
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
ux_translator:
|
ux_translator:
|
||||||
# The directory where the JavaScript translations are dumped
|
# The directory where the JavaScript translations are dumped
|
||||||
dump_directory: '%kernel.project_dir%/var/translations'
|
dump_directory: '%kernel.project_dir%/var/translations'
|
||||||
|
|
||||||
|
when@prod:
|
||||||
|
ux_translator:
|
||||||
|
# Control whether TypeScript types are dumped alongside translations.
|
||||||
|
# Disable this if you do not use TypeScript (e.g. in production when using AssetMapper), to speed up cache warmup.
|
||||||
|
# dump_typescript: false
|
||||||
|
|
|
||||||
4
config/packages/ux_turbo.yaml
Normal file
4
config/packages/ux_turbo.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Enable stateless CSRF protection for forms and logins/logouts
|
||||||
|
framework:
|
||||||
|
csrf_protection:
|
||||||
|
check_header: true
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
framework:
|
framework:
|
||||||
validation:
|
validation:
|
||||||
email_validation_mode: html5
|
|
||||||
|
|
||||||
# Enables validator auto-mapping support.
|
# Enables validator auto-mapping support.
|
||||||
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
||||||
#auto_mapping:
|
#auto_mapping:
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
when@dev:
|
when@dev:
|
||||||
web_profiler:
|
web_profiler:
|
||||||
toolbar: true
|
toolbar:
|
||||||
intercept_redirects: false
|
ajax_replace: true
|
||||||
|
|
||||||
framework:
|
framework:
|
||||||
profiler:
|
profiler:
|
||||||
only_exceptions: false
|
|
||||||
collect_serializer_data: true
|
collect_serializer_data: true
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
web_profiler:
|
|
||||||
toolbar: false
|
|
||||||
intercept_redirects: false
|
|
||||||
|
|
||||||
framework:
|
framework:
|
||||||
profiler: { collect: false }
|
profiler:
|
||||||
|
collect: false
|
||||||
|
collect_serializer_data: true
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,12 @@ parameters:
|
||||||
######################################################################################################################
|
######################################################################################################################
|
||||||
# Common
|
# Common
|
||||||
######################################################################################################################
|
######################################################################################################################
|
||||||
partdb.locale: '%env(string:DEFAULT_LANG)%' # The default language to use serverwide
|
|
||||||
partdb.timezone: '%env(string:DEFAULT_TIMEZONE)%' # The default timezone
|
|
||||||
partdb.title: '%env(trim:string:INSTANCE_NAME)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage)
|
|
||||||
partdb.banner: '%env(trim:string:BANNER)%' # The info text shown in the homepage, if empty config/banner.md is used
|
|
||||||
partdb.default_currency: '%env(string:BASE_CURRENCY)%' # The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country
|
|
||||||
partdb.global_theme: '' # The theme to use globally (see public/build/themes/ for choices, use name without .css). Set to '' for default bootstrap theme
|
|
||||||
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] # The languages that are shown in user drop down menu
|
|
||||||
partdb.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
|
# This is used as workaround for places where we can not access the settings directly (like the 2FA application names)
|
||||||
|
partdb.title: '%env(string:settings:customization:instanceName)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage)
|
||||||
|
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl', 'hu'] # The languages that are shown in user drop down menu
|
||||||
|
|
||||||
|
partdb.default_uri: '%env(addSlash: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.
|
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.
|
||||||
|
|
||||||
|
|
@ -22,11 +18,8 @@ parameters:
|
||||||
# Users and Privacy
|
# Users and Privacy
|
||||||
######################################################################################################################
|
######################################################################################################################
|
||||||
partdb.gdpr_compliance: true # If this option is activated, IP addresses are anonymized to be GDPR compliant
|
partdb.gdpr_compliance: true # If this option is activated, IP addresses are anonymized to be GDPR compliant
|
||||||
partdb.users.use_gravatar: '%env(bool:USE_GRAVATAR)%' # Set to false, if no Gravatar images should be used for user profiles.
|
|
||||||
partdb.users.email_pw_reset: '%env(bool:ALLOW_EMAIL_PW_RESET)%' # Config if users are able, to reset their password by email. By default this enabled, when a mail server is configured.
|
partdb.users.email_pw_reset: '%env(bool:ALLOW_EMAIL_PW_RESET)%' # Config if users are able, to reset their password by email. By default this enabled, when a mail server is configured.
|
||||||
|
|
||||||
partdb.check_for_updates: '%env(bool:CHECK_FOR_UPDATES)' # Set to false, if Part-DB should not contact the GitHub API to check for updates
|
|
||||||
|
|
||||||
######################################################################################################################
|
######################################################################################################################
|
||||||
# Mail settings
|
# Mail settings
|
||||||
######################################################################################################################
|
######################################################################################################################
|
||||||
|
|
@ -36,11 +29,8 @@ parameters:
|
||||||
######################################################################################################################
|
######################################################################################################################
|
||||||
# Attachments and files
|
# Attachments and files
|
||||||
######################################################################################################################
|
######################################################################################################################
|
||||||
partdb.attachments.allow_downloads: '%env(bool:ALLOW_ATTACHMENT_DOWNLOADS)%' # Allow users to download attachments to server. Warning: This can be dangerous, because via that feature attackers maybe can access ressources on your intranet!
|
|
||||||
partdb.attachments.download_by_default: '%env(bool:ATTACHMENT_DOWNLOAD_BY_DEFAULT)%' # If this is set the 'download external files' checkbox is set by default for new attachments (only if allow_downloads is set to true)
|
|
||||||
partdb.attachments.dir.media: 'public/media/' # The folder where uploaded attachment files are saved (must be in public folder)
|
partdb.attachments.dir.media: 'public/media/' # The folder where uploaded attachment files are saved (must be in public folder)
|
||||||
partdb.attachments.dir.secure: 'uploads/' # The folder where secured attachment files are saved (must not be in public/)
|
partdb.attachments.dir.secure: 'uploads/' # The folder where secured attachment files are saved (must not be in public/)
|
||||||
partdb.attachments.max_file_size: '%env(string:MAX_ATTACHMENT_FILE_SIZE)%' # The maximum size of an attachment file (in bytes, you can use M for megabytes and G for gigabytes)
|
|
||||||
|
|
||||||
######################################################################################################################
|
######################################################################################################################
|
||||||
# Error pages
|
# Error pages
|
||||||
|
|
@ -53,22 +43,6 @@ parameters:
|
||||||
######################################################################################################################
|
######################################################################################################################
|
||||||
partdb.saml.enabled: '%env(bool:SAML_ENABLED)%' # If this is set to true, SAML authentication is enabled
|
partdb.saml.enabled: '%env(bool:SAML_ENABLED)%' # If this is set to true, SAML authentication is enabled
|
||||||
|
|
||||||
######################################################################################################################
|
|
||||||
# Table settings
|
|
||||||
######################################################################################################################
|
|
||||||
partdb.table.default_page_size: '%env(int:TABLE_DEFAULT_PAGE_SIZE)%' # The default number of entries shown per page in tables
|
|
||||||
partdb.table.parts.default_columns: '%env(trim:string:TABLE_PARTS_DEFAULT_COLUMNS)%' # The default columns in part tables and their order
|
|
||||||
|
|
||||||
######################################################################################################################
|
|
||||||
# Sidebar
|
|
||||||
######################################################################################################################
|
|
||||||
# You can configures the default shown tree items in the sidebar here. You can add or remove entries here, to change the number of trees in the sidebar. The possible entries are: categories, locations, footprints, manufacturers, suppliers, devices, tools
|
|
||||||
partdb.sidebar.items:
|
|
||||||
- categories
|
|
||||||
- devices
|
|
||||||
- tools
|
|
||||||
partdb.sidebar.root_expanded: true # If this is set to true, the root node of the sidebar is expanded by default
|
|
||||||
partdb.sidebar.root_node_enable: true # Put all entities below a root node in the sidebar
|
|
||||||
|
|
||||||
######################################################################################################################
|
######################################################################################################################
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
|
|
@ -110,30 +84,18 @@ parameters:
|
||||||
# Env default values
|
# Env default values
|
||||||
######################################################################################################################
|
######################################################################################################################
|
||||||
|
|
||||||
env(DEFAULT_LANG): 'en'
|
|
||||||
env(DEFAULT_TIMEZONE): 'Europe/Berlin'
|
|
||||||
env(INSTANCE_NAME): 'Part-DB'
|
|
||||||
env(BASE_CURRENCY): 'EUR'
|
|
||||||
env(USE_GRAVATAR): '0'
|
|
||||||
env(MAX_ATTACHMENT_FILE_SIZE): '100M'
|
|
||||||
|
|
||||||
env(REDIRECT_TO_HTTPS): 0
|
env(REDIRECT_TO_HTTPS): 0
|
||||||
|
|
||||||
env(ENFORCE_CHANGE_COMMENTS_FOR): ''
|
|
||||||
|
|
||||||
env(ERROR_PAGE_ADMIN_EMAIL): ''
|
env(ERROR_PAGE_ADMIN_EMAIL): ''
|
||||||
env(ERROR_PAGE_SHOW_HELP): 1
|
env(ERROR_PAGE_SHOW_HELP): 1
|
||||||
|
|
||||||
env(DEMO_MODE): 0
|
env(DEMO_MODE): 0
|
||||||
env(BANNER): ''
|
|
||||||
|
|
||||||
|
|
||||||
env(EMAIL_SENDER_EMAIL): 'noreply@partdb.changeme'
|
env(EMAIL_SENDER_EMAIL): 'noreply@partdb.changeme'
|
||||||
env(EMAIL_SENDER_NAME): 'Part-DB Mailer'
|
env(EMAIL_SENDER_NAME): 'Part-DB Mailer'
|
||||||
env(ALLOW_EMAIL_PW_RESET): 0
|
env(ALLOW_EMAIL_PW_RESET): 0
|
||||||
|
|
||||||
env(TABLE_DEFAULT_PAGE_SIZE): 50
|
|
||||||
|
|
||||||
env(TRUSTED_PROXIES): '127.0.0.1' #By default trust only our own server
|
env(TRUSTED_PROXIES): '127.0.0.1' #By default trust only our own server
|
||||||
env(TRUSTED_HOSTS): '' # Trust all host names by default
|
env(TRUSTED_HOSTS): '' # Trust all host names by default
|
||||||
|
|
||||||
|
|
@ -141,11 +103,10 @@ parameters:
|
||||||
|
|
||||||
env(SAML_ROLE_MAPPING): '{}'
|
env(SAML_ROLE_MAPPING): '{}'
|
||||||
|
|
||||||
env(HISTORY_SAVE_CHANGED_DATA): 1
|
|
||||||
env(HISTORY_SAVE_CHANGED_FIELDS): 1
|
|
||||||
env(HISTORY_SAVE_REMOVED_DATA): 1
|
|
||||||
env(HISTORY_SAVE_NEW_DATA): 1
|
|
||||||
|
|
||||||
env(EDA_KICAD_CATEGORY_DEPTH): 0
|
|
||||||
|
|
||||||
env(DATABASE_EMULATE_NATURAL_SORT): 0
|
env(DATABASE_EMULATE_NATURAL_SORT): 0
|
||||||
|
|
||||||
|
######################################################################################################################
|
||||||
|
# Bulk Info Provider Import Configuration
|
||||||
|
######################################################################################################################
|
||||||
|
partdb.bulk_import.batch_size: 20 # Number of parts to process in each batch during bulk operations
|
||||||
|
partdb.bulk_import.max_parts_per_operation: 1000 # Maximum number of parts allowed per bulk import operation
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||||
|
|
||||||
parts: # e.g. this maps to perms_parts in User/Group database
|
parts: # e.g. this maps to perms_parts in User/Group database
|
||||||
group: "data"
|
group: "data"
|
||||||
label: "perm.parts"
|
label: "[[Part]]"
|
||||||
operations: # Here are all possible operations are listed => the op name is mapped to bit value
|
operations: # Here are all possible operations are listed => the op name is mapped to bit value
|
||||||
read:
|
read:
|
||||||
label: "perm.read"
|
label: "perm.read"
|
||||||
# If a part can be read by a user, he can also see all the datastructures (except devices)
|
# If a part can be read by a user, he can also see all the datastructures (except devices)
|
||||||
alsoSet: ['storelocations.read', 'footprints.read', 'categories.read', 'suppliers.read', 'manufacturers.read',
|
alsoSet: ['storelocations.read', 'footprints.read', 'categories.read', 'suppliers.read', 'manufacturers.read',
|
||||||
'currencies.read', 'attachment_types.read', 'measurement_units.read']
|
'currencies.read', 'attachment_types.read', 'measurement_units.read', 'part_custom_states.read']
|
||||||
apiTokenRole: ROLE_API_READ_ONLY
|
apiTokenRole: ROLE_API_READ_ONLY
|
||||||
edit:
|
edit:
|
||||||
label: "perm.edit"
|
label: "perm.edit"
|
||||||
|
|
@ -71,7 +71,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||||
|
|
||||||
|
|
||||||
storelocations: &PART_CONTAINING
|
storelocations: &PART_CONTAINING
|
||||||
label: "perm.storelocations"
|
label: "[[Storage_location]]"
|
||||||
group: "data"
|
group: "data"
|
||||||
operations:
|
operations:
|
||||||
read:
|
read:
|
||||||
|
|
@ -103,35 +103,39 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||||
|
|
||||||
footprints:
|
footprints:
|
||||||
<<: *PART_CONTAINING
|
<<: *PART_CONTAINING
|
||||||
label: "perm.part.footprints"
|
label: "[[Footprint]]"
|
||||||
|
|
||||||
categories:
|
categories:
|
||||||
<<: *PART_CONTAINING
|
<<: *PART_CONTAINING
|
||||||
label: "perm.part.categories"
|
label: "[[Category]]"
|
||||||
|
|
||||||
suppliers:
|
suppliers:
|
||||||
<<: *PART_CONTAINING
|
<<: *PART_CONTAINING
|
||||||
label: "perm.part.supplier"
|
label: "[[Supplier]]"
|
||||||
|
|
||||||
manufacturers:
|
manufacturers:
|
||||||
<<: *PART_CONTAINING
|
<<: *PART_CONTAINING
|
||||||
label: "perm.part.manufacturers"
|
label: "[[Manufacturer]]"
|
||||||
|
|
||||||
projects:
|
projects:
|
||||||
<<: *PART_CONTAINING
|
<<: *PART_CONTAINING
|
||||||
label: "perm.projects"
|
label: "[[Project]]"
|
||||||
|
|
||||||
attachment_types:
|
attachment_types:
|
||||||
<<: *PART_CONTAINING
|
<<: *PART_CONTAINING
|
||||||
label: "perm.part.attachment_types"
|
label: "[[Attachment_type]]"
|
||||||
|
|
||||||
currencies:
|
currencies:
|
||||||
<<: *PART_CONTAINING
|
<<: *PART_CONTAINING
|
||||||
label: "perm.currencies"
|
label: "[[Currency]]"
|
||||||
|
|
||||||
measurement_units:
|
measurement_units:
|
||||||
<<: *PART_CONTAINING
|
<<: *PART_CONTAINING
|
||||||
label: "perm.measurement_units"
|
label: "[[Measurement_unit]]"
|
||||||
|
|
||||||
|
part_custom_states:
|
||||||
|
<<: *PART_CONTAINING
|
||||||
|
label: "[[Part_custom_state]]"
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
label: "perm.part.tools"
|
label: "perm.part.tools"
|
||||||
|
|
@ -265,17 +269,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||||
# label: "perm.database.write_db_settings"
|
# label: "perm.database.write_db_settings"
|
||||||
# alsoSet: ['read_db_settings', 'see_status']
|
# alsoSet: ['read_db_settings', 'see_status']
|
||||||
|
|
||||||
#config:
|
config:
|
||||||
# label: "perm.config"
|
label: "perm.config"
|
||||||
# group: "system"
|
group: "system"
|
||||||
# operations:
|
operations:
|
||||||
# read_config:
|
change_system_settings:
|
||||||
# label: "perm.config.read_config"
|
label: "perm.config.change_system_settings"
|
||||||
# edit_config:
|
apiTokenRole: ROLE_API_ADMIN
|
||||||
# label: "perm.config.edit_config"
|
|
||||||
# alsoSet: 'read_config'
|
|
||||||
# server_info:
|
|
||||||
# label: "perm.config.server_info"
|
|
||||||
|
|
||||||
system:
|
system:
|
||||||
label: "perm.system"
|
label: "perm.system"
|
||||||
|
|
@ -363,6 +363,10 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||||
label: "perm.revert_elements"
|
label: "perm.revert_elements"
|
||||||
alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles', 'delete_profiles']
|
alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles', 'delete_profiles']
|
||||||
apiTokenRole: ROLE_API_EDIT
|
apiTokenRole: ROLE_API_EDIT
|
||||||
|
import:
|
||||||
|
label: "perm.import"
|
||||||
|
alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles' ]
|
||||||
|
apiTokenRole: ROLE_API_EDIT
|
||||||
|
|
||||||
api:
|
api:
|
||||||
label: "perm.api"
|
label: "perm.api"
|
||||||
|
|
@ -373,4 +377,4 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||||
manage_tokens:
|
manage_tokens:
|
||||||
label: "perm.api.manage_tokens"
|
label: "perm.api.manage_tokens"
|
||||||
alsoSet: ['access_api']
|
alsoSet: ['access_api']
|
||||||
apiTokenRole: ROLE_API_FULL
|
apiTokenRole: ROLE_API_FULL
|
||||||
|
|
|
||||||
2901
config/reference.php
Normal file
2901
config/reference.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,12 @@
|
||||||
|
# yaml-language-server: $schema=../vendor/symfony/routing/Loader/schema/routing.schema.json
|
||||||
|
|
||||||
|
# This file is the entry point to configure the routes of your app.
|
||||||
|
# Methods with the #[Route] attribute are automatically imported.
|
||||||
|
# See also https://symfony.com/doc/current/routing.html
|
||||||
|
|
||||||
|
# To list all registered routes, run the following command:
|
||||||
|
# bin/console debug:router
|
||||||
|
|
||||||
# Redirect every url without an locale to the locale of the user/the global base locale
|
# Redirect every url without an locale to the locale of the user/the global base locale
|
||||||
|
|
||||||
scan_qr:
|
scan_qr:
|
||||||
|
|
@ -16,4 +25,4 @@ redirector:
|
||||||
url: ".*"
|
url: ".*"
|
||||||
controller: App\Controller\RedirectController::addLocalePart
|
controller: App\Controller\RedirectController::addLocalePart
|
||||||
# Dont match localized routes (no redirection loop, if no root with that name exists) or API prefixed routes
|
# Dont match localized routes (no redirection loop, if no root with that name exists) or API prefixed routes
|
||||||
condition: "not (request.getPathInfo() matches '/^\\\\/([a-z]{2}(_[A-Z]{2})?|api)\\\\//')"
|
condition: "not (request.getPathInfo() matches '/^\\\\/([a-z]{2}(_[A-Z]{2})?|api)\\\\//')"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
when@dev:
|
when@dev:
|
||||||
_errors:
|
_errors:
|
||||||
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
|
||||||
prefix: /_error
|
prefix: /_error
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
when@dev:
|
when@dev:
|
||||||
web_profiler_wdt:
|
web_profiler_wdt:
|
||||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
|
||||||
prefix: /_wdt
|
prefix: /_wdt
|
||||||
|
|
||||||
web_profiler_profiler:
|
web_profiler_profiler:
|
||||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
|
||||||
prefix: /_profiler
|
prefix: /_profiler
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
# yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json
|
||||||
|
|
||||||
# This file is the entry point to configure your own services.
|
# This file is the entry point to configure your own services.
|
||||||
# Files in the packages/ subdirectory configure your dependencies.
|
# Files in the packages/ subdirectory configure your dependencies.
|
||||||
|
# See also https://symfony.com/doc/current/service_container/import.html
|
||||||
|
|
||||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||||
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
|
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
|
||||||
|
|
@ -17,8 +20,6 @@ services:
|
||||||
bool $gdpr_compliance: '%partdb.gdpr_compliance%'
|
bool $gdpr_compliance: '%partdb.gdpr_compliance%'
|
||||||
bool $kernel_debug_enabled: '%kernel.debug%'
|
bool $kernel_debug_enabled: '%kernel.debug%'
|
||||||
string $kernel_cache_dir: '%kernel.cache_dir%'
|
string $kernel_cache_dir: '%kernel.cache_dir%'
|
||||||
string $partdb_title: '%partdb.title%'
|
|
||||||
string $base_currency: '%partdb.default_currency%'
|
|
||||||
|
|
||||||
_instanceof:
|
_instanceof:
|
||||||
App\Services\LabelSystem\PlaceholderProviders\PlaceholderProviderInterface:
|
App\Services\LabelSystem\PlaceholderProviders\PlaceholderProviderInterface:
|
||||||
|
|
@ -32,9 +33,8 @@ services:
|
||||||
App\:
|
App\:
|
||||||
resource: '../src/'
|
resource: '../src/'
|
||||||
exclude:
|
exclude:
|
||||||
- '../src/DependencyInjection/'
|
|
||||||
- '../src/Entity/'
|
- '../src/Entity/'
|
||||||
- '../src/Kernel.php'
|
- '../src/Helpers/'
|
||||||
|
|
||||||
# controllers are imported separately to make sure services can be injected
|
# controllers are imported separately to make sure services can be injected
|
||||||
# as action arguments even if you don't extend any base controller class
|
# as action arguments even if you don't extend any base controller class
|
||||||
|
|
@ -76,28 +76,10 @@ services:
|
||||||
# Only the event classes specified here are saved to DB (set to []) to log all events
|
# Only the event classes specified here are saved to DB (set to []) to log all events
|
||||||
$whitelist: []
|
$whitelist: []
|
||||||
|
|
||||||
App\EventListener\LogSystem\EventLoggerListener:
|
|
||||||
arguments:
|
|
||||||
$save_changed_fields: '%env(bool:HISTORY_SAVE_CHANGED_FIELDS)%'
|
|
||||||
$save_changed_data: '%env(bool:HISTORY_SAVE_CHANGED_DATA)%'
|
|
||||||
$save_removed_data: '%env(bool:HISTORY_SAVE_REMOVED_DATA)%'
|
|
||||||
$save_new_data: '%env(bool:HISTORY_SAVE_NEW_DATA)%'
|
|
||||||
|
|
||||||
App\Form\AttachmentFormType:
|
|
||||||
arguments:
|
|
||||||
$allow_attachments_download: '%partdb.attachments.allow_downloads%'
|
|
||||||
$max_file_size: '%partdb.attachments.max_file_size%'
|
|
||||||
$download_by_default: '%partdb.attachments.download_by_default%'
|
|
||||||
|
|
||||||
App\Services\Attachments\AttachmentSubmitHandler:
|
App\Services\Attachments\AttachmentSubmitHandler:
|
||||||
arguments:
|
arguments:
|
||||||
$allow_attachments_downloads: '%partdb.attachments.allow_downloads%'
|
|
||||||
$mimeTypes: '@mime_types'
|
$mimeTypes: '@mime_types'
|
||||||
$max_upload_size: '%partdb.attachments.max_file_size%'
|
|
||||||
|
|
||||||
App\Services\LogSystem\EventCommentNeededHelper:
|
|
||||||
arguments:
|
|
||||||
$enforce_change_comments_for: '%partdb.enforce_change_comments_for%'
|
|
||||||
|
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
# Attachment system
|
# Attachment system
|
||||||
|
|
@ -156,29 +138,6 @@ services:
|
||||||
tags:
|
tags:
|
||||||
- { name: doctrine.orm.entity_listener }
|
- { name: doctrine.orm.entity_listener }
|
||||||
|
|
||||||
####################################################################################################################
|
|
||||||
# Price system
|
|
||||||
####################################################################################################################
|
|
||||||
App\Command\Currencies\UpdateExchangeRatesCommand:
|
|
||||||
arguments:
|
|
||||||
$base_current: '%partdb.default_currency%'
|
|
||||||
|
|
||||||
App\Form\Type\CurrencyEntityType:
|
|
||||||
arguments:
|
|
||||||
$base_currency: '%partdb.default_currency%'
|
|
||||||
|
|
||||||
App\Services\Parts\PricedetailHelper:
|
|
||||||
arguments:
|
|
||||||
$base_currency: '%partdb.default_currency%'
|
|
||||||
|
|
||||||
App\Services\Formatters\MoneyFormatter:
|
|
||||||
arguments:
|
|
||||||
$base_currency: '%partdb.default_currency%'
|
|
||||||
|
|
||||||
App\Services\Tools\ExchangeRateUpdater:
|
|
||||||
arguments:
|
|
||||||
$base_currency: '%partdb.default_currency%'
|
|
||||||
|
|
||||||
###################################################################################################################
|
###################################################################################################################
|
||||||
# User system
|
# User system
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
|
|
@ -186,10 +145,6 @@ services:
|
||||||
arguments:
|
arguments:
|
||||||
$demo_mode: '%partdb.demo_mode%'
|
$demo_mode: '%partdb.demo_mode%'
|
||||||
|
|
||||||
App\EventSubscriber\UserSystem\SetUserTimezoneSubscriber:
|
|
||||||
arguments:
|
|
||||||
$default_timezone: '%partdb.timezone%'
|
|
||||||
|
|
||||||
App\Controller\SecurityController:
|
App\Controller\SecurityController:
|
||||||
arguments:
|
arguments:
|
||||||
$allow_email_pw_reset: '%partdb.users.email_pw_reset%'
|
$allow_email_pw_reset: '%partdb.users.email_pw_reset%'
|
||||||
|
|
@ -203,10 +158,6 @@ services:
|
||||||
tags:
|
tags:
|
||||||
- { name: 'translation.extractor', alias: 'permissionExtractor'}
|
- { name: 'translation.extractor', alias: 'permissionExtractor'}
|
||||||
|
|
||||||
App\Services\UserSystem\UserAvatarHelper:
|
|
||||||
arguments:
|
|
||||||
$use_gravatar: '%partdb.users.use_gravatar%'
|
|
||||||
|
|
||||||
App\Form\Type\ThemeChoiceType:
|
App\Form\Type\ThemeChoiceType:
|
||||||
arguments:
|
arguments:
|
||||||
$available_themes: '%partdb.available_themes%'
|
$available_themes: '%partdb.available_themes%'
|
||||||
|
|
@ -222,9 +173,6 @@ services:
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
# Table settings
|
# Table settings
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
App\DataTables\PartsDataTable:
|
|
||||||
arguments:
|
|
||||||
$visible_columns: '%partdb.table.parts.default_columns%'
|
|
||||||
|
|
||||||
App\DataTables\Helpers\ColumnSortHelper:
|
App\DataTables\Helpers\ColumnSortHelper:
|
||||||
shared: false # Service has a state so not share it between different tables
|
shared: false # Service has a state so not share it between different tables
|
||||||
|
|
@ -246,14 +194,6 @@ services:
|
||||||
$fontDirectory: '%kernel.project_dir%/var/dompdf/fonts/'
|
$fontDirectory: '%kernel.project_dir%/var/dompdf/fonts/'
|
||||||
$tmpDirectory: '%kernel.project_dir%/var/dompdf/tmp/'
|
$tmpDirectory: '%kernel.project_dir%/var/dompdf/tmp/'
|
||||||
|
|
||||||
####################################################################################################################
|
|
||||||
# Trees
|
|
||||||
####################################################################################################################
|
|
||||||
App\Services\Trees\TreeViewGenerator:
|
|
||||||
arguments:
|
|
||||||
$rootNodeExpandedByDefault: '%partdb.sidebar.root_expanded%'
|
|
||||||
$rootNodeEnabled: '%partdb.sidebar.root_node_enable%'
|
|
||||||
|
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
# Part info provider system
|
# Part info provider system
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
|
|
@ -261,76 +201,12 @@ services:
|
||||||
arguments:
|
arguments:
|
||||||
$providers: !tagged_iterator 'app.info_provider'
|
$providers: !tagged_iterator 'app.info_provider'
|
||||||
|
|
||||||
App\Services\InfoProviderSystem\Providers\Element14Provider:
|
|
||||||
arguments:
|
|
||||||
$api_key: '%env(string:PROVIDER_ELEMENT14_KEY)%'
|
|
||||||
$store_id: '%env(string:PROVIDER_ELEMENT14_STORE_ID)%'
|
|
||||||
|
|
||||||
App\Services\InfoProviderSystem\Providers\DigikeyProvider:
|
|
||||||
arguments:
|
|
||||||
$clientId: '%env(string:PROVIDER_DIGIKEY_CLIENT_ID)%'
|
|
||||||
$currency: '%env(string:PROVIDER_DIGIKEY_CURRENCY)%'
|
|
||||||
$language: '%env(string:PROVIDER_DIGIKEY_LANGUAGE)%'
|
|
||||||
$country: '%env(string:PROVIDER_DIGIKEY_COUNTRY)%'
|
|
||||||
|
|
||||||
App\Services\InfoProviderSystem\Providers\TMEClient:
|
|
||||||
arguments:
|
|
||||||
$secret: '%env(string:PROVIDER_TME_SECRET)%'
|
|
||||||
$token: '%env(string:PROVIDER_TME_KEY)%'
|
|
||||||
|
|
||||||
App\Services\InfoProviderSystem\Providers\TMEProvider:
|
|
||||||
arguments:
|
|
||||||
$currency: '%env(string:PROVIDER_TME_CURRENCY)%'
|
|
||||||
$country: '%env(string:PROVIDER_TME_COUNTRY)%'
|
|
||||||
$language: '%env(string:PROVIDER_TME_LANGUAGE)%'
|
|
||||||
$get_gross_prices: '%env(bool:PROVIDER_TME_GET_GROSS_PRICES)%'
|
|
||||||
|
|
||||||
App\Services\InfoProviderSystem\Providers\OctopartProvider:
|
|
||||||
arguments:
|
|
||||||
$clientId: '&env(string:PROVIDER_OCTOPART_CLIENT_ID)%'
|
|
||||||
$secret: '%env(string:PROVIDER_OCTOPART_SECRET)%'
|
|
||||||
$country: '%env(string:PROVIDER_OCTOPART_COUNTRY)%'
|
|
||||||
$currency: '%env(string:PROVIDER_OCTOPART_CURRENCY)%'
|
|
||||||
$search_limit: '%env(int:PROVIDER_OCTOPART_SEARCH_LIMIT)%'
|
|
||||||
$onlyAuthorizedSellers: '%env(bool:PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS)%'
|
|
||||||
|
|
||||||
App\Services\InfoProviderSystem\Providers\MouserProvider:
|
|
||||||
arguments:
|
|
||||||
$api_key: '%env(string:PROVIDER_MOUSER_KEY)%'
|
|
||||||
$language: '%env(string:PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE)%'
|
|
||||||
$options: '%env(string:PROVIDER_MOUSER_SEARCH_OPTION)%'
|
|
||||||
$search_limit: '%env(int:PROVIDER_MOUSER_SEARCH_LIMIT)%'
|
|
||||||
|
|
||||||
App\Services\InfoProviderSystem\Providers\LCSCProvider:
|
|
||||||
arguments:
|
|
||||||
$enabled: '%env(bool:PROVIDER_LCSC_ENABLED)%'
|
|
||||||
$currency: '%env(string:PROVIDER_LCSC_CURRENCY)%'
|
|
||||||
|
|
||||||
App\Services\InfoProviderSystem\Providers\OEMSecretsProvider:
|
|
||||||
arguments:
|
|
||||||
$api_key: '%env(string:PROVIDER_OEMSECRETS_KEY)%'
|
|
||||||
$country_code: '%env(string:PROVIDER_OEMSECRETS_COUNTRY_CODE)%'
|
|
||||||
$currency: '%env(PROVIDER_OEMSECRETS_CURRENCY)%'
|
|
||||||
$zero_price: '%env(PROVIDER_OEMSECRETS_ZERO_PRICE)%'
|
|
||||||
$set_param: '%env(PROVIDER_OEMSECRETS_SET_PARAM)%'
|
|
||||||
$sort_criteria: '%env(PROVIDER_OEMSECRETS_SORT_CRITERIA)%'
|
|
||||||
|
|
||||||
|
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
# API system
|
# API system
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
App\State\PartDBInfoProvider:
|
App\State\PartDBInfoProvider:
|
||||||
arguments:
|
arguments:
|
||||||
$default_uri: '%partdb.default_uri%'
|
$default_uri: '%partdb.default_uri%'
|
||||||
$global_locale: '%partdb.locale%'
|
|
||||||
$global_timezone: '%partdb.timezone%'
|
|
||||||
|
|
||||||
####################################################################################################################
|
|
||||||
# EDA system
|
|
||||||
####################################################################################################################
|
|
||||||
App\Services\EDA\KiCadHelper:
|
|
||||||
arguments:
|
|
||||||
$category_depth: '%env(int:EDA_KICAD_CATEGORY_DEPTH)%'
|
|
||||||
|
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
# Symfony overrides
|
# Symfony overrides
|
||||||
|
|
@ -355,12 +231,17 @@ services:
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
App\Controller\RedirectController:
|
App\Controller\RedirectController:
|
||||||
arguments:
|
arguments:
|
||||||
$default_locale: '%partdb.locale%'
|
|
||||||
$enforce_index_php: '%env(bool:NO_URL_REWRITE_AVAILABLE)%'
|
$enforce_index_php: '%env(bool:NO_URL_REWRITE_AVAILABLE)%'
|
||||||
|
|
||||||
App\Doctrine\Purger\ResetAutoIncrementPurgerFactory:
|
App\Repository\PartRepository:
|
||||||
|
arguments:
|
||||||
|
$translator: '@translator'
|
||||||
|
tags: ['doctrine.repository_service']
|
||||||
|
|
||||||
|
App\EventSubscriber\UserSystem\PartUniqueIpnSubscriber:
|
||||||
tags:
|
tags:
|
||||||
- { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' }
|
- { name: doctrine.event_listener, event: onFlush, connection: default }
|
||||||
|
|
||||||
|
|
||||||
# We are needing this service inside a migration, where only the container is injected. So we need to define it as public, to access it from the container.
|
# We are needing this service inside a migration, where only the container is injected. So we need to define it as public, to access it from the container.
|
||||||
App\Services\UserSystem\PermissionPresetsHelper:
|
App\Services\UserSystem\PermissionPresetsHelper:
|
||||||
|
|
@ -370,14 +251,6 @@ services:
|
||||||
arguments:
|
arguments:
|
||||||
$project_dir: '%kernel.project_dir%'
|
$project_dir: '%kernel.project_dir%'
|
||||||
|
|
||||||
App\Services\System\UpdateAvailableManager:
|
|
||||||
arguments:
|
|
||||||
$check_for_updates: '%partdb.check_for_updates%'
|
|
||||||
|
|
||||||
App\Services\System\BannerHelper:
|
|
||||||
arguments:
|
|
||||||
$partdb_banner: '%partdb.banner%'
|
|
||||||
$project_dir: '%kernel.project_dir%'
|
|
||||||
|
|
||||||
App\Doctrine\Middleware\MySQLSSLConnectionMiddlewareWrapper:
|
App\Doctrine\Middleware\MySQLSSLConnectionMiddlewareWrapper:
|
||||||
arguments:
|
arguments:
|
||||||
|
|
@ -401,7 +274,11 @@ services:
|
||||||
tags:
|
tags:
|
||||||
- { name: monolog.processor }
|
- { name: monolog.processor }
|
||||||
|
|
||||||
when@test:
|
App\Doctrine\Purger\ResetAutoIncrementPurgerFactory:
|
||||||
|
tags:
|
||||||
|
- { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' }
|
||||||
|
|
||||||
|
when@test: &test
|
||||||
services:
|
services:
|
||||||
# Decorate the doctrine fixtures load command to use our custom purger by default
|
# Decorate the doctrine fixtures load command to use our custom purger by default
|
||||||
doctrine.fixtures_load_command.custom:
|
doctrine.fixtures_load_command.custom:
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ This allows external applications to interact with Part-DB, extend it or integra
|
||||||
> Some features might be missing or not working yet.
|
> Some features might be missing or not working yet.
|
||||||
> Also be aware, that there might be security issues in the API, which could allow attackers to access or edit data via
|
> Also be aware, that there might be security issues in the API, which could allow attackers to access or edit data via
|
||||||
> the API, which
|
> the API, which
|
||||||
> they normally should be able to access. So currently you should only use the API with trusted users and trusted
|
> they normally should not be able to access. So currently you should only use the API with trusted users and trusted
|
||||||
> applications.
|
> applications.
|
||||||
|
|
||||||
Part-DB uses [API Platform](https://api-platform.com/) to provide the API, which allows for easy creation of REST APIs
|
Part-DB uses [API Platform](https://api-platform.com/) to provide the API, which allows for easy creation of REST APIs
|
||||||
|
|
@ -46,7 +46,7 @@ See [Authentication chapter]({% link api/authentication.md %}) for more details.
|
||||||
|
|
||||||
The API is split into different endpoints, which are reachable under the `/api/` path of your Part-DB instance (
|
The API is split into different endpoints, which are reachable under the `/api/` path of your Part-DB instance (
|
||||||
e.g. `https://your-part-db.local/api/`).
|
e.g. `https://your-part-db.local/api/`).
|
||||||
There are various endpoints for each entity type (like `part`, `manufacturer`, etc.), which allow you to read and write data, and some special endpoints like `search` or `statistics`.
|
There are various endpoints for each entity type (like `parts`, `manufacturers`, etc.), which allow you to read and write data, and some special endpoints like `search` or `statistics`.
|
||||||
|
|
||||||
For example, all API endpoints for managing categories are available under `/api/categories/`. Depending on the exact
|
For example, all API endpoints for managing categories are available under `/api/categories/`. Depending on the exact
|
||||||
path and the HTTP method used, you can read, create, update or delete categories.
|
path and the HTTP method used, you can read, create, update or delete categories.
|
||||||
|
|
@ -56,7 +56,7 @@ For most entities, there are endpoints like this:
|
||||||
* **POST**: `/api/categories/` - Create a new category
|
* **POST**: `/api/categories/` - Create a new category
|
||||||
* **GET**: `/api/categories/{id}` - Get a specific category by its ID
|
* **GET**: `/api/categories/{id}` - Get a specific category by its ID
|
||||||
* **DELETE**: `/api/categories/{id}` - Delete a specific category by its ID
|
* **DELETE**: `/api/categories/{id}` - Delete a specific category by its ID
|
||||||
* **UPDATE**: `/api/categories/{id}` - Update a specific category by its ID. Only the fields which are sent in the
|
* **PATCH**: `/api/categories/{id}` - Update a specific category by its ID. Only the fields which are sent in the
|
||||||
request are updated, all other fields are left unchanged.
|
request are updated, all other fields are left unchanged.
|
||||||
Be aware that you have to set the [JSON Merge Patch](https://datatracker.ietf.org/doc/html/rfc7386) content type
|
Be aware that you have to set the [JSON Merge Patch](https://datatracker.ietf.org/doc/html/rfc7386) content type
|
||||||
header (`Content-Type: application/merge-patch+json`) for this to work.
|
header (`Content-Type: application/merge-patch+json`) for this to work.
|
||||||
|
|
@ -106,11 +106,11 @@ This is a great way to test the API and see how it works, without having to writ
|
||||||
|
|
||||||
By default, all list endpoints are paginated, which means only a certain number of results is returned per request.
|
By default, all list endpoints are paginated, which means only a certain number of results is returned per request.
|
||||||
To get another page of the results, you have to use the `page` query parameter, which contains the page number you want
|
To get another page of the results, you have to use the `page` query parameter, which contains the page number you want
|
||||||
to get (e.g. `/api/categoues/?page=2`).
|
to get (e.g. `/api/categories/?page=2`).
|
||||||
When using JSONLD, the links to the next page are also included in the `hydra:view` property of the response.
|
When using JSONLD, the links to the next page are also included in the `hydra:view` property of the response.
|
||||||
|
|
||||||
To change the size of the pages (the number of items in a single page) use the `itemsPerPage` query parameter (
|
To change the size of the pages (the number of items in a single page) use the `itemsPerPage` query parameter (
|
||||||
e.g. `/api/categoues/?itemsPerPage=50`).
|
e.g. `/api/categories/?itemsPerPage=50`).
|
||||||
|
|
||||||
See [API Platform docs](https://api-platform.com/docs/core/pagination) for more infos.
|
See [API Platform docs](https://api-platform.com/docs/core/pagination) for more infos.
|
||||||
|
|
||||||
|
|
|
||||||
BIN
docs/assets/getting_started/system_settings.png
Normal file
BIN
docs/assets/getting_started/system_settings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue