Compare commits
216 Commits
2.0.15
...
activity-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d5cef1a2f | ||
|
|
52a9ff9860 | ||
|
|
ff9bf163ee | ||
|
|
5fac6a642c | ||
|
|
b1b81ee8a6 | ||
|
|
1ea64bf18e | ||
|
|
0c4fdda00a | ||
|
|
215bd6a01a | ||
|
|
63b8164926 | ||
|
|
8c26ff9aed | ||
|
|
921a703ea2 | ||
|
|
855a546683 | ||
|
|
52b949daa3 | ||
|
|
ee040d29b6 | ||
|
|
fb9a0dc499 | ||
|
|
40154be8f9 | ||
|
|
13649e146c | ||
|
|
4076e1c3d3 | ||
|
|
8de54e1fa7 | ||
|
|
1965c35b43 | ||
|
|
3fd770047e | ||
|
|
036b297a76 | ||
|
|
537c120ad9 | ||
|
|
3e0dadc0c8 | ||
|
|
004ac5a6b9 | ||
|
|
3204dcb03e | ||
|
|
b1903ba183 | ||
|
|
75fa7a20b8 | ||
|
|
a19426147a | ||
|
|
9d933f62c0 | ||
|
|
9e3d243b4b | ||
|
|
62047c7c01 | ||
|
|
0bcd45babe | ||
|
|
2a72815481 | ||
|
|
ff6a3e8262 | ||
|
|
0947b88d36 | ||
|
|
5de5b4fef5 | ||
|
|
cde856a9c1 | ||
|
|
6f8877a740 | ||
|
|
e7d71bff64 | ||
|
|
cca0657291 | ||
|
|
a4904d3335 | ||
|
|
b9c1036b21 | ||
|
|
818c080d28 | ||
|
|
a5a6ae065b | ||
|
|
8b06ca9d10 | ||
|
|
7e1025d61d | ||
|
|
8788311faf | ||
|
|
ce22520d7f | ||
|
|
f5d76cfd9a | ||
|
|
27f7f14539 | ||
|
|
f7c32e633a | ||
|
|
6d9055826f | ||
|
|
4627a9047e | ||
|
|
987b46bf8f | ||
|
|
8792a1df09 | ||
|
|
a8678892d6 | ||
|
|
72350b76c0 | ||
|
|
8578bbe033 | ||
|
|
93ca983e0b | ||
|
|
e29ef25581 | ||
|
|
a617eba535 | ||
|
|
0c445d1bc4 | ||
|
|
5f7114e008 | ||
|
|
e2c511cf46 | ||
|
|
e042ef6bfa | ||
|
|
c059e51b3c | ||
|
|
661bb16342 | ||
|
|
94d6b43dcc | ||
|
|
99c296a751 | ||
|
|
8f8d228fb3 | ||
|
|
1214e508fe | ||
|
|
7d394fbe56 | ||
|
|
fd4dfc6be4 | ||
|
|
9dc9c71a46 | ||
|
|
345bb6601e | ||
|
|
5c8c21d609 | ||
|
|
7219b3f421 | ||
|
|
b76a5d5c35 | ||
|
|
40a9027d34 | ||
|
|
553bd20e06 | ||
|
|
3ea5803e08 | ||
|
|
4949afb098 | ||
|
|
d328badca1 | ||
|
|
175d371ef9 | ||
|
|
6bb7b38252 | ||
|
|
afe0656502 | ||
|
|
dd51c8dfba | ||
|
|
b6c98f871d | ||
|
|
19b2d47591 | ||
|
|
a636683dee | ||
|
|
c1f5d3def4 | ||
|
|
3e88a007d0 | ||
|
|
1fdd11e94c | ||
|
|
2ac783a300 | ||
|
|
3a358b1434 | ||
|
|
216da19cc9 | ||
|
|
dee90bb15e | ||
|
|
a78e8ed8c4 | ||
|
|
e293d62aa1 | ||
|
|
f016221e8e | ||
|
|
36c578452e | ||
|
|
6e643ee99c | ||
|
|
58cbf1776f | ||
|
|
85a16a9363 | ||
|
|
7abe787778 | ||
|
|
e98732f44a | ||
|
|
c2afebbb5e | ||
|
|
49e69d1ae3 | ||
|
|
a9a32c83ac | ||
|
|
64fa67f15d | ||
|
|
df3ca9d5a9 | ||
|
|
12ef19df28 | ||
|
|
a5b4a92086 | ||
|
|
3aaaf9ffc0 | ||
|
|
3f73f2c570 | ||
|
|
46739795ff | ||
|
|
afd60a67f6 | ||
|
|
bb325888a1 | ||
|
|
fdda9841fa | ||
|
|
5acd33dd64 | ||
|
|
6531e7fe52 | ||
|
|
c6a651456e | ||
|
|
8eeb9c120a | ||
|
|
d8f89d9f3d | ||
|
|
26a89725eb | ||
|
|
9f0165ef4d | ||
|
|
3d0a35f0cd | ||
|
|
c5dcf9e0fd | ||
|
|
ad645f5b58 | ||
|
|
f86eca05ae | ||
|
|
ac2a6ba2c8 | ||
|
|
e23d0474fd | ||
|
|
2edcbcca50 | ||
|
|
5f4b6d3cfa | ||
|
|
af82fd040d | ||
|
|
e0769bc3dc | ||
|
|
8fd2e7029e | ||
|
|
f570b5506a | ||
|
|
0e1bd818bf | ||
|
|
02bcd45116 | ||
|
|
16d4a691f8 | ||
|
|
ecded0a065 | ||
|
|
20164295f8 | ||
|
|
9d8e5ecd7a | ||
|
|
8a1059fdd1 | ||
|
|
92c6922afb | ||
|
|
2fb9ea9239 | ||
|
|
350875be2a | ||
|
|
6a828cc9bd | ||
|
|
7034066de9 | ||
|
|
2a9bdb7982 | ||
|
|
a1d9874c13 | ||
|
|
0b1625175b | ||
|
|
0f6a6e7561 | ||
|
|
7bbd497ef1 | ||
|
|
0b3f645bda | ||
|
|
c840fee911 | ||
|
|
e06acc95c5 | ||
|
|
95f15e6c88 | ||
|
|
114617a052 | ||
|
|
38e37f1c2e | ||
|
|
7a5b12762a | ||
|
|
de419350b2 | ||
|
|
a6b237936e | ||
|
|
6e1207578e | ||
|
|
31348da4c0 | ||
|
|
452de88e18 | ||
|
|
18d953e79b | ||
|
|
636422e342 | ||
|
|
20149ae765 | ||
|
|
a392f61d61 | ||
|
|
3e61e0dce6 | ||
|
|
9baa477bed | ||
|
|
4d182ad1a7 | ||
|
|
e77056c4b7 | ||
|
|
d7f1df2987 | ||
|
|
8c1a793b0a | ||
|
|
adc0779912 | ||
|
|
c7bc8fae73 | ||
|
|
0a0ffb4ac9 | ||
|
|
b831532323 | ||
|
|
385753fbc3 | ||
|
|
651f812da8 | ||
|
|
6a99ad41ca | ||
|
|
dd89363c72 | ||
|
|
983bf2c88b | ||
|
|
ce7fe67fcc | ||
|
|
053a1d9017 | ||
|
|
2e2bf986a4 | ||
|
|
6e781b59e2 | ||
|
|
0663995d06 | ||
|
|
2ba9cff281 | ||
|
|
9c1150c50e | ||
|
|
b4a4d81ca8 | ||
|
|
2681b81990 | ||
|
|
97092acae9 | ||
|
|
fdb6cf5cf8 | ||
|
|
7ab0a8cea1 | ||
|
|
2664d6117f | ||
|
|
6bdf27fc9b | ||
|
|
f0298335ee | ||
|
|
a58a33fc7d | ||
|
|
9bd5965ecd | ||
|
|
29624354a2 | ||
|
|
04422f3530 | ||
|
|
43bd7bf1ab | ||
|
|
4b2a3669e6 | ||
|
|
1099e76244 | ||
|
|
d8f72b8b88 | ||
|
|
b2314de315 | ||
|
|
66413d789c | ||
|
|
7112e932a2 | ||
|
|
01e26dca9e | ||
|
|
844062b90e | ||
|
|
f60065d089 |
20
.docker/000-default.conf
Normal file
20
.docker/000-default.conf
Normal file
@@ -0,0 +1,20 @@
|
||||
<VirtualHost *:8001>
|
||||
ServerAdmin webmaster@localhost
|
||||
DocumentRoot /opt/kimai/public
|
||||
|
||||
PassEnv MAILER_FROM
|
||||
PassEnv APP_ENV
|
||||
PassEnv APP_SECRET
|
||||
PassEnv DATABASE_URL
|
||||
PassEnv MAILER_URL
|
||||
PassEnv TRUSTED_PROXIES
|
||||
|
||||
<Directory "/opt/kimai/public">
|
||||
Require all granted
|
||||
DirectoryIndex index.php
|
||||
AllowOverride All
|
||||
</Directory>
|
||||
|
||||
</VirtualHost>
|
||||
|
||||
ServerName localhost
|
||||
52
.docker/dbtest.php
Normal file
52
.docker/dbtest.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
$DATABASE_HOST = urldecode($argv[1]);
|
||||
$DATABASE_BASE = urldecode($argv[2]);
|
||||
$DATABASE_PORT = $argv[3];
|
||||
$DATABASE_USER = urldecode($argv[4]);
|
||||
$DATABASE_PASS = urldecode($argv[5]);
|
||||
|
||||
echo "Testing DB:";
|
||||
|
||||
try {
|
||||
$pdo = new \PDO("mysql:host=$DATABASE_HOST;dbname=$DATABASE_BASE;port=$DATABASE_PORT", "$DATABASE_USER", "$DATABASE_PASS", [
|
||||
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
|
||||
]);
|
||||
} catch(\Exception $ex) {
|
||||
switch ($ex->getCode()) {
|
||||
// we can immediately stop startup here and show the error message
|
||||
case 1045:
|
||||
echo 'Access denied (1045)';
|
||||
die(1);
|
||||
// we can immediately stop startup here and show the error message
|
||||
case 1049:
|
||||
echo 'Unknown database (1049)';
|
||||
die(2);
|
||||
// a lot of errors share the same meaningless error code zero
|
||||
case 0:
|
||||
// this error includes the database name, so we can only search for the static part of the error message
|
||||
if (stripos($ex->getMessage(), 'SQLSTATE[HY000] [1049] Unknown database') !== false) {
|
||||
echo 'Unknown database (0-1049)';
|
||||
die(3);
|
||||
}
|
||||
switch ($ex->getMessage()) {
|
||||
// eg. no response (fw) - the startup script should retry it a couple of times
|
||||
case 'SQLSTATE[HY000] [2002] Operation timed out':
|
||||
echo 'Operation timed out (0-2002)';
|
||||
die(4);
|
||||
// special case "localhost" with a stopped db server (should not happen in docker compose setup)
|
||||
case 'SQLSTATE[HY000] [2002] No such file or directory':
|
||||
echo 'Connection could not be established (0-2002)';
|
||||
die(5);
|
||||
// using IP with stopped db server - the startup script should retry it a couple of times
|
||||
case 'SQLSTATE[HY000] [2002] Connection refused':
|
||||
echo 'Connection refused (0-2002)';
|
||||
die(5);
|
||||
}
|
||||
echo $ex->getMessage() . " (0)";
|
||||
die(7);
|
||||
default:
|
||||
// unknown error
|
||||
echo $ex->getMessage() . " (?)";
|
||||
die(10);
|
||||
}
|
||||
}
|
||||
103
.docker/entrypoint.sh
Executable file
103
.docker/entrypoint.sh
Executable file
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash -x
|
||||
|
||||
KIMAI=$(cat /opt/kimai/version.txt)
|
||||
echo $KIMAI
|
||||
|
||||
function waitForDB() {
|
||||
# Parse sql connection data
|
||||
DATABASE_USER=$(awk -F '[/:@]' '{print $4}' <<< "$DATABASE_URL")
|
||||
DATABASE_PASS=$(awk -F '[/:@]' '{print $5}' <<< "$DATABASE_URL")
|
||||
DATABASE_HOST=$(awk -F '[/:@]' '{print $6}' <<< "$DATABASE_URL")
|
||||
DATABASE_PORT=$(awk -F '[/:@]' '{print $7}' <<< "$DATABASE_URL")
|
||||
DATABASE_BASE=$(awk -F '[/?]' '{print $4}' <<< "$DATABASE_URL")
|
||||
|
||||
re='^[0-9]+$'
|
||||
if ! [[ $DATABASE_PORT =~ $re ]] ; then
|
||||
DATABASE_PORT=3306
|
||||
fi
|
||||
|
||||
echo "Wait for database connection ..."
|
||||
until php /dbtest.php "$DATABASE_HOST" "$DATABASE_BASE" "$DATABASE_PORT" "$DATABASE_USER" "$DATABASE_PASS"; do
|
||||
echo Checking DB: $?
|
||||
sleep 3
|
||||
done
|
||||
echo "Connection established"
|
||||
}
|
||||
|
||||
function handleStartup() {
|
||||
# set mem limits and copy in custom logger config
|
||||
if [ -z "$memory_limit" ]; then
|
||||
memory_limit=512M
|
||||
fi
|
||||
sed -i "s/memory_limit.*/memory_limit=$memory_limit/g" /usr/local/etc/php/php.ini
|
||||
cp /assets/monolog.yaml /opt/kimai/config/packages/monolog.yaml
|
||||
|
||||
if [ -z "$USER_ID" ]; then
|
||||
USER_ID=$(id -u www-data)
|
||||
fi
|
||||
if [ -z "$GROUP_ID" ]; then
|
||||
GROUP_ID=$(id -g www-data)
|
||||
fi
|
||||
|
||||
# if group doesn't exist
|
||||
if grep -w "$GROUP_ID" /etc/group &>/dev/null; then
|
||||
echo Group already exists
|
||||
else
|
||||
echo www-kimai:x:"$GROUP_ID": >> /etc/group
|
||||
grpconv
|
||||
fi
|
||||
|
||||
# if user doesn't exist
|
||||
if id "$USER_ID" &>/dev/null; then
|
||||
echo User already exists
|
||||
else
|
||||
echo www-kimai:x:"$USER_ID":"$GROUP_ID":www-kimai:/var/www:/usr/sbin/nologin >> /etc/passwd
|
||||
pwconv
|
||||
fi
|
||||
|
||||
if [ -e /use_apache ]; then
|
||||
export APACHE_RUN_USER=$(id -nu "$USER_ID")
|
||||
# This doesn't _exactly_ run as the specified GID, it runs as the GID of the specified user but WTF
|
||||
export APACHE_RUN_GROUP=$(id -ng "$USER_ID")
|
||||
export APACHE_PID_FILE=/var/run/apache2/apache2.pid
|
||||
export APACHE_RUN_DIR=/var/run/apache2
|
||||
export APACHE_LOCK_DIR=/var/lock/apache2
|
||||
export APACHE_LOG_DIR=/var/log/apache2
|
||||
export LANG=C
|
||||
elif [ -e /use_fpm ]; then
|
||||
sed -i "s/user = .*/user = $USER_ID/g" /usr/local/etc/php-fpm.d/www.conf
|
||||
sed -i "s/group = .*/group = $GROUP_ID/g" /usr/local/etc/php-fpm.d/www.conf
|
||||
echo "Setting fpm to run as ${USER_ID}:${GROUP_ID}"
|
||||
else
|
||||
echo "Error, unknown server type"
|
||||
fi
|
||||
}
|
||||
|
||||
function prepareKimai() {
|
||||
# These are idempotent, so we can run them on every start-up
|
||||
/opt/kimai/bin/console -n kimai:install
|
||||
/opt/kimai/bin/console -n kimai:update
|
||||
if [ ! -z "$ADMINPASS" ] && [ ! -a "$ADMINMAIL" ]; then
|
||||
/opt/kimai/bin/console kimai:user:create admin "$ADMINMAIL" ROLE_SUPER_ADMIN "$ADMINPASS"
|
||||
fi
|
||||
echo "$KIMAI" > /opt/kimai/var/installed
|
||||
echo "Kimai is ready"
|
||||
}
|
||||
|
||||
function runServer() {
|
||||
# Just while I'm fixing things
|
||||
/opt/kimai/bin/console kimai:reload --env="$APP_ENV"
|
||||
chown -R $USER_ID:$GROUP_ID /opt/kimai/var
|
||||
if [ -e /use_apache ]; then
|
||||
exec /usr/sbin/apache2 -D FOREGROUND
|
||||
elif [ -e /use_fpm ]; then
|
||||
exec php-fpm
|
||||
else
|
||||
echo "Error, unknown server type"
|
||||
fi
|
||||
}
|
||||
|
||||
waitForDB
|
||||
handleStartup
|
||||
prepareKimai
|
||||
runServer
|
||||
48
.docker/monolog.yaml
Normal file
48
.docker/monolog.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
when@prod:
|
||||
monolog:
|
||||
channels: ["deprecation"]
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [403, 404]
|
||||
nested:
|
||||
type: stream
|
||||
level: info
|
||||
path: php://stderr
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine"]
|
||||
deprecation:
|
||||
type: stream
|
||||
channels: ["deprecation"]
|
||||
path: php://stderr
|
||||
|
||||
when@dev:
|
||||
monolog:
|
||||
channels: ["deprecation"]
|
||||
handlers:
|
||||
main:
|
||||
type: stream
|
||||
path: php://stderr
|
||||
level: info
|
||||
channels: ["!event"]
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine", "!console"]
|
||||
deprecation:
|
||||
type: stream
|
||||
channels: ["deprecation"]
|
||||
path: php://stderr
|
||||
|
||||
when@test:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: stream
|
||||
path: php://stderr
|
||||
level: info
|
||||
channels: ["!event"]
|
||||
14
.env.dist
14
.env.dist
@@ -7,19 +7,19 @@
|
||||
# Configure your database connection and set the correct server version.
|
||||
#
|
||||
# You have to replace the following values with your defaults:
|
||||
# - the version "5.7"
|
||||
# - the version "5.7" (the database server version, examples for MySQL and MariaDB below)
|
||||
# - the database username "user"
|
||||
# - the database password "password"
|
||||
# - the database schema name "database"
|
||||
# - the password "password" for "user"
|
||||
# - the database schema "database"
|
||||
# - you might have to adapt port "3306" and server IP "127.0.0.1" as well
|
||||
#
|
||||
# For MySQL that would be "serverVersion=5.7" as in:
|
||||
# DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8&serverVersion=5.7
|
||||
# DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8mb4&serverVersion=5.7
|
||||
#
|
||||
# For MariaDB it would be "serverVersion=mariadb-10.5.8":
|
||||
# DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8&serverVersion=mariadb-10.5.8
|
||||
# For MariaDB it would be "serverVersion=10.5.8-MariaDB":
|
||||
# DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8mb4&serverVersion=10.5.8-MariaDB
|
||||
#
|
||||
DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8&serverVersion=5.7
|
||||
DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8mb4&serverVersion=10.5.8-MariaDB
|
||||
|
||||
#================================================================================
|
||||
# The full documentation can be found at https://www.kimai.org/documentation/emails.html
|
||||
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,2 +1,2 @@
|
||||
github: [kevinpapst]
|
||||
custom: ["https://www.kimai.org/donate/", "https://www.paypal.me/kevinpapst"]
|
||||
custom: ["https://www.kimai.org/", "https://www.kimai.cloud/", "https://www.kevinpapst.de/"]
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/config.yml
vendored
7
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -3,12 +3,9 @@ contact_links:
|
||||
- name: Read our documentation
|
||||
url: https://www.kimai.org/documentation/
|
||||
about: Save your time! There is an instant solution for many issues in the documentation.
|
||||
- name: Docker image issue tracker
|
||||
url: https://github.com/tobybatch/kimai2/issues
|
||||
about: For issues specific for Kimai Docker deployment
|
||||
- name: Ask a Question
|
||||
url: https://github.com/kimai/kimai/discussions
|
||||
about: Want to discuss something with a community? Do it in discussions!
|
||||
- name: Get Professional support
|
||||
url: https://www.kimai.org/store/custom-plugins.html
|
||||
about: As a customer, you will always receive fast and helpful replies from the developer.
|
||||
url: https://www.kimai.org/en/support.html
|
||||
about: As a customer, you will always receive fast replies from the developer.
|
||||
|
||||
40
.github/ISSUE_TEMPLATE/docker.yml
vendored
Normal file
40
.github/ISSUE_TEMPLATE/docker.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Docker issue
|
||||
description: Experiencing an issue with your Docker Setup?
|
||||
labels: [ "docker" ]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for reporting an issue with your docker setup! This form will guide you to create a useful issue report.
|
||||
- type: textarea
|
||||
id: describe
|
||||
attributes:
|
||||
label: Describe the problem
|
||||
description: >
|
||||
Please provide a clear and concise description of what the issue is and the steps to reproduce the behaviour:
|
||||
1. Start the container '...'
|
||||
2. Click on '....'
|
||||
3. See error xyz
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Describe your setup and add your Docker compose file (redact your credentials)
|
||||
description: >
|
||||
Your working environment:
|
||||
- OS: [e.g. Linux, Windows, Mac]
|
||||
- Docker version: [e.g. 19.03.2]
|
||||
- Docker compose version: [e.g. 1.21.0]
|
||||
|
||||
Docker compose file:
|
||||
version: '3.5'
|
||||
services:
|
||||
image: ...
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: command
|
||||
attributes:
|
||||
label: Command used to run the container
|
||||
description: e.g. docker run -v ....
|
||||
18
.github/release-drafter.yml
vendored
18
.github/release-drafter.yml
vendored
@@ -1,17 +1,5 @@
|
||||
name-template: '$RESOLVED_VERSION'
|
||||
tag-template: '$RESOLVED_VERSION'
|
||||
categories:
|
||||
- title: 'Enhancements'
|
||||
labels:
|
||||
- 'feature request'
|
||||
- 'technical debt'
|
||||
- 'translation'
|
||||
- title: 'Fixed bugs'
|
||||
labels:
|
||||
- 'bug'
|
||||
- title: 'Infrastructure'
|
||||
labels:
|
||||
- 'infrastructure'
|
||||
exclude-labels:
|
||||
- 'duplicate'
|
||||
- 'invalid'
|
||||
@@ -33,10 +21,8 @@ version-resolver:
|
||||
- 'translation'
|
||||
default: patch
|
||||
template: |
|
||||
[Upgrade Kimai](https://www.kimai.org/documentation/updates.html) - [Install Kimai](https://www.kimai.org/documentation/installation.html) - [Docker](https://tobybatch.github.io/kimai2/)
|
||||
|
||||
**Compatible with PHP 8.1 and 8.2**
|
||||
**Compatible with PHP 8.1 to 8.3**
|
||||
|
||||
$CHANGES
|
||||
|
||||
Involved in this release: $CONTRIBUTORS - thank you!
|
||||
Involved in this release: $CONTRIBUTORS - thank you!
|
||||
|
||||
101
.github/workflows/docker.yaml
vendored
101
.github/workflows/docker.yaml
vendored
@@ -1,16 +1,93 @@
|
||||
name: 'Docker Build'
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
kimai_tag:
|
||||
description: 'Kimai tag to build'
|
||||
required: true
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Trigger docker image build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Emit repository_dispatch
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
repository: tobybatch/kimai2
|
||||
event_type: kimai_release
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{secrets.DOCKERHUB_USERNAME}}
|
||||
password: ${{secrets.DOCKERHUB_PASSWORD}}
|
||||
|
||||
- name: Determine version
|
||||
run: |
|
||||
input="${{ github.event.inputs.kimai_tag }}"
|
||||
|
||||
# Determine between manual trigger and release event
|
||||
if [ -z "$input" ]; then
|
||||
echo "Using release tag: ${{ github.event.release.tag_name }}"
|
||||
version="${{ github.event.release.tag_name }}"
|
||||
else
|
||||
echo "Using tag provided: $input"
|
||||
version="$input"
|
||||
fi
|
||||
|
||||
if [[ ! $version =~ ^2\.(0|[1-9]*)(0?)\.(0|[0-9]*)(0?)$ ]]; then
|
||||
echo "Invalid version number: $version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "kimai_version=$version" >> $GITHUB_ENV
|
||||
|
||||
- name: FPM image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
build-args: |
|
||||
KIMAI=${{ env.kimai_version }}
|
||||
BASE=fpm
|
||||
target: prod
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
kimai/kimai2:latest
|
||||
kimai/kimai2:fpm
|
||||
kimai/kimai2:fpm-${{ env.kimai_version }}
|
||||
push: true
|
||||
|
||||
- name: Apache image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
build-args: |
|
||||
KIMAI=${{ env.kimai_version }}
|
||||
BASE=apache
|
||||
target: prod
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
kimai/kimai2:apache
|
||||
kimai/kimai2:apache-${{ env.kimai_version }}
|
||||
push: true
|
||||
|
||||
- name: Development image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
build-args: |
|
||||
KIMAI=${{ env.kimai_version }}
|
||||
BASE=apache
|
||||
target: dev
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
kimai/kimai2:dev
|
||||
push: true
|
||||
|
||||
29
.github/workflows/lock-threads.yaml
vendored
Normal file
29
.github/workflows/lock-threads.yaml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: 'Lock Threads'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 1 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: lock-threads
|
||||
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
process-only: 'issues, prs'
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-inactive-days: '90'
|
||||
issue-comment: >
|
||||
This thread has been automatically locked since there has not been any recent activity after it was closed.
|
||||
Please share your experience with the community and [leave a testimonial](https://love.kimai.org/) to support Kimai.
|
||||
issue-lock-reason: 'resolved'
|
||||
pr-inactive-days: '180'
|
||||
log-output: true
|
||||
56
.github/workflows/lock.yaml
vendored
56
.github/workflows/lock.yaml
vendored
@@ -1,56 +0,0 @@
|
||||
name: 'Lock Threads'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: lock
|
||||
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '90'
|
||||
exclude-issue-created-before: ''
|
||||
exclude-issue-created-after: ''
|
||||
exclude-issue-created-between: ''
|
||||
exclude-issue-closed-before: ''
|
||||
exclude-issue-closed-after: ''
|
||||
exclude-issue-closed-between: ''
|
||||
include-any-issue-labels: ''
|
||||
include-all-issue-labels: ''
|
||||
exclude-any-issue-labels: ''
|
||||
add-issue-labels: ''
|
||||
remove-issue-labels: ''
|
||||
issue-comment: >
|
||||
This thread has been automatically locked since there has not been
|
||||
any recent activity after it was closed. Please open a new issue for
|
||||
related bugs.
|
||||
If you use Kimai on a daily basis, please [consider donating](https://www.kimai.org/donate/) to
|
||||
support further development of Kimai.
|
||||
issue-lock-reason: 'resolved'
|
||||
pr-inactive-days: '180'
|
||||
exclude-pr-created-before: ''
|
||||
exclude-pr-created-after: ''
|
||||
exclude-pr-created-between: ''
|
||||
exclude-pr-closed-before: ''
|
||||
exclude-pr-closed-after: ''
|
||||
exclude-pr-closed-between: ''
|
||||
include-any-pr-labels: ''
|
||||
include-all-pr-labels: ''
|
||||
exclude-any-pr-labels: ''
|
||||
add-pr-labels: ''
|
||||
remove-pr-labels: ''
|
||||
pr-comment: ''
|
||||
pr-lock-reason: ''
|
||||
process-only: ''
|
||||
log-output: false
|
||||
4
.github/workflows/lockfiles.yaml
vendored
4
.github/workflows/lockfiles.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Clone Kimai
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -19,5 +19,5 @@ jobs:
|
||||
uses: xalvarez/prevent-file-change-action@v1
|
||||
with:
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
pattern: .*.lock
|
||||
pattern: .*\.lock$
|
||||
trustedAuthors: kevinpapst, dependabot
|
||||
|
||||
2
.github/workflows/release-drafter.yaml
vendored
2
.github/workflows/release-drafter.yaml
vendored
@@ -25,6 +25,6 @@ jobs:
|
||||
needs: correct_repository
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
- uses: release-drafter/release-drafter@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
33
.github/workflows/testing.yaml
vendored
33
.github/workflows/testing.yaml
vendored
@@ -19,13 +19,13 @@ jobs:
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
strategy:
|
||||
matrix:
|
||||
php: ['8.1', '8.2']
|
||||
php: ['8.1', '8.2', '8.3']
|
||||
|
||||
name: Integration (${{ matrix.php }})
|
||||
steps:
|
||||
|
||||
- name: Clone Kimai
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -39,12 +39,15 @@ jobs:
|
||||
env:
|
||||
fail-fast: true
|
||||
|
||||
- name: Determine software versions
|
||||
run: mysql --version && php --version
|
||||
|
||||
- name: Determine composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "composer_cache_directory=$(composer config cache-dir)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache Composer dependencies
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "${{ env.composer_cache_directory }}"
|
||||
key: ${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
@@ -53,7 +56,7 @@ jobs:
|
||||
run: composer install
|
||||
|
||||
- name: Validate Composer
|
||||
run: composer validate --strict
|
||||
run: composer validate --strict --no-check-all
|
||||
|
||||
- name: Warmup cache
|
||||
run: APP_ENV=dev bin/console kimai:reload -n
|
||||
@@ -79,21 +82,29 @@ jobs:
|
||||
- name: Run quick unit-tests
|
||||
run: composer tests-unit
|
||||
env:
|
||||
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?serverVersion=5.7
|
||||
APP_ENV: dev
|
||||
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?charset=utf8mb4&serverVersion=8.0.35
|
||||
APP_ENV: test
|
||||
MAILER_URL: null://localhost
|
||||
|
||||
- name: Full test-suite
|
||||
if: matrix.php != '8.2'
|
||||
run: vendor/bin/phpunit tests/
|
||||
env:
|
||||
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?charset=utf8mb4&serverVersion=8.0.35
|
||||
APP_ENV: test
|
||||
MAILER_URL: null://localhost
|
||||
|
||||
- name: Full test-suite with coverage
|
||||
if: matrix.php == '8.2'
|
||||
run: vendor/bin/phpunit tests/ --coverage-clover=coverage.xml
|
||||
env:
|
||||
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?serverVersion=5.7
|
||||
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?charset=utf8mb4&serverVersion=8.0.35
|
||||
APP_ENV: dev
|
||||
MAILER_URL: null://localhost
|
||||
TEST_WITH_BUNDLES: 1
|
||||
|
||||
- name: Upload code coverage
|
||||
if: matrix.php == '8.1'
|
||||
uses: codecov/codecov-action@v3
|
||||
if: matrix.php == '8.2'
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./coverage.xml
|
||||
@@ -106,7 +117,7 @@ jobs:
|
||||
bin/console doctrine:migrations:migrate -n
|
||||
bin/console doctrine:migrations:migrate first -n
|
||||
env:
|
||||
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?serverVersion=5.7
|
||||
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?charset=utf8mb4&serverVersion=8.0.35
|
||||
APP_ENV: dev
|
||||
MAILER_URL: null://localhost
|
||||
|
||||
|
||||
42
.github/workflows/website.yaml
vendored
Normal file
42
.github/workflows/website.yaml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: 'Website update'
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
kimai_version:
|
||||
description: 'Kimai version for the website'
|
||||
required: true
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Trigger version update for website
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Determine Version"
|
||||
run: |
|
||||
input="${{ github.event.inputs.kimai_version }}"
|
||||
|
||||
# Determine between manual trigger and release event
|
||||
if [ -z "$input" ]; then
|
||||
echo "No input provided, using release tag"
|
||||
version="${{ github.event.release.tag_name }}"
|
||||
else
|
||||
echo "Using input provided: $input"
|
||||
version="$input"
|
||||
fi
|
||||
|
||||
echo "kimai_version=$version" >> $GITHUB_ENV
|
||||
|
||||
if [[ ! $version =~ ^2\.(0|[1-9]*)(0?)\.(0|[0-9]*)(0?)$ ]]; then
|
||||
echo "Invalid version number: $version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Emit repository_dispatch
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.WEBSITE_ACCESS_TOKEN }}
|
||||
repository: kimai/www.kimai.org
|
||||
event-type: kimai_release
|
||||
client-payload: '{"kimai_version": "${{ env.kimai_version }}"}'
|
||||
43
.gitignore
vendored
43
.gitignore
vendored
@@ -1,41 +1,32 @@
|
||||
# some hosters require a htaccess to change the PHP version
|
||||
.htaccess
|
||||
.env-*
|
||||
.idea/
|
||||
/public/.htaccess
|
||||
/.env-*
|
||||
/.idea/
|
||||
.DS_Store
|
||||
rector.php
|
||||
phpstan.sh
|
||||
|
||||
# custom apache rules e.g. to deactivate ioncube loader
|
||||
/public/.user.ini
|
||||
|
||||
# for symfony local webserver
|
||||
php.ini
|
||||
.php-version
|
||||
/php.ini
|
||||
/.php-version
|
||||
|
||||
# YARN 2
|
||||
.yarnrc.yml
|
||||
.yarn
|
||||
/.yarnrc.yml
|
||||
/.yarn
|
||||
|
||||
# for keeping empty directories
|
||||
!.gitkeep
|
||||
|
||||
/bin/*
|
||||
!/bin/console
|
||||
*local.yaml
|
||||
/config/packages/local.yaml
|
||||
/config/bundles-local.php
|
||||
|
||||
/templates/invoice/renderer/.~lock*
|
||||
/translations/branding.*.xlf
|
||||
|
||||
/var/data/*
|
||||
/var/coverage/
|
||||
/var/cache/*
|
||||
|
||||
/var/invoices/*
|
||||
/var/export/*
|
||||
/var/invoices*
|
||||
/var/export*
|
||||
/var/log/*
|
||||
/var/sessions/*
|
||||
/var/packages/*
|
||||
/var/plugins/*
|
||||
*.disabled
|
||||
/var/plugins*
|
||||
/var/plugins/*/*.disabled
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
/.env.local
|
||||
@@ -67,4 +58,6 @@ php.ini
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
###< symfony/webpack-encore-bundle ###
|
||||
/nbproject/*
|
||||
/nbproject/*
|
||||
|
||||
.dockerhub.secrets
|
||||
|
||||
@@ -9,6 +9,7 @@ COMMENT;
|
||||
|
||||
$fixer = new PhpCsFixer\Config();
|
||||
$fixer
|
||||
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
|
||||
->setRiskyAllowed(true)
|
||||
->setRules([
|
||||
'encoding' => true,
|
||||
@@ -33,7 +34,7 @@ $fixer
|
||||
'no_break_comment' => true,
|
||||
'no_closing_tag' => true,
|
||||
'no_spaces_after_function_name' => true,
|
||||
'no_spaces_inside_parenthesis' => true,
|
||||
'spaces_inside_parentheses' => ['space' => 'none'],
|
||||
'no_trailing_whitespace' => true,
|
||||
'no_trailing_whitespace_in_comment' => true,
|
||||
'single_blank_line_at_eof' => true,
|
||||
@@ -54,13 +55,13 @@ $fixer
|
||||
'class_attributes_separation' => ['elements' => ['method' => 'one']],
|
||||
'concat_space' => ['spacing' => 'one'],
|
||||
'declare_equal_normalize' => true,
|
||||
'function_typehint_space' => true,
|
||||
'type_declaration_spaces' => ['elements' => ['function', 'property']],
|
||||
'include' => true,
|
||||
'lowercase_cast' => true,
|
||||
'lowercase_static_reference' => true,
|
||||
'magic_constant_casing' => true,
|
||||
'native_function_casing' => true,
|
||||
'new_with_braces' => true,
|
||||
'new_with_parentheses' => true,
|
||||
'no_blank_lines_after_class_opening' => true,
|
||||
'no_blank_lines_after_phpdoc' => true,
|
||||
'no_empty_comment' => true,
|
||||
@@ -82,7 +83,7 @@ $fixer
|
||||
'no_singleline_whitespace_before_semicolons' => true,
|
||||
'no_spaces_around_offset' => true,
|
||||
'no_trailing_comma_in_singleline' => true,
|
||||
'no_unneeded_curly_braces' => true,
|
||||
'no_unneeded_braces' => true,
|
||||
'no_unneeded_final_method' => true,
|
||||
'no_unused_imports' => true,
|
||||
'no_whitespace_before_comma_in_array' => true,
|
||||
@@ -123,7 +124,7 @@ $fixer
|
||||
'return_type_declaration' => true,
|
||||
'semicolon_after_instruction' => true,
|
||||
'short_scalar_cast' => true,
|
||||
'single_blank_line_before_namespace' => true,
|
||||
'blank_lines_before_namespace' => ['min_line_breaks' => 2, 'max_line_breaks' => 2],
|
||||
'single_line_comment_style' => [
|
||||
'comment_types' => ['hash'],
|
||||
],
|
||||
@@ -151,7 +152,7 @@ $fixer
|
||||
],
|
||||
'scope' => 'namespaced'
|
||||
],
|
||||
'native_function_type_declaration_casing' => true,
|
||||
'native_type_declaration_casing' => true,
|
||||
'no_alias_functions' => [
|
||||
'sets' => [
|
||||
'@internal'
|
||||
@@ -170,6 +171,7 @@ $fixer
|
||||
->setFinder(
|
||||
PhpCsFixer\Finder::create()
|
||||
->in([
|
||||
__DIR__ . '/migrations/',
|
||||
__DIR__ . '/src/',
|
||||
__DIR__ . '/tests/',
|
||||
])
|
||||
|
||||
333
Dockerfile
Normal file
333
Dockerfile
Normal file
@@ -0,0 +1,333 @@
|
||||
# _ ___ _
|
||||
# | |/ (_)_ __ ___ __ _(_)
|
||||
# | ' /| | '_ ` _ \ / _` | |
|
||||
# | . \| | | | | | | (_| | |
|
||||
# |_|\_\_|_| |_| |_|\__,_|_|
|
||||
#
|
||||
# Kimai images for:
|
||||
# - plain PHP FPM (kimai/kimai2:fpm)
|
||||
# - Apache with PHP (kimai/kimai2:apache)
|
||||
# - Development (kimai/kimai2:dev)
|
||||
# ---------------------------------------------------------------------
|
||||
# For local testing by maintainer:
|
||||
#
|
||||
# docker build --no-cache -t kimai-fpm --build-arg BASE=fpm .
|
||||
# docker build --no-cache -t kimai-apache --build-arg BASE=apache .
|
||||
# docker run -d --name kimai-apache-app kimai-apache
|
||||
# docker exec -ti kimai-apache-app /bin/bash
|
||||
# ---------------------------------------------------------------------
|
||||
# Official PHP images: https://hub.docker.com/_/php/
|
||||
# https://github.com/docker-library/docs/blob/master/php/README.md#supported-tags-and-respective-dockerfile-links
|
||||
# Pass-through Arguments: https://benkyriakou.com/posts/docker-args-empty
|
||||
# Best practices: https://docs.docker.com/build/building/best-practices/
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
# Source base, one of: fpm, apache
|
||||
ARG BASE="fpm"
|
||||
# Kimai branch/tag to run
|
||||
ARG KIMAI="main"
|
||||
# Timezone for images
|
||||
ARG TIMEZONE="Europe/Berlin"
|
||||
|
||||
###########################
|
||||
# Shared tools
|
||||
###########################
|
||||
|
||||
# composer base image
|
||||
FROM composer:latest AS composer
|
||||
|
||||
###########################
|
||||
# PHP extensions
|
||||
###########################
|
||||
|
||||
# fpm alpine php extension base
|
||||
FROM php:8.3-fpm-alpine AS fpm-php-ext-base
|
||||
RUN apk add --no-cache \
|
||||
# build-tools
|
||||
autoconf \
|
||||
dpkg \
|
||||
dpkg-dev \
|
||||
file \
|
||||
g++ \
|
||||
gcc \
|
||||
icu-dev \
|
||||
libatomic \
|
||||
libc-dev \
|
||||
libgomp \
|
||||
libmagic \
|
||||
m4 \
|
||||
make \
|
||||
mpc1 \
|
||||
mpfr4 \
|
||||
musl-dev \
|
||||
perl \
|
||||
re2c \
|
||||
# gd
|
||||
freetype-dev \
|
||||
libpng-dev \
|
||||
# icu
|
||||
icu-dev \
|
||||
icu-data-full \
|
||||
# ldap
|
||||
openldap-dev \
|
||||
libldap \
|
||||
# zip
|
||||
libzip-dev \
|
||||
# xsl
|
||||
libxslt-dev
|
||||
|
||||
# apache debian php extension base
|
||||
FROM php:8.3-apache-bookworm AS apache-php-ext-base
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
libldap2-dev \
|
||||
libicu-dev \
|
||||
libpng-dev \
|
||||
libzip-dev \
|
||||
libxslt1-dev \
|
||||
libfreetype6-dev
|
||||
|
||||
# php extension gd - 13.86s
|
||||
FROM ${BASE}-php-ext-base AS php-ext-gd
|
||||
RUN docker-php-ext-configure gd \
|
||||
--with-freetype && \
|
||||
docker-php-ext-install -j$(nproc) gd
|
||||
|
||||
# php extension intl : 15.26s
|
||||
FROM ${BASE}-php-ext-base AS php-ext-intl
|
||||
RUN docker-php-ext-install -j$(nproc) intl
|
||||
|
||||
# php extension ldap : 8.45s
|
||||
FROM ${BASE}-php-ext-base AS php-ext-ldap
|
||||
RUN docker-php-ext-configure ldap && \
|
||||
docker-php-ext-install -j$(nproc) ldap
|
||||
|
||||
# php extension pdo_mysql : 6.14s
|
||||
FROM ${BASE}-php-ext-base AS php-ext-pdo_mysql
|
||||
RUN docker-php-ext-install -j$(nproc) pdo_mysql
|
||||
|
||||
# php extension zip : 8.18s
|
||||
FROM ${BASE}-php-ext-base AS php-ext-zip
|
||||
RUN docker-php-ext-install -j$(nproc) zip
|
||||
|
||||
# php extension xsl : ?.?? s
|
||||
FROM ${BASE}-php-ext-base AS php-ext-xsl
|
||||
RUN docker-php-ext-install -j$(nproc) xsl
|
||||
|
||||
# php extension opcache
|
||||
FROM ${BASE}-php-ext-base AS php-ext-opcache
|
||||
RUN docker-php-ext-install -j$(nproc) opcache
|
||||
|
||||
###########################
|
||||
# fpm base build
|
||||
###########################
|
||||
|
||||
FROM php:8.3-fpm-alpine AS fpm-base
|
||||
ARG TIMEZONE
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
coreutils \
|
||||
freetype \
|
||||
haveged \
|
||||
icu \
|
||||
icu-data-full \
|
||||
libldap \
|
||||
libpng \
|
||||
libzip \
|
||||
libxslt-dev \
|
||||
fcgi \
|
||||
tzdata && \
|
||||
touch /use_fpm && \
|
||||
sed -i "s/;ping.path/ping.path/g" /usr/local/etc/php-fpm.d/www.conf && \
|
||||
sed -i "s/;access.suppress_path\[\] = \/ping/access.suppress_path\[\] = \/ping/g" /usr/local/etc/php-fpm.d/www.conf
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
HEALTHCHECK --interval=20s --timeout=10s --retries=3 \
|
||||
CMD \
|
||||
SCRIPT_NAME=/ping \
|
||||
SCRIPT_FILENAME=/ping \
|
||||
REQUEST_METHOD=GET \
|
||||
cgi-fcgi -bind -connect 127.0.0.1:9000 || exit 1
|
||||
|
||||
###########################
|
||||
# apache base build
|
||||
###########################
|
||||
|
||||
FROM php:8.3-apache-bookworm AS apache-base
|
||||
ARG TIMEZONE
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
bash \
|
||||
haveged \
|
||||
libicu72 \
|
||||
libldap-common \
|
||||
libpng16-16 \
|
||||
libzip4 \
|
||||
libxslt1.1 \
|
||||
libfreetype6 \
|
||||
unzip && \
|
||||
echo "Listen 8001" > /etc/apache2/ports.conf && \
|
||||
a2enmod rewrite && \
|
||||
touch /use_apache
|
||||
|
||||
COPY .docker/000-default.conf /etc/apache2/sites-available/000-default.conf
|
||||
|
||||
EXPOSE 8001
|
||||
|
||||
HEALTHCHECK --interval=20s --timeout=10s --retries=3 \
|
||||
CMD curl -f http://127.0.0.1:8001 || exit 1
|
||||
|
||||
###########################
|
||||
# global base build
|
||||
###########################
|
||||
|
||||
FROM ${BASE}-base AS php-base
|
||||
ARG TIMEZONE
|
||||
|
||||
ENV TIMEZONE=${TIMEZONE}
|
||||
RUN ln -snf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && echo ${TIMEZONE} > /etc/timezone && \
|
||||
# make composer home dir
|
||||
mkdir /composer && \
|
||||
chown -R www-data:www-data /composer
|
||||
|
||||
# copy composer
|
||||
COPY --from=composer /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# copy php extensions
|
||||
|
||||
# PHP extension xsl
|
||||
COPY --from=php-ext-xsl /usr/local/etc/php/conf.d/docker-php-ext-xsl.ini /usr/local/etc/php/conf.d/docker-php-ext-xsl.ini
|
||||
COPY --from=php-ext-xsl /usr/local/lib/php/extensions/no-debug-non-zts-20230831/xsl.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/xsl.so
|
||||
# PHP extension pdo_mysql
|
||||
COPY --from=php-ext-pdo_mysql /usr/local/etc/php/conf.d/docker-php-ext-pdo_mysql.ini /usr/local/etc/php/conf.d/docker-php-ext-pdo_mysql.ini
|
||||
COPY --from=php-ext-pdo_mysql /usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_mysql.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_mysql.so
|
||||
# PHP extension zip
|
||||
COPY --from=php-ext-zip /usr/local/etc/php/conf.d/docker-php-ext-zip.ini /usr/local/etc/php/conf.d/docker-php-ext-zip.ini
|
||||
COPY --from=php-ext-zip /usr/local/lib/php/extensions/no-debug-non-zts-20230831/zip.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/zip.so
|
||||
# PHP extension ldap
|
||||
COPY --from=php-ext-ldap /usr/local/etc/php/conf.d/docker-php-ext-ldap.ini /usr/local/etc/php/conf.d/docker-php-ext-ldap.ini
|
||||
COPY --from=php-ext-ldap /usr/local/lib/php/extensions/no-debug-non-zts-20230831/ldap.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/ldap.so
|
||||
# PHP extension gd
|
||||
COPY --from=php-ext-gd /usr/local/etc/php/conf.d/docker-php-ext-gd.ini /usr/local/etc/php/conf.d/docker-php-ext-gd.ini
|
||||
COPY --from=php-ext-gd /usr/local/lib/php/extensions/no-debug-non-zts-20230831/gd.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/gd.so
|
||||
# PHP extension intl
|
||||
COPY --from=php-ext-intl /usr/local/etc/php/conf.d/docker-php-ext-intl.ini /usr/local/etc/php/conf.d/docker-php-ext-intl.ini
|
||||
COPY --from=php-ext-intl /usr/local/lib/php/extensions/no-debug-non-zts-20230831/intl.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/intl.so
|
||||
# PHP extension opcache
|
||||
COPY --from=php-ext-opcache /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini
|
||||
|
||||
###########################
|
||||
# Shared tools
|
||||
###########################
|
||||
|
||||
# full kimai source
|
||||
FROM alpine:latest AS git-dev
|
||||
ARG KIMAI
|
||||
ARG TIMEZONE
|
||||
RUN apk add --no-cache git && \
|
||||
git clone --depth 1 --branch ${KIMAI} https://github.com/kimai/kimai.git /opt/kimai
|
||||
|
||||
# production kimai source
|
||||
FROM git-dev AS git-prod
|
||||
WORKDIR /opt/kimai
|
||||
RUN rm -r tests
|
||||
|
||||
###########################
|
||||
# global base build
|
||||
###########################
|
||||
|
||||
FROM php-base AS base
|
||||
ARG KIMAI
|
||||
ARG TIMEZONE
|
||||
|
||||
LABEL org.opencontainers.image.title="Kimai" \
|
||||
org.opencontainers.image.description="Kimai is a time-tracking application." \
|
||||
org.opencontainers.image.authors="Kimai Community" \
|
||||
org.opencontainers.image.url="https://www.kimai.org/" \
|
||||
org.opencontainers.image.documentation="https://www.kimai.org/documentation/" \
|
||||
org.opencontainers.image.source="https://github.com/kimai/kimai" \
|
||||
org.opencontainers.image.version="${KIMAI}" \
|
||||
org.opencontainers.image.vendor="Kevin Papst" \
|
||||
org.opencontainers.image.licenses="AGPL-3.0"
|
||||
|
||||
ENV KIMAI=${KIMAI}
|
||||
ENV TIMEZONE=${TIMEZONE}
|
||||
RUN ln -snf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && echo ${TIMEZONE} > /etc/timezone && \
|
||||
mkdir -p /composer && \
|
||||
chown -R www-data:www-data /composer
|
||||
|
||||
# copy startup script & DB checking script
|
||||
COPY .docker/dbtest.php /dbtest.php
|
||||
COPY .docker/entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENV DATABASE_URL="mysql://kimai:kimai@127.0.0.1:3306/kimai?charset=utf8mb4&serverVersion=8.3"
|
||||
ENV APP_SECRET=change_this_to_something_unique
|
||||
# The default container name for nginx is nginx
|
||||
ENV TRUSTED_PROXIES=nginx,localhost,127.0.0.1
|
||||
ENV MAILER_FROM=kimai@example.com
|
||||
ENV MAILER_URL=null://localhost
|
||||
ENV ADMINPASS=
|
||||
ENV ADMINMAIL=
|
||||
ENV USER_ID=
|
||||
ENV GROUP_ID=
|
||||
# default values to configure composer behavior
|
||||
ENV COMPOSER_MEMORY_LIMIT=-1
|
||||
ENV COMPOSER_ALLOW_SUPERUSER=1
|
||||
|
||||
VOLUME [ "/opt/kimai/var" ]
|
||||
|
||||
CMD [ "/entrypoint.sh" ]
|
||||
|
||||
###########################
|
||||
# final builds
|
||||
###########################
|
||||
|
||||
# development build
|
||||
FROM base AS dev
|
||||
# copy kimai develop source
|
||||
COPY --from=git-dev --chown=www-data:www-data /opt/kimai /opt/kimai
|
||||
COPY .docker /assets
|
||||
# do the composer deps installation
|
||||
RUN echo \$PATH
|
||||
RUN \
|
||||
export COMPOSER_HOME=/composer && \
|
||||
composer --no-ansi install --working-dir=/opt/kimai --optimize-autoloader && \
|
||||
composer --no-ansi clearcache && \
|
||||
composer --no-ansi require --working-dir=/opt/kimai laminas/laminas-ldap && \
|
||||
cp /usr/local/etc/php/php.ini-development /usr/local/etc/php/php.ini && \
|
||||
chown -R www-data:www-data /opt/kimai /usr/local/etc/php/php.ini && \
|
||||
mkdir -p /opt/kimai/var/logs && chmod 777 /opt/kimai/var/logs && \
|
||||
sed "s/128M/-1/g" /usr/local/etc/php/php.ini-development > /opt/kimai/php-cli.ini && \
|
||||
sed -i "s/env php/env -S php -c \/opt\/kimai\/php-cli.ini/g" /opt/kimai/bin/console && \
|
||||
/opt/kimai/bin/console kimai:version | awk '{print $2}' > /opt/kimai/version.txt
|
||||
ENV APP_ENV=dev
|
||||
ENV DATABASE_URL=
|
||||
ENV memory_limit=512M
|
||||
|
||||
# production build
|
||||
FROM base AS prod
|
||||
# copy kimai production source
|
||||
COPY --from=git-prod --chown=www-data:www-data /opt/kimai /opt/kimai
|
||||
COPY .docker /assets
|
||||
# do the composer deps installation
|
||||
RUN \
|
||||
export COMPOSER_HOME=/composer && \
|
||||
composer --no-ansi install --working-dir=/opt/kimai --no-dev --optimize-autoloader && \
|
||||
composer --no-ansi clearcache && \
|
||||
composer --no-ansi require --update-no-dev --working-dir=/opt/kimai laminas/laminas-ldap && \
|
||||
cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini && \
|
||||
sed -i "s/expose_php = On/expose_php = Off/g" /usr/local/etc/php/php.ini && \
|
||||
sed -i "s/;opcache.enable=1/opcache.enable=1/g" /usr/local/etc/php/php.ini && \
|
||||
sed -i "s/;opcache.memory_consumption=128/opcache.memory_consumption=256/g" /usr/local/etc/php/php.ini && \
|
||||
sed -i "s/;opcache.interned_strings_buffer=8/opcache.interned_strings_buffer=24/g" /usr/local/etc/php/php.ini && \
|
||||
sed -i "s/;opcache.max_accelerated_files=10000/opcache.max_accelerated_files=100000/g" /usr/local/etc/php/php.ini && \
|
||||
sed -i "s/opcache.validate_timestamps=1/opcache.validate_timestamps=0/g" /usr/local/etc/php/php.ini && \
|
||||
sed -i "s/session.gc_maxlifetime = 1440/session.gc_maxlifetime = 604800/g" /usr/local/etc/php/php.ini && \
|
||||
mkdir -p /opt/kimai/var/logs && chmod 777 /opt/kimai/var/logs && \
|
||||
sed "s/128M/-1/g" /usr/local/etc/php/php.ini-development > /opt/kimai/php-cli.ini && \
|
||||
chown -R www-data:www-data /opt/kimai /usr/local/etc/php/php.ini && \
|
||||
/opt/kimai/bin/console kimai:version | awk '{print $2}' > /opt/kimai/version.txt
|
||||
ENV APP_ENV=prod
|
||||
ENV DATABASE_URL=
|
||||
ENV memory_limit=512M
|
||||
32
README.md
32
README.md
@@ -3,7 +3,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/kimai/kimai/actions"><img alt="CI Status" src="https://github.com/kimai/kimai/workflows/CI/badge.svg"></a>
|
||||
<a href="https://github.com/kimai/kimai/actions"><img alt="CI Status" src="https://github.com/kimai/kimai/actions/workflows/testing.yaml/badge.svg"></a>
|
||||
<a href="https://codecov.io/gh/kimai/kimai"><img alt="Code Coverage" src="https://codecov.io/gh/kimai/kimai/branch/main/graph/badge.svg"></a>
|
||||
<a href="https://packagist.org/packages/kimai/kimai"><img alt="Latest stable version" src="https://poser.pugx.org/kimai/kimai/v/stable"></a>
|
||||
<a href="https://www.gnu.org/licenses/agpl-3.0.en.html"><img alt="License" src="https://poser.pugx.org/kimai/kimai/license"></a>
|
||||
@@ -23,10 +23,10 @@ and so much more.
|
||||
|
||||
### Versions
|
||||
|
||||
There are two versions of Kimai existing:
|
||||
There are two [versions](https://www.kimai.org/documentation/versions.html) of Kimai existing:
|
||||
|
||||
- [Version 1](https://github.com/kimai/kimai/tree/1.x) — compatible with PHP 7.4, which is in maintenance mode since 2023
|
||||
- [Version 2](https://github.com/kimai/kimai) — stable and "almost released" (waiting for some major plugins, which are not yet migrated)
|
||||
- [Version 2](https://github.com/kimai/kimai) — the current stable release (PHP 8.1+)
|
||||
- [Version 1](https://github.com/kimai/kimai/tree/1.x) — do **NOT** use, EOL since mid of 2023 (PHP 7.4)
|
||||
|
||||
### Links
|
||||
|
||||
@@ -36,21 +36,20 @@ There are two versions of Kimai existing:
|
||||
|
||||
### Requirements
|
||||
|
||||
- PHP 8.1 minimum
|
||||
- PHP 8.1.3 minimum (support for PHP 8.2 and 8.3)
|
||||
- MariaDB or MySQL
|
||||
- A webserver and subdomain (subdirectory is not supported)
|
||||
- PHP extensions: `gd`, `intl`, `json`, `mbstring`, `pdo`, `tokenizer`, `xml`, `xsl`, `zip`
|
||||
|
||||
## Installation
|
||||
|
||||
- [Recommended setup](https://www.kimai.org/documentation/installation.html#recommended-setup) — with Git and Composer
|
||||
- [Docker](https://hub.docker.com/r/kimai/kimai2) — containerized by [@tobybatch](https://github.com/tobybatch/kimai2)
|
||||
- Caddy with Docker-Compose at [Hetzner](https://www.kimai.org/documentation/hosting-hetzner-cloud.html) and [DigitalOcean](https://www.kimai.org/documentation/hosting-digital-ocean.html)
|
||||
- [SSH setup](https://www.kimai.org/documentation/installation.html) with Git and Composer
|
||||
- [Docker images](https://hub.docker.com/r/kimai/kimai2) with FPM only or incl. Apache
|
||||
- [Synology](https://www.kimai.org/documentation/synology.html) user can host the Docker version
|
||||
- [Developer setups](https://www.kimai.org/documentation/developers.html) if you want to create Kimai integrations
|
||||
|
||||
There are also documentations for:
|
||||
- [developer setups](https://www.kimai.org/documentation/developers.html) — on your local machine
|
||||
- [shared hostings](https://www.kimai.org/documentation/installation.html#shared-hosting) — the least favorable option
|
||||
- [Synology](https://www.kimai.org/documentation/synology.html) — you could try to host the Docker version instead
|
||||
- [1-click installer](https://www.kimai.org/documentation/installation.html#hosting-and-1-click-installations) — hosted environments
|
||||
There are more documented ways for [on-premise hosting](https://www.kimai.org/documentation/chapter-on-premise.html).
|
||||
|
||||
And if you don't want to host Kimai, you can use [the Cloud version](https://www.kimai.cloud/) of it.
|
||||
|
||||
@@ -61,19 +60,16 @@ And if you don't want to host Kimai, you can use [the Cloud version](https://www
|
||||
|
||||
### Plugins
|
||||
|
||||
- [Plugin marketplace](https://www.kimai.org/store/) — find existing plugins here
|
||||
- [Plugins](https://www.kimai.org/store/) — paid and free plugin marketplace
|
||||
- [Developer documentation](https://www.kimai.org/documentation/developers.html) — how to create a plugin
|
||||
|
||||
## Roadmap and releases
|
||||
|
||||
You can see a rough development roadmap in the [Milestones](https://github.com/kimai/kimai/milestones) sections.
|
||||
It is open for changes and input from the community, your [ideas and questions](https://github.com/kimai/kimai/issues) are welcome.
|
||||
You can see a rough development [roadmap](https://github.com/orgs/kimai/projects/2), which is open for changes and input from the community, your [ideas](https://github.com/kimai/kimai/issues) are welcome.
|
||||
|
||||
Release versions will be created on a regular basis, every couple of weeks latest.
|
||||
Every code change, whether it's a new feature or a bugfix, will be done on the `main` branch.
|
||||
|
||||
For the time being and until 2.0 is widely adopted, the [1.x branch](https://github.com/kimai/kimai/tree/1.x) will receive bug fixes.
|
||||
|
||||
## Contributing
|
||||
|
||||
You want to contribute to this repository? This is so great!
|
||||
@@ -81,7 +77,7 @@ The best way to start is to [open a new issue](https://github.com/kimai/kimai/is
|
||||
|
||||
In case you want to contribute, but you wouldn't know how, here are some suggestions:
|
||||
|
||||
- Spread the word: More user means more people testing and contributing to Kimai, which in turn means better stability and more and better features. Please vote for Kimai on any software platform, you can toot or tweet about it, share it on LinkedIn, Reddit or any of your favorite social media platforms. Every bit helps!
|
||||
- Spread the word: Please [write a testimonial for our Wall of love](https://love.kimai.org), vote for Kimai on any software platform, you can toot or tweet about it, share it on LinkedIn, Reddit and any other social media platform!
|
||||
- Answer questions: You know the answer to another user's problem? Share your knowledge.
|
||||
- Something can be done better? An essential feature is missing? Create a feature request.
|
||||
- Report bugs makes Kimai better for everyone.
|
||||
|
||||
17
SECURITY.md
17
SECURITY.md
@@ -1,24 +1,11 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
As announced in the [README](README.md) I only support the latest available release and `main` branch.
|
||||
As announced in the [README](README.md) security fixes will only be added to the `main` branch.
|
||||
|
||||
| Version | Supported |
|
||||
|----------------------|--------------------|
|
||||
| main branch | :white_check_mark: |
|
||||
| latest minor release | :white_check_mark: |
|
||||
| older releases | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please read the [Bughunter](https://www.kimai.org/documentation/bughunter.html) documentation before posting.
|
||||
You can report any security related vulnerability in the [advisory section at GitHub](https://github.com/kimai/kimai/security/advisories) or via email to kpapst@gmx.net.
|
||||
|
||||
I will work as fast as I can to fix the problem and publish a bugfix release / security update.
|
||||
Depending on the size of the required fixes, this might take a couple of hours or a couple of days.
|
||||
|
||||
You can expect that your message will be answered ASAP.
|
||||
|
||||
You will be mentioned in the release notes if your issue is valid.
|
||||
I am grateful for any (discrete) disclosure of vulnerabilities!
|
||||
You find all information in our [Bughunter documentation](https://www.kimai.org/documentation/bughunter.html).
|
||||
|
||||
368
UPGRADING-1.md
Normal file
368
UPGRADING-1.md
Normal file
@@ -0,0 +1,368 @@
|
||||
# Upgrading Kimai - Version 1.x
|
||||
|
||||
_Make sure to create a backup before you start!_
|
||||
|
||||
Read the [updates documentation](https://www.kimai.org/documentation/updates.html) to find out how
|
||||
you can upgrade your Kimai installation to the latest stable release.
|
||||
|
||||
Check below if there are more version specific steps required, which need to be executed after the normal update process.
|
||||
Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation.
|
||||
|
||||
## [1.16](https://github.com/kimai/kimai/releases/tag/1.16)
|
||||
|
||||
If you are using MariaDB, it must be at least version 10.1, older versions are not supported any longer.
|
||||
|
||||
**DEVELOPER**
|
||||
|
||||
- Removed `formDateTime` field from API model `I18nConfig`
|
||||
- Upgraded to Doctrine DBAL 3, see [docs](https://github.com/doctrine/dbal/blob/3.1.x/UPGRADE.md) - you might have to update your bundle
|
||||
|
||||
## [1.15](https://github.com/kimai/kimai/releases/tag/1.15)
|
||||
|
||||
**Many database changes: don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
Updating the database might take quite a while, depending on the amount of timesheet entries and speed of your database server (~1 minute per 100k records).
|
||||
|
||||
**ATTENTION**
|
||||
|
||||
- This release bumps the minimum required [PHP version to 7.3](https://www.kimai.org/blog/2021/php8-support-php72-dropped/)
|
||||
- Self-registration is disabled by default
|
||||
- Self-registration now always requires email confirmation
|
||||
- All plugins that use own databases need to be updated as well
|
||||
- Removed the YearChart widget and the related configs named `userRecapThisYear`, `userRecapLastYear`, `userRecapTwoYears`, `userRecapThreeYears`
|
||||
|
||||
**LDAP & SAML**
|
||||
|
||||
Please verify your config with the [LDAP](https://www.kimai.org/documentation/ldap.html) and [SAML](https://www.kimai.org/documentation/saml.html) documentation, especially:
|
||||
|
||||
- SAML users: activate it by setting the `kimai.saml.activate: true` config key
|
||||
- LDAP users: activate it by setting the `kimai.ldap.activate: true` config key
|
||||
- LDAP and SAML users need to remove the complete `security` section from their `local.yaml`
|
||||
|
||||
**DEVELOPER**
|
||||
|
||||
PHP 8 compatibility forced to upgrade MANY libraries, including but not limited to:
|
||||
|
||||
- Removed FOSUserBundle and hslavich/oneloginsaml
|
||||
- Doctrine Migrations, whose new major version forces plugin updates
|
||||
- Gedmo v3 (which include BC breaks in definitions)
|
||||
- Doctrine DBAL and others, which required PHP 7.3 as well
|
||||
|
||||
**API BC break**: Due to team structure changes, it was impossible to keep the (writing) API structure. Please adjust your code accordingly!
|
||||
|
||||
## [1.14](https://github.com/kimai/kimai/releases/tag/1.14)
|
||||
|
||||
**CRITICAL BC break**: SQLite support was removed. If you are using SQLite, you have to [read this blog post](https://www.kimai.org/blog/2021/sqlite-and-ftp-support-removed/) and migrate to MySQL/MariaDB first!
|
||||
|
||||
**New database tables and fields: don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
Permission changes:
|
||||
- `history_invoice` - removed permission entirely
|
||||
|
||||
## [1.13](https://github.com/kimai/kimai/releases/tag/1.13)
|
||||
|
||||
- Deprecated `now` variable in export templates: create it yourself with `{% set now = create_date('now', app.user) %}`
|
||||
- Changed invoice filename generation (check if you use cronjob for invoices)
|
||||
- **BC break**: duration entered as plain numbers will now be treated as decimal duration in hours instead of seconds
|
||||
|
||||
## [1.12](https://github.com/kimai/kimai/releases/tag/1.12)
|
||||
|
||||
- Export templates can now include items from plugins (eg. Expenses).
|
||||
|
||||
## [1.10](https://github.com/kimai/kimai/releases/tag/1.10)
|
||||
|
||||
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
- Invoice renderer `CSV` was removed
|
||||
- Sessions are now stored in the database (all users have to re-login after upgrade)
|
||||
- New permissions: `lockdown_grace_timesheet`, `lockdown_override_timesheet`, `view_all_data`
|
||||
- Fixed team permissions on user queries: depending on your previous team & permission setup your users might see less data (SUPER_ADMINS see all data, but new: ADMINS only see all data if they own the `view_all_data` permission)
|
||||
- Markdown does not support headings anymore, text like `# foo` is not converted to `<h1 id="foo">foo</h1>` anymore
|
||||
|
||||
### Developer
|
||||
|
||||
- **BC break**: removed registration of `.env` with `putenv()` - do not rely on `getenv()` as it is not thread-safe
|
||||
- **BC break**: interface method signature `HtmlToPdfConverter::convertToPdf()` changed
|
||||
- **BC break**: the macros `badge` and `label` do not apply the `|trans` filter any more
|
||||
- **BC Break**: removed `getVisible()` (deprecated since 1.4) method on Customer, Project and Activity (use `isVisible()` instead, templates are still working)
|
||||
- **BC Break**: API changes
|
||||
- some representation names changed (eg. from `ActivityMetaField` to `ActivityMeta`, `TimesheetSubCollection` vs `TimesheetCollectionExpanded`), you could use `class_alias()` if you use auto-generated code from Swagger-Gen or alike
|
||||
- new result types were introduced
|
||||
- result data changed in some areas to smooth out inconsistencies (eg. TeamEntity fields changed in nested results)
|
||||
|
||||
## [1.9](https://github.com/kimai/kimai/releases/tag/1.9)
|
||||
|
||||
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
- The directory `var/data/invoices/` will be used to store archived invoice files (check file permissions)
|
||||
- The default invoice number format changed. If you want to use the old one: configure `{date}` as format - see [invoice documentation](https://www.kimai.org/documentation/invoices.html)
|
||||
- HTML invoice templates are now treated like other files and offered as download. If you are using relative URLs for including assets (CSS, images) you need to either inline them (see the default templates) or use absolute URLs
|
||||
- Invoice templates that use the templates variables `${activity.X}` or `${project.X}` should be checked and possibly adapted, as multi-select is now possible for filtering
|
||||
- Invoice templates have access to all meta-fields as variables, not only the ones marked as visible
|
||||
- Rates configuration/structure changed for customer, project and activity
|
||||
- **Invoice templates**: rates variables were removed
|
||||
|
||||
Permission changes:
|
||||
- `history_invoice` - NEW: grants all features for the new invoice archive (by default for all admins)
|
||||
|
||||
### Developer
|
||||
|
||||
- **BC break**: `InvoiceItemInterface` has new methods `getType()` and `getCategory()`
|
||||
- **BC break**: API fields changed - see new `/rates` endpoints
|
||||
|
||||
## [1.8](https://github.com/kimai/kimai/releases/tag/1.8)
|
||||
|
||||
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
- New PHP requirement: `ext-xsl` - which should be pre-installed in most environments when `ext-xml` is loaded
|
||||
- New mailer library: check if emails are still working (eg. by using the "password forgotten" function) or if you need to adjust your configuration, [see docs at symfony.com](https://symfony.com/doc/current/components/mailer.html#transport)
|
||||
- Support for line breaks in multiline invoice fields for spreadsheets (check your invoice templates after the update)
|
||||
|
||||
Permission changes:
|
||||
|
||||
- `comments_create_customer` - NEW: permission that allows to add new comments for customers
|
||||
- `comments_create_team_customer` - NEW: permission that allows to add new comments for team members of the current customer
|
||||
- `comments_create_teamlead_customer` - NEW: permission that allows to add new comments for a teamlead of the current customer
|
||||
- `comments_create_project` - NEW: permission that allows to add new comments for project
|
||||
- `comments_create_team_project` - NEW: permission that allows to add new comments for team members of the current project
|
||||
- `comments_create_teamlead_project` - NEW: permission that allows to add new comments for a teamlead of the current project
|
||||
- `edit_teamlead_project` - removed default permission from ROLE_TEAMLEAD (if you use it: change it in the Role & Permission UI)
|
||||
- `edit_teamlead_customer` - removed default permission from ROLE_TEAMLEAD (if you use it: change it in the Role & Permission UI)
|
||||
- `upload_invoice_template` - NEW: permission that allows to upload invoice documents from the UI
|
||||
|
||||
## [1.7](https://github.com/kimai/kimai/releases/tag/1.7)
|
||||
|
||||
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
New permissions:
|
||||
|
||||
- `comments_customer` - show comment list on customer detail page (new feature)
|
||||
- `details_customer` - show detail information for customers (customer number, vat, rates, meta-fields, assigned teams ...)
|
||||
- `comments_project` - show comment list on project detail page (new feature)
|
||||
- `details_project` - show detail information for projects (rates, meta-fields, assigned teams ...)
|
||||
|
||||
If you are using teams, please read on: The following list of permissions are now also available in the UI and they (can) replace the `X_project` and `X_customer` permissions.
|
||||
They are more strict, as they allow only access to team specific items, the older permissions without `_teamlead_`/`_team_` work on a global level instead.
|
||||
|
||||
- `view_teamlead_customer`, `edit_teamlead_customer`, `budget_teamlead_customer`, `permissions_teamlead_customer`, `comments_teamlead_customer`, `details_teamlead_customer` - allows access to customer data when user is teamlead of a team assigned to the customer (replaces more global permission like `view_customer` for teamleads)
|
||||
- `view_team_customer`, `edit_team_customer`, `budget_team_customer`, `comments_team_customer`, `details_team_customer` - allows access to customer data when user is member of a team assigned to the customer (replaces more global permission like `view_customer` for users)
|
||||
- `view_teamlead_project`, `edit_teamlead_project`, `budget_teamlead_project`, `permissions_teamlead_project`, `comments_teamlead_project`, `details_teamlead_project` - allows access to customer data when user is teamlead of a team assigned to the project (replaces more global permission like `view_project` for teamleads)
|
||||
- `view_team_project`, `edit_team_project`, `budget_team_project`, `comments_team_project`, `details_team_project` - allows access to customer data when user is member of a team assigned to the project (replaces more global permission like `view_project` for users)
|
||||
|
||||
### ExpenseBundle
|
||||
|
||||
**ATTENTION** due to incompatibilities in the underlying frameworks users of the ExpenseBundle need to do one more step:
|
||||
You need to delete the bundle before updating: `rm -r var/plugins/ExpenseBundle`, otherwise you will run into errors during the update.
|
||||
After the Kimai update was successful, you have to re-install the latest bundle version, which is compatible with Kimai 1.7 only.
|
||||
|
||||
### Developer
|
||||
|
||||
- Projects now have a start and end date and the API will only return those, which are either unconfigured or currently active, you might want to reload the list of projects once the user entered begin and end datetime OR use the new `ignoreDates` parameter.
|
||||
- Doctrine bundle was updated to v2, check your code for [the usage of RegistryInterface and ObjectManager](https://github.com/doctrine/DoctrineBundle/blob/master/UPGRADE-2.0.md)
|
||||
- Removed the webserver bundle and the command `server:run` - see [docs](https://www.kimai.org/documentation/developers.html)
|
||||
|
||||
## [1.6](https://github.com/kimai/kimai/releases/tag/1.6), [1.6.1](https://github.com/kimai/kimai/releases/tag/1.6.1), [1.6.2](https://github.com/kimai/kimai/releases/tag/1.6.2)
|
||||
|
||||
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
- Invoice changes:
|
||||
- Moved CSV, ODS and XSLX invoice templates to [another repository](https://github.com/Keleo/kimai2-invoice-templates). Using them? Install them manually (see [invoice documentation](https://www.kimai.org/documentation/invoices.html)).
|
||||
- Added new invoice fields (VAT, contact, payment details) and customer field (VAT). Used the twig settings before? Move them to the respective invoice template settings.
|
||||
- Permissions can be managed via Admin UI. Please move your permission settings from [local.yaml to your database](https://www.kimai.org/documentation/permissions.html).
|
||||
- Important permission change: regular users with the `view_other_timesheet` permission could see all timesheets. This was a legacy from the time before team permissions were introduced. If you rely on this behavior, you need to create a team with all users and the teamlead being the user who needs access to all timesheets.
|
||||
|
||||
### Developer
|
||||
|
||||
Please add default permissions to your [plugin](https://www.kimai.org/documentation/plugins.html).
|
||||
|
||||
## [1.5](https://github.com/kimai/kimai/releases/tag/1.5)
|
||||
|
||||
[Update as usual](https://www.kimai.org/documentation/updates.html)
|
||||
|
||||
## [1.4](https://github.com/kimai/kimai/releases/tag/1.4)
|
||||
|
||||
**There is a new directory, which needs to be writable by the webserver: `public/avatars/`.**
|
||||
|
||||
New permission (used in new dashboard widget):
|
||||
- `view_team_member` - display team assignments (names, teamleads and members) for the current user
|
||||
|
||||
Activated Javascript select component by default (check mobile devices).
|
||||
|
||||
### Developer: BC breaks
|
||||
|
||||
- Dashboard widgets and rows need to define their `type` by FQCN
|
||||
- Switched to Symfony 4.3 event types, this could fail in plugins, but only if they didn't use the official constants for event names
|
||||
|
||||
## [1.3](https://github.com/kimai/kimai/releases/tag/1.3)
|
||||
|
||||
Added `manage_tag` permission for new tag features
|
||||
|
||||
### Developer: BC breaks
|
||||
|
||||
- Refactored toolbars and search, plugins needs to be checked
|
||||
- Invoices now supports multiple repositories, some method signatures had to be changed (eg. `calculateSumIdentifier()`)
|
||||
|
||||
## [1.2](https://github.com/kimai/kimai/releases/tag/1.2)
|
||||
|
||||
**If you are still using 0.7 or below, you need to upgrade to 1.1 before upgrading to this version.**
|
||||
|
||||
- Deleted timezone conversion command.
|
||||
- Minimum password length raised from 5 to 8 character (applies only for password changes and new users)
|
||||
- Maximum customer name length lowered to 150 character
|
||||
- Maximum project name length lowered to 150 character
|
||||
- Maximum activity name length lowered to 150 character
|
||||
- Added new permission: `manage_invoice_template`
|
||||
- Removed permissions: `view_invoice_template`, `create_invoice_template`, `edit_invoice_template`, `delete_invoice_template`
|
||||
- Removed permission: `view_export` (using `create_export` only)
|
||||
|
||||
### Developer: BC breaks
|
||||
|
||||
- Custom export renderer need to check for usage of `Timesheet::getEnd()` as running entries can now be exported as well
|
||||
|
||||
## [1.1](https://github.com/kimai/kimai/releases/tag/1.1)
|
||||
|
||||
[Update as usual](https://www.kimai.org/documentation/updates.html), nothing special for this release if you upgrade from 1.0 / 1.0.1.
|
||||
|
||||
## [1.0](https://github.com/kimai/kimai/releases/tag/1.0)
|
||||
|
||||
This release contains several changes, as I still have the goal to stabilize the code base to prevent
|
||||
such "challenges" after 1.0 for a while.
|
||||
|
||||
### Changes for your local.yaml
|
||||
|
||||
New permissions are available. You have to add them to your `local.yaml` ONLY if you use a custom permission structure,
|
||||
otherwise you can't use the new features:
|
||||
- `view_tag` - view all tags
|
||||
- `delete_tag` - delete tags
|
||||
- `edit_exported_timesheet` - allows to edit records which were exported
|
||||
- `role_permissions` - view calculated permissions for user roles
|
||||
- `budget_activity` - view and edit budgets for activities
|
||||
- `budget_project` - view and edit budgets for projects
|
||||
- `budget_customer` - view and edit budgets for customers
|
||||
|
||||
Removed permission:
|
||||
- `system_actions` - removed experimental feature to flush app cache from the about screen
|
||||
|
||||
### BC BREAKS
|
||||
|
||||
- API: Format for queries including a datetime object fixed to use HTML5 format (previously `2019-03-02 14:23` - now `2019-03-02T14:23:00`)
|
||||
- **Permission config**: the `permissions` definition in your `local.yaml` needs to be verified/changed, as the internal structure was highly optimized to simplify the definition.
|
||||
Thanks to the new structure, you should be able to remove almost everything from your `local.yaml` (tip: start over from scratch!). Please read [the updated permission docu](https://www.kimai.org/documentation/permissions.html).
|
||||
- default widgets were removed from `kimai.yaml`, that shouldn't cause any issues ... but if something is odd: [look here for help](https://www.kimai.org/documentation/dashboard.html)
|
||||
|
||||
## [0.9](https://github.com/kimai/kimai/releases/tag/0.9)
|
||||
|
||||
Remember to execute the necessary timezone conversion script, if you haven't updated to 0.8 before (see below)!
|
||||
|
||||
### BC BREAKS
|
||||
|
||||
This release contains some BC breaks which were necessary before 1.0 will be released (_now or never_), to prevent those BC breaks after 1.0.
|
||||
|
||||
- **Kimai requires PHP 7.2 now => [PHP 7.1 expired 4 month ago](https://www.php.net/supported-versions.php)**
|
||||
- The `.env` variable `DATABASE_PREFIX` was removed and the table prefix is now hardcoded to `kimai2_`. If you used another prefix,
|
||||
you have to rename your tables manually before starting the update process. You can delete the row `DATABASE_PREFIX` from your `.env` file.
|
||||
- API: Format for DateTime objects changed, now including timezone identifier (previously `2019-03-02 14:23` - now `2019-03-02T14:23:00+00:00`), see [#718](https://github.com/kimai/kimai/pull/718)
|
||||
- API: changed from snake_case to camelCase (affected fields: hourlyRate vs hourly_rate / fixedRate vs fixed_rate / orderNumber vs order_number / i18n config object)
|
||||
- Plugin mechanism changed: existing Plugins have to be deleted or updated
|
||||
|
||||
### Apply necessary changes to your `local.yaml`:
|
||||
|
||||
New permissions are available:
|
||||
- `system_configuration` - for accessing the new system configuration screen
|
||||
- `system_actions` - for the experimental feature to flush your cache from the about screen
|
||||
- `plugins` - for accessing the new plugins screen
|
||||
|
||||
The setting `kimai.timesheet.mode` replaces the setting `kimai.timesheet.duration_only`. If you used the duration_only mode, you need to change your config:
|
||||
```yaml
|
||||
# Before
|
||||
kimai:
|
||||
timesheet:
|
||||
duration_only: true
|
||||
|
||||
# After
|
||||
kimai:
|
||||
timesheet:
|
||||
mode: duration_only
|
||||
```
|
||||
Or switch the mode directly in the new System configuration screen within Kimai.
|
||||
|
||||
## [0.8.1](https://github.com/kimai/kimai/releases/tag/0.8.1)
|
||||
|
||||
A bug fixing release. Remember to execute the necessary timezone conversion script, if you haven't updated to 0.8 before (see below)!
|
||||
|
||||
## [0.8](https://github.com/kimai/kimai/releases/tag/0.8)
|
||||
|
||||
After you followed the normal update and database migration process (see above), you need to execute a bash command to convert your timesheet data for timezone support:
|
||||
|
||||
- Read this [pull request](https://github.com/kimai/kimai/pull/372) BEFORE you follow the instructions to convert the
|
||||
timezones in your existing time records with `bin/console kimai:convert-timezone`. Without that, you will end up with wrong times in your database.
|
||||
|
||||
### Apply necessary changes to your `local.yaml`:
|
||||
|
||||
- A new boolean setting `kimai.timesheet.rules.allow_future_times` was introduced
|
||||
- New permissions are available:
|
||||
- `view_export` - for the new export feature
|
||||
- `create_export` - for the new export feature
|
||||
- `edit_export_own_timesheet` - for the new export feature
|
||||
- `edit_export_other_timesheet` - for the new export feature
|
||||
- `system_information` - to see the new about screen
|
||||
|
||||
## [0.7](https://github.com/kimai/kimai/releases/tag/0.7)
|
||||
|
||||
The configuration `kimai.theme.active_warning` was deprecated and should be replaced in your local.yaml,
|
||||
[read config docs for more information](https://www.kimai.org/documentation/timesheet.html#limit-active-entries).
|
||||
|
||||
## [0.6.1](https://github.com/kimai/kimai/releases/tag/0.6.1)
|
||||
|
||||
A bugfix release to address database compatibility issues with older MySQL/MariaDB versions.
|
||||
|
||||
## [0.6](https://github.com/kimai/kimai/releases/tag/0.6)
|
||||
|
||||
The API has some minor BC breaks: some fields were renamed and entities have a larger attribute set than collections.
|
||||
Be aware that the API is still is development mode and shouldn't be considered stable for now.
|
||||
|
||||
## [0.5](https://github.com/kimai/kimai/releases/tag/0.5)
|
||||
|
||||
Some configuration nodes were removed, if you have one of them in your `local.yaml` you need to delete them before you start the update:
|
||||
- `kimai.invoice.calculator`
|
||||
- `kimai.invoice.renderer`
|
||||
- `kimai.invoice.number_generator`
|
||||
|
||||
The new config `kimai.invoice.documents` was introduced, holding a list of directories ([read more](https://www.kimai.org/documentation/invoices.html)).
|
||||
|
||||
**BC break:** InvoiceTemplate name was changed from 255 characters to 60. If you used longer invoice-template names, they will be truncated when upgrading the database.
|
||||
Please make sure that they are unique in the first 60 character before you upgrade your database with `doctrine:migrations:migrate`.
|
||||
|
||||
## [0.4](https://github.com/kimai/kimai/releases/tag/0.4)
|
||||
|
||||
In the time between 0.3 and 0.4 there was a release of composer that introduced a BC break,
|
||||
which leads to problems between Composer and Symfony Flex, resulting in an error like this when running it:
|
||||
|
||||
```
|
||||
[ErrorException]
|
||||
Declaration of Symfony\Flex\ParallelDownloader::getRemoteContents($originUrl, $fileUrl, $context) should be compatible with Composer\Util\RemoteFilesystem::getRemoteContents($originUrl, $fileUrl, $context, ?array &$responseHeaders = NULL)
|
||||
```
|
||||
|
||||
This can be fixed by updating Composer and Flex before executing the Kimai update:
|
||||
```
|
||||
sudo composer self-update
|
||||
sudo -u www-data composer update symfony/flex --no-plugins --no-scripts
|
||||
```
|
||||
|
||||
## [0.3](https://github.com/kimai/kimai/releases/tag/0.3)
|
||||
|
||||
You need to adjust your `.env` file and add your `from` address for [all emails](https://www.kimai.org/documentation/emails.html) generated by Kimai 2:
|
||||
```
|
||||
MAILER_FROM=kimai@example.com
|
||||
```
|
||||
|
||||
Create a file and database backup before executing the following steps:
|
||||
|
||||
```bash
|
||||
git pull origin master
|
||||
sudo -u www-data composer install --no-dev --optimize-autoloader
|
||||
sudo -u www-data bin/console cache:clear --env=prod
|
||||
sudo -u www-data bin/console cache:warmup --env=prod
|
||||
bin/console doctrine:migrations:version --add 20180701120000
|
||||
bin/console doctrine:migrations:migrate
|
||||
```
|
||||
381
UPGRADING.md
381
UPGRADING.md
@@ -1,4 +1,4 @@
|
||||
# Upgrading Kimai
|
||||
# Upgrading Kimai - Version2.x
|
||||
|
||||
_Make sure to create a backup before you start!_
|
||||
|
||||
@@ -8,21 +8,31 @@ you can upgrade your Kimai installation to the latest stable release.
|
||||
Check below if there are more version specific steps required, which need to be executed after the normal update process.
|
||||
Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation.
|
||||
|
||||
## [2.0.30](https://github.com/kimai/kimai/releases/tag/2.0.30)
|
||||
|
||||
The `DATABASE_URL` in your environment settings (e.g. [.env](https://github.com/kimai/kimai/issues/4246), [docker-compose.yaml](https://github.com/tobybatch/kimai2/issues/531) or webserver config)
|
||||
now requires the `charset` and `serverVersion` params, e.g.: `DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8mb4&serverVersion=10.5.8-MariaDB` (examples in `.env`).
|
||||
|
||||
## [2.0](https://github.com/kimai/kimai/releases/tag/2.0)
|
||||
|
||||
**!! This release requires minimum PHP version to 8.1 !!**
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- All plugins need to be updated: delete all previous version from your installation (`rm -r var/plugins/*`) before updating!
|
||||
- The `local.yaml` is not compatible with old version, remove it before the update and then re-create it after everything works
|
||||
- removed: configuring the `dashboard` is not supported any longer
|
||||
- removed: custom translation files via `theme.branding.translation`
|
||||
- removed: changing the plugin directory via `kimai.plugin_dir`
|
||||
|
||||
### Developer
|
||||
|
||||
Developer read the full documentation at [https://www.kimai.org/documentation/migration-v2.html](https://www.kimai.org/documentation/migration-v2.html).
|
||||
|
||||
- **All plugins need to be updated**: delete all previous version from your installation (`rm -r var/plugins/*`) before updating!
|
||||
- Invoice renderer and templates for XML, JSON and TEXT were moved to the [Extended invoicing plugin](https://www.kimai.org/store/invoice-bundle.html) (install if you use one of those)
|
||||
- Moved `company.docx` to [external repo](https://github.com/kimai/invoice-templates/tree/main/docx-company) (needs to be re-uploaded if you want to keep on using it!)
|
||||
- Role names are forced to be uppercase
|
||||
- Removed unused `public/avatars/` directory
|
||||
- `local.yaml` is not compatible with old version, remove it before the update and then re-create it after everything works
|
||||
- dashboard default config
|
||||
- removed: theme.branding.translation
|
||||
- removed: kimai.plugin_dir
|
||||
- Time-tracking mode `duration_only` was removed, existing installations will be switched to `duration_fixed_begin`
|
||||
- Removed Twig filters. You might have to replace them in your custom export/invoice templates:
|
||||
- `date_full` => `date_time`
|
||||
@@ -38,362 +48,3 @@ Developer read the full documentation at [https://www.kimai.org/documentation/mi
|
||||
- `composer require symfony/mailgun-mailer`
|
||||
- `composer require symfony/postmark-mailer`
|
||||
- `composer require symfony/sendgrid-mailer`
|
||||
|
||||
## [1.16](https://github.com/kimai/kimai/releases/tag/1.16)
|
||||
|
||||
If you are using MariaDB, it must be at least version 10.1, older versions are not supported any longer.
|
||||
|
||||
**DEVELOPER**
|
||||
|
||||
- Removed `formDateTime` field from API model `I18nConfig`
|
||||
- Upgraded to Doctrine DBAL 3, see [docs](https://github.com/doctrine/dbal/blob/3.1.x/UPGRADE.md) - you might have to update your bundle
|
||||
|
||||
## [1.15](https://github.com/kimai/kimai/releases/tag/1.15)
|
||||
|
||||
**Many database changes: don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
Updating the database might take quite a while, depending on the amount of timesheet entries and speed of your database server (~1 minute per 100k records).
|
||||
|
||||
**ATTENTION**
|
||||
|
||||
- This release bumps the minimum required [PHP version to 7.3](https://www.kimai.org/blog/2021/php8-support-php72-dropped/)
|
||||
- Self-registration is disabled by default
|
||||
- Self-registration now always requires email confirmation
|
||||
- All plugins that use own databases need to be updated as well
|
||||
- Removed the YearChart widget and the related configs named `userRecapThisYear`, `userRecapLastYear`, `userRecapTwoYears`, `userRecapThreeYears`
|
||||
|
||||
**LDAP & SAML**
|
||||
|
||||
Please verify your config with the [LDAP](https://www.kimai.org/documentation/ldap.html) and [SAML](https://www.kimai.org/documentation/saml.html) documentation, especially:
|
||||
|
||||
- SAML users: activate it by setting the `kimai.saml.activate: true` config key
|
||||
- LDAP users: activate it by setting the `kimai.ldap.activate: true` config key
|
||||
- LDAP and SAML users need to remove the complete `security` section from their `local.yaml`
|
||||
|
||||
**DEVELOPER**
|
||||
|
||||
PHP 8 compatibility forced to upgrade MANY libraries, including but not limited to:
|
||||
|
||||
- Removed FOSUserBundle and hslavich/oneloginsaml
|
||||
- Doctrine Migrations, whose new major version forces plugin updates
|
||||
- Gedmo v3 (which include BC breaks in definitions)
|
||||
- Doctrine DBAL and others, which required PHP 7.3 as well
|
||||
|
||||
**API BC break**: Due to team structure changes, it was impossible to keep the (writing) API structure. Please adjust your code accordingly!
|
||||
|
||||
## [1.14](https://github.com/kimai/kimai/releases/tag/1.14)
|
||||
|
||||
**CRITICAL BC break**: SQLite support was removed. If you are using SQLite, you have to [read this blog post](https://www.kimai.org/blog/2021/sqlite-and-ftp-support-removed/) and migrate to MySQL/MariaDB first!
|
||||
|
||||
**New database tables and fields: don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
Permission changes:
|
||||
- `history_invoice` - removed permission entirely
|
||||
|
||||
## [1.13](https://github.com/kimai/kimai/releases/tag/1.13)
|
||||
|
||||
- Deprecated `now` variable in export templates: create it yourself with `{% set now = create_date('now', app.user) %}`
|
||||
- Changed invoice filename generation (check if you use cronjob for invoices)
|
||||
- **BC break**: duration entered as plain numbers will now be treated as decimal duration in hours instead of seconds
|
||||
|
||||
## [1.12](https://github.com/kimai/kimai/releases/tag/1.12)
|
||||
|
||||
- Export templates can now include items from plugins (eg. Expenses).
|
||||
|
||||
## [1.10](https://github.com/kimai/kimai/releases/tag/1.10)
|
||||
|
||||
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
- Invoice renderer `CSV` was removed
|
||||
- Sessions are now stored in the database (all users have to re-login after upgrade)
|
||||
- New permissions: `lockdown_grace_timesheet`, `lockdown_override_timesheet`, `view_all_data`
|
||||
- Fixed team permissions on user queries: depending on your previous team & permission setup your users might see less data (SUPER_ADMINS see all data, but new: ADMINS only see all data if they own the `view_all_data` permission)
|
||||
- Markdown does not support headings anymore, text like `# foo` is not converted to `<h1 id="foo">foo</h1>` anymore
|
||||
|
||||
### Developer
|
||||
|
||||
- **BC break**: removed registration of `.env` with `putenv()` - do not rely on `getenv()` as it is not thread-safe
|
||||
- **BC break**: interface method signature `HtmlToPdfConverter::convertToPdf()` changed
|
||||
- **BC break**: the macros `badge` and `label` do not apply the `|trans` filter any more
|
||||
- **BC Break**: removed `getVisible()` (deprecated since 1.4) method on Customer, Project and Activity (use `isVisible()` instead, templates are still working)
|
||||
- **BC Break**: API changes
|
||||
- some representation names changed (eg. from `ActivityMetaField` to `ActivityMeta`, `TimesheetSubCollection` vs `TimesheetCollectionExpanded`), you could use `class_alias()` if you use auto-generated code from Swagger-Gen or alike
|
||||
- new result types were introduced
|
||||
- result data changed in some areas to smooth out inconsistencies (eg. TeamEntity fields changed in nested results)
|
||||
|
||||
## [1.9](https://github.com/kimai/kimai/releases/tag/1.9)
|
||||
|
||||
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
- The directory `var/data/invoices/` will be used to store archived invoice files (check file permissions)
|
||||
- The default invoice number format changed. If you want to use the old one: configure `{date}` as format - see [invoice documentation](https://www.kimai.org/documentation/invoices.html)
|
||||
- HTML invoice templates are now treated like other files and offered as download. If you are using relative URLs for including assets (CSS, images) you need to either inline them (see the default templates) or use absolute URLs
|
||||
- Invoice templates that use the templates variables `${activity.X}` or `${project.X}` should be checked and possibly adapted, as multi-select is now possible for filtering
|
||||
- Invoice templates have access to all meta-fields as variables, not only the ones marked as visible
|
||||
- Rates configuration/structure changed for customer, project and activity
|
||||
- **Invoice templates**: rates variables were removed
|
||||
|
||||
Permission changes:
|
||||
- `history_invoice` - NEW: grants all features for the new invoice archive (by default for all admins)
|
||||
|
||||
### Developer
|
||||
|
||||
- **BC break**: `InvoiceItemInterface` has new methods `getType()` and `getCategory()`
|
||||
- **BC break**: API fields changed - see new `/rates` endpoints
|
||||
|
||||
## [1.8](https://github.com/kimai/kimai/releases/tag/1.8)
|
||||
|
||||
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
- New PHP requirement: `ext-xsl` - which should be pre-installed in most environments when `ext-xml` is loaded
|
||||
- New mailer library: check if emails are still working (eg. by using the "password forgotten" function) or if you need to adjust your configuration, [see docs at symfony.com](https://symfony.com/doc/current/components/mailer.html#transport)
|
||||
- Support for line breaks in multiline invoice fields for spreadsheets (check your invoice templates after the update)
|
||||
|
||||
Permission changes:
|
||||
|
||||
- `comments_create_customer` - NEW: permission that allows to add new comments for customers
|
||||
- `comments_create_team_customer` - NEW: permission that allows to add new comments for team members of the current customer
|
||||
- `comments_create_teamlead_customer` - NEW: permission that allows to add new comments for a teamlead of the current customer
|
||||
- `comments_create_project` - NEW: permission that allows to add new comments for project
|
||||
- `comments_create_team_project` - NEW: permission that allows to add new comments for team members of the current project
|
||||
- `comments_create_teamlead_project` - NEW: permission that allows to add new comments for a teamlead of the current project
|
||||
- `edit_teamlead_project` - removed default permission from ROLE_TEAMLEAD (if you use it: change it in the Role & Permission UI)
|
||||
- `edit_teamlead_customer` - removed default permission from ROLE_TEAMLEAD (if you use it: change it in the Role & Permission UI)
|
||||
- `upload_invoice_template` - NEW: permission that allows to upload invoice documents from the UI
|
||||
|
||||
## [1.7](https://github.com/kimai/kimai/releases/tag/1.7)
|
||||
|
||||
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
New permissions:
|
||||
|
||||
- `comments_customer` - show comment list on customer detail page (new feature)
|
||||
- `details_customer` - show detail information for customers (customer number, vat, rates, meta-fields, assigned teams ...)
|
||||
- `comments_project` - show comment list on project detail page (new feature)
|
||||
- `details_project` - show detail information for projects (rates, meta-fields, assigned teams ...)
|
||||
|
||||
If you are using teams, please read on: The following list of permissions are now also available in the UI and they (can) replace the `X_project` and `X_customer` permissions.
|
||||
They are more strict, as they allow only access to team specific items, the older permissions without `_teamlead_`/`_team_` work on a global level instead.
|
||||
|
||||
- `view_teamlead_customer`, `edit_teamlead_customer`, `budget_teamlead_customer`, `permissions_teamlead_customer`, `comments_teamlead_customer`, `details_teamlead_customer` - allows access to customer data when user is teamlead of a team assigned to the customer (replaces more global permission like `view_customer` for teamleads)
|
||||
- `view_team_customer`, `edit_team_customer`, `budget_team_customer`, `comments_team_customer`, `details_team_customer` - allows access to customer data when user is member of a team assigned to the customer (replaces more global permission like `view_customer` for users)
|
||||
- `view_teamlead_project`, `edit_teamlead_project`, `budget_teamlead_project`, `permissions_teamlead_project`, `comments_teamlead_project`, `details_teamlead_project` - allows access to customer data when user is teamlead of a team assigned to the project (replaces more global permission like `view_project` for teamleads)
|
||||
- `view_team_project`, `edit_team_project`, `budget_team_project`, `comments_team_project`, `details_team_project` - allows access to customer data when user is member of a team assigned to the project (replaces more global permission like `view_project` for users)
|
||||
|
||||
### ExpenseBundle
|
||||
|
||||
**ATTENTION** due to incompatibilities in the underlying frameworks users of the ExpenseBundle need to do one more step:
|
||||
You need to delete the bundle before updating: `rm -r var/plugins/ExpenseBundle`, otherwise you will run into errors during the update.
|
||||
After the Kimai update was successful, you have to re-install the latest bundle version, which is compatible with Kimai 1.7 only.
|
||||
|
||||
### Developer
|
||||
|
||||
- Projects now have a start and end date and the API will only return those, which are either unconfigured or currently active, you might want to reload the list of projects once the user entered begin and end datetime OR use the new `ignoreDates` parameter.
|
||||
- Doctrine bundle was updated to v2, check your code for [the usage of RegistryInterface and ObjectManager](https://github.com/doctrine/DoctrineBundle/blob/master/UPGRADE-2.0.md)
|
||||
- Removed the webserver bundle and the command `server:run` - see [docs](https://www.kimai.org/documentation/developers.html)
|
||||
|
||||
## [1.6](https://github.com/kimai/kimai/releases/tag/1.6), [1.6.1](https://github.com/kimai/kimai/releases/tag/1.6.1), [1.6.2](https://github.com/kimai/kimai/releases/tag/1.6.2)
|
||||
|
||||
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||
|
||||
- Invoice changes:
|
||||
- Moved CSV, ODS and XSLX invoice templates to [another repository](https://github.com/Keleo/kimai2-invoice-templates). Using them? Install them manually (see [invoice documentation](https://www.kimai.org/documentation/invoices.html)).
|
||||
- Added new invoice fields (VAT, contact, payment details) and customer field (VAT). Used the twig settings before? Move them to the respective invoice template settings.
|
||||
- Permissions can be managed via Admin UI. Please move your permission settings from [local.yaml to your database](https://www.kimai.org/documentation/permissions.html).
|
||||
- Important permission change: regular users with the `view_other_timesheet` permission could see all timesheets. This was a legacy from the time before team permissions were introduced. If you rely on this behavior, you need to create a team with all users and the teamlead being the user who needs access to all timesheets.
|
||||
|
||||
### Developer
|
||||
|
||||
Please add default permissions to your [plugin](https://www.kimai.org/documentation/plugins.html).
|
||||
|
||||
## [1.5](https://github.com/kimai/kimai/releases/tag/1.5)
|
||||
|
||||
[Update as usual](https://www.kimai.org/documentation/updates.html)
|
||||
|
||||
## [1.4](https://github.com/kimai/kimai/releases/tag/1.4)
|
||||
|
||||
**There is a new directory, which needs to be writable by the webserver: `public/avatars/`.**
|
||||
|
||||
New permission (used in new dashboard widget):
|
||||
- `view_team_member` - display team assignments (names, teamleads and members) for the current user
|
||||
|
||||
Activated Javascript select component by default (check mobile devices).
|
||||
|
||||
### Developer: BC breaks
|
||||
|
||||
- Dashboard widgets and rows need to define their `type` by FQCN
|
||||
- Switched to Symfony 4.3 event types, this could fail in plugins, but only if they didn't use the official constants for event names
|
||||
|
||||
## [1.3](https://github.com/kimai/kimai/releases/tag/1.3)
|
||||
|
||||
Added `manage_tag` permission for new tag features
|
||||
|
||||
### Developer: BC breaks
|
||||
|
||||
- Refactored toolbars and search, plugins needs to be checked
|
||||
- Invoices now supports multiple repositories, some method signatures had to be changed (eg. `calculateSumIdentifier()`)
|
||||
|
||||
## [1.2](https://github.com/kimai/kimai/releases/tag/1.2)
|
||||
|
||||
**If you are still using 0.7 or below, you need to upgrade to 1.1 before upgrading to this version.**
|
||||
|
||||
- Deleted timezone conversion command.
|
||||
- Minimum password length raised from 5 to 8 character (applies only for password changes and new users)
|
||||
- Maximum customer name length lowered to 150 character
|
||||
- Maximum project name length lowered to 150 character
|
||||
- Maximum activity name length lowered to 150 character
|
||||
- Added new permission: `manage_invoice_template`
|
||||
- Removed permissions: `view_invoice_template`, `create_invoice_template`, `edit_invoice_template`, `delete_invoice_template`
|
||||
- Removed permission: `view_export` (using `create_export` only)
|
||||
|
||||
### Developer: BC breaks
|
||||
|
||||
- Custom export renderer need to check for usage of `Timesheet::getEnd()` as running entries can now be exported as well
|
||||
|
||||
## [1.1](https://github.com/kimai/kimai/releases/tag/1.1)
|
||||
|
||||
[Update as usual](https://www.kimai.org/documentation/updates.html), nothing special for this release if you upgrade from 1.0 / 1.0.1.
|
||||
|
||||
## [1.0](https://github.com/kimai/kimai/releases/tag/1.0)
|
||||
|
||||
This release contains several changes, as I still have the goal to stabilize the code base to prevent
|
||||
such "challenges" after 1.0 for a while.
|
||||
|
||||
### Changes for your local.yaml
|
||||
|
||||
New permissions are available. You have to add them to your `local.yaml` ONLY if you use a custom permission structure,
|
||||
otherwise you can't use the new features:
|
||||
- `view_tag` - view all tags
|
||||
- `delete_tag` - delete tags
|
||||
- `edit_exported_timesheet` - allows to edit records which were exported
|
||||
- `role_permissions` - view calculated permissions for user roles
|
||||
- `budget_activity` - view and edit budgets for activities
|
||||
- `budget_project` - view and edit budgets for projects
|
||||
- `budget_customer` - view and edit budgets for customers
|
||||
|
||||
Removed permission:
|
||||
- `system_actions` - removed experimental feature to flush app cache from the about screen
|
||||
|
||||
### BC BREAKS
|
||||
|
||||
- API: Format for queries including a datetime object fixed to use HTML5 format (previously `2019-03-02 14:23` - now `2019-03-02T14:23:00`)
|
||||
- **Permission config**: the `permissions` definition in your `local.yaml` needs to be verified/changed, as the internal structure was highly optimized to simplify the definition.
|
||||
Thanks to the new structure, you should be able to remove almost everything from your `local.yaml` (tip: start over from scratch!). Please read [the updated permission docu](https://www.kimai.org/documentation/permissions.html).
|
||||
- default widgets were removed from `kimai.yaml`, that shouldn't cause any issues ... but if something is odd: [look here for help](https://www.kimai.org/documentation/dashboard.html)
|
||||
|
||||
## [0.9](https://github.com/kimai/kimai/releases/tag/0.9)
|
||||
|
||||
Remember to execute the necessary timezone conversion script, if you haven't updated to 0.8 before (see below)!
|
||||
|
||||
### BC BREAKS
|
||||
|
||||
This release contains some BC breaks which were necessary before 1.0 will be released (_now or never_), to prevent those BC breaks after 1.0.
|
||||
|
||||
- **Kimai requires PHP 7.2 now => [PHP 7.1 expired 4 month ago](https://www.php.net/supported-versions.php)**
|
||||
- The `.env` variable `DATABASE_PREFIX` was removed and the table prefix is now hardcoded to `kimai2_`. If you used another prefix,
|
||||
you have to rename your tables manually before starting the update process. You can delete the row `DATABASE_PREFIX` from your `.env` file.
|
||||
- API: Format for DateTime objects changed, now including timezone identifier (previously `2019-03-02 14:23` - now `2019-03-02T14:23:00+00:00`), see [#718](https://github.com/kimai/kimai/pull/718)
|
||||
- API: changed from snake_case to camelCase (affected fields: hourlyRate vs hourly_rate / fixedRate vs fixed_rate / orderNumber vs order_number / i18n config object)
|
||||
- Plugin mechanism changed: existing Plugins have to be deleted or updated
|
||||
|
||||
### Apply necessary changes to your `local.yaml`:
|
||||
|
||||
New permissions are available:
|
||||
- `system_configuration` - for accessing the new system configuration screen
|
||||
- `system_actions` - for the experimental feature to flush your cache from the about screen
|
||||
- `plugins` - for accessing the new plugins screen
|
||||
|
||||
The setting `kimai.timesheet.mode` replaces the setting `kimai.timesheet.duration_only`. If you used the duration_only mode, you need to change your config:
|
||||
```yaml
|
||||
# Before
|
||||
kimai:
|
||||
timesheet:
|
||||
duration_only: true
|
||||
|
||||
# After
|
||||
kimai:
|
||||
timesheet:
|
||||
mode: duration_only
|
||||
```
|
||||
Or switch the mode directly in the new System configuration screen within Kimai.
|
||||
|
||||
## [0.8.1](https://github.com/kimai/kimai/releases/tag/0.8.1)
|
||||
|
||||
A bug fixing release. Remember to execute the necessary timezone conversion script, if you haven't updated to 0.8 before (see below)!
|
||||
|
||||
## [0.8](https://github.com/kimai/kimai/releases/tag/0.8)
|
||||
|
||||
After you followed the normal update and database migration process (see above), you need to execute a bash command to convert your timesheet data for timezone support:
|
||||
|
||||
- Read this [pull request](https://github.com/kimai/kimai/pull/372) BEFORE you follow the instructions to convert the
|
||||
timezones in your existing time records with `bin/console kimai:convert-timezone`. Without that, you will end up with wrong times in your database.
|
||||
|
||||
### Apply necessary changes to your `local.yaml`:
|
||||
|
||||
- A new boolean setting `kimai.timesheet.rules.allow_future_times` was introduced
|
||||
- New permissions are available:
|
||||
- `view_export` - for the new export feature
|
||||
- `create_export` - for the new export feature
|
||||
- `edit_export_own_timesheet` - for the new export feature
|
||||
- `edit_export_other_timesheet` - for the new export feature
|
||||
- `system_information` - to see the new about screen
|
||||
|
||||
## [0.7](https://github.com/kimai/kimai/releases/tag/0.7)
|
||||
|
||||
The configuration `kimai.theme.active_warning` was deprecated and should be replaced in your local.yaml,
|
||||
[read config docs for more information](https://www.kimai.org/documentation/timesheet.html#limit-active-entries).
|
||||
|
||||
## [0.6.1](https://github.com/kimai/kimai/releases/tag/0.6.1)
|
||||
|
||||
A bugfix release to address database compatibility issues with older MySQL/MariaDB versions.
|
||||
|
||||
## [0.6](https://github.com/kimai/kimai/releases/tag/0.6)
|
||||
|
||||
The API has some minor BC breaks: some fields were renamed and entities have a larger attribute set than collections.
|
||||
Be aware that the API is still is development mode and shouldn't be considered stable for now.
|
||||
|
||||
## [0.5](https://github.com/kimai/kimai/releases/tag/0.5)
|
||||
|
||||
Some configuration nodes were removed, if you have one of them in your `local.yaml` you need to delete them before you start the update:
|
||||
- `kimai.invoice.calculator`
|
||||
- `kimai.invoice.renderer`
|
||||
- `kimai.invoice.number_generator`
|
||||
|
||||
The new config `kimai.invoice.documents` was introduced, holding a list of directories ([read more](https://www.kimai.org/documentation/invoices.html)).
|
||||
|
||||
**BC break:** InvoiceTemplate name was changed from 255 characters to 60. If you used longer invoice-template names, they will be truncated when upgrading the database.
|
||||
Please make sure that they are unique in the first 60 character before you upgrade your database with `doctrine:migrations:migrate`.
|
||||
|
||||
## [0.4](https://github.com/kimai/kimai/releases/tag/0.4)
|
||||
|
||||
In the time between 0.3 and 0.4 there was a release of composer that introduced a BC break,
|
||||
which leads to problems between Composer and Symfony Flex, resulting in an error like this when running it:
|
||||
|
||||
```
|
||||
[ErrorException]
|
||||
Declaration of Symfony\Flex\ParallelDownloader::getRemoteContents($originUrl, $fileUrl, $context) should be compatible with Composer\Util\RemoteFilesystem::getRemoteContents($originUrl, $fileUrl, $context, ?array &$responseHeaders = NULL)
|
||||
```
|
||||
|
||||
This can be fixed by updating Composer and Flex before executing the Kimai update:
|
||||
```
|
||||
sudo composer self-update
|
||||
sudo -u www-data composer update symfony/flex --no-plugins --no-scripts
|
||||
```
|
||||
|
||||
## [0.3](https://github.com/kimai/kimai/releases/tag/0.3)
|
||||
|
||||
You need to adjust your `.env` file and add your `from` address for [all emails](https://www.kimai.org/documentation/emails.html) generated by Kimai 2:
|
||||
```
|
||||
MAILER_FROM=kimai@example.com
|
||||
```
|
||||
|
||||
Create a file and database backup before executing the following steps:
|
||||
|
||||
```bash
|
||||
git pull origin master
|
||||
sudo -u www-data composer install --no-dev --optimize-autoloader
|
||||
sudo -u www-data bin/console cache:clear --env=prod
|
||||
sudo -u www-data bin/console cache:warmup --env=prod
|
||||
bin/console doctrine:migrations:version --add 20180701120000
|
||||
bin/console doctrine:migrations:migrate
|
||||
```
|
||||
|
||||
1
assets/app-rtl.js
Normal file
1
assets/app-rtl.js
Normal file
@@ -0,0 +1 @@
|
||||
require('./sass/_app-rtl.scss');
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
require('./sass/app.scss');
|
||||
require('./sass/_app.scss');
|
||||
|
||||
// ------ Kimai itself ------
|
||||
require('./js/KimaiWebLoader.js');
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
Legend,
|
||||
Title,
|
||||
Tooltip,
|
||||
Colors,
|
||||
// SubTitle
|
||||
} from 'chart.js';
|
||||
|
||||
@@ -50,6 +51,7 @@ Chart.register(
|
||||
Legend,
|
||||
Title,
|
||||
Tooltip,
|
||||
Colors
|
||||
// SubTitle
|
||||
);
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
|
||||
require('./sass/export-pdf.scss');
|
||||
require('./sass/_export-pdf.scss');
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
|
||||
require('./sass/invoice-pdf.scss');
|
||||
require('./sass/_invoice-pdf.scss');
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
|
||||
require('./sass/invoice.scss');
|
||||
require('./sass/_invoice.scss');
|
||||
|
||||
@@ -88,4 +88,11 @@ export default class KimaiContainer {
|
||||
return this._configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {KimaiUser}
|
||||
*/
|
||||
getUser() {
|
||||
return this.getPlugin('user');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ import KimaiDateNowForm from "./forms/KimaiDateNowForm";
|
||||
import KimaiNotification from "./plugins/KimaiNotification";
|
||||
import KimaiHotkeys from "./plugins/KimaiHotkeys";
|
||||
import KimaiRemoteModal from "./plugins/KimaiRemoteModal";
|
||||
import KimaiUser from "./plugins/KimaiUser";
|
||||
import KimaiAutocompleteTags from "./forms/KimaiAutocompleteTags";
|
||||
|
||||
export default class KimaiLoader {
|
||||
|
||||
@@ -55,6 +57,7 @@ export default class KimaiLoader {
|
||||
);
|
||||
|
||||
// GLOBAL HELPER PLUGINS
|
||||
kimai.registerPlugin(new KimaiUser());
|
||||
kimai.registerPlugin(new KimaiEscape());
|
||||
kimai.registerPlugin(new KimaiEvent());
|
||||
kimai.registerPlugin(new KimaiAPI());
|
||||
@@ -68,6 +71,7 @@ export default class KimaiLoader {
|
||||
kimai.registerPlugin(new KimaiDateRangePicker('input[data-daterangepicker="on"]'));
|
||||
kimai.registerPlugin(new KimaiDatePicker('input[data-datepicker="on"]'));
|
||||
kimai.registerPlugin(new KimaiAutocomplete());
|
||||
kimai.registerPlugin(new KimaiAutocompleteTags());
|
||||
kimai.registerPlugin(new KimaiTimesheetForm());
|
||||
kimai.registerPlugin(new KimaiTeamForm());
|
||||
kimai.registerPlugin(new KimaiCopyDataForm());
|
||||
@@ -81,7 +85,7 @@ export default class KimaiLoader {
|
||||
kimai.registerPlugin(new KimaiDatatable('section.content', 'table.dataTable'));
|
||||
kimai.registerPlugin(new KimaiToolbar('form.searchform', 'toolbar-action'));
|
||||
kimai.registerPlugin(new KimaiAlternativeLinks('.alternative-link'));
|
||||
kimai.registerPlugin(new KimaiAjaxModalForm('.modal-ajax-form'));
|
||||
kimai.registerPlugin(new KimaiAjaxModalForm('.modal-ajax-form', ['td.multiCheckbox', 'td.actions']));
|
||||
kimai.registerPlugin(new KimaiRemoteModal());
|
||||
kimai.registerPlugin(new KimaiActiveRecords());
|
||||
kimai.registerPlugin(new KimaiAPILink('api-link'));
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
*/
|
||||
|
||||
import TomSelect from 'tom-select';
|
||||
import KimaiFormPlugin from "./KimaiFormPlugin";
|
||||
import KimaiFormTomselectPlugin from "./KimaiFormTomselectPlugin";
|
||||
|
||||
/**
|
||||
* Supporting auto-complete fields via API.
|
||||
* Used for timesheet tagging in toolbar and edit dialogs.
|
||||
*/
|
||||
export default class KimaiAutocomplete extends KimaiFormPlugin {
|
||||
export default class KimaiAutocomplete extends KimaiFormTomselectPlugin {
|
||||
|
||||
init()
|
||||
{
|
||||
@@ -28,11 +27,23 @@ export default class KimaiAutocomplete extends KimaiFormPlugin {
|
||||
return true;
|
||||
}
|
||||
|
||||
activateForm(form)
|
||||
{
|
||||
loadData(apiUrl, query, callback) {
|
||||
/** @type {KimaiAPI} API */
|
||||
const API = this.getContainer().getPlugin('api');
|
||||
|
||||
API.get(apiUrl, {'name': query}, (data) => {
|
||||
let results = [];
|
||||
for (let item of data) {
|
||||
results.push({text: item.name, value: item.name});
|
||||
}
|
||||
callback(results);
|
||||
}, () => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
activateForm(form)
|
||||
{
|
||||
[].slice.call(form.querySelectorAll(this.selector)).map((node) => {
|
||||
const apiUrl = node.dataset['autocompleteUrl'];
|
||||
let minChars = 3;
|
||||
@@ -40,7 +51,12 @@ export default class KimaiAutocomplete extends KimaiFormPlugin {
|
||||
minChars = parseInt(node.dataset['minimumCharacter']);
|
||||
}
|
||||
|
||||
new TomSelect(node, {
|
||||
let options = {
|
||||
// see https://github.com/orchidjs/tom-select/issues/543#issuecomment-1664342257
|
||||
onItemAdd: function(){
|
||||
// remove remaining characters from input after selecting an item
|
||||
this.setTextboxValue('');
|
||||
},
|
||||
// if there are more than 500, they need to be found by "typing"
|
||||
maxOptions: 500,
|
||||
// the autocomplete is ONLY used, when the user can create tags
|
||||
@@ -53,36 +69,21 @@ export default class KimaiAutocomplete extends KimaiFormPlugin {
|
||||
return query.length >= minChars;
|
||||
},
|
||||
load: (query, callback) => {
|
||||
API.get(apiUrl, {'name': query}, (data) => {
|
||||
const results = [].slice.call(data).map((result) => {
|
||||
return {text: result, value: result};
|
||||
});
|
||||
callback(results);
|
||||
}, () => {
|
||||
callback();
|
||||
});
|
||||
this.loadData(apiUrl, query, callback);
|
||||
},
|
||||
render: {
|
||||
// eslint-disable-next-line
|
||||
not_loading: (data, escape) => {
|
||||
// no default content
|
||||
},
|
||||
option_create: (data, escape) => {
|
||||
const name = escape(data.input);
|
||||
if (name.length < 3) {
|
||||
return null;
|
||||
}
|
||||
const tpl = this.translate('select.search.create');
|
||||
const tplReplaced = tpl.replace('%input%', '<strong>' + name + '</strong>')
|
||||
return '<div class="create">' + tplReplaced + '</div>';
|
||||
},
|
||||
no_results: (data, escape) => {
|
||||
const tpl = this.translate('select.search.notfound');
|
||||
const tplReplaced = tpl.replace('%input%', '<strong>' + escape(data.input) + '</strong>')
|
||||
return '<div class="no-results">' + tplReplaced + '</div>';
|
||||
},
|
||||
};
|
||||
|
||||
let render = {
|
||||
// eslint-disable-next-line
|
||||
not_loading: (data, escape) => {
|
||||
// no default content
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const rendererType = (node.dataset['renderer'] !== undefined) ? node.dataset['renderer'] : 'default';
|
||||
options.render = {...render, ...this.getRenderer(rendererType)};
|
||||
|
||||
new TomSelect(node, options);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
34
assets/js/forms/KimaiAutocompleteTags.js
Normal file
34
assets/js/forms/KimaiAutocompleteTags.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import KimaiAutocomplete from "./KimaiAutocomplete";
|
||||
|
||||
/**
|
||||
* Used for timesheet tagging in toolbar and edit dialogs.
|
||||
*/
|
||||
export default class KimaiAutocompleteTags extends KimaiAutocomplete {
|
||||
|
||||
init()
|
||||
{
|
||||
this.selector = '[data-form-widget="tags"]';
|
||||
}
|
||||
|
||||
loadData(apiUrl, query, callback) {
|
||||
/** @type {KimaiAPI} API */
|
||||
const API = this.getContainer().getPlugin('api');
|
||||
|
||||
API.get(apiUrl, {'name': query}, (data) => {
|
||||
let results = [];
|
||||
for (let item of data) {
|
||||
results.push({text: item.name, value: item.name, color: item['color-safe']});
|
||||
}
|
||||
callback(results);
|
||||
}, () => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,16 @@ export default class KimaiDatePicker extends KimaiFormPlugin {
|
||||
if (element.dataset.format === undefined) {
|
||||
console.log('Trying to bind litepicker to an element without data-format attribute');
|
||||
}
|
||||
if (element.hasAttribute('min') !== undefined) {
|
||||
options = {...options, ...{
|
||||
'minDate': element.getAttribute('min'),
|
||||
}};
|
||||
}
|
||||
if (element.hasAttribute('max') !== undefined) {
|
||||
options = {...options, ...{
|
||||
'maxDate': element.getAttribute('max'),
|
||||
}};
|
||||
}
|
||||
options = {...options, ...{
|
||||
format: element.dataset.format,
|
||||
showTooltip: false,
|
||||
@@ -66,7 +76,7 @@ export default class KimaiDatePicker extends KimaiFormPlugin {
|
||||
firstDay: FIRST_DOW, // Litepicker: 0 = Sunday, 1 = Monday
|
||||
setup: (picker) => {
|
||||
// nasty hack, because litepicker does not trigger change event on the input and the available
|
||||
// event "selected" is triggered why to often, even when moving the cursor inside the input
|
||||
// event "selected" is triggered way to often, even when moving the cursor inside the input
|
||||
// element (not even typing is necessary) and so we have to make sure that the manual "click" event
|
||||
// (works for touch as well) happened before we actually dispatch the change event manually ...
|
||||
// what? report forms would be submitted upon cursor move without the "preselect” check
|
||||
@@ -79,6 +89,12 @@ export default class KimaiDatePicker extends KimaiFormPlugin {
|
||||
delete picker._wasPreselected;
|
||||
}
|
||||
});
|
||||
|
||||
// only if mobile.friendly plugin is activated
|
||||
if (picker.backdrop !== undefined) {
|
||||
// the node needs to be moved, so the flat form layout works properly (e.g. for date types)
|
||||
document.body.appendChild(picker.backdrop);
|
||||
}
|
||||
},
|
||||
}};
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
*/
|
||||
|
||||
import TomSelect from 'tom-select';
|
||||
import KimaiFormPlugin from "./KimaiFormPlugin";
|
||||
import KimaiFormTomselectPlugin from "./KimaiFormTomselectPlugin";
|
||||
|
||||
export default class KimaiFormSelect extends KimaiFormPlugin {
|
||||
export default class KimaiFormSelect extends KimaiFormTomselectPlugin {
|
||||
|
||||
constructor(selector, apiSelects)
|
||||
{
|
||||
@@ -52,6 +52,15 @@ export default class KimaiFormSelect extends KimaiFormPlugin {
|
||||
let plugins = ['change_listener'];
|
||||
|
||||
const isMultiple = node.multiple !== undefined && node.multiple === true;
|
||||
const isRequired = node.required !== undefined && node.required === true;
|
||||
|
||||
if (isRequired) {
|
||||
plugins.push('no_backspace_delete');
|
||||
}
|
||||
|
||||
if (isMultiple) {
|
||||
plugins.push('remove_button');
|
||||
}
|
||||
|
||||
/*
|
||||
const isOrdering = false;
|
||||
@@ -61,39 +70,32 @@ export default class KimaiFormSelect extends KimaiFormPlugin {
|
||||
}
|
||||
*/
|
||||
|
||||
if (isMultiple) {
|
||||
plugins.push('remove_button');
|
||||
}
|
||||
|
||||
let options = {
|
||||
// see https://github.com/orchidjs/tom-select/issues/543#issuecomment-1664342257
|
||||
onItemAdd: function(){
|
||||
// remove remaining characters from input after selecting an item
|
||||
this.setTextboxValue('');
|
||||
},
|
||||
lockOptgroupOrder: true,
|
||||
allowEmptyOption: true,
|
||||
allowEmptyOption: !isRequired,
|
||||
hidePlaceholder: false,
|
||||
plugins: plugins,
|
||||
// if there are more than X entries, the other ones are hidden and can only be found
|
||||
// by typing some characters to trigger the internal option search
|
||||
// see App\Form\Type\TagsType::MAX_AMOUNT_SELECT
|
||||
maxOptions: 500,
|
||||
sortField:[{field: '$order'}, {field: '$score'}],
|
||||
};
|
||||
|
||||
let render = {
|
||||
option_create: (data, escape) => {
|
||||
const name = escape(data.input);
|
||||
if (name.length < 3) {
|
||||
return null;
|
||||
}
|
||||
const tpl = this.translate('select.search.create');
|
||||
const tplReplaced = tpl.replace('%input%', '<strong>' + name + '</strong>');
|
||||
return '<div class="create">' + tplReplaced + '</div>';
|
||||
},
|
||||
no_results: (data, escape) => {
|
||||
const tpl = this.translate('select.search.notfound');
|
||||
const tplReplaced = tpl.replace('%input%', '<strong>' + escape(data.input) + '</strong>');
|
||||
return '<div class="no-results">' + tplReplaced + '</div>';
|
||||
},
|
||||
onOptionAdd: (value) => {
|
||||
node.dispatchEvent(new CustomEvent('create', {detail: {'value': value}}));
|
||||
},
|
||||
};
|
||||
|
||||
const rendererType = (node.dataset['renderer'] !== undefined) ? node.dataset['renderer'] : 'default';
|
||||
options.render = {...render, ...this.getRenderer(rendererType)};
|
||||
|
||||
if (node.dataset['create'] !== undefined) {
|
||||
options = {...options, ...{
|
||||
persist: true,
|
||||
@@ -112,44 +114,6 @@ export default class KimaiFormSelect extends KimaiFormPlugin {
|
||||
}};
|
||||
}
|
||||
|
||||
if (node.dataset['renderer'] !== undefined && node.dataset['renderer'] === 'color') {
|
||||
options.render = {...render, ...{
|
||||
option: function(data, escape) {
|
||||
let item = '<div class="list-group-item border-0 p-1 ps-2 text-nowrap">';
|
||||
if (data.color !== undefined) {
|
||||
item += '<span style="background-color:' + data.color + '" class="color-choice-item"> </span>';
|
||||
} else {
|
||||
item += '<span class="color-choice-item"> </span>';
|
||||
}
|
||||
item += escape(data.text) + '</div>';
|
||||
return item;
|
||||
},
|
||||
item: function(data, escape) {
|
||||
let item = '<div class="text-nowrap">';
|
||||
if (data.color !== undefined) {
|
||||
item += '<span style="background-color:' + data.color + '" class="color-choice-item"> </span>';
|
||||
} else {
|
||||
item += '<span class="color-choice-item"> </span>';
|
||||
}
|
||||
item += escape(data.text) + '</div>';
|
||||
return item;
|
||||
}
|
||||
}};
|
||||
} else {
|
||||
options.render = {...render, ...{
|
||||
// the empty entry would collapse and only show as a tiny 5px line if there is no content inside
|
||||
option: function(data, escape) {
|
||||
let text = data.text;
|
||||
if (text === null || text.trim() === '') {
|
||||
text = ' ';
|
||||
} else {
|
||||
text = escape(text);
|
||||
}
|
||||
return '<div>' + text + '</div>';
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
const select = new TomSelect(node, options);
|
||||
node.addEventListener('data-reloaded', (event) => {
|
||||
select.clear(true);
|
||||
@@ -272,8 +236,13 @@ export default class KimaiFormSelect extends KimaiFormPlugin {
|
||||
options.push(optGroup);
|
||||
}
|
||||
|
||||
// log the one with a group name first (e.g. non-global activities)
|
||||
options.forEach(child => node.appendChild(child));
|
||||
emptyOpts.forEach(child => node.appendChild(child));
|
||||
|
||||
// append the ones with no parent at the end (e.g. global activities)
|
||||
const optGroupEmpty = this._createOptgroup('');
|
||||
emptyOpts.forEach(child => optGroupEmpty.appendChild(child));
|
||||
node.appendChild(optGroupEmpty)
|
||||
|
||||
// if available, re-select the previous selected option (mostly usable for global activities)
|
||||
node.value = selectedValue;
|
||||
@@ -342,7 +311,13 @@ export default class KimaiFormSelect extends KimaiFormPlugin {
|
||||
--end;
|
||||
}
|
||||
|
||||
return (start > 0 || end < title.length) ? title.substring(start, end) : title;
|
||||
let result = (start > 0 || end < title.length) ? title.substring(start, end) : title;
|
||||
|
||||
if (result === '' && entity['name'] !== undefined) {
|
||||
return entity['name'];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
80
assets/js/forms/KimaiFormTomselectPlugin.js
Normal file
80
assets/js/forms/KimaiFormTomselectPlugin.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* [KIMAI] KimaiFormPlugin: base class for all none ID plugin that handle forms
|
||||
*/
|
||||
|
||||
import KimaiFormPlugin from './KimaiFormPlugin';
|
||||
|
||||
export default class KimaiFormTomselectPlugin extends KimaiFormPlugin {
|
||||
|
||||
/**
|
||||
* @param {string} rendererType
|
||||
* @return array
|
||||
*/
|
||||
getRenderer(rendererType)
|
||||
{
|
||||
// default renderer
|
||||
|
||||
let render = {
|
||||
option_create: (data, escape) => {
|
||||
const name = escape(data.input);
|
||||
if (name.length < 3) {
|
||||
return null;
|
||||
}
|
||||
const tpl = this.translate('select.search.create');
|
||||
const tplReplaced = tpl.replace('%input%', '<strong>' + name + '</strong>')
|
||||
return '<div class="create">' + tplReplaced + '</div>';
|
||||
},
|
||||
no_results: (data, escape) => {
|
||||
const tpl = this.translate('select.search.notfound');
|
||||
const tplReplaced = tpl.replace('%input%', '<strong>' + escape(data.input) + '</strong>')
|
||||
return '<div class="no-results">' + tplReplaced + '</div>';
|
||||
},
|
||||
};
|
||||
|
||||
if (rendererType === 'color') {
|
||||
render = {...render, ...{
|
||||
option: function(data, escape) {
|
||||
let item = '<div class="list-group-item border-0 p-1 ps-2 text-nowrap">';
|
||||
// if no color is set, do NOT add an empty placeholder
|
||||
if (data.color !== undefined) {
|
||||
item += '<span style="background-color:' + data.color + '" class="color-choice-item me-2"> </span>';
|
||||
}
|
||||
item += escape(data.text) + '</div>';
|
||||
return item;
|
||||
},
|
||||
item: function(data, escape) {
|
||||
let item = '<div class="text-nowrap">';
|
||||
// if no color is set, do NOT add an empty placeholder
|
||||
if (data.color !== undefined) {
|
||||
item += '<span style="background-color:' + data.color + '" class="color-choice-item me-2"> </span>';
|
||||
}
|
||||
item += escape(data.text) + '</div>';
|
||||
return item;
|
||||
}
|
||||
}};
|
||||
} else {
|
||||
render = {...render, ...{
|
||||
// the empty entry would collapse and only show as a tiny 5px line if there is no content inside
|
||||
option: function(data, escape) {
|
||||
let text = data.text;
|
||||
if (text === null || text.trim() === '') {
|
||||
text = ' ';
|
||||
} else {
|
||||
text = escape(text);
|
||||
}
|
||||
return '<div>' + text + '</div>';
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
return render;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -147,18 +147,49 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
const date = this.getDateUtils().fromFormat(
|
||||
this._beginDate.value + ' ' + this._beginTime.value,
|
||||
this._beginDate.dataset['format'] + ' ' + this._beginTime.dataset['format'],
|
||||
);
|
||||
let date = this._parseBegin(this._beginTime.dataset['format']);
|
||||
|
||||
if (date.invalid) {
|
||||
return null;
|
||||
date = this._parseBegin(this._fixTimeFormat(this._beginTime.dataset['format']));
|
||||
|
||||
if (date.invalid) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
_parseBegin(timeFormat)
|
||||
{
|
||||
return this.getDateUtils().fromFormat(
|
||||
this._beginDate.value + ' ' + this._beginTime.value,
|
||||
this._beginDate.dataset['format'] + ' ' + timeFormat,
|
||||
);
|
||||
}
|
||||
|
||||
_parseEnd(endDate, timeFormat)
|
||||
{
|
||||
let date = this.getDateUtils().fromFormat(
|
||||
endDate.toFormat('yyyy-LL-dd') + ' ' + this._endTime.value,
|
||||
'yyyy-LL-dd ' + timeFormat,
|
||||
);
|
||||
|
||||
if (date.invalid) {
|
||||
date = this.getDateUtils().fromFormat(
|
||||
endDate.toFormat('yyyy-LL-dd') + ' ' + this._endTime.value,
|
||||
'yyyy-LL-dd ' + this._fixTimeFormat(timeFormat),
|
||||
);
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
_fixTimeFormat(format)
|
||||
{
|
||||
return format.replace('HH', 'H').replace('hh', 'h');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {DateTime|null}
|
||||
* @private
|
||||
@@ -169,17 +200,11 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
let date = this.getDateUtils().fromFormat(
|
||||
DateTime.now().toFormat('yyyy-LL-dd') + ' ' + this._endTime.value,
|
||||
'yyyy-LL-dd ' + this._endTime.dataset['format'],
|
||||
);
|
||||
let date = this._parseEnd(DateTime.now(), this._endTime.dataset['format']);
|
||||
|
||||
const begin = this._getBegin();
|
||||
if (begin !== null) {
|
||||
date = this.getDateUtils().fromFormat(
|
||||
begin.toFormat('yyyy-LL-dd') + ' ' + this._endTime.value,
|
||||
'yyyy-LL-dd ' + this._endTime.dataset['format'],
|
||||
);
|
||||
date = this._parseEnd(begin, this._endTime.dataset['format']);
|
||||
|
||||
if (date < begin) {
|
||||
date = date.plus({days: 1});
|
||||
@@ -293,7 +318,7 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
|
||||
this._applyDateToField(newBegin.plus({seconds: seconds}), null, this._endTime);
|
||||
} else if (begin === null && end !== null) {
|
||||
this._applyDateToField(end.minus({seconds: seconds}), this._beginDate, this._beginTime);
|
||||
} else if (begin !== null && seconds > 0) {
|
||||
} else if (begin !== null && seconds >= 0) {
|
||||
this._applyDateToField(begin.plus({seconds: seconds}), null, this._endTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ export default class KimaiAPI extends KimaiPlugin {
|
||||
|
||||
_headers() {
|
||||
const headers = new Headers();
|
||||
headers.append('X-AUTH-SESSION', '1');
|
||||
headers.append('Content-Type', 'application/json');
|
||||
|
||||
return headers;
|
||||
|
||||
@@ -18,6 +18,7 @@ export default class KimaiActiveRecords extends KimaiPlugin {
|
||||
super();
|
||||
this._selector = '.ticktac-menu';
|
||||
this._selectorEmpty = '.ticktac-menu-empty';
|
||||
this._favIconUrl = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,8 +53,6 @@ export default class KimaiActiveRecords extends KimaiPlugin {
|
||||
// handle duration in the visible UI
|
||||
this._updateBrowserTitle = !!this.getConfiguration('updateBrowserTitle');
|
||||
|
||||
// deactivated direct duration update, becuase it is unclear
|
||||
// this._updateDuration();
|
||||
const handle = () => {
|
||||
this._updateDuration();
|
||||
};
|
||||
@@ -77,6 +76,10 @@ export default class KimaiActiveRecords extends KimaiPlugin {
|
||||
// needs to search in document, to find all running entries, both in "ticktac" and listing pages
|
||||
const activeRecords = document.querySelectorAll('[data-since]:not([data-since=""])');
|
||||
|
||||
if (this._updateBrowserTitle) {
|
||||
this._changeFavicon(activeRecords.length > 0);
|
||||
}
|
||||
|
||||
if (activeRecords.length === 0) {
|
||||
if (this._updateBrowserTitle) {
|
||||
if (document.body.dataset['title'] === undefined) {
|
||||
@@ -195,4 +198,36 @@ export default class KimaiActiveRecords extends KimaiPlugin {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} running
|
||||
* @private
|
||||
*/
|
||||
_changeFavicon(running)
|
||||
{
|
||||
const canvas = document.createElement('canvas');
|
||||
const orig = document.getElementById('favicon');
|
||||
if (this._favIconUrl === null) {
|
||||
this._favIconUrl = orig.href;
|
||||
}
|
||||
const link = orig.cloneNode(true);
|
||||
|
||||
if (canvas.getContext && link) {
|
||||
const ratio = window.devicePixelRatio;
|
||||
const img = document.createElement('img');
|
||||
canvas.height = canvas.width = 16 * ratio;
|
||||
img.onload = function () {
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
|
||||
if (running) {
|
||||
const width = 5.5 * ratio;
|
||||
ctx.fillStyle = 'rgb(182,57,57)';
|
||||
ctx.fillRect((canvas.width / 2) - (width / 2), (canvas.height / 2) - (width / 2), width, width);
|
||||
}
|
||||
link.href = canvas.toDataURL('image/png');
|
||||
orig.remove();
|
||||
document.head.appendChild(link);
|
||||
};
|
||||
img.src = this._favIconUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,10 @@ import { Modal } from 'bootstrap';
|
||||
|
||||
export default class KimaiAjaxModalForm extends KimaiReducedClickHandler {
|
||||
|
||||
constructor(selector) {
|
||||
constructor(selector, stopSelector) {
|
||||
super();
|
||||
this._selector = selector;
|
||||
this._stopSelector = stopSelector;
|
||||
}
|
||||
|
||||
getId()
|
||||
@@ -63,7 +64,7 @@ export default class KimaiAjaxModalForm extends KimaiReducedClickHandler {
|
||||
|
||||
this.addClickHandler(this._selector, (href) => {
|
||||
this.openUrlInModal(href);
|
||||
});
|
||||
}, this._stopSelector);
|
||||
}
|
||||
|
||||
_getModal()
|
||||
|
||||
@@ -52,7 +52,7 @@ export default class KimaiAlert extends KimaiPlugin {
|
||||
}
|
||||
|
||||
const html = `
|
||||
<div class="modal modal-blur fade" id="` + id + `" tabindex="-1" role="dialog">
|
||||
<div class="modal" id="` + id + `" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-status bg-` + this._mapClass('danger') + `"></div>
|
||||
@@ -128,7 +128,7 @@ export default class KimaiAlert extends KimaiPlugin {
|
||||
}
|
||||
|
||||
const html = `
|
||||
<div class="modal modal-blur fade" tabindex="-1" role="dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-status bg-` + this._mapClass(type) + `"></div>
|
||||
@@ -194,7 +194,7 @@ export default class KimaiAlert extends KimaiPlugin {
|
||||
<div class="toast-body">
|
||||
` + icon + ' ' + message + `
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="` + translation.get('close') + `"></button>
|
||||
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="` + translation.get('close') + `"></button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
@@ -227,7 +227,7 @@ export default class KimaiAlert extends KimaiPlugin {
|
||||
|
||||
const css = this._mapClass('info');
|
||||
const html = `
|
||||
<div class="modal modal-blur fade" tabindex="-1" role="dialog" data-bs-backdrop="static">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" data-bs-backdrop="static">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-status bg-` + css + `"></div>
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class KimaiAlternativeLinks extends KimaiReducedClickHandler {
|
||||
init() {
|
||||
this.addClickHandler(this._selector, function(href) {
|
||||
window.location = href;
|
||||
});
|
||||
}, []);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@ export default class KimaiReducedClickHandler extends KimaiPlugin {
|
||||
|
||||
/**
|
||||
* No _underscore naming for now, as it would be mangled otherwise
|
||||
* @param selector
|
||||
* @param callback
|
||||
* @param {string} selector
|
||||
* @param {callback} callback
|
||||
* @param {array<string>} stopSelector
|
||||
*/
|
||||
addClickHandler(selector, callback) {
|
||||
addClickHandler(selector, callback, stopSelector) {
|
||||
document.body.addEventListener('click', (event) => {
|
||||
// event.currentTarget is ALWAYS the body
|
||||
|
||||
@@ -38,6 +39,12 @@ export default class KimaiReducedClickHandler extends KimaiPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let x of stopSelector) {
|
||||
if (target.matches(x)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
target = target.parentNode;
|
||||
}
|
||||
|
||||
@@ -54,6 +61,12 @@ export default class KimaiReducedClickHandler extends KimaiPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let x of stopSelector) {
|
||||
if (target.matches(x)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* [KIMAI] KimaiThemeInitializer: initialize theme functionality
|
||||
*/
|
||||
|
||||
import { Tooltip } from 'bootstrap';
|
||||
import { Tooltip, Offcanvas } from 'bootstrap';
|
||||
import KimaiPlugin from '../KimaiPlugin';
|
||||
|
||||
export default class KimaiThemeInitializer extends KimaiPlugin {
|
||||
@@ -21,6 +21,10 @@ export default class KimaiThemeInitializer extends KimaiPlugin {
|
||||
return new Tooltip(tooltipTriggerEl);
|
||||
});
|
||||
|
||||
// support for offcanvas elements
|
||||
const offcanvasElementList = document.querySelectorAll('.offcanvas');
|
||||
[...offcanvasElementList].map(offcanvasEl => new Offcanvas(offcanvasEl));
|
||||
|
||||
// activate all form plugins
|
||||
/** @type {KimaiForm} FORMS */
|
||||
const FORMS = this.getContainer().getPlugin('form');
|
||||
|
||||
52
assets/js/plugins/KimaiUser.js
Normal file
52
assets/js/plugins/KimaiUser.js
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* [KIMAI] KimaiUser: information about the current user
|
||||
*/
|
||||
|
||||
import KimaiPlugin from "../KimaiPlugin";
|
||||
|
||||
export default class KimaiUser extends KimaiPlugin {
|
||||
|
||||
getId() {
|
||||
return 'user';
|
||||
}
|
||||
|
||||
init() {
|
||||
this.user = this.getConfigurations().get('user');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
getUserId() {
|
||||
return this.user.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
getName() {
|
||||
return this.user.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isAdmin() {
|
||||
return this.user.admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isSuperAdmin() {
|
||||
return this.user.superAdmin;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -109,12 +109,11 @@ export default class KimaiCalendar {
|
||||
nextYear: this.options['icons']['nextYear'],
|
||||
};
|
||||
BootstrapTheme.prototype.rtlIconClasses = {
|
||||
prev: this.options['icons']['next'],
|
||||
next: this.options['icons']['previous'],
|
||||
prevYear: this.options['icons']['nextYear'],
|
||||
nextYear: this.options['icons']['previousYear'],
|
||||
prev: this.options['icons']['previous'],
|
||||
next: this.options['icons']['next'],
|
||||
prevYear: this.options['icons']['previousYear'],
|
||||
nextYear: this.options['icons']['nextYear'],
|
||||
};
|
||||
|
||||
let calendarOptions = {
|
||||
locales: [ enGbLocale, enUsLocale, arLocale, csLocale, daLocale, deLocale, deAtLocale, elLocale,
|
||||
esLocale, euLocale, faLocale, fiLocale, frLocale, heLocale, hrLocale, huLocale, itLocale, jaLocale, koLocale,
|
||||
@@ -220,15 +219,23 @@ export default class KimaiCalendar {
|
||||
}
|
||||
|
||||
const element = mouseEnterInfo.el;
|
||||
let popover = Popover.getInstance(element);
|
||||
const popoverTitle = DATES.getFormattedDate(event.start) + ' | ' + DATES.formatTime(event.start) + ' - ' + (event.end ? DATES.formatTime(event.end) : '');
|
||||
const popoverContent = this.renderEventPopoverContent(event);
|
||||
|
||||
if (popover === null) {
|
||||
let popover = Popover.getInstance(element);
|
||||
if (popover !== null) {
|
||||
// see https://github.com/kimai/kimai/issues/4043
|
||||
popover.setContent({
|
||||
'.popover-header': popoverTitle,
|
||||
'.popover-body': popoverContent
|
||||
});
|
||||
} else {
|
||||
// https://getbootstrap.com/docs/5.0/components/popovers/#options
|
||||
popover = new Popover(element, {
|
||||
title: DATES.getFormattedDate(event.start) + ' | ' + DATES.formatTime(event.start) + ' - ' + (event.end ? DATES.formatTime(event.end) : ''),
|
||||
title: popoverTitle,
|
||||
placement: 'top',
|
||||
html: true,
|
||||
content: this.renderEventPopoverContent(event),
|
||||
content: popoverContent,
|
||||
trigger: 'focus',
|
||||
});
|
||||
}
|
||||
@@ -432,8 +439,14 @@ export default class KimaiCalendar {
|
||||
calendarSource = {...calendarSource, ...{
|
||||
id: 'kimai-' + source.id,
|
||||
events: (fetchInfo, successCallback, failureCallback) => {
|
||||
let url = source.url.replace('{from}', DATES.formatForAPI(fetchInfo.start));
|
||||
url = url.replace('{to}', DATES.formatForAPI(fetchInfo.end));
|
||||
const targetFrom = DATES.formatForAPI(fetchInfo.start);
|
||||
const targetTo = DATES.formatForAPI(fetchInfo.end);
|
||||
|
||||
let url = source.url;
|
||||
url = url.replace('{from}', targetFrom);
|
||||
url = url.replace('__FROM__', targetFrom);
|
||||
url = url.replace('{to}', targetTo);
|
||||
url = url.replace('__TO__', targetTo);
|
||||
|
||||
API.get(url, {}, result => {
|
||||
let apiEvents = [];
|
||||
@@ -450,6 +463,29 @@ export default class KimaiCalendar {
|
||||
name: 'google',
|
||||
editable: false,
|
||||
}};
|
||||
} else if (source.type === 'json') {
|
||||
calendarSource = {...calendarSource, ...{
|
||||
id: 'json-' + source.id,
|
||||
editable: false,
|
||||
events: (fetchInfo, successCallback, failureCallback) => {
|
||||
const targetFrom = DATES.formatForAPI(fetchInfo.start);
|
||||
const targetTo = DATES.formatForAPI(fetchInfo.end);
|
||||
|
||||
let url = source.url;
|
||||
url = url.replace('{from}', targetFrom);
|
||||
url = url.replace('__FROM__', targetFrom);
|
||||
url = url.replace('{to}', targetTo);
|
||||
url = url.replace('__TO__', targetTo);
|
||||
|
||||
API.get(url, {}, result => {
|
||||
let apiEvents = [];
|
||||
for (const record of result) {
|
||||
apiEvents.push(record);
|
||||
}
|
||||
successCallback(apiEvents);
|
||||
}, failureCallback);
|
||||
},
|
||||
}};
|
||||
} else if (source.type === 'ical') {
|
||||
calendarSource = {...calendarSource, ...{
|
||||
id: 'ical-' + source.id,
|
||||
@@ -598,7 +634,7 @@ export default class KimaiCalendar {
|
||||
<li>` + this.options['translations']['activity'] + `: ` + escaper.escapeForHtml(eventObj.activity) + `</li>
|
||||
</ul>` +
|
||||
(eventObj.description !== null || eventObj.tags.length > 0 ? '<hr>' : '') +
|
||||
(eventObj.description ? '<p>' + escaper.escapeForHtml(eventObj.description) + '</p>' : '') + tags + `
|
||||
(eventObj.description ? '<div>' + escaper.escapeForHtml(eventObj.description) + '</div>' : '') + tags + `
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,10 @@ export default class KimaiContextMenu {
|
||||
{
|
||||
const dropdownElement = this.getContextMenuElement();
|
||||
|
||||
dropdownElement.style.zIndex = '1021'; // stay on top of sticky elements (like table header)
|
||||
if (!dropdownElement.classList.contains('action-dropdown')) {
|
||||
dropdownElement.classList.add('action-dropdown');
|
||||
}
|
||||
|
||||
dropdownElement.innerHTML = html;
|
||||
dropdownElement.style.position = 'fixed';
|
||||
dropdownElement.style.top = (event.clientY) + 'px';
|
||||
|
||||
45
assets/sass/_app-rtl.scss
Normal file
45
assets/sass/_app-rtl.scss
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
$modal-backdrop-opacity: 0.6 !default;
|
||||
|
||||
/*
|
||||
THIS DOES NOT WORK AS EXPECTED, AS TABLER IS NOT REGENERATED HERE.
|
||||
SO WE CANNOT CHANGE VARIABLES, LIKE THE ABOVE...
|
||||
*/
|
||||
@import '~@tabler/core/dist/css/tabler.rtl.css';
|
||||
@import '~@tabler/core/dist/css/tabler-vendors.rtl.css';
|
||||
|
||||
/**
|
||||
* Everyone needs icons
|
||||
*/
|
||||
$fa-font-path: "~@fortawesome/fontawesome-free/webfonts/";
|
||||
@import '~@fortawesome/fontawesome-free/scss/fontawesome';
|
||||
@import '~@fortawesome/fontawesome-free/scss/regular';
|
||||
@import '~@fortawesome/fontawesome-free/scss/solid';
|
||||
@import '~@fortawesome/fontawesome-free/scss/brands';
|
||||
|
||||
/**
|
||||
* We need that, to compile the rest of the file
|
||||
*/
|
||||
@import "bootstrap/scss/functions";
|
||||
|
||||
@import "@tabler/core/src/scss/variables";
|
||||
@import "@tabler/core/src/scss/variables-dark";
|
||||
@import "@tabler/core/src/scss/mixins";
|
||||
|
||||
@import "bootstrap/scss/variables";
|
||||
@import "bootstrap/scss/variables-dark";
|
||||
@import "bootstrap/scss/maps";
|
||||
@import "bootstrap/scss/mixins";
|
||||
@import "bootstrap/scss/utilities";
|
||||
|
||||
/**
|
||||
* Kimai related stuff
|
||||
*/
|
||||
@import "kimai";
|
||||
@import "rtl";
|
||||
29
assets/sass/_app.scss
Normal file
29
assets/sass/_app.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
$modal-backdrop-opacity: 0.6 !default;
|
||||
|
||||
@import '~@tabler/core/src/scss/tabler';
|
||||
@import '~@tabler/core/src/scss/vendor/litepicker';
|
||||
@import '~@tabler/core/src/scss/vendor/tom-select';
|
||||
|
||||
/**
|
||||
* Everyone needs icons
|
||||
*/
|
||||
|
||||
$fa-font-path: "~@fortawesome/fontawesome-free/webfonts/";
|
||||
|
||||
@import '~@fortawesome/fontawesome-free/scss/fontawesome';
|
||||
@import '~@fortawesome/fontawesome-free/scss/regular';
|
||||
@import '~@fortawesome/fontawesome-free/scss/solid';
|
||||
@import '~@fortawesome/fontawesome-free/scss/brands';
|
||||
|
||||
/**
|
||||
* Kimai specific additions
|
||||
*/
|
||||
|
||||
@import "kimai";
|
||||
@@ -7,15 +7,17 @@
|
||||
|
||||
@import "bootstrap/scss/functions";
|
||||
@import "bootstrap/scss/variables";
|
||||
@import "bootstrap/scss/variables-dark";
|
||||
@import "bootstrap/scss/maps";
|
||||
@import "bootstrap/scss/mixins";
|
||||
@import "bootstrap/scss/utilities";
|
||||
|
||||
@import "bootstrap/scss/reboot";
|
||||
@import "bootstrap/scss/type";
|
||||
@import "bootstrap/scss/grid";
|
||||
@import "bootstrap/scss/tables";
|
||||
@import "bootstrap/scss/list-group";
|
||||
@import "bootstrap/scss/card";
|
||||
@import "bootstrap/scss/utilities";
|
||||
|
||||
body {
|
||||
font-family: $font-family-sans-serif;
|
||||
18
assets/sass/_kimai.scss
Normal file
18
assets/sass/_kimai.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
@import "layout";
|
||||
@import "error-page";
|
||||
@import "print";
|
||||
@import "content";
|
||||
@import "loading-spinner";
|
||||
@import "tables";
|
||||
@import "calendar";
|
||||
@import "ticktac";
|
||||
@import "selectpicker";
|
||||
@import "forms";
|
||||
@import "modal";
|
||||
@import "progressbar";
|
||||
@import "avatar";
|
||||
@import "theme-dark";
|
||||
@import "pages";
|
||||
@import "tabler-fixes";
|
||||
@import "help";
|
||||
@import "rtl";
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
@import 'variables';
|
||||
@import 'layout';
|
||||
@import 'error-page';
|
||||
@import 'print';
|
||||
@import 'content';
|
||||
@import 'loading-spinner';
|
||||
@import 'tables';
|
||||
@import 'calendar';
|
||||
@import 'ticktac';
|
||||
@import 'sweetalert';
|
||||
@import 'selectpicker';
|
||||
@import 'forms';
|
||||
@import 'modal';
|
||||
@import 'progressbar';
|
||||
@import 'avatar';
|
||||
@import 'theme-dark';
|
||||
@import 'pages';
|
||||
@import 'tabler-fixes';
|
||||
@import 'help';
|
||||
@import 'rtl';
|
||||
@@ -3,18 +3,18 @@ $avatar-base-size: 30;
|
||||
|
||||
.initials {
|
||||
font-weight: bold;
|
||||
font-size: ceil($avatar-base-size / 2 - 2)+px;
|
||||
font-size: ceil(calc($avatar-base-size / 2 - 2))+px;
|
||||
}
|
||||
|
||||
$avatarSizes: "xs" .75, "sm" 1.25, "md" 1.5, "lg" 2;
|
||||
|
||||
@each $avatarSizeName, $avatarBaseSize in $avatarSizes {
|
||||
.avatar-#{$avatarSizeName} {
|
||||
width: ceil($avatar-base-size * $avatarBaseSize)+px;
|
||||
height: ceil($avatar-base-size * $avatarBaseSize)+px;
|
||||
width: ceil(calc($avatar-base-size * $avatarBaseSize))+px;
|
||||
height: ceil(calc($avatar-base-size * $avatarBaseSize))+px;
|
||||
|
||||
.initials {
|
||||
font-size: ceil($avatar-base-size * $avatarBaseSize / 2 - 2)+px;
|
||||
font-size: ceil(calc($avatar-base-size * $avatarBaseSize / 2 - 2))+px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,10 @@ $avatarSizes: "xs" .75, "sm" 1.25, "md" 1.5, "lg" 2;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #fff;
|
||||
.avatar {
|
||||
width: ceil($avatar-base-size * 2.75)+px;
|
||||
height: ceil($avatar-base-size * 2.75)+px;
|
||||
width: ceil(calc($avatar-base-size * 2.75))+px;
|
||||
height: ceil(calc($avatar-base-size * 2.75))+px;
|
||||
.initials {
|
||||
font-size: ceil($avatar-base-size * 2.75 / 2 - 2)+px;
|
||||
font-size: ceil(calc($avatar-base-size * 2.75 / 2 - 2))+px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,3 +39,8 @@ table.dataTable.table > tbody > tr > td {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* decreases the margin between avatars, see https://github.com/tabler/tabler/issues/1567 */
|
||||
.avatar-list-stacked .avatar {
|
||||
margin-right: calc(var(--tblr-avatar-size)*-.2)!important;
|
||||
}
|
||||
@@ -3,6 +3,9 @@
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
div {
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
|
||||
.draggable {
|
||||
@@ -13,3 +16,5 @@
|
||||
--bs-gray-400: var(--tblr-border-color);
|
||||
--bs-gray-200: var(--tblr-gray-200);
|
||||
}
|
||||
|
||||
.fc-day-sat, .fc-day-sun { background-color: var(--tblr-bg-surface-secondary); }
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/* used in security screens and other full-size pages */
|
||||
.page-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* make sure that elements can be hidden without taking space (eg. hidden modals like the search or column visibility) */
|
||||
.hidden-no-space {
|
||||
margin: 0;
|
||||
@@ -33,11 +38,6 @@ td {
|
||||
}
|
||||
}
|
||||
|
||||
.label-gray {
|
||||
background-color: $gray-lte;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.open-edit {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -79,15 +79,6 @@ table.dataTable thead > tr > th.hw-min {
|
||||
max-height: 340px;
|
||||
}
|
||||
|
||||
.card {
|
||||
.card-tools {
|
||||
/* a button that is directly connected with a pagination element inside the box tools (eg. project list on customer detail page) */
|
||||
.btn-pager {
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
#customer_details_box,
|
||||
#project_details_box,
|
||||
#activity_details_box {
|
||||
|
||||
19
assets/sass/fontawesome.scss
vendored
19
assets/sass/fontawesome.scss
vendored
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
$fa-font-path: "~@fortawesome/fontawesome-free/webfonts/";
|
||||
@import "@fortawesome/fontawesome-free/scss/fontawesome";
|
||||
@import "@fortawesome/fontawesome-free/scss/regular";
|
||||
@import "@fortawesome/fontawesome-free/scss/solid";
|
||||
@import "@fortawesome/fontawesome-free/scss/brands";
|
||||
|
||||
/* all icon-buttons with fontawesome icons */
|
||||
.btn-icon i.icon {
|
||||
font-size: 16px;
|
||||
color: $text-muted;
|
||||
padding-top: 2px;
|
||||
}
|
||||
@@ -26,6 +26,9 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.input-group {
|
||||
width: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +36,23 @@
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
border-radius: var(--tblr-border-radius);
|
||||
}
|
||||
|
||||
/* e.g. the work contract settings is user profile render rows with mb-3 and the last row doesn't need that */
|
||||
fieldset > .mb-3.row:last-child {
|
||||
margin-bottom: 0!important;
|
||||
}
|
||||
|
||||
.form-fieldset-light {
|
||||
border-radius: 0;
|
||||
margin-bottom: 0;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
.form-fieldset-light:last-child {
|
||||
border:none;
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
.inline-search {
|
||||
max-width: 200px;
|
||||
/* the entire inline-search should actually use .input-group-flat, but that causes some weird css problems inside the dropdown menu */
|
||||
#searchTerm {
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 360px) {
|
||||
@@ -38,3 +34,26 @@ h1.navbar-brand a {
|
||||
/* bootstrap thead.sticky-top has z-index 1020 and the reporting form dropdowns hide behind that */
|
||||
z-index: 1021;
|
||||
}
|
||||
|
||||
/* reduce the default font size for the brand header */
|
||||
.navbar {
|
||||
--tblr-navbar-brand-font-size: 1rem;
|
||||
}
|
||||
|
||||
/* user shortcuts dropdown */
|
||||
.user-shortcuts {
|
||||
.dropdown-menu-card {
|
||||
min-width: 260px;
|
||||
}
|
||||
.user-shortcuts-icon {
|
||||
background-color: var(--tblr-bg-surface-secondary);
|
||||
color: var(--tblr-gray-500);
|
||||
height: 3.125rem;
|
||||
width: 3.125rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 50;
|
||||
z-index: 1021;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
> .fas {
|
||||
position: absolute;
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
@import "tom-select/dist/scss/tom-select.bootstrap5";
|
||||
|
||||
/* bootstrap thead.sticky-top has z-index 1020 and tomselect dropdowns often hide behind that */
|
||||
.ts-dropdown {
|
||||
z-index: 1021;
|
||||
}
|
||||
|
||||
/* Tabler defines a min-width of 7rem, what makes many input fields become a two-line field
|
||||
e.g. a 7 rem input does not fit behind a word like "Germany" in a 3 column form inside a modal */
|
||||
.ts-control {
|
||||
> input {
|
||||
min-width: unset;
|
||||
}
|
||||
.item {
|
||||
white-space: nowrap!important;
|
||||
}
|
||||
}
|
||||
|
||||
.ts-dropdown {
|
||||
.optgroup-header {
|
||||
background-color: #eee;
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
}
|
||||
[data-selectable].option {
|
||||
padding-left: 20px;
|
||||
.optgroup {
|
||||
[data-selectable].option {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
.swal2-popup {
|
||||
font-size: unset;
|
||||
}
|
||||
|
||||
body.swal2-toast-shown .swal2-container {
|
||||
// never block the top two bars double-bars display (smaller displays)
|
||||
@media only screen and (max-width: 768px) {
|
||||
top: 95px !important;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
// never block the top bar on single-bar display (larger devices)
|
||||
@media only screen and (min-width: 768px) {
|
||||
top: 60px !important;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,51 @@
|
||||
@import "@tabler/core/src/scss/variables";
|
||||
|
||||
/* fix for vertical labels in date-time pickers, if they are used in half-column forms, e.g. "create tasks" or "create expenses" */
|
||||
fieldset legend.col-form-label {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
/* See project assignment to teams */
|
||||
fieldset.form-fieldset legend {
|
||||
fieldset.form-fieldset > legend {
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fixing the margin between the divider and items in context menu or dropdowns with optgroups */
|
||||
.ts-dropdown .optgroup:before {
|
||||
--tblr-spacer: .5rem;
|
||||
}
|
||||
.dropdown-divider {
|
||||
--tblr-dropdown-divider-margin-y: .5rem;
|
||||
}
|
||||
|
||||
/* fix translucent scroll border (e.g. timesheet modal duration dropdown) - see https://github.com/tabler/tabler/issues/1606 */
|
||||
.dropdown-menu {
|
||||
background-clip: border-box;
|
||||
}
|
||||
|
||||
/* hide empty fieldset - see https://github.com/tabler/tabler/issues/1650 */
|
||||
fieldset:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host,:root,[data-bs-theme=light] {
|
||||
/* remove the shadow from all form element groups */
|
||||
--tblr-box-shadow-input: none;
|
||||
}
|
||||
|
||||
.navbar[data-bs-theme=dark] {
|
||||
/* give navigation elements slightly higher contrast */
|
||||
--tblr-navbar-color: hsla(0,0%,100%,.8);
|
||||
}
|
||||
|
||||
/* this is not a bugfix, but it adds a border for better visual segregation */
|
||||
.nav-pills .nav-link.active {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
/* fixes https://github.com/tabler/tabler/issues/1974 */
|
||||
.litepicker .container__days .day-item.is-in-range {
|
||||
--litepicker-is-in-range-color: var(--tblr-primary-text-emphasis);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
@import "variables";
|
||||
|
||||
table.dataTable {
|
||||
clear: both;
|
||||
@@ -27,7 +26,7 @@ table.dataTable {
|
||||
thead .sorting_asc:after,
|
||||
thead .sorting_desc:after {
|
||||
padding-left: 5px;
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
opacity: 0.5;
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -93,7 +92,7 @@ table.dataTable {
|
||||
&.summary td {
|
||||
font-weight: bold;
|
||||
border: 0;
|
||||
background-color: var(--tblr-table-striped-bg);
|
||||
background-color: var(--tblr-bg-surface-secondary);
|
||||
}
|
||||
th.multiCheckbox {
|
||||
width: 15px;
|
||||
@@ -118,30 +117,17 @@ table.dataTable {
|
||||
padding: .3em .6em .3em;
|
||||
line-height: 24px;
|
||||
}
|
||||
span.label-activity,
|
||||
span.label-project,
|
||||
span.label-customer {
|
||||
a {
|
||||
color: unset;
|
||||
padding-bottom: 2px;
|
||||
border-bottom: 1px dotted $gray-lte;
|
||||
&:hover {
|
||||
border-bottom: 1px dotted darken($gray-lte, 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
span.label-invisible {
|
||||
opacity: 0.8;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
th.weekend,
|
||||
td.weekend {
|
||||
background-color: #f9f9f9;
|
||||
background-color: var(--tblr-bg-surface-tertiary);
|
||||
}
|
||||
/* order is important, "today” should overwrite "weekend" therefor later in the file */
|
||||
th.today,
|
||||
td.today {
|
||||
background-color: $highlight-today;
|
||||
th.today {
|
||||
background-color: var(--tblr-highlight-bg);
|
||||
}
|
||||
th.total,
|
||||
td.total {
|
||||
@@ -167,3 +153,10 @@ table.table-hover {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* For the context menu */
|
||||
.action-dropdown,
|
||||
.actions .dropdown .dropdown-menu {
|
||||
// stay on top of sticky elements (like table header)
|
||||
z-index: 1021;
|
||||
}
|
||||
@@ -1,48 +1,22 @@
|
||||
@include dark-mode {
|
||||
.form-check-input {
|
||||
border-color: $input-border-color;
|
||||
}
|
||||
|
||||
/* selectpicker.scss / Tom Select */
|
||||
.ts-wrapper.form-select .ts-control,
|
||||
.ts-wrapper.form-select.single.input-active .ts-control {
|
||||
color: $dark-mode-text;
|
||||
}
|
||||
.ts-dropdown {
|
||||
background-color: $dark-mode-lighten;
|
||||
color: $dark-mode-text;
|
||||
}
|
||||
.ts-control {
|
||||
> input {
|
||||
min-width: unset;
|
||||
color: $dark-mode-text;
|
||||
}
|
||||
@include color-mode(dark, true) {
|
||||
$dark-font-opacity: 0.6;
|
||||
|
||||
.item {
|
||||
background-color: $dark;
|
||||
color: $dark-mode-text;
|
||||
}
|
||||
}
|
||||
.optgroup-header {
|
||||
background-color: $dropdown-dark-bg;
|
||||
color: $dark-mode-text;
|
||||
}
|
||||
/*
|
||||
--tblr-body-bg: #1e222b;
|
||||
--tblr-bg-surface-tertiary: #1e222b;
|
||||
--tblr-bg-surface: #1e222b;
|
||||
--tblr-bg-surface-secondary: #17202b;
|
||||
//--tblr-muted: #d9dcdf;
|
||||
*/
|
||||
|
||||
.table thead th {
|
||||
color: inherit;
|
||||
opacity: $dark-font-opacity;
|
||||
}
|
||||
table.dataTable {
|
||||
th.weekend, td.weekend {
|
||||
background-color: $dark-mode-weekend;
|
||||
color: $dark-mode-text;
|
||||
}
|
||||
/* must be defined after weekend, to overwrite it */
|
||||
th.today, td.today {
|
||||
background-color: $dark-mode-today;
|
||||
color: $dark-mode-text;
|
||||
}
|
||||
|
||||
tr {
|
||||
&.summary td {
|
||||
background-color: $dark-mode-darken;
|
||||
}
|
||||
thead .sorting:after {
|
||||
opacity: $dark-font-opacity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +24,19 @@
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
|
||||
> .fas {
|
||||
color: $dark-mode-text;
|
||||
color: var(--tblr-white);
|
||||
}
|
||||
}
|
||||
/* Fix tabs pills (e.g. user profile) */
|
||||
.nav {
|
||||
--tblr-nav-link-color: inherit;
|
||||
opacity: $dark-font-opacity;
|
||||
}
|
||||
.datagrid-title {
|
||||
color: inherit;
|
||||
opacity: $dark-font-opacity;
|
||||
}
|
||||
.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,6 @@
|
||||
@include media-breakpoint-down(lg) {
|
||||
aside.navbar .btn-outline-white,
|
||||
header.navbar .btn-outline-white {
|
||||
background-color: var(--tblr-body-color);
|
||||
border-color: hsla(0,0%,100%,.2);
|
||||
color: hsla(0,0%,100%,.7);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
@import "bootstrap/scss/functions";
|
||||
@import "bootstrap/scss/variables";
|
||||
@import "bootstrap/scss/mixins";
|
||||
|
||||
@import "@tabler/core/src/scss/variables";
|
||||
@import "@tabler/core/src/scss/mixins";
|
||||
|
||||
/* For highlighting rows, fieldsets and so on */
|
||||
$highlight-bg: #f5f5f5;
|
||||
$highlight-today: #fcf8e3;
|
||||
|
||||
$red: #dd4b39;
|
||||
$green: #00a65a;
|
||||
$yellow: #f0ad4e;
|
||||
$blue: #0073b7;
|
||||
|
||||
$gray-lte: #d2d6de;
|
||||
$kimai-default: $gray-lte;
|
||||
|
||||
$box-border-radius: 3px;
|
||||
|
||||
$dark-mode-today: var(--tblr-blue);
|
||||
$dark-mode-weekend: lighten($dark, 5%) !default;
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "8.1.*||8.2.*",
|
||||
"php": "8.1.*||8.2.*||8.3.*",
|
||||
"ext-gd": "*",
|
||||
"ext-intl": "*",
|
||||
"ext-json": "*",
|
||||
@@ -24,27 +24,28 @@
|
||||
"ext-xml": "*",
|
||||
"ext-xsl": "*",
|
||||
"ext-zip": "*",
|
||||
"composer/package-versions-deprecated": "^1.8",
|
||||
"composer-runtime-api": "^2.0",
|
||||
"azuyalabs/yasumi": "^2.6",
|
||||
"composer/semver": "^3.3",
|
||||
"doctrine/doctrine-bundle": "^2.7",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||
"doctrine/orm": "^2.8",
|
||||
"endroid/qr-code": "^4.4.9",
|
||||
"endroid/qr-code": "^4.8",
|
||||
"erusev/parsedown": "^1.6",
|
||||
"friendsofsymfony/rest-bundle": "^3.0",
|
||||
"gedmo/doctrine-extensions": "^3.6",
|
||||
"jms/serializer-bundle": "^5.0",
|
||||
"kevinpapst/tabler-bundle": "^0.14",
|
||||
"kevinpapst/tabler-bundle": "^1.4",
|
||||
"league/csv": "^9.4",
|
||||
"mpdf/mpdf": "^8.0",
|
||||
"nelmio/api-doc-bundle": "^4.0",
|
||||
"nelmio/cors-bundle": "^2.0",
|
||||
"onelogin/php-saml": "^4.0",
|
||||
"pagerfanta/pagerfanta": "^3.0",
|
||||
"phpoffice/phpspreadsheet": "^1.16",
|
||||
"phpoffice/phpspreadsheet": "^2.0",
|
||||
"phpoffice/phpword": "^1.0",
|
||||
"psr/container": "^1.0||^2.0",
|
||||
"psr/log": "^2.0||^3.0",
|
||||
"psr/container": "^2.0",
|
||||
"psr/log": "^3.0",
|
||||
"scheb/2fa-backup-code": "^6.2",
|
||||
"scheb/2fa-bundle": "^6.2",
|
||||
"scheb/2fa-totp": "^6.2",
|
||||
@@ -58,6 +59,7 @@
|
||||
"symfony/http-client": "^6.0",
|
||||
"symfony/intl": "^6.0",
|
||||
"symfony/mailer": "^6.0",
|
||||
"symfony/mime": "^6.0",
|
||||
"symfony/monolog-bundle": "^3.4",
|
||||
"symfony/rate-limiter": "^6.0",
|
||||
"symfony/runtime": "^6.0",
|
||||
@@ -67,7 +69,7 @@
|
||||
"symfony/translation": "^6.0",
|
||||
"symfony/twig-bundle": "^6.0",
|
||||
"symfony/validator": "^6.0",
|
||||
"symfony/webpack-encore-bundle": "^1.16",
|
||||
"symfony/webpack-encore-bundle": "^2.0",
|
||||
"symfony/yaml": "^6.0",
|
||||
"twig/cssinliner-extra": "^3.0",
|
||||
"twig/extra-bundle": "^3.0",
|
||||
@@ -77,11 +79,12 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-simplexml": "*",
|
||||
"dama/doctrine-test-bundle": "^7.0",
|
||||
"dama/doctrine-test-bundle": "^8.0",
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.2",
|
||||
"fakerphp/faker": "^1.15",
|
||||
"friendsofphp/php-cs-fixer": "^3.3",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.0",
|
||||
"phpstan/phpstan-doctrine": "^1.0",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.0",
|
||||
@@ -104,7 +107,6 @@
|
||||
],
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"composer/package-versions-deprecated": true,
|
||||
"symfony/flex": true,
|
||||
"symfony/runtime": true
|
||||
},
|
||||
@@ -129,6 +131,7 @@
|
||||
}
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-ctype": "*",
|
||||
"symfony/polyfill-mbstring": "*",
|
||||
"symfony/polyfill-intl": "*",
|
||||
"symfony/polyfill-iconv": "*",
|
||||
@@ -168,18 +171,23 @@
|
||||
"@tests-integration"
|
||||
],
|
||||
"linting": [
|
||||
"composer validate --strict",
|
||||
"bin/console lint:container",
|
||||
"bin/console lint:yaml config --parse-tags",
|
||||
"bin/console lint:twig templates --show-deprecations",
|
||||
"bin/console lint:xliff translations",
|
||||
"bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction"
|
||||
"bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction",
|
||||
"bin/console lint:xliff translations"
|
||||
],
|
||||
"tests": "vendor/bin/phpunit tests/",
|
||||
"tests-unit": "vendor/bin/phpunit --exclude-group integration tests/",
|
||||
"tests-integration": "vendor/bin/phpunit --group integration tests/",
|
||||
"phpstan": [
|
||||
"vendor/bin/phpstan analyse -c phpstan.neon",
|
||||
"@phpstan-src",
|
||||
"@phpstan-tests"
|
||||
],
|
||||
"phpstan-src": [
|
||||
"vendor/bin/phpstan analyse -c phpstan.neon"
|
||||
],
|
||||
"phpstan-tests": [
|
||||
"vendor/bin/phpstan analyse -c tests/phpstan.neon"
|
||||
],
|
||||
"codestyle": "vendor/bin/php-cs-fixer fix --dry-run --verbose --show-progress=none",
|
||||
@@ -189,13 +197,10 @@
|
||||
"symfony/symfony": "*"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.0.x-dev"
|
||||
},
|
||||
"symfony": {
|
||||
"id": "01C3FWRDJJEX9K6Y3A4XDFXPBR",
|
||||
"allow-contrib": true,
|
||||
"require": "6.2.*"
|
||||
"require": "6.4.*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5711
composer.lock
generated
5711
composer.lock
generated
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@ parameters:
|
||||
# environment variables are not available yet.
|
||||
# You should not need to change this value.
|
||||
env(DATABASE_URL): ''
|
||||
env(DATABASE_VERSION): ~
|
||||
|
||||
doctrine:
|
||||
dbal:
|
||||
@@ -15,22 +14,26 @@ doctrine:
|
||||
#schema_filter: ~^(?!(bundle_migration_|kimai2_sessions))~
|
||||
url: '%env(DATABASE_URL)%'
|
||||
driver: 'pdo_mysql'
|
||||
# If this is wrong, automatic database detection might fail and find false-negatives when running
|
||||
# doctrine:migrations:diff on null columns with MariaDB.
|
||||
# This here is mainly a fallback for times, when users have not configured the database version in the URL
|
||||
server_version: '%env(string:DATABASE_VERSION)%'
|
||||
charset: utf8mb4
|
||||
default_table_options:
|
||||
charset: utf8mb4
|
||||
collate: utf8mb4_unicode_ci
|
||||
collation: utf8mb4_unicode_ci
|
||||
schema_manager_factory: doctrine.dbal.default_schema_manager_factory
|
||||
|
||||
types:
|
||||
datetime: App\Doctrine\UTCDateTimeType
|
||||
datetime_immutable: App\Doctrine\UTCDateTimeImmutableType
|
||||
orm:
|
||||
controller_resolver:
|
||||
# FIXME this is causing a deprecation and needs to be changed
|
||||
# auto_mapping: false
|
||||
auto_mapping: true
|
||||
auto_generate_proxy_classes: '%kernel.debug%'
|
||||
default_entity_manager: default
|
||||
enable_lazy_ghost_objects: true
|
||||
entity_managers:
|
||||
default:
|
||||
report_fields_where_declared: true
|
||||
connection: default
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
auto_mapping: true
|
||||
@@ -50,7 +53,10 @@ doctrine:
|
||||
when@test:
|
||||
doctrine:
|
||||
dbal:
|
||||
logging: false
|
||||
connections:
|
||||
default:
|
||||
logging: false
|
||||
use_savepoints: true
|
||||
|
||||
when@prod:
|
||||
doctrine:
|
||||
|
||||
@@ -5,3 +5,4 @@ doctrine_migrations:
|
||||
table_name: 'migration_versions'
|
||||
migrations_paths:
|
||||
'DoctrineMigrations': '%kernel.project_dir%/migrations'
|
||||
custom_template: '%kernel.project_dir%/migrations/MigrationTemplate.txt'
|
||||
@@ -1,11 +1,25 @@
|
||||
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||
parameters:
|
||||
env(TRUSTED_PROXIES): ''
|
||||
|
||||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
csrf_protection: true
|
||||
annotations: false
|
||||
handle_all_throwables: true
|
||||
disallow_search_engine_index: true
|
||||
set_content_language_from_locale: true
|
||||
|
||||
serializer:
|
||||
enable_attributes: true
|
||||
|
||||
trusted_proxies: '%env(string:TRUSTED_PROXIES)%'
|
||||
|
||||
exceptions:
|
||||
App\Validator\ValidationFailedException:
|
||||
log_level: debug
|
||||
|
||||
secret: '%env(APP_SECRET)%'
|
||||
default_locale: en
|
||||
csrf_protection: true
|
||||
http_method_override: false
|
||||
|
||||
# Enables session support. Note that the session will ONLY be started if you read or write from it.
|
||||
@@ -17,8 +31,6 @@ framework:
|
||||
cookie_httponly: true
|
||||
storage_factory_id: session.storage.factory.native
|
||||
|
||||
#esi: ~
|
||||
#fragments: ~
|
||||
php_errors:
|
||||
log: true
|
||||
|
||||
@@ -27,6 +39,7 @@ framework:
|
||||
|
||||
validation:
|
||||
email_validation_mode: html5
|
||||
enable_attributes: true
|
||||
|
||||
when@dev:
|
||||
framework:
|
||||
|
||||
@@ -75,7 +75,7 @@ kimai:
|
||||
# PERMISSIONS
|
||||
# --------------------------------------------------------------------------------
|
||||
permissions:
|
||||
# mapping complex rule sets of single permissions to named "sets" ("set name" = [array of "permissions and @SETS"])
|
||||
# mapping a "set name" to an array of "permission names"
|
||||
sets:
|
||||
ACTIVITIES: ['view_activity','create_activity','edit_activity','budget_activity','time_activity','delete_activity','permissions_activity']
|
||||
ACTIVITIES_ALL_TEAMLEAD: ['view_teamlead_activity','edit_teamlead_activity','budget_teamlead_activity','time_teamlead_activity','permissions_teamlead_activity']
|
||||
@@ -95,7 +95,7 @@ kimai:
|
||||
TIMESHEET: ['view_own_timesheet','start_own_timesheet','stop_own_timesheet','create_own_timesheet','edit_own_timesheet','export_own_timesheet','delete_own_timesheet','weekly_own_timesheet']
|
||||
TIMESHEET_OTHER: ['view_other_timesheet','start_other_timesheet','stop_other_timesheet','create_other_timesheet','edit_other_timesheet','export_other_timesheet','delete_other_timesheet']
|
||||
PROFILE: ['view_own_profile','edit_own_profile','password_own_profile','preferences_own_profile','api-token_own_profile']
|
||||
PROFILE_OTHER: ['view_other_profile','edit_other_profile','password_other_profile','roles_other_profile','preferences_other_profile','api-token_other_profile','teams_other_profile']
|
||||
PROFILE_OTHER: ['view_other_profile','edit_other_profile','password_other_profile','roles_other_profile','preferences_other_profile','api-token_other_profile','teams_other_profile','supervisor_other_profile']
|
||||
TAGS: ['view_tag','manage_tag','create_tag','delete_tag']
|
||||
USER: ['view_user','create_user','delete_user','role_permissions']
|
||||
RATE: ['view_rate_own_timesheet','edit_rate_own_timesheet']
|
||||
@@ -104,29 +104,23 @@ kimai:
|
||||
BILLABLE: ['edit_billable_own_timesheet','edit_billable_other_timesheet']
|
||||
TEAMS: ['view_team','create_team','edit_team','delete_team']
|
||||
LOCKDOWN: ['lockdown_grace_timesheet','lockdown_override_timesheet']
|
||||
REPORTING: ['view_reporting','view_other_reporting','project_reporting','customer_reporting']
|
||||
# some single default definitions for roles
|
||||
SINGLE_USER: ['view_team_member','time_team_project','create_tag','view_reporting']
|
||||
SINGLE_TEAMLEAD: ['view_rate_own_timesheet','view_rate_other_timesheet','hourly-rate_own_profile','view_team_member']
|
||||
SINGLE_ADMIN: ['hourly-rate_own_profile','edit_exported_timesheet','teams_own_profile','view_team_member','view_all_data']
|
||||
SINGLE_SUPER_ADMIN: ['hourly-rate_own_profile','hourly-rate_other_profile','roles_own_profile','system_information','system_configuration','plugins','edit_exported_timesheet','teams_own_profile','view_team_member','upload_invoice_template','view_all_data']
|
||||
# link above sets to one complete set for each user role
|
||||
ROLE_USER: ['@TIMESHEET','@PROFILE','@SINGLE_USER']
|
||||
ROLE_TEAMLEAD: ['@ACTIVITIES_TEAMLEAD','@PROJECTS_TEAMLEAD','@CUSTOMERS_TEAMLEAD','@TIMESHEET_OTHER','@INVOICE','@TIMESHEET','@PROFILE','@EXPORT','@BILLABLE','@TAGS','@REPORTING','@SINGLE_TEAMLEAD']
|
||||
ROLE_ADMIN: ['@ACTIVITIES','@PROJECTS','@CUSTOMERS','@INVOICE','@INVOICE_ADMIN','@TIMESHEET','@TIMESHEET_OTHER','@PROFILE','@TEAMS','@RATE','@RATE_OTHER','@EXPORT','@BILLABLE','@TAGS','@LOCKDOWN','@REPORTING','@SINGLE_ADMIN']
|
||||
ROLE_SUPER_ADMIN: ['@ACTIVITIES','@PROJECTS','@CUSTOMERS','@INVOICE','@INVOICE_ADMIN','@TIMESHEET','@TIMESHEET_OTHER','@PROFILE','@PROFILE_OTHER','@USER','@TEAMS','@RATE','@RATE_OTHER','@EXPORT','@BILLABLE','@TAGS','@LOCKDOWN','@REPORTING','@SINGLE_SUPER_ADMIN']
|
||||
# mapping "sets" or permissions to user roles ("role name" = [array of "set names"])
|
||||
REPORTING: ['view_reporting','view_other_reporting','activity_reporting','project_reporting','customer_reporting']
|
||||
EVERYONE: ['api_access','hours_own_profile']
|
||||
# permissions which are deactivated, as these features are hidden for now
|
||||
# brave users can try to activate them and be surprised what happens
|
||||
REGISTER_BETA: []
|
||||
# mapping a "role name" to an array of "set names"
|
||||
maps:
|
||||
ROLE_USER: ['ROLE_USER']
|
||||
ROLE_TEAMLEAD: ['ROLE_TEAMLEAD']
|
||||
ROLE_ADMIN: ['ROLE_ADMIN']
|
||||
ROLE_SUPER_ADMIN: ['ROLE_SUPER_ADMIN']
|
||||
# add or remove single permissions
|
||||
ROLE_USER: ['TIMESHEET','PROFILE', 'EVERYONE']
|
||||
ROLE_TEAMLEAD: ['ACTIVITIES_TEAMLEAD','PROJECTS_TEAMLEAD','CUSTOMERS_TEAMLEAD','TIMESHEET_OTHER','INVOICE','TIMESHEET','PROFILE','EXPORT','BILLABLE','TAGS','REPORTING', 'EVERYONE']
|
||||
ROLE_ADMIN: ['ACTIVITIES','PROJECTS','CUSTOMERS','INVOICE','INVOICE_ADMIN','TIMESHEET','TIMESHEET_OTHER','PROFILE','TEAMS','RATE','RATE_OTHER','EXPORT','BILLABLE','TAGS','LOCKDOWN','REPORTING', 'EVERYONE']
|
||||
ROLE_SUPER_ADMIN: ['ACTIVITIES','PROJECTS','CUSTOMERS','INVOICE','INVOICE_ADMIN','TIMESHEET','TIMESHEET_OTHER','PROFILE','PROFILE_OTHER','USER','TEAMS','RATE','RATE_OTHER','EXPORT','BILLABLE','TAGS','LOCKDOWN','REPORTING', 'EVERYONE']
|
||||
# mapping a "role name" to an array of "permission names"
|
||||
roles:
|
||||
ROLE_USER: []
|
||||
ROLE_TEAMLEAD: []
|
||||
ROLE_ADMIN: []
|
||||
ROLE_SUPER_ADMIN: []
|
||||
ROLE_USER: ['view_team_member','time_team_project','create_tag','view_reporting']
|
||||
ROLE_TEAMLEAD: ['view_rate_own_timesheet','view_rate_other_timesheet','hourly-rate_own_profile','view_team_member','hours_other_profile']
|
||||
ROLE_ADMIN: ['hourly-rate_own_profile','edit_exported_timesheet','teams_own_profile','view_team_member','view_all_data','contract_other_profile','hours_other_profile']
|
||||
ROLE_SUPER_ADMIN: ['hourly-rate_own_profile','hourly-rate_other_profile','roles_own_profile','supervisor_own_profile','system_information','system_configuration','plugins','edit_exported_timesheet','teams_own_profile','view_team_member','upload_invoice_template','view_all_data','contract_other_profile','hours_other_profile']
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -158,14 +152,20 @@ kimai:
|
||||
# --------------------------------------------------------------------------------
|
||||
# INVOICES
|
||||
# --------------------------------------------------------------------------------
|
||||
# invoice:
|
||||
# # all files in these directories will be used as invoice documents (if supported by a renderer)
|
||||
# documents:
|
||||
# - 'var/invoices/'
|
||||
# - 'templates/invoice/renderer/'
|
||||
# invoice:
|
||||
# # all files in these directories will be used as invoice documents (if supported by a renderer)
|
||||
# documents:
|
||||
# - 'var/invoices/'
|
||||
# - 'templates/invoice/renderer/'
|
||||
# # allow to upload twig templates as invoice documents
|
||||
# upload_twig: true
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
|
||||
when@test:
|
||||
kimai:
|
||||
data_dir: '%kernel.project_dir%/tests/_data'
|
||||
permissions:
|
||||
roles:
|
||||
ROLE_ADMIN: ['contract_other_profile']
|
||||
ROLE_SUPER_ADMIN: ['contract_other_profile']
|
||||
|
||||
@@ -1,42 +1,58 @@
|
||||
monolog:
|
||||
channels: ["deprecation"]
|
||||
|
||||
when@prod:
|
||||
monolog:
|
||||
channels: ["deprecation"]
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [403, 404]
|
||||
nested:
|
||||
handler: file_log
|
||||
excluded_http_codes:
|
||||
- {400: ['^/api/']}
|
||||
- {401: ['^/api/']}
|
||||
- 403
|
||||
- 404
|
||||
- {405: ['/homepage$', '/login_check$', '/export/data$']}
|
||||
channels: ["!deprecation"]
|
||||
file_log:
|
||||
type: stream
|
||||
level: info
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
formatter: monolog.formatter.kimai
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine"]
|
||||
deprecation:
|
||||
type: stream
|
||||
channels: ["deprecation"]
|
||||
path: "%kernel.logs_dir%/deprecations.log"
|
||||
channels: ["!event", "!doctrine", "!deprecation"]
|
||||
|
||||
when@dev:
|
||||
monolog:
|
||||
channels: ["deprecation"]
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: notice
|
||||
handler: file_log
|
||||
excluded_http_codes:
|
||||
- {400: ['^/api/']}
|
||||
- {401: ['^/api/']}
|
||||
- 403
|
||||
- 404
|
||||
- {405: ['/homepage$', '/login_check$', '/export/data$']}
|
||||
channels: ["!event", "!deprecation"]
|
||||
file_log:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: info
|
||||
channels: ["!event"]
|
||||
level: debug
|
||||
formatter: monolog.formatter.kimai
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine", "!console"]
|
||||
channels: ["!event", "!doctrine", "!deprecation"]
|
||||
deprecation:
|
||||
type: stream
|
||||
channels: ["deprecation"]
|
||||
path: "%kernel.logs_dir%/deprecations.log"
|
||||
formatter: monolog.formatter.deprecation
|
||||
|
||||
when@test:
|
||||
monolog:
|
||||
@@ -46,3 +62,4 @@ when@test:
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: info
|
||||
channels: ["!event"]
|
||||
formatter: monolog.formatter.kimai
|
||||
|
||||
@@ -40,27 +40,23 @@ nelmio_api_doc:
|
||||
- { alias: TeamCollection, type: App\Entity\Team, groups: [Default, Collection, Team] }
|
||||
- { alias: TeamMember, type: App\Entity\TeamMember, groups: [Team_Entity] }
|
||||
- { alias: TeamMembership, type: App\Entity\TeamMember, groups: [User_Entity] }
|
||||
- { alias: Invoice, type: App\Entity\Invoice, groups: [Default, Entity, Invoice, Invoice_Entity] }
|
||||
- { alias: InvoiceCollection, type: App\Entity\Invoice, groups: [Default, Collection, Invoice] }
|
||||
areas:
|
||||
path_patterns:
|
||||
- ^/api(?!/doc)
|
||||
documentation:
|
||||
info:
|
||||
title: Kimai - API Docs
|
||||
title: Kimai - API
|
||||
description: |
|
||||
JSON API for the Kimai time-tracking software: [API documentation](https://www.kimai.org/documentation/rest-api.html), [Swagger definition file](doc.json)
|
||||
version: '0.7'
|
||||
JSON API for the Kimai time-tracking software. Read our [API documentation](https://www.kimai.org/documentation/rest-api.html) and download the [Open API definition](doc.json) to import into your API client.
|
||||
version: '1.0'
|
||||
components:
|
||||
securitySchemes:
|
||||
apiUser:
|
||||
type: apiKey
|
||||
description: 'Value: {Username}'
|
||||
name: X-AUTH-USER
|
||||
in: header
|
||||
apiToken:
|
||||
type: apiKey
|
||||
description: 'Value: {API Token}'
|
||||
name: X-AUTH-TOKEN
|
||||
in: header
|
||||
bearer:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: KIMAI
|
||||
description: API Token
|
||||
security:
|
||||
- X-AUTH-USER: []
|
||||
X-AUTH-TOKEN: []
|
||||
- bearer: []
|
||||
|
||||
@@ -3,12 +3,7 @@ parameters:
|
||||
|
||||
nelmio_cors:
|
||||
defaults:
|
||||
allow_credentials: false
|
||||
# allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
|
||||
# allow_headers: ['Content-Type', 'Authorization', 'X-AUTH-USER', 'X-AUTH-TOKEN']
|
||||
# allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
|
||||
# expose_headers: ['Link']
|
||||
# max_age: 3600
|
||||
# for security reasons we do not allow CORS by default
|
||||
allow_origin: []
|
||||
allow_headers: []
|
||||
allow_methods: []
|
||||
@@ -18,8 +13,17 @@ nelmio_cors:
|
||||
origin_regex: true
|
||||
forced_allow_origin_value: ~
|
||||
paths:
|
||||
# only the API endpoints are accessible
|
||||
'^/api/':
|
||||
# allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
|
||||
allow_origin: ['*']
|
||||
allow_headers: ['X-AUTH-USER', 'X-AUTH-TOKEN', 'Content-Type']
|
||||
allow_headers: ['Content-Type', 'Authorization', 'X-AUTH-USER', 'X-AUTH-TOKEN']
|
||||
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
|
||||
# expose_headers: ['Link']
|
||||
max_age: 3600
|
||||
|
||||
when@dev:
|
||||
nelmio_cors:
|
||||
paths:
|
||||
'^/api/':
|
||||
allow_private_network: true
|
||||
|
||||
21
config/packages/rate_limiter.yaml
Normal file
21
config/packages/rate_limiter.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
framework:
|
||||
rate_limiter:
|
||||
session_prediction:
|
||||
policy: 'fixed_window'
|
||||
limit: 250
|
||||
interval: '1 hour'
|
||||
lock_factory: null
|
||||
reset_password:
|
||||
policy: 'fixed_window'
|
||||
limit: 10
|
||||
interval: '1 hour'
|
||||
lock_factory: null
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
rate_limiter:
|
||||
reset_password:
|
||||
policy: 'fixed_window'
|
||||
limit: 100
|
||||
interval: '1 minute'
|
||||
lock_factory: null
|
||||
@@ -3,12 +3,15 @@ scheb_two_factor:
|
||||
security_tokens:
|
||||
- Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
|
||||
- Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken
|
||||
- App\Saml\SamlToken
|
||||
|
||||
totp:
|
||||
enabled: true
|
||||
template: security/2fa.html.twig # Overwritten template
|
||||
window: 1 # How many codes before/after the current one would be accepted as valid
|
||||
# server_name: Server Name # Server name used in QR code
|
||||
leeway: 29 # How many seconds the code is valid
|
||||
issuer: Kimai # Issuer name used in QR code
|
||||
# parameters: # Additional parameters added in the QR code
|
||||
# image: 'https://my-service/img/logo.png'
|
||||
|
||||
two_factor_condition: App\Security\TwoFactorCondition
|
||||
|
||||
# TODO add: backup codes - https://symfony.com/bundles/SchebTwoFactorBundle/current/backup_codes.html
|
||||
# TODO add: Trusted device feature - https://github.com/scheb/2fa/blob/6.x/doc/configuration.rst
|
||||
|
||||
@@ -18,6 +18,10 @@ security:
|
||||
security: false
|
||||
|
||||
api:
|
||||
access_token:
|
||||
token_handler: App\API\Authentication\AccessTokenHandler
|
||||
success_handler: App\API\Authentication\AccessTokenSuccessHandler
|
||||
remember_me: false
|
||||
request_matcher: App\API\Authentication\ApiRequestMatcher
|
||||
user_checker: App\Security\UserChecker
|
||||
stateless: true
|
||||
@@ -35,7 +39,6 @@ security:
|
||||
entry_point: form_login
|
||||
|
||||
custom_authenticators:
|
||||
- App\API\Authentication\SessionAuthenticator
|
||||
- App\Saml\SamlAuthenticator
|
||||
|
||||
remember_me:
|
||||
@@ -60,12 +63,20 @@ security:
|
||||
logout:
|
||||
path: logout
|
||||
target: homepage
|
||||
enable_csrf: false
|
||||
|
||||
login_throttling:
|
||||
max_attempts: 5
|
||||
interval: '5 minutes'
|
||||
|
||||
login_link:
|
||||
check_route: link_login_check
|
||||
signature_properties: ['id']
|
||||
lifetime: 900
|
||||
max_uses: 3
|
||||
|
||||
access_decision_manager:
|
||||
# only grants access if there is no voter denying access
|
||||
strategy: unanimous
|
||||
allow_if_all_abstain: false
|
||||
|
||||
@@ -78,13 +89,13 @@ security:
|
||||
access_control:
|
||||
- { path: '^/auth/2fa', role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
|
||||
- { path: '^/auth', roles: PUBLIC_ACCESS }
|
||||
- { path: '^/(%app_locales%)$', role: PUBLIC_ACCESS }
|
||||
- { path: '^/(%app_locales%)/auth', role: PUBLIC_ACCESS }
|
||||
- { path: '^/(%app_locales%)/login', role: PUBLIC_ACCESS }
|
||||
- { path: '^/(%app_locales%)/register', role: PUBLIC_ACCESS }
|
||||
- { path: '^/(%app_locales%)/resetting', role: PUBLIC_ACCESS }
|
||||
- { path: '^/(%app_locales%)/', roles: ROLE_USER }
|
||||
- { path: '^/api', roles: IS_AUTHENTICATED_REMEMBERED }
|
||||
- { path: '^/{_locale}$', role: PUBLIC_ACCESS }
|
||||
- { path: '^/{_locale}/auth', role: PUBLIC_ACCESS }
|
||||
- { path: '^/{_locale}/login', role: PUBLIC_ACCESS }
|
||||
- { path: '^/{_locale}/register', role: PUBLIC_ACCESS }
|
||||
- { path: '^/{_locale}/resetting', role: PUBLIC_ACCESS }
|
||||
- { path: '^/{_locale}/', roles: ROLE_USER }
|
||||
- { path: '^/api', roles: IS_AUTHENTICATED }
|
||||
|
||||
when@test:
|
||||
# this configuration simplifies testing URLs protected by the security mechanism
|
||||
@@ -97,7 +108,3 @@ when@test:
|
||||
cost: 4 # Lowest possible value for bcrypt
|
||||
time_cost: 3 # Lowest possible value for argon
|
||||
memory_cost: 10 # Lowest possible value for argon
|
||||
|
||||
firewalls:
|
||||
secured_area:
|
||||
http_basic: ~
|
||||
|
||||
@@ -29,7 +29,6 @@ tabler:
|
||||
routes:
|
||||
tabler_welcome: dashboard
|
||||
tabler_login: login
|
||||
tabler_logout: logout
|
||||
tabler_login_check: security_check
|
||||
tabler_registration: registration_register
|
||||
tabler_registration_register: registration_register
|
||||
@@ -55,6 +54,7 @@ tabler:
|
||||
columns: fas fa-table
|
||||
comment: far fa-comment
|
||||
configuration: fas fa-cog
|
||||
contract: fas fa-balance-scale
|
||||
copy: far fa-copy
|
||||
create: fas fa-plus
|
||||
csv: fas fa-table
|
||||
@@ -77,6 +77,8 @@ tabler:
|
||||
filter: fas fa-filter
|
||||
help: far fa-question-circle
|
||||
home: fas fa-home
|
||||
info: fas fa-info-circle
|
||||
import: fas fa-file-import
|
||||
invoice: fas fa-file-contract
|
||||
invoice-template: fas fa-file-signature
|
||||
left: fas fa-chevron-left
|
||||
@@ -97,6 +99,7 @@ tabler:
|
||||
password: fas fa-key
|
||||
pause: fas fa-pause
|
||||
pause-small: far fa-pause-circle
|
||||
pending: fas fa-hourglass-half
|
||||
pin: fas fa-thumbtack
|
||||
pdf: fas fa-file-pdf
|
||||
permissions: fas fa-user-lock
|
||||
@@ -106,9 +109,11 @@ tabler:
|
||||
profile: fas fa-user-edit
|
||||
profile-stats: far fa-chart-bar
|
||||
project: fas fa-briefcase
|
||||
repeat: fas fa-redo-alt
|
||||
repeat: fas fa-repeat
|
||||
reporting: far fa-chart-bar
|
||||
report: far fa-chart-bar
|
||||
review: fas fa-user-check
|
||||
rejected: fas fa-ban
|
||||
right: fas fa-chevron-right
|
||||
right2: fas fa-angle-double-right
|
||||
roles: fas fa-user-shield
|
||||
@@ -116,7 +121,7 @@ tabler:
|
||||
search: fas fa-search
|
||||
settings: fas fa-cog
|
||||
shop: fas fa-shopping-cart
|
||||
spinner: fas fa-spinner fa-pulse
|
||||
spinner: fas fa-spinner
|
||||
start: fas fa-play
|
||||
start-small: far fa-play-circle
|
||||
stop: fas fa-stop
|
||||
@@ -136,4 +141,5 @@ tabler:
|
||||
weekly-times: fas fa-th
|
||||
widget_add: fas fa-folder-plus
|
||||
widget_remove: fas fa-folder-minus
|
||||
work_times: fas fa-calculator
|
||||
xlsx: fas fa-file-excel
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
framework:
|
||||
# https://symfony.com/doc/current/reference/configuration/framework.html#enabled-locales
|
||||
enabled_locales: '%kimai_locales%'
|
||||
default_locale: '%locale%'
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
controllers:
|
||||
resource: ../src/Controller/
|
||||
type: annotation
|
||||
type: attribute
|
||||
prefix: /{_locale}
|
||||
requirements:
|
||||
_locale: '%app_locales%'
|
||||
defaults:
|
||||
_locale: '%locale%'
|
||||
|
||||
@@ -19,26 +17,24 @@ api.swagger:
|
||||
|
||||
api:
|
||||
resource: ../src/API/
|
||||
type: annotation
|
||||
type: attribute
|
||||
prefix: /api
|
||||
|
||||
auth:
|
||||
resource: ../src/Controller/Auth/
|
||||
type: annotation
|
||||
type: attribute
|
||||
prefix: /auth
|
||||
|
||||
security:
|
||||
resource: ../src/Controller/Security/
|
||||
type: annotation
|
||||
type: attribute
|
||||
prefix: /{_locale}
|
||||
requirements:
|
||||
_locale: '%app_locales%'
|
||||
defaults:
|
||||
_locale: '%locale%'
|
||||
|
||||
kernel:
|
||||
resource: ../src/Kernel.php
|
||||
type: annotation
|
||||
type: attribute
|
||||
|
||||
home:
|
||||
path: /
|
||||
@@ -49,8 +45,6 @@ home:
|
||||
|
||||
homeLocale:
|
||||
path: /{_locale}
|
||||
requirements:
|
||||
_locale: '%app_locales%'
|
||||
defaults:
|
||||
_controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction
|
||||
_locale: '%locale%'
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||
prefix: /{_locale}/_error
|
||||
requirements:
|
||||
_locale: '%app_locales%'
|
||||
defaults:
|
||||
_locale: '%locale%'
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
app.test.active_entries:
|
||||
path: /{_locale}/layou/active_entries
|
||||
controller: App\Controller\LayoutController::activeEntries
|
||||
requirements:
|
||||
_locale: '%app_locales%'
|
||||
defaults:
|
||||
_locale: '%locale%'
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
parameters:
|
||||
locale: en
|
||||
# the original list of all locales is the next line, it can be used to regenerate the locale list in case new locales will be added
|
||||
#app_locales: ar|cs|da|de|el|en|eo|es|eu|fa|fi|fo|fr|he|hr|hu|it|ja|ko|nb_NO|nl|pl|pt|ro|ru|sk|sv|tr|uk|vi|zh_CN|zh_Hant
|
||||
app_locales: ar|cs|da|de|el|en|eo|es|eu|fa|fi|fo|fr|he|hr|hu|it|ja|ko|nb_NO|nl|pl|pt|pt_BR|ro|ru|sk|sv|tr|uk|vi|zh_CN|zh_Hant|cs_CZ|da_DK|da_GL|de_AT|de_BE|de_CH|de_DE|de_IT|de_LI|de_LU|el_CY|el_GR|en_AE|en_AG|en_AI|en_AS|en_AT|en_AU|en_BB|en_BE|en_BI|en_BM|en_BS|en_BW|en_BZ|en_CA|en_CC|en_CH|en_CK|en_CM|en_CX|en_CY|en_DE|en_DK|en_DM|en_ER|en_FI|en_FJ|en_FK|en_FM|en_GB|en_GD|en_GG|en_GH|en_GI|en_GM|en_GU|en_GY|en_HK|en_IE|en_IL|en_IM|en_IN|en_IO|en_JE|en_JM|en_KE|en_KI|en_KN|en_KY|en_LC|en_LR|en_LS|en_MG|en_MH|en_MO|en_MP|en_MS|en_MT|en_MU|en_MV|en_MW|en_MY|en_NA|en_NF|en_NG|en_NL|en_NR|en_NU|en_NZ|en_PG|en_PH|en_PK|en_PN|en_PR|en_PW|en_RW|en_SB|en_SC|en_SD|en_SE|en_SG|en_SH|en_SI|en_SL|en_SS|en_SX|en_SZ|en_TC|en_TK|en_TO|en_TT|en_TV|en_TZ|en_UG|en_UM|en_US|en_VC|en_VG|en_VI|en_VU|en_WS|en_ZA|en_ZM|en_ZW|es_AR|es_BO|es_BR|es_BZ|es_CL|es_CO|es_CR|es_CU|es_DO|es_EC|es_ES|es_GQ|es_GT|es_HN|es_MX|es_NI|es_PA|es_PE|es_PH|es_PR|es_PY|es_SV|es_US|es_UY|es_VE|eu_ES|fa_AF|fa_IR|fi_FI|fo_DK|fo_FO|fr_BE|fr_BF|fr_BI|fr_BJ|fr_BL|fr_CA|fr_CD|fr_CF|fr_CG|fr_CH|fr_CI|fr_CM|fr_DJ|fr_DZ|fr_FR|fr_GA|fr_GF|fr_GN|fr_GP|fr_GQ|fr_HT|fr_KM|fr_LU|fr_MA|fr_MC|fr_MF|fr_MG|fr_ML|fr_MQ|fr_MR|fr_MU|fr_NC|fr_NE|fr_PF|fr_PM|fr_RE|fr_RW|fr_SC|fr_SN|fr_SY|fr_TD|fr_TG|fr_TN|fr_VU|fr_WF|fr_YT|he_IL|hr_BA|hr_HR|hu_HU|it_CH|it_IT|it_SM|it_VA|ja_JP|ko_KP|ko_KR|nl_AW|nl_BE|nl_BQ|nl_CW|nl_NL|nl_SR|nl_SX|pl_PL|pt_AO|pt_CH|pt_CV|pt_GQ|pt_GW|pt_LU|pt_MO|pt_MZ|pt_PT|pt_ST|pt_TL|ro_MD|ro_RO|ru_BY|ru_KG|ru_KZ|ru_MD|ru_RU|ru_UA|sk_SK|sv_AX|sv_FI|sv_SE|tr_CY|tr_TR|vi_VN
|
||||
# can be regenerated with: bin/console kimai:reset:locales
|
||||
kimai_locales: ['ar', 'ca', 'cs', 'da', 'de', 'de_CH', 'el', 'en', 'eo', 'es', 'eu', 'fa', 'fi', 'fo', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'nb_NO', 'nl', 'pa', 'pl', 'pt', 'pt_BR', 'ro', 'ru', 'sk', 'sl', 'sv', 'tr', 'uk', 'vi', 'zh_CN', 'zh_Hant', 'zh_Hant_TW', 'ca_AD', 'ca_ES', 'ca_FR', 'ca_IT', 'cs_CZ', 'da_DK', 'da_GL', 'de_AT', 'de_BE', 'de_CH', 'de_DE', 'de_IT', 'de_LI', 'de_LU', 'el_CY', 'el_GR', 'en_AE', 'en_AG', 'en_AI', 'en_AS', 'en_AT', 'en_AU', 'en_BB', 'en_BE', 'en_BI', 'en_BM', 'en_BS', 'en_BW', 'en_BZ', 'en_CA', 'en_CC', 'en_CH', 'en_CK', 'en_CM', 'en_CX', 'en_CY', 'en_DE', 'en_DG', 'en_DK', 'en_DM', 'en_ER', 'en_FI', 'en_FJ', 'en_FK', 'en_FM', 'en_GB', 'en_GD', 'en_GG', 'en_GH', 'en_GI', 'en_GM', 'en_GU', 'en_GY', 'en_HK', 'en_ID', 'en_IE', 'en_IL', 'en_IM', 'en_IN', 'en_IO', 'en_JE', 'en_JM', 'en_KE', 'en_KI', 'en_KN', 'en_KY', 'en_LC', 'en_LR', 'en_LS', 'en_MG', 'en_MH', 'en_MO', 'en_MP', 'en_MS', 'en_MT', 'en_MU', 'en_MV', 'en_MW', 'en_MY', 'en_NA', 'en_NF', 'en_NG', 'en_NH', 'en_NL', 'en_NR', 'en_NU', 'en_NZ', 'en_PG', 'en_PH', 'en_PK', 'en_PN', 'en_PR', 'en_PW', 'en_RH', 'en_RW', 'en_SB', 'en_SC', 'en_SD', 'en_SE', 'en_SG', 'en_SH', 'en_SI', 'en_SL', 'en_SS', 'en_SX', 'en_SZ', 'en_TC', 'en_TK', 'en_TO', 'en_TT', 'en_TV', 'en_TZ', 'en_UG', 'en_UM', 'en_US', 'en_VC', 'en_VG', 'en_VI', 'en_VU', 'en_WS', 'en_ZA', 'en_ZM', 'en_ZW', 'es_AR', 'es_BO', 'es_BR', 'es_BZ', 'es_CL', 'es_CO', 'es_CR', 'es_CU', 'es_DO', 'es_EA', 'es_EC', 'es_ES', 'es_GQ', 'es_GT', 'es_HN', 'es_IC', 'es_MX', 'es_NI', 'es_PA', 'es_PE', 'es_PH', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'eu_ES', 'fa_AF', 'fa_IR', 'fi_FI', 'fo_DK', 'fo_FO', 'fr_BE', 'fr_BF', 'fr_BI', 'fr_BJ', 'fr_BL', 'fr_CA', 'fr_CD', 'fr_CF', 'fr_CG', 'fr_CH', 'fr_CI', 'fr_CM', 'fr_DJ', 'fr_DZ', 'fr_FR', 'fr_GA', 'fr_GF', 'fr_GN', 'fr_GP', 'fr_GQ', 'fr_HT', 'fr_KM', 'fr_LU', 'fr_MA', 'fr_MC', 'fr_MF', 'fr_MG', 'fr_ML', 'fr_MQ', 'fr_MR', 'fr_MU', 'fr_NC', 'fr_NE', 'fr_PF', 'fr_PM', 'fr_RE', 'fr_RW', 'fr_SC', 'fr_SN', 'fr_SY', 'fr_TD', 'fr_TG', 'fr_TN', 'fr_VU', 'fr_WF', 'fr_YT', 'he_IL', 'hr_BA', 'hr_HR', 'hu_HU', 'it_CH', 'it_IT', 'it_SM', 'it_VA', 'ja_JP', 'ko_CN', 'ko_KP', 'ko_KR', 'nl_AW', 'nl_BE', 'nl_BQ', 'nl_CW', 'nl_NL', 'nl_SR', 'nl_SX', 'pl_PL', 'pt_AO', 'pt_BR', 'pt_CH', 'pt_CV', 'pt_GQ', 'pt_GW', 'pt_LU', 'pt_MO', 'pt_MZ', 'pt_PT', 'pt_ST', 'pt_TL', 'ro_MD', 'ro_RO', 'ru_BY', 'ru_KG', 'ru_KZ', 'ru_MD', 'ru_RU', 'ru_UA', 'sk_SK', 'sv_AX', 'sv_FI', 'sv_SE', 'tr_CY', 'tr_TR', 'uk_UA', 'vi_VN']
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
@@ -16,20 +15,23 @@ services:
|
||||
$projectDirectory: '%kernel.project_dir%'
|
||||
$kernelEnvironment: '%kernel.environment%'
|
||||
|
||||
# make classes in src/ available to be used as services
|
||||
# creates one service per class whose id is the fully-qualified class name
|
||||
# makes classes in src/ available to be used as services
|
||||
# this creates a service per class whose id is the fully-qualified class name
|
||||
App\:
|
||||
resource: '../src/*'
|
||||
exclude:
|
||||
- '../src/API/Model/'
|
||||
- '../src/DependencyInjection/'
|
||||
- '../src/Doctrine/Extensions/'
|
||||
- '../src/Entity/'
|
||||
- '../src/Event/'
|
||||
- '../src/Form/Model/'
|
||||
- '../src/Model/'
|
||||
- '../src/Repository/Loader/'
|
||||
- '../src/Repository/Paginator/'
|
||||
- '../src/Repository/Query/'
|
||||
- '../src/Repository/Result/'
|
||||
- '../src/Event/'
|
||||
- '../src/Model/'
|
||||
- '../src/WorkingTime/Calculator/'
|
||||
- '../src/Kernel.php'
|
||||
- '../src/Constants.php'
|
||||
|
||||
@@ -60,18 +62,6 @@ services:
|
||||
arguments:
|
||||
$cacheDirectory: '%kernel.cache_dir%'
|
||||
|
||||
App\Plugin\PluginManager:
|
||||
arguments: [!tagged kimai.plugin]
|
||||
|
||||
App\Validator\Constraints\TimesheetValidator:
|
||||
arguments: [!tagged timesheet.validator]
|
||||
|
||||
App\Validator\Constraints\ProjectValidator:
|
||||
arguments: [!tagged project.validator]
|
||||
|
||||
App\Validator\Constraints\QuickEntryTimesheetValidator:
|
||||
arguments: [!tagged timesheet.validator]
|
||||
|
||||
App\Utils\FileHelper:
|
||||
arguments:
|
||||
$dataDir: '%kimai.data_dir%'
|
||||
@@ -83,41 +73,19 @@ services:
|
||||
arguments:
|
||||
$mailer: '@App\Mail\KimaiMailer'
|
||||
|
||||
# ================================================================================
|
||||
# DATABASE
|
||||
# ================================================================================
|
||||
|
||||
# updates timesheet records and apply configured rate & rounding rules
|
||||
App\Doctrine\TimesheetSubscriber:
|
||||
class: App\Doctrine\TimesheetSubscriber
|
||||
arguments: [!tagged timesheet.calculator]
|
||||
tags:
|
||||
- { name: doctrine.event_subscriber, priority: 50 }
|
||||
|
||||
# updates timestampable columns (higher priority, so the TimesheetSubscriber will be executed later)
|
||||
Gedmo\Timestampable\TimestampableListener:
|
||||
class: Gedmo\Timestampable\TimestampableListener
|
||||
tags:
|
||||
- { name: doctrine.event_subscriber, priority: 60 }
|
||||
calls:
|
||||
- [ setAnnotationReader, [ "@annotation_reader" ] ]
|
||||
|
||||
# ================================================================================
|
||||
# TIMESHEET RECORD CALCULATOR
|
||||
# ================================================================================
|
||||
|
||||
App\Timesheet\RoundingService:
|
||||
arguments:
|
||||
$roundingModes: !tagged timesheet.rounding_mode
|
||||
# this is currently required, as local.yaml allows to configure several rules,
|
||||
# while the database system only allows one rounding rule
|
||||
$rules: '%kimai.timesheet.rounding%'
|
||||
|
||||
App\Timesheet\RateService:
|
||||
arguments: ['%kimai.timesheet.rates%']
|
||||
|
||||
App\Timesheet\TrackingModeService:
|
||||
arguments:
|
||||
$modes: !tagged timesheet.tracking_mode
|
||||
|
||||
# ================================================================================
|
||||
# SECURITY & VOTER
|
||||
# ================================================================================
|
||||
@@ -135,7 +103,9 @@ services:
|
||||
# SAML Services
|
||||
# ================================================================================
|
||||
App\Saml\SamlProvider:
|
||||
arguments: ['@App\Repository\UserRepository', '@security.user.provider.concrete.kimai_internal', '@App\Configuration\SamlConfigurationInterface']
|
||||
arguments:
|
||||
$userProvider: '@security.user.provider.concrete.kimai_internal'
|
||||
$configuration: '@App\Configuration\SamlConfigurationInterface'
|
||||
|
||||
# ================================================================================
|
||||
# REPOSITORIES
|
||||
@@ -224,3 +194,25 @@ services:
|
||||
class: App\Repository\BookmarkRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\Bookmark']
|
||||
|
||||
App\Repository\WorkingTimeRepository:
|
||||
class: App\Repository\WorkingTimeRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\WorkingTime']
|
||||
|
||||
App\Repository\AccessTokenRepository:
|
||||
class: App\Repository\AccessTokenRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\AccessToken']
|
||||
|
||||
monolog.formatter.kimai:
|
||||
class: Monolog\Formatter\LineFormatter
|
||||
arguments:
|
||||
- "[%%datetime%%] [%%extra.channel%%] %%level_name%%: %%message%% %%context%% %%extra%%\n"
|
||||
- "Y-m-d H:i:s"
|
||||
|
||||
monolog.formatter.deprecation:
|
||||
class: Monolog\Formatter\LineFormatter
|
||||
arguments:
|
||||
- "[%%datetime%%] %%message%% %%context%%\n"
|
||||
- "Y-m-d H:i:s"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
parameters:
|
||||
locale: en
|
||||
app_locales: ar|cs|de|de_CH|da|en|eo|es|eu|fr|he|hu|it|ja|ko|nl|pl|pt_BR|ro|ru|sk|sv|tr|vi|zh_CN
|
||||
kimai_locales: ['ar', 'cs', 'de', 'de_CH', 'da', 'en', 'eo', 'es', 'eu', 'fr', 'he', 'hu', 'it', 'ja', 'ko', 'nl', 'pl', 'pt_BR', 'ro', 'ru', 'sk', 'sv', 'tr', 'vi', 'zh_CN']
|
||||
|
||||
services:
|
||||
_defaults:
|
||||
@@ -23,7 +23,3 @@ services:
|
||||
App\User\UserService:
|
||||
public: true
|
||||
arguments: ['@App\Repository\UserRepository', '@event_dispatcher', '@validator', '@App\Configuration\SystemConfiguration', '@security.user_password_hasher']
|
||||
|
||||
App\Plugin\PluginManager:
|
||||
public: true
|
||||
arguments: [!tagged kimai.plugin]
|
||||
|
||||
38
migrations/MigrationTemplate.txt
Normal file
38
migrations/MigrationTemplate.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* @version 2.x
|
||||
*/
|
||||
final class <className> extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->abortIf(true, 'Migration was auto-generated, adapt to your needs before running it.');
|
||||
|
||||
<up>
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->abortIf(true, 'Migration was auto-generated, adapt to your needs before running it.');
|
||||
|
||||
<down>
|
||||
}<override>
|
||||
}
|
||||
@@ -41,6 +41,6 @@ final class Version20210316224358 extends AbstractMigration
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE kimai2_bookmarks');
|
||||
$schema->dropTable('kimai2_bookmarks');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ final class Version20230126002049 extends AbstractMigration
|
||||
|
||||
$users = $schema->getTable('kimai2_users');
|
||||
if (!$users->hasColumn('totp_secret')) {
|
||||
$users->addColumn('totp_secret', 'string', ['notnull' => false, 'default' => null]);
|
||||
$users->addColumn('totp_secret', 'string', ['length' => 255, 'notnull' => false, 'default' => null]);
|
||||
$users->addColumn('totp_enabled', 'boolean', ['notnull' => true, 'default' => false]);
|
||||
}
|
||||
if (!$users->hasColumn('system_account')) {
|
||||
@@ -85,7 +85,7 @@ final class Version20230126002049 extends AbstractMigration
|
||||
$this->addSql("UPDATE kimai2_configuration SET `value` = '15' WHERE `name` = 'timesheet.time_increment' and `value` = '0'");
|
||||
$this->addSql("UPDATE kimai2_configuration SET `value` = '5' WHERE `name` = 'timesheet.time_increment' and `value` IN ('1', '2', '3', '4')");
|
||||
$this->addSql("UPDATE kimai2_configuration SET `value` = '0' WHERE `name` = 'user.registration'");
|
||||
$this->addSql("UPDATE kimai2_roles SET `name` = UPPER(`name`)");
|
||||
$this->addSql('UPDATE kimai2_roles SET `name` = UPPER(`name`)');
|
||||
$this->addSql("UPDATE kimai2_invoice_templates SET `renderer` = 'service-date' WHERE `renderer` = 'freelancer'");
|
||||
$this->addSql("UPDATE kimai2_invoice_templates SET `renderer` = 'invoice' WHERE `renderer` = 'default'");
|
||||
$this->addSql("UPDATE kimai2_invoice_templates SET `renderer` = 'default' WHERE `renderer` = 'default-pdf'");
|
||||
|
||||
54
migrations/Version20230327143628.php
Normal file
54
migrations/Version20230327143628.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* @version 2.0.20
|
||||
*/
|
||||
final class Version20230327143628 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Creates the working times table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$workingTimes = $schema->createTable('kimai2_working_times');
|
||||
|
||||
$workingTimes->addColumn('id', 'integer', ['autoincrement' => true, 'notnull' => true]);
|
||||
$workingTimes->addColumn('user_id', 'integer', ['notnull' => true]);
|
||||
$workingTimes->addColumn('approved_by', 'integer', ['notnull' => false, 'default' => null]);
|
||||
$workingTimes->addColumn('date', 'date', ['notnull' => true]);
|
||||
$workingTimes->addColumn('expected', 'integer', ['notnull' => true]);
|
||||
$workingTimes->addColumn('actual', 'integer', ['notnull' => true]);
|
||||
$workingTimes->addColumn('approved_at', 'datetime', ['notnull' => false, 'default' => null]);
|
||||
$workingTimes->setPrimaryKey(['id']);
|
||||
$workingTimes->addIndex(['user_id'], 'IDX_F95E4933A76ED395');
|
||||
$workingTimes->addIndex(['approved_by'], 'IDX_F95E49334EA3CB3D');
|
||||
$workingTimes->addUniqueIndex(['user_id', 'date'], 'UNIQ_F95E4933A76ED395AA9E377A');
|
||||
$workingTimes->addForeignKeyConstraint('kimai2_users', ['user_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_F95E4933A76ED395');
|
||||
$workingTimes->addForeignKeyConstraint('kimai2_users', ['approved_by'], ['id'], ['onDelete' => 'SET NULL'], 'FK_F95E49334EA3CB3D');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$workingTimes = $schema->getTable('kimai2_working_times');
|
||||
|
||||
$workingTimes->removeForeignKey('FK_F95E4933A76ED395');
|
||||
$workingTimes->removeForeignKey('FK_F95E49334EA3CB3D');
|
||||
$schema->dropTable('kimai2_working_times');
|
||||
}
|
||||
}
|
||||
38
migrations/Version20230606125948.php
Normal file
38
migrations/Version20230606125948.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* @version 2.0.26
|
||||
*/
|
||||
final class Version20230606125948 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Adds the visible column for tags';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$tags = $schema->getTable('kimai2_tags');
|
||||
$tags->addColumn('visible', 'boolean', ['notnull' => false, 'default' => true]);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$tags = $schema->getTable('kimai2_tags');
|
||||
$tags->dropColumn('visible');
|
||||
}
|
||||
}
|
||||
43
migrations/Version20230819090536.php
Normal file
43
migrations/Version20230819090536.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* @version 2.0.31
|
||||
*/
|
||||
final class Version20230819090536 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Adds the supervisor columns to the user-table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$table = $schema->getTable('kimai2_users');
|
||||
$table->addColumn('supervisor_id', 'integer', ['length' => 11, 'notnull' => false, 'default' => null]);
|
||||
$table->addForeignKeyConstraint('kimai2_users', ['supervisor_id'], ['id'], ['onDelete' => 'SET NULL'], 'FK_B9AC5BCE19E9AC5F');
|
||||
$table->addIndex(['supervisor_id'], 'IDX_B9AC5BCE19E9AC5F');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$table = $schema->getTable('kimai2_users');
|
||||
|
||||
$table->removeForeignKey('FK_B9AC5BCE19E9AC5F');
|
||||
$table->dropIndex('IDX_B9AC5BCE19E9AC5F');
|
||||
$table->dropColumn('supervisor_id');
|
||||
}
|
||||
}
|
||||
39
migrations/Version20231130000719.php
Normal file
39
migrations/Version20231130000719.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* @version 2.5.0
|
||||
*/
|
||||
final class Version20231130000719 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Fix user preferences with dots in the name';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// select u1.* from kimai2_user_preferences u1 where u1.name like '%.%' and exists(select 1 from kimai2_user_preferences u2 where u2.name = replace(u1.name, '.', '_') and u1.user_id = u2.user_id);
|
||||
// select u1.* from kimai2_user_preferences u1 where u1.name like '%.%';
|
||||
$this->addSql("delete u1 from kimai2_user_preferences u1 where u1.name like '%.%' and exists(select 1 from (SELECT name, user_id FROM kimai2_user_preferences WHERE name LIKE '%_%') u2 where u2.name = replace(u1.name, '.', '_') and u1.user_id = u2.user_id)");
|
||||
$this->addSql("update kimai2_user_preferences set `name` = replace(`name`, '.', '_')");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->preventEmptyMigrationWarning();
|
||||
}
|
||||
}
|
||||
51
migrations/Version20240214061246.php
Normal file
51
migrations/Version20240214061246.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* @version 2.14
|
||||
*/
|
||||
final class Version20240214061246 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Adds the table for API access tokens';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$accessTokens = $schema->createTable('kimai2_access_token');
|
||||
|
||||
$accessTokens->addColumn('id', 'integer', ['autoincrement' => true, 'notnull' => true]);
|
||||
$accessTokens->addColumn('user_id', 'integer', ['notnull' => true]);
|
||||
$accessTokens->addColumn('token', 'string', ['notnull' => true, 'length' => 100]);
|
||||
$accessTokens->addColumn('name', 'string', ['notnull' => true, 'length' => 50]);
|
||||
$accessTokens->addColumn('last_usage', 'datetime_immutable', ['notnull' => false, 'default' => null]);
|
||||
$accessTokens->addColumn('expires_at', 'datetime_immutable', ['notnull' => false, 'default' => null]);
|
||||
|
||||
$accessTokens->setPrimaryKey(['id']);
|
||||
|
||||
$accessTokens->addIndex(['user_id'], 'IDX_6FB0DB1EA76ED395');
|
||||
$accessTokens->addUniqueIndex(['token'], 'UNIQ_6FB0DB1E5F37A13B');
|
||||
|
||||
$accessTokens->addForeignKeyConstraint('kimai2_users', ['user_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_6FB0DB1EA76ED395');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$table = $schema->getTable('kimai2_access_token');
|
||||
$table->removeForeignKey('FK_6FB0DB1EA76ED395');
|
||||
|
||||
$schema->dropTable('kimai2_access_token');
|
||||
}
|
||||
}
|
||||
36
migrations/Version20240326125247.php
Normal file
36
migrations/Version20240326125247.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* @version 2.14
|
||||
*/
|
||||
final class Version20240326125247 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Adds the number columns to activity and project';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE kimai2_activities ADD number VARCHAR(10) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE kimai2_projects ADD number VARCHAR(10) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE kimai2_projects DROP number');
|
||||
$this->addSql('ALTER TABLE kimai2_activities DROP number');
|
||||
}
|
||||
}
|
||||
61
migrations/Version20240920105524.php
Normal file
61
migrations/Version20240920105524.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* @version 2.22
|
||||
*/
|
||||
final class Version20240920105524 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Updates the user preferences for configurable work contract type';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$ids = $this->connection->fetchFirstColumn("
|
||||
SELECT DISTINCT user_id
|
||||
FROM kimai2_user_preferences AS kp
|
||||
WHERE kp.value > 0
|
||||
AND kp.name IN ('work_monday', 'work_tuesday', 'work_wednesday', 'work_thursday', 'work_friday', 'work_saturday', 'work_sunday')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM kimai2_user_preferences AS kp2
|
||||
WHERE kp2.user_id = kp.user_id
|
||||
AND kp2.name = 'work_contract_type'
|
||||
);");
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$this->addSql('INSERT INTO kimai2_user_preferences (`user_id`, `name`, `value`) VALUES (:id, :name, :value)', [
|
||||
'id' => $id,
|
||||
'name' => 'work_contract_type',
|
||||
'value' => 'day',
|
||||
]);
|
||||
}
|
||||
|
||||
if (\count($ids) === 0) {
|
||||
$this->preventEmptyMigrationWarning();
|
||||
}
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql("DELETE FROM kimai2_user_preferences WHERE `name` = 'work_contract_type'");
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
40
migrations/Version20240926111739.php
Normal file
40
migrations/Version20240926111739.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* @version 2.23
|
||||
*/
|
||||
final class Version20240926111739 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Adds created_at and break duration column';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE kimai2_activities ADD created_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('ALTER TABLE kimai2_customers ADD created_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('ALTER TABLE kimai2_projects ADD created_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('ALTER TABLE kimai2_timesheet ADD break INT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE kimai2_timesheet DROP break');
|
||||
$this->addSql('ALTER TABLE kimai2_projects DROP created_at');
|
||||
$this->addSql('ALTER TABLE kimai2_customers DROP created_at');
|
||||
$this->addSql('ALTER TABLE kimai2_activities DROP created_at');
|
||||
}
|
||||
}
|
||||
21
package.json
21
package.json
@@ -24,10 +24,10 @@
|
||||
],
|
||||
"packageManager": "yarn@3.2.1",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19.6",
|
||||
"@babel/eslint-parser": "^7.18.2",
|
||||
"@babel/preset-env": "^7.19.4",
|
||||
"@fortawesome/fontawesome-free": "^6.2.0",
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/eslint-parser": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"@fortawesome/fontawesome-free": "^6.5",
|
||||
"@fullcalendar/bootstrap5": "^5.11",
|
||||
"@fullcalendar/core": "^5.11",
|
||||
"@fullcalendar/daygrid": "^5.11",
|
||||
@@ -36,19 +36,18 @@
|
||||
"@fullcalendar/interaction": "^5.11",
|
||||
"@fullcalendar/timegrid": "^5.11",
|
||||
"@symfony/webpack-encore": "^4.0",
|
||||
"@tabler/core": "1.0.0-beta16",
|
||||
"chart.js": "^3.9",
|
||||
"@tabler/core": "1.0.0-beta21",
|
||||
"chart.js": "^4.0",
|
||||
"core-js": "^3.6",
|
||||
"eslint": "^8.0",
|
||||
"eslint-webpack-plugin": "^3.0",
|
||||
"gridstack": "^7.1",
|
||||
"litepicker": "^2.0",
|
||||
"luxon": "^3.3",
|
||||
"node-sass": "^8.0",
|
||||
"sass": "^1.62",
|
||||
"sass-loader": "^13.2",
|
||||
"tom-select": "^2.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-notifier": "^1.13"
|
||||
"tom-select": "^2.2",
|
||||
"webpack": "^5.91",
|
||||
"webpack-cli": "^4.10"
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user