Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e02313213 | ||
|
|
5bcf2a3546 | ||
|
|
1e10ed4de8 | ||
|
|
f10ff26b30 | ||
|
|
40a1bdd379 | ||
|
|
36c4ffcced | ||
|
|
99a5863f56 | ||
|
|
6cb5386205 |
+12
-15
@@ -1,25 +1,22 @@
|
|||||||
config/.env.db
|
config/.env.db
|
||||||
config/.env.db-tables
|
|
||||||
config/.env.db-guest
|
config/.env.db-guest
|
||||||
config/.env.redis
|
config/.env.redis
|
||||||
config/.env.pw-encryption-key
|
config/.env.pw-encryption-key
|
||||||
config/.env.wk-id
|
|
||||||
config/.env.mail
|
config/.env.mail
|
||||||
|
config/.env.db-wkvs-user
|
||||||
|
config/.env.docker.db
|
||||||
websocket/node_modules
|
websocket/node_modules
|
||||||
websocket/package-lock.json
|
composer/vendor
|
||||||
websocket/package.json
|
|
||||||
composer
|
|
||||||
private-files/rechnungen/*
|
private-files/rechnungen/*
|
||||||
private-files/config-uploads/*
|
private-files/config-uploads/*
|
||||||
.php-ini
|
.php-ini
|
||||||
.php-version
|
.php-version
|
||||||
www/externe-geraete/json/*
|
html/externe-geraete/json/*
|
||||||
www/files/ranglisten/*
|
html/files/ranglisten/*
|
||||||
www/sponsoren.php
|
html/sponsoren.php
|
||||||
www/css/sponsoren.css
|
html/css/sponsoren.css
|
||||||
www/scripts
|
html/scripts
|
||||||
www/files/music/*
|
html/files/music/*
|
||||||
!www/files/music/piep.mp3
|
!html/files/music/piep.mp3
|
||||||
www/.well-known
|
html/.well-known
|
||||||
www/intern/kampfrichter/ajax/ajax-neu_dynamic-rangliste.php
|
html/intern/kampfrichter/ajax/ajax-neu_dynamic-rangliste.php
|
||||||
setup/database/*
|
|
||||||
-310
@@ -1,310 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# ==========================================
|
|
||||||
# WKVS Full Stack Installation Script
|
|
||||||
# Autor: Euria
|
|
||||||
# Datum: 2026-04-20
|
|
||||||
# ==========================================
|
|
||||||
|
|
||||||
set -e # Stoppt das Skript bei jedem Fehler
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo ""
|
|
||||||
echo "=========================================="
|
|
||||||
echo " WKVS Full Stack Installation"
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
echo ""
|
|
||||||
read -p "Dieses Skript funktioniert nur auf einer leeren Linux Instalation. Fohrtfahren? [y/n/c]: " choiceInstall
|
|
||||||
|
|
||||||
case $choiceInstall in
|
|
||||||
[yY]*) ;;
|
|
||||||
[nN]*)
|
|
||||||
echo "Installation abgebrochen."
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
[cC]*)
|
|
||||||
echo "Installation abgebrochen."
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Ungültige Eingabe."
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# --- 1. System Update & Basisinstallation ---
|
|
||||||
echo "Schritt 1: System aktualisieren und Basis-Pakete installieren..."
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y mariadb-server apache2 nginx curl dig certbot python3-certbot-nginx
|
|
||||||
|
|
||||||
# --- 2. MariaDB Installation & Konfiguration ---
|
|
||||||
echo "Schritt 2: MariaDB Konfiguration..."
|
|
||||||
|
|
||||||
read -p "Willst du die Installation komplett automatisch abschließen? [y/n/c]: " choice
|
|
||||||
|
|
||||||
case $choice in
|
|
||||||
[yY]*)
|
|
||||||
PREFIX=$(openssl rand -hex 3)
|
|
||||||
DB_NAME="${PREFIX}_wkvs_db"
|
|
||||||
DB_USER_NORMAL="${PREFIX}_wkvs_user"
|
|
||||||
DB_USER_GUEST="${PREFIX}_wkvs_guest"
|
|
||||||
DB_PASS_NORMAL=$(openssl rand -base64 24)
|
|
||||||
DB_PASS_GUEST=$(openssl rand -base64 24)
|
|
||||||
DB_PORT="3306"
|
|
||||||
AUTO_INSTALL="true"
|
|
||||||
;;
|
|
||||||
[nN]*)
|
|
||||||
read -p "Name der Datenbank: " DB_NAME
|
|
||||||
read -sp "Passwort für $DB_USER_NORMAL: " DB_PASS_NORMAL
|
|
||||||
echo ""
|
|
||||||
read -p "Name des Gastbenutzers: " DB_USER_GUEST
|
|
||||||
read -sp "Passwort für $DB_USER_GUEST: " DB_PASS_GUEST
|
|
||||||
echo ""
|
|
||||||
read -p "Custom Port (Standard 3306): " DB_PORT
|
|
||||||
DB_PORT=${DB_PORT:-3306}
|
|
||||||
AUTO_INSTALL="false"
|
|
||||||
;;
|
|
||||||
[cC]*)
|
|
||||||
echo "Installation abgebrochen."
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Ungültige Eingabe."
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# MariaDB Port umstellen (falls Custom)
|
|
||||||
if [ "$DB_PORT" != "3306" ]; then
|
|
||||||
echo " Stelle MariaDB auf Port $DB_PORT um..."
|
|
||||||
if [ -f /etc/mysql/mariadb.conf.d/50-server.cnf ]; then
|
|
||||||
sudo sed -i "s/.*port\s*=\s*3306/port = $DB_PORT/" /etc/mysql/mariadb.conf.d/50-server.cnf
|
|
||||||
sudo systemctl restart mariadb
|
|
||||||
else
|
|
||||||
echo " Fehler: Konfigurationsdatei nicht gefunden."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
sudo systemctl restart mariadb
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- 3. Datenbank Setup ---
|
|
||||||
echo "Schritt 3: Datenbank und Benutzer erstellen..."
|
|
||||||
sudo mysql -P "$DB_PORT" <<EOF
|
|
||||||
CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\`;
|
|
||||||
|
|
||||||
-- Normaler User
|
|
||||||
CREATE USER IF NOT EXISTS \`$DB_USER_NORMAL\`@'127.0.0.1' IDENTIFIED BY '$DB_PASS_NORMAL';
|
|
||||||
GRANT SELECT, INSERT, UPDATE, DELETE ON \`${DB_NAME}\`.* TO \`${DB_USER_NORMAL}\`@'127.0.0.1';
|
|
||||||
|
|
||||||
-- Gast User (Nur SELECT)
|
|
||||||
CREATE USER IF NOT EXISTS \`$DB_USER_GUEST\`@'127.0.0.1' IDENTIFIED BY '$DB_PASS_GUEST';
|
|
||||||
GRANT SELECT ON \`${DB_NAME}\`.* TO \`${DB_USER_GUEST}\`@'127.0.0.1';
|
|
||||||
|
|
||||||
FLUSH PRIVILEGES;
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# --- 4. Environment Files & SQL Import ---
|
|
||||||
mkdir -p config
|
|
||||||
|
|
||||||
cat <<EOT > config/.env.db
|
|
||||||
DB_USER=$DB_USER_NORMAL
|
|
||||||
DB_PASSWORD=$DB_PASS_NORMAL
|
|
||||||
DB_NAME=$DB_NAME
|
|
||||||
DB_HOST=127.0.0.1
|
|
||||||
DB_PORT=$DB_PORT
|
|
||||||
EOT
|
|
||||||
|
|
||||||
cat <<EOT > config/.env.db-guest
|
|
||||||
DB_GUEST_USER=$DB_USER_GUEST
|
|
||||||
DB_GUEST_PASSWORD=$DB_PASS_GUEST
|
|
||||||
DB_NAME=$DB_NAME
|
|
||||||
DB_HOST=127.0.0.1
|
|
||||||
DB_PORT=$DB_PORT
|
|
||||||
EOT
|
|
||||||
|
|
||||||
# SQL Import (falls vorhanden)
|
|
||||||
if [ -f "setup/database/generate-sql.php" ]; then
|
|
||||||
echo " Generiere SQL-Struktur..."
|
|
||||||
php setup/database/generate-sql.php
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "setup/database/sql-database.sql" ]; then
|
|
||||||
echo " Importiere Datenbank-Struktur..."
|
|
||||||
sudo mysql -P "$DB_PORT" "$DB_NAME" < setup/database/sql-database.sql
|
|
||||||
echo " Datenbank-Schema erfolgreich importiert."
|
|
||||||
else
|
|
||||||
echo " Warnung: SQL-Datei nicht gefunden. Import übersprungen."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- 5. Verschlüsselungsschlüssel ---
|
|
||||||
if [ "$AUTO_INSTALL" = "true" ]; then
|
|
||||||
ENCRYPTION_KEY=$(openssl rand -base64 32)
|
|
||||||
echo " Automatischer Modus: Verschlüsselungsschlüssel generiert."
|
|
||||||
else
|
|
||||||
read -sp "Schlüssel zur symmetrischen Verschlüsselung (durch WK-Leitung): " ENCRYPTION_KEY
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat <<EOT > config/.env.pw-encryption-key
|
|
||||||
PW_ENCRYPTION_KEY=$ENCRYPTION_KEY
|
|
||||||
EOT
|
|
||||||
|
|
||||||
# --- 6. Composer & PHP Dependencies ---
|
|
||||||
if ! command -v composer &> /dev/null; then
|
|
||||||
echo " Composer wird installiert..."
|
|
||||||
sudo apt install -y composer
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo " Installiere Composer Pakete..."
|
|
||||||
composer require vlucas/phpdotenv
|
|
||||||
composer require phpmailer/phpmailer
|
|
||||||
composer require shuchkin/simplexlsx
|
|
||||||
composer require sprain/swiss-qr-bill
|
|
||||||
composer require james-heinrich/getid3
|
|
||||||
composer require chriskonnertz/string-calc
|
|
||||||
composer require tecnickcom/tcpdf
|
|
||||||
|
|
||||||
# --- 7. Node.js WebSocket ---
|
|
||||||
cd websocket || { echo "Fehler: websocket Ordner nicht gefunden!"; exit 1; }
|
|
||||||
|
|
||||||
npm init -y
|
|
||||||
npm install redis ws dotenv
|
|
||||||
npm install -g pm2
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
echo " WebSocket (Node.js) Installation abgeschlossen."
|
|
||||||
|
|
||||||
# --- 8. Apache & Nginx Konfiguration ---
|
|
||||||
echo "Schritt 8: Webserver konfigurieren..."
|
|
||||||
|
|
||||||
# Apache auf internen Port umstellen
|
|
||||||
APACHE_INTERNAL_PORT=8080
|
|
||||||
if grep -q "Listen 80" /etc/apache2/ports.conf 2>/dev/null; then
|
|
||||||
sudo sed -i "s/^Listen 80$/Listen $APACHE_INTERNAL_PORT/" /etc/apache2/ports.conf
|
|
||||||
# Falls Default VHost auch 80 hat
|
|
||||||
if [ -f /etc/apache2/sites-enabled/000-default.conf ]; then
|
|
||||||
sudo sed -i "s/Listen 80/Listen $APACHE_INTERNAL_PORT/" /etc/apache2/sites-enabled/000-default.conf 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
sudo systemctl restart apache2
|
|
||||||
echo " Apache auf internen Port $APACHE_INTERNAL_PORT umgestellt."
|
|
||||||
|
|
||||||
# --- 9. Nginx Reverse Proxy & SSL ---
|
|
||||||
echo "Schritt 9: Nginx Reverse Proxy & HTTPS einrichten..."
|
|
||||||
|
|
||||||
read -p "Domain der Webseite: (Beispiel: wk.wkvs.ch) " DOMAIN
|
|
||||||
|
|
||||||
if [ -z "$DOMAIN" ]; then
|
|
||||||
echo "❌ FEHLER: Die Domain darf nicht leer sein!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
DOMAIN=$(echo "$DOMAIN" | xargs)
|
|
||||||
|
|
||||||
# DNS Prüfung
|
|
||||||
echo " DNS-Prüfung für $DOMAIN..."
|
|
||||||
RESOLVED_IP=$(dig +short $DOMAIN 2>/dev/null | head -n1)
|
|
||||||
SERVER_IP=$(curl -s https://api.ipify.org 2>/dev/null)
|
|
||||||
|
|
||||||
if [ -z "$RESOLVED_IP" ]; then
|
|
||||||
echo "❌ FEHLER: Domain '$DOMAIN' kann nicht aufgelöst werden."
|
|
||||||
echo " Bitte warten Sie, bis die DNS-Einträge aktiv sind."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$SERVER_IP" ] && [ "$RESOLVED_IP" != "$SERVER_IP" ]; then
|
|
||||||
echo "⚠️ WARNUNG: Domain zeigt auf $RESOLVED_IP, Server ist $SERVER_IP."
|
|
||||||
echo " Bitte prüfen Sie Ihre DNS-Einträge."
|
|
||||||
read -p "Möchten Sie trotzdem fortfahren? [y/n]: " confirm
|
|
||||||
if [ "$confirm" != "y" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Nginx Konfiguration erstellen
|
|
||||||
NGINX_CONF="/etc/nginx/sites-available/$DOMAIN"
|
|
||||||
NGINX_LINK="/etc/nginx/sites-enabled/$DOMAIN"
|
|
||||||
|
|
||||||
rm -f /etc/nginx/sites-enabled/default 2>/dev/null || true
|
|
||||||
rm -f "$NGINX_CONF" 2>/dev/null || true
|
|
||||||
|
|
||||||
cat > "$NGINX_CONF" <<EOF
|
|
||||||
# HTTP -> HTTPS Weiterleitung
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name $DOMAIN;
|
|
||||||
return 301 https://\$host\$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
# HTTPS Server
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
server_name $DOMAIN;
|
|
||||||
|
|
||||||
ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;
|
|
||||||
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
||||||
ssl_prefer_server_ciphers on;
|
|
||||||
|
|
||||||
# WebSocket Route
|
|
||||||
location /ws/ {
|
|
||||||
proxy_pass http://127.0.0.1:8082;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade \$http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_set_header Host \$host;
|
|
||||||
proxy_set_header X-Real-IP \$remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
||||||
proxy_read_timeout 3600s;
|
|
||||||
proxy_send_timeout 3600s;
|
|
||||||
proxy_connect_timeout 75s;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Versteckte Dateien
|
|
||||||
location ~ /\. {
|
|
||||||
deny all;
|
|
||||||
access_log off;
|
|
||||||
log_not_found off;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Apache Proxy
|
|
||||||
location / {
|
|
||||||
proxy_pass http://127.0.0.1:$APACHE_INTERNAL_PORT;
|
|
||||||
proxy_set_header Host \$host;
|
|
||||||
proxy_set_header X-Real-IP \$remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
sudo ln -sf "$NGINX_CONF" "$NGINX_LINK"
|
|
||||||
|
|
||||||
# Let's Encrypt Zertifikat
|
|
||||||
echo " Starte Let's Encrypt Zertifikatsanfrage..."
|
|
||||||
read -p "E-Mail für Let's Encrypt: " EMAIL
|
|
||||||
|
|
||||||
sudo certbot --nginx -d $DOMAIN --agree-tos --no-eff-email --redirect --email "$EMAIL" --non-interactive
|
|
||||||
|
|
||||||
# Dienste neu starten
|
|
||||||
sudo systemctl reload nginx
|
|
||||||
sudo systemctl restart apache2
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo "✅ Installation erfolgreich abgeschlossen!"
|
|
||||||
echo "=========================================="
|
|
||||||
echo "Zusammenfassung:"
|
|
||||||
echo "- Datenbank: $DB_NAME (Port: $DB_PORT)"
|
|
||||||
echo "- Apache intern: Port $APACHE_INTERNAL_PORT"
|
|
||||||
echo "- Node.js WS: Port 8082 (unter /ws/)"
|
|
||||||
echo "- HTTPS: https://$DOMAIN/intern/wk-leitung/logindata"
|
|
||||||
echo ""
|
|
||||||
echo "Nächste Schritte:"
|
|
||||||
echo "1. Starten Sie den WebSocket Server: cd websocket && pm2 start app.js"
|
|
||||||
echo "2. Testen Sie die Seite: https://$DOMAIN/intern/wk-leitung/logindata"
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
services:
|
||||||
|
nginx:
|
||||||
|
image: nginx:latest
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8081:80"
|
||||||
|
depends_on:
|
||||||
|
- apache
|
||||||
|
- node
|
||||||
|
volumes:
|
||||||
|
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
apache:
|
||||||
|
build: ./docker/apache
|
||||||
|
volumes:
|
||||||
|
- .:/var/www/wkvs
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
mariadb:
|
||||||
|
image: mariadb:11
|
||||||
|
container_name: test-mariadb
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- ./config/.env.docker.db
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/mysql
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
node:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: docker/node/Dockerfile
|
||||||
|
expose:
|
||||||
|
- "8082"
|
||||||
|
volumes:
|
||||||
|
- .:/var/www/wkvs
|
||||||
|
- /var/www/wkvs/websocket/node_modules
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7
|
||||||
|
container_name: wkvs-redis
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app-network:
|
||||||
|
driver: bridge
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"vlucas/phpdotenv": "^5.6",
|
||||||
|
"phpmailer/phpmailer": "^7.0",
|
||||||
|
"shuchkin/simplexlsx": "^1.1",
|
||||||
|
"sprain/swiss-qr-bill": "^5.3",
|
||||||
|
"james-heinrich/getid3": "^1.9",
|
||||||
|
"chriskonnertz/string-calc": "^2.0",
|
||||||
|
"tecnickcom/tcpdf": "^6.11",
|
||||||
|
"predis/predis": "^3.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+1583
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
|||||||
# This Account must have have permissions SELECT, INSERT, UPDATE !
|
|
||||||
|
|
||||||
DB_USER=DB_GUESTUSER_USERNAME
|
|
||||||
DB_PASSWORD=DB_GUESTUSER_PASSWORD
|
|
||||||
DB_NAME=DATABASE_NAME # Same as .env.db
|
|
||||||
DB_HOST=DB_HOST # Same as .env.db, An IP without Port, often 127.0.0.1 or localhost
|
|
||||||
DB_PORT=3306 # default Port is 3306
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Tabellennamen
|
||||||
|
DB_TABLE_TEILNEHMENDE=teilnehmende
|
||||||
|
DB_TABLE_TEILNEHMENDE_GRUPPEN=teilnehmende_gruppen
|
||||||
|
DB_TABLE_TEILNEHMENDE_AUDIOFILES=teilnehmende_audiofiles
|
||||||
|
|
||||||
|
DB_TABLE_VEREINE=vereine
|
||||||
|
DB_TABLE_DISZIPLINEN=disziplinen
|
||||||
|
DB_TABLE_KATEGORIEN=kategorien
|
||||||
|
|
||||||
|
DB_TABLE_WERTUNGEN=wertungen
|
||||||
|
DB_TABLE_WERTUNGEN_LOG=wertungen_log
|
||||||
|
DB_TABLE_WERTUNGSTYPEN=wertungstypen
|
||||||
|
|
||||||
|
DB_TABLE_GRUPPEN=gruppen
|
||||||
|
DB_TABLE_GRUPPEN_ZEITEN=gruppen_zeiten
|
||||||
|
|
||||||
|
DB_TABLE_WARENKORB_STARTGEBUEREN=warenkorb_startgebueren
|
||||||
|
DB_TABLE_VERBUCHTE_STARTGEBUEREN=verbuchte_startgebueren
|
||||||
|
DB_TABLE_VERBUCHTE_STARTGEBUEREN_LOG=verbuchte_startgebueren_log
|
||||||
|
|
||||||
|
DB_TABLE_ZEITPLAN_TYPES=zeitplan_types
|
||||||
|
DB_TABLE_TABELLEN_KONFIGURATION=tabellen_konfiguration
|
||||||
|
|
||||||
|
DB_TABLE_VARIABLES=variables
|
||||||
|
DB_EINMAL_LINKS=einmal_links
|
||||||
|
DB_TABLE_INTERN_BENUTZENDE=intern_benutzende
|
||||||
|
DB_TABLE_AUDIOFILES=audiofiles
|
||||||
|
DB_TABLE_WETTKAEMPFE=wettkaempfe
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
DB_PREFIX=PREFIX
|
|
||||||
|
|
||||||
# Tabellennamen
|
|
||||||
DB_TABLE_TEILNEHMENDE=teilnehmende
|
|
||||||
DB_TABLE_ORDERS=orders
|
|
||||||
DB_TABLE_BASKET_ITEMS=basket_items
|
|
||||||
DB_TABLE_VARIABLES=variables
|
|
||||||
DB_TABLE_OTL=onetimeloginlinks
|
|
||||||
DB_TABLE_KR_PROTOKOLL=kampfricherinnen_protokoll
|
|
||||||
DB_TABLE_PROGRAMME=programme
|
|
||||||
DB_TABLE_INTERN_USERS=intern_users
|
|
||||||
DB_TABLE_VEREINE=vereine
|
|
||||||
DB_TABLE_ABTEILUNGEN=abteilungen
|
|
||||||
DB_TABLE_TEILNEHMENDE_ABTEILUNGEN=teilnehmende_abteilungen
|
|
||||||
DB_TABLE_GERAETE=geraete
|
|
||||||
DB_TABLE_AUDIOFILES=audiofiles
|
|
||||||
DB_TABLE_TEILNEHMENDE_AUDIOFILES=teilnehmende_audiofiles
|
|
||||||
DB_TABLE_NOTEN=noten
|
|
||||||
DB_TABLE_NOTEN_CHANGES=noten_changes
|
|
||||||
DB_TABLE_NOTEN_BEZEICHNUNGEN=noten_bezeichnungen
|
|
||||||
DB_TABLE_ORDERS_LOG=orders_log
|
|
||||||
DB_TABLE_ZEITPLAN_TYPES=zeitplan_types
|
|
||||||
DB_TABLE_ABTEILUNGEN_ZEITEN=abteilungen_zeiten
|
|
||||||
DB_TABLE_RANKLIVE_CONFIGS=rankLive_configs
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# This Account must have at least have permissions SELECT, INSERT, UPDATE, DELETE !
|
|
||||||
|
|
||||||
DB_USER=DB_USER_USERNAME
|
|
||||||
DB_PASSWORD=DB_USER_PASSWORD
|
|
||||||
DB_NAME=DATABASE_NAME
|
|
||||||
DB_HOST=DB_HOST # An IP with Port 127.0.0.1 or localhost
|
|
||||||
DB_PORT=3306 # default Port is 3306
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
PW_ENCRYPTION_KEY=YOUR_CUSTOM_PASSWORD_ENCRYPTION_KEY
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
REDDIS_PASSWORD=YOUR_REDDIS_PASSWORD
|
|
||||||
REDDIS_HOST=YOUR_REDDIS_IP # An IP, often 127.0.0.1 or localhost
|
|
||||||
REDDIS_PORT=YOUR_REDDIS_PORT # Default 6379
|
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 1. Reset containers and volumes
|
||||||
|
docker compose down -v
|
||||||
|
|
||||||
|
# Ensure the config directory exists
|
||||||
|
mkdir -p config
|
||||||
|
|
||||||
|
# 2. Generate Random Variables correctly using $(...)
|
||||||
|
MYSQL_USER=$(openssl rand -hex 16)
|
||||||
|
MYSQL_PASSWORD=$(openssl rand -base64 32)
|
||||||
|
MYSQL_DATABASE="wkvs_database"
|
||||||
|
MYSQL_ROOT_PASSWORD=$(openssl rand -base64 32)
|
||||||
|
|
||||||
|
cat <<EOF > config/.env.docker.db
|
||||||
|
MYSQL_ROOT_PASSWORD="$MYSQL_ROOT_PASSWORD"
|
||||||
|
MYSQL_DATABASE=$MYSQL_DATABASE
|
||||||
|
MYSQL_USER=$MYSQL_USER
|
||||||
|
MYSQL_PASSWORD="$MYSQL_PASSWORD"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 3. Handle Admin Hashing
|
||||||
|
KEYWORD=$(openssl rand -hex 16)
|
||||||
|
HASHED_KEYWORD=$(php -r "echo password_hash('$KEYWORD', PASSWORD_ARGON2ID);")
|
||||||
|
|
||||||
|
# Replace placeholder in SQL template
|
||||||
|
sed "s|__ARGON_HASH_PLACEHOLDER__|$HASHED_KEYWORD|g" docker/database/wkvs-database-template.sql > docker/database/db-sql.sql
|
||||||
|
|
||||||
|
# 1. Generate password
|
||||||
|
REDIS_PASSWORD=$(openssl rand -hex 32)
|
||||||
|
|
||||||
|
# 3. Save variables to your .env file
|
||||||
|
cat <<EOF > config/.env.redis
|
||||||
|
REDIS_PASSWORD="$REDIS_PASSWORD"
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_PORT=6379
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 4. Start Docker Containers
|
||||||
|
docker compose up -d --build
|
||||||
|
|
||||||
|
# 5. Wait until MariaDB is fully up and accepting connections
|
||||||
|
echo "Waiting for MariaDB to start..."
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
until docker compose exec -T mariadb mariadb-admin ping -h localhost -u root -p"$MYSQL_ROOT_PASSWORD" --silent >/dev/null 2>&1; do
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
# 6. Execute Primary Schema Import
|
||||||
|
docker compose exec -T mariadb mariadb -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < docker/database/db-sql.sql
|
||||||
|
rm docker/database/db-sql.sql
|
||||||
|
|
||||||
|
# 7. Generate Additional Application Users & Passwords
|
||||||
|
MYSQL_WKVS_USERNAME="wkvs_normal_user"
|
||||||
|
MYSQL_GUEST_USERNAME="wkvs_guest_user"
|
||||||
|
|
||||||
|
MYSQL_WKVS_PASSWORD=$(openssl rand -hex 32)
|
||||||
|
MYSQL_GUEST_PASSWORD=$(openssl rand -hex 16)
|
||||||
|
|
||||||
|
# Write app user configuration files
|
||||||
|
cat <<EOF > config/.env.db-wkvs-user
|
||||||
|
DB_USER=$MYSQL_WKVS_USERNAME
|
||||||
|
DB_PASSWORD="$MYSQL_WKVS_PASSWORD"
|
||||||
|
DB_NAME=$MYSQL_DATABASE
|
||||||
|
DB_HOST=mariadb
|
||||||
|
DB_PORT=3306
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cat <<EOF > config/.env.db-guest
|
||||||
|
DB_GUEST_USER=$MYSQL_GUEST_USERNAME
|
||||||
|
DB_GUEST_PASSWORD="$MYSQL_GUEST_PASSWORD"
|
||||||
|
DB_NAME=$MYSQL_DATABASE
|
||||||
|
DB_HOST=mariadb
|
||||||
|
DB_PORT=3306
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 8. Create Additional Users & Grant Permissions in MariaDB
|
||||||
|
echo "Creating application users and setting permissions..."
|
||||||
|
|
||||||
|
docker compose exec -T mariadb mariadb -u root -p"$MYSQL_ROOT_PASSWORD" <<EOF
|
||||||
|
-- Create Normal User
|
||||||
|
CREATE USER IF NOT EXISTS '$MYSQL_WKVS_USERNAME'@'%' IDENTIFIED BY '$MYSQL_WKVS_PASSWORD';
|
||||||
|
GRANT SELECT, INSERT, UPDATE, DELETE ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_WKVS_USERNAME'@'%';
|
||||||
|
|
||||||
|
-- Create Guest User
|
||||||
|
CREATE USER IF NOT EXISTS '$MYSQL_GUEST_USERNAME'@'%' IDENTIFIED BY '$MYSQL_GUEST_PASSWORD';
|
||||||
|
GRANT SELECT, INSERT, UPDATE ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_GUEST_USERNAME'@'%';
|
||||||
|
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
SERVICE_NAME="apache"
|
||||||
|
COMPOSER_DIR="/var/www/wkvs/composer"
|
||||||
|
|
||||||
|
echo "Running Composer setup via Docker Compose..."
|
||||||
|
|
||||||
|
# 1. Download Composer inside the service container if not installed globally
|
||||||
|
docker compose exec "$SERVICE_NAME" bash -c '
|
||||||
|
if ! command -v composer &> /dev/null && [ ! -f "composer.phar" ]; then
|
||||||
|
php -r "copy('\''https://getcomposer.org/installer'\'', '\''composer-setup.php'\'');"
|
||||||
|
php composer-setup.php --quiet
|
||||||
|
php -r "unlink('\''composer-setup.php'\'');"
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
|
||||||
|
# 2. Run composer install against the specified directory
|
||||||
|
docker compose exec "$SERVICE_NAME" bash -c "
|
||||||
|
COMPOSER_CMD=\$(command -v composer || echo \"php \$(pwd)/composer.phar\")
|
||||||
|
TARGET_DIR=\"$COMPOSER_DIR\"
|
||||||
|
|
||||||
|
if [ -f \"\$TARGET_DIR/composer.lock\" ] || [ -f \"\$TARGET_DIR/composer.json\" ]; then
|
||||||
|
\$COMPOSER_CMD install --working-dir=\"\$TARGET_DIR\" --no-interaction --prefer-dist --quiet
|
||||||
|
else
|
||||||
|
echo \"Error: Neither composer.json nor composer.lock found in '\$TARGET_DIR'.\" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "Konfiguriere Redis..."
|
||||||
|
|
||||||
|
# 2. Set password in the currently running Redis container
|
||||||
|
docker compose exec redis redis-cli CONFIG SET requirepass "$REDIS_PASSWORD"
|
||||||
|
|
||||||
|
PASSWORD_KEY=openssl rand -base64 32
|
||||||
|
|
||||||
|
cat <<EOF > config/.env.pw-encryption-key
|
||||||
|
PW_ENCRYPTION_KEY=$PASSWORD_KEY
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "------------------------------------------------------"
|
||||||
|
echo "Setup Complete!"
|
||||||
|
echo "Admin Plain Keyword: $KEYWORD"
|
||||||
|
echo "------------------------------------------------------"
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
FROM php:8.5-apache
|
||||||
|
|
||||||
|
# Enable Apache rewrite module
|
||||||
|
RUN a2enmod rewrite
|
||||||
|
|
||||||
|
# Install system dependencies, PHP extensions, and clean up apt cache
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
unzip \
|
||||||
|
libzip-dev \
|
||||||
|
libicu-dev \
|
||||||
|
git \
|
||||||
|
&& docker-php-ext-install \
|
||||||
|
bcmath \
|
||||||
|
mysqli \
|
||||||
|
pdo_mysql \
|
||||||
|
zip \
|
||||||
|
intl \
|
||||||
|
&& pecl install redis \
|
||||||
|
&& docker-php-ext-enable redis \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Update Apache DocumentRoot to /var/www/wkvs/html/ and enable .htaccess overrides
|
||||||
|
ENV APACHE_DOCUMENT_ROOT=/var/www/wkvs/html/
|
||||||
|
|
||||||
|
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
|
||||||
|
&& sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
|
||||||
|
&& sed -i 's/AllowOverride None/AllowOverride All/g' /etc/apache2/apache2.conf
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
include_path=".:/var/www/html:/usr/local/lib/php"
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||||
|
START TRANSACTION;
|
||||||
|
SET time_zone = "+00:00";
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `audiofiles`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `audiofiles` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`file_name` varchar(1024) NOT NULL,
|
||||||
|
`file_path` varchar(1024) NOT NULL,
|
||||||
|
`edited_by` int(11) DEFAULT NULL,
|
||||||
|
`timestamp` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `disziplinen`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `disziplinen` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(1024) DEFAULT NULL,
|
||||||
|
`start_index` int(11) DEFAULT NULL,
|
||||||
|
`color_kampfrichter` char(7) NOT NULL DEFAULT '#424242',
|
||||||
|
`audiofile` tinyint(1) DEFAULT NULL,
|
||||||
|
`audiofile_max_duration` int(11) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `einmal_links`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `einmal_links` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`url` varchar(1000) DEFAULT NULL,
|
||||||
|
`user_id` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`type` enum('login','pwreset','create_profile','') NOT NULL,
|
||||||
|
`timestamp` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`expires_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `url` (`url`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE TRIGGER `before_insert_login_tokens`
|
||||||
|
BEFORE INSERT ON `einmal_links`
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
IF NEW.url IS NULL THEN
|
||||||
|
SET NEW.url = HEX(RANDOM_BYTES(32));
|
||||||
|
END IF;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `gruppen`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `gruppen` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(100) NOT NULL DEFAULT 'KEIN NAME',
|
||||||
|
`order_index` int(11) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `gruppen_zeiten`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `gruppen_zeiten` (
|
||||||
|
`zeitplan_id` int(11) NOT NULL,
|
||||||
|
`start_time` time NOT NULL,
|
||||||
|
`end_time` time DEFAULT NULL,
|
||||||
|
`abt_id` int(11) NOT NULL,
|
||||||
|
UNIQUE KEY `unique_index` (`zeitplan_id`,`abt_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `intern_benutzende`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `intern_benutzende` (
|
||||||
|
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`username` varchar(191) DEFAULT NULL,
|
||||||
|
`name_person` varchar(255) DEFAULT NULL,
|
||||||
|
`password_hash` varchar(255) DEFAULT NULL,
|
||||||
|
`password_cipher` text DEFAULT NULL,
|
||||||
|
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
`edited_by` varchar(20) NOT NULL DEFAULT '0',
|
||||||
|
`freigabe` varchar(500) NOT NULL DEFAULT 'keine',
|
||||||
|
`login_active` tinyint(1) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `username_page_unique` (`username`,`password_hash`) USING BTREE,
|
||||||
|
KEY `indexFreigabe` (`freigabe`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Admin Seed Entry (Uses Argon2id placeholder for replacement via bash sed script)
|
||||||
|
INSERT INTO `intern_benutzende` (`username`, `password_hash`, `password_cipher`, `freigabe`, `login_active`)
|
||||||
|
VALUES ('admin', '__ARGON_HASH_PLACEHOLDER__', 'not_set', '{"types":["wk_leitung","trainer","kampfrichter"],"freigabenTrainer":["admin"],"freigabenKampfrichter":["A"]}', 1);
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `kategorien`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `kategorien` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`programm` text NOT NULL,
|
||||||
|
`order_index` int(11) DEFAULT NULL,
|
||||||
|
`preis` decimal(10,2) DEFAULT 0.00,
|
||||||
|
`aktiv` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `tabellen_konfiguration`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `tabellen_konfiguration` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`type_slug` varchar(128) NOT NULL,
|
||||||
|
`json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`json`)),
|
||||||
|
`all_noten_json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`all_noten_json`)),
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
`created_by` int(11) DEFAULT NULL,
|
||||||
|
`updated_by` int(11) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `unique_slug` (`type_slug`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `teilnehmende`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `teilnehmende` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`wk_id` int(11) DEFAULT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`vorname` text NOT NULL,
|
||||||
|
`geburtsdatum` date NOT NULL,
|
||||||
|
`programm` text NOT NULL,
|
||||||
|
`verein` text NOT NULL,
|
||||||
|
`bezahlt` int(11) NOT NULL DEFAULT 1,
|
||||||
|
`bezahltoverride` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`betrag_bezahlt` decimal(12,2) NOT NULL DEFAULT 0.00,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `wk_index` (`wk_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `teilnehmende_audiofiles`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `teilnehmende_audiofiles` (
|
||||||
|
`person_id` int(7) NOT NULL,
|
||||||
|
`geraet_id` int(7) NOT NULL,
|
||||||
|
`audiofile_id` int(7) NOT NULL,
|
||||||
|
UNIQUE KEY `unique_combo` (`person_id`,`geraet_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `teilnehmende_gruppen`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `teilnehmende_gruppen` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`turnerin_id` int(11) NOT NULL,
|
||||||
|
`abteilung_id` int(11) NOT NULL,
|
||||||
|
`geraet_id` int(11) NOT NULL,
|
||||||
|
`turnerin_index` int(11) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `unique_turnerin` (`turnerin_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `variables`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `variables` (
|
||||||
|
`name` varchar(255) NOT NULL DEFAULT 'NaN',
|
||||||
|
`value` varchar(255) DEFAULT NULL,
|
||||||
|
UNIQUE KEY `uniqueIndex` (`name`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `verbuchte_startgebueren`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `verbuchte_startgebueren` (
|
||||||
|
`order_id` int(11) NOT NULL,
|
||||||
|
`order_type` varchar(100) DEFAULT NULL,
|
||||||
|
`preis` float DEFAULT NULL,
|
||||||
|
`user_id` int(11) NOT NULL,
|
||||||
|
`item_ids` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`item_ids`)),
|
||||||
|
`used_verein_konten` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
|
||||||
|
`order_status` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`timestamp` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`edited_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
PRIMARY KEY (`order_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `verbuchte_startgebueren_log`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `verbuchte_startgebueren_log` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`order_id` int(11) NOT NULL,
|
||||||
|
`old_order_status` int(11) NOT NULL,
|
||||||
|
`new_order_status` int(11) NOT NULL,
|
||||||
|
`edited_by` int(11) NOT NULL,
|
||||||
|
`edited_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `vereine`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `vereine` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`verein` text NOT NULL,
|
||||||
|
`email` varchar(100) NOT NULL DEFAULT 'forms@testseite-fh.ch',
|
||||||
|
`konto` decimal(12,2) NOT NULL DEFAULT 0.00,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `indexturnerinnen` (`id`,`verein`(31))
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `warenkorb_startgebueren`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `warenkorb_startgebueren` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` bigint(20) UNSIGNED NOT NULL,
|
||||||
|
`item_id` int(11) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `basket_items_ibfk_1` (`user_id`),
|
||||||
|
KEY `basket_items_ibfk_2` (`item_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `wertungen`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `wertungen` (
|
||||||
|
`person_id` int(11) NOT NULL,
|
||||||
|
`note_bezeichnung_id` int(11) NOT NULL,
|
||||||
|
`geraet_id` int(11) NOT NULL,
|
||||||
|
`wk_id` int(11) NOT NULL,
|
||||||
|
`run_number` tinyint(3) NOT NULL DEFAULT 1,
|
||||||
|
`value` float DEFAULT NULL,
|
||||||
|
`is_public` tinyint(1) DEFAULT NULL,
|
||||||
|
`public_value` float DEFAULT NULL,
|
||||||
|
`timestamp` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
UNIQUE KEY `uniquePersonNoteJahrGereat` (`person_id`,`note_bezeichnung_id`,`wk_id`,`geraet_id`,`run_number`) USING BTREE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `wertungen_log`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `wertungen_log` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`person_id` int(11) NOT NULL,
|
||||||
|
`note_bezeichnung_id` int(11) NOT NULL,
|
||||||
|
`geraet_id` int(11) NOT NULL,
|
||||||
|
`wk_id` int(11) NOT NULL,
|
||||||
|
`run_number` tinyint(3) NOT NULL DEFAULT 1,
|
||||||
|
`old_value` float DEFAULT NULL,
|
||||||
|
`new_value` float DEFAULT NULL,
|
||||||
|
`edited_by` int(11) NOT NULL,
|
||||||
|
`timestamp` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `wertungstypen`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `wertungstypen` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(255) NOT NULL,
|
||||||
|
`default_value` float DEFAULT NULL,
|
||||||
|
`type` enum('input','berechnung') NOT NULL DEFAULT 'input',
|
||||||
|
`berechnung` varchar(255) DEFAULT NULL,
|
||||||
|
`berechnung_json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`berechnung_json`)),
|
||||||
|
`max_value` float DEFAULT NULL,
|
||||||
|
`min_value` float DEFAULT NULL,
|
||||||
|
`pro_geraet` tinyint(1) NOT NULL DEFAULT 1,
|
||||||
|
`anzahl_laeufe_json` text DEFAULT NULL,
|
||||||
|
`geraete_json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '[]',
|
||||||
|
`zeige_auf_rangliste` float DEFAULT NULL,
|
||||||
|
`groesse_auf_rangliste` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`nullstellen` int(11) NOT NULL DEFAULT 2,
|
||||||
|
`display_string` varchar(63) NOT NULL DEFAULT '${note}',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uniqueName` (`name`) USING BTREE,
|
||||||
|
KEY `indexMaxValue` (`max_value`),
|
||||||
|
KEY `indexMinValue` (`min_value`),
|
||||||
|
KEY `indexBerechnungJson` (`berechnung_json`(768)),
|
||||||
|
KEY `indexDefaultValue` (`default_value`),
|
||||||
|
KEY `indexType` (`type`),
|
||||||
|
KEY `indexNullstellen` (`nullstellen`) USING BTREE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `wettkaempfe`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `wettkaempfe` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(256) NOT NULL,
|
||||||
|
`slug` varchar(64) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `slug` (`slug`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Tabellenstruktur für Tabelle `zeitplan_types`
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE `zeitplan_types` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(2048) NOT NULL,
|
||||||
|
`has_endtime` tinyint(1) DEFAULT NULL,
|
||||||
|
`display_index` int(11) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
# Dynamically set Connection header (handles 'upgrade' and 'Upgrade')
|
||||||
|
map $http_upgrade $connection_upgrade {
|
||||||
|
default upgrade;
|
||||||
|
'' close;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
# Standard HTTP Proxy to Apache
|
||||||
|
location / {
|
||||||
|
proxy_pass http://apache:80;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# WebSocket Proxy to Node
|
||||||
|
location /ws/ {
|
||||||
|
# Note: Add trailing slash if you want Nginx to strip '/ws/' before reaching Node
|
||||||
|
proxy_pass http://node:8082/;
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade; # Dynamic connection header
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Keep-alive settings for long-lived WebSocket connections
|
||||||
|
proxy_read_timeout 36000s;
|
||||||
|
proxy_send_timeout 36000s;
|
||||||
|
proxy_connect_timeout 75s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Block access to hidden files (.htaccess, .git, .env, etc.)
|
||||||
|
location ~ /\. {
|
||||||
|
deny all;
|
||||||
|
access_log off;
|
||||||
|
log_not_found off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
WORKDIR /var/www/wkvs/websocket
|
||||||
|
|
||||||
|
# Install pm2 globally
|
||||||
|
RUN npm install -g pm2
|
||||||
|
|
||||||
|
# Copy package files from the websocket folder
|
||||||
|
COPY websocket/package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies strictly following lockfile
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy the rest of the application source code from websocket folder
|
||||||
|
COPY websocket/ ./
|
||||||
|
|
||||||
|
# Change ownership to the non-root node user for security
|
||||||
|
RUN chown -R node:node /var/www/wkvs/websocket
|
||||||
|
USER node
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["pm2-runtime", "start", "index.js"]
|
||||||
Vendored
BIN
Binary file not shown.
@@ -316,7 +316,6 @@ function initConditionalVisibility() {
|
|||||||
$rows.each((ind, el) => {
|
$rows.each((ind, el) => {
|
||||||
const $row = $(el);
|
const $row = $(el);
|
||||||
$row.find('.singleValueSpan[data-linked-el-uid]').each(function() {
|
$row.find('.singleValueSpan[data-linked-el-uid]').each(function() {
|
||||||
console.log('ja');
|
|
||||||
const linkedUid = $(this).data('linked-el-uid');
|
const linkedUid = $(this).data('linked-el-uid');
|
||||||
|
|
||||||
const $linkedEl = $row.find(`.singleValueSpan[data-uid="${linkedUid}"]`);
|
const $linkedEl = $row.find(`.singleValueSpan[data-uid="${linkedUid}"]`);
|
||||||
@@ -26,7 +26,7 @@ require $baseDir . '/../scripts/db/db-verbindung-script-guest.php';
|
|||||||
require $baseDir . "/../scripts/db/db-tables.php";
|
require $baseDir . "/../scripts/db/db-tables.php";
|
||||||
require $baseDir . "/../scripts/db/db-functions.php";
|
require $baseDir . "/../scripts/db/db-functions.php";
|
||||||
|
|
||||||
$rankLive_public = db_get_variable($guest, $tableVar, ['rankLivePublic']) ?? 0;
|
$rankLive_public = db_get_variable($guest, $db_tabelle_var, ['rankLivePublic']) ?? 0;
|
||||||
|
|
||||||
require $baseDir . '/../scripts/websocket/ws-create-token.php';
|
require $baseDir . '/../scripts/websocket/ws-create-token.php';
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ $is_live = $type === 'live';
|
|||||||
|
|
||||||
if ($slug !== null && !$is_live) {
|
if ($slug !== null && !$is_live) {
|
||||||
|
|
||||||
$alle_programme_db = db_select($guest, $tableProgramme, '`programm`', 'aktiv = ?', ['1']);
|
$alle_programme_db = db_select($guest, $db_tabelle_kategorien, '`programm`', 'aktiv = ?', ['1']);
|
||||||
$raw_programme = array_column($alle_programme_db, 'programm', 'programm');
|
$raw_programme = array_column($alle_programme_db, 'programm', 'programm');
|
||||||
|
|
||||||
$alle_programme = [];
|
$alle_programme = [];
|
||||||
@@ -69,20 +69,14 @@ $selectedFreigabeId = 'A';
|
|||||||
|
|
||||||
$json_type = $is_live ? 'rankLive-geraet' : 'rankLive-overview';
|
$json_type = $is_live ? 'rankLive-geraet' : 'rankLive-overview';
|
||||||
|
|
||||||
$jsonstr = db_select($guest, $tableRankLiveConfigs, 'json', 'type_slug = ?', [$json_type], '', 1);
|
$jsonstr = db_select($guest, $db_tabelle_tabellen_konfiguration, 'json', 'type_slug = ?', [$json_type], '', 1);
|
||||||
|
|
||||||
$data = json_decode($jsonstr[0]['json'], true);
|
$data = json_decode($jsonstr[0]['json'], true);
|
||||||
|
|
||||||
$headers = $data['header'] ?? [];
|
$headers = $data['header'] ?? [];
|
||||||
$bodyColumns = $data['body'] ?? [];
|
$bodyColumns = $data['body'] ?? [];
|
||||||
|
|
||||||
$current_year = date('Y');
|
$alle_programme_db = db_select($guest, $db_tabelle_kategorien, '`programm`, `id`', 'aktiv = ?', ['1']);
|
||||||
$monat = date('n');
|
|
||||||
if ($monat > 6) {
|
|
||||||
$current_year = $current_year + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$alle_programme_db = db_select($guest, $tableProgramme, '`programm`, `id`', 'aktiv = ?', ['1']);
|
|
||||||
$indexedProgrammes = array_column($alle_programme_db, 'programm', 'id');
|
$indexedProgrammes = array_column($alle_programme_db, 'programm', 'id');
|
||||||
|
|
||||||
$personData = [
|
$personData = [
|
||||||
@@ -93,7 +87,7 @@ $personData = [
|
|||||||
'programm' => 'Programm'
|
'programm' => 'Programm'
|
||||||
];
|
];
|
||||||
|
|
||||||
$disciplines = db_select($guest, $tableGeraete, '*', '', [], 'start_index ASC');
|
$disciplines = db_select($guest, $db_tabelle_disziplinen, '*', '', [], 'start_index ASC');
|
||||||
|
|
||||||
$indexedArrayGeraete = array_column($disciplines, 'name', 'id');
|
$indexedArrayGeraete = array_column($disciplines, 'name', 'id');
|
||||||
|
|
||||||
@@ -102,8 +96,8 @@ if ($is_live) {
|
|||||||
|
|
||||||
$indexed_array_geraete_index_id = array_flip($indexed_array_geraete_id_index);
|
$indexed_array_geraete_index_id = array_flip($indexed_array_geraete_id_index);
|
||||||
|
|
||||||
$abt = (int) (db_get_variable($guest, $tableVar, ['wk_panel_current_abt']) ?? 1);
|
$abt = (int) (db_get_variable($guest, $db_tabelle_var, ['wk_panel_current_abt']) ?? 1);
|
||||||
$akt_subabt = (int) (db_get_variable($guest, $tableVar, ['wk_panel_current_subabt_admin']) ?? 1);
|
$akt_subabt = (int) (db_get_variable($guest, $db_tabelle_var, ['wk_panel_current_subabt_admin']) ?? 1);
|
||||||
|
|
||||||
$max_subabt = count($disciplines);
|
$max_subabt = count($disciplines);
|
||||||
|
|
||||||
@@ -118,11 +112,11 @@ if ($is_live) {
|
|||||||
tabt.`turnerin_index` AS start_index,
|
tabt.`turnerin_index` AS start_index,
|
||||||
tabt.`geraet_id` AS startgeraet,
|
tabt.`geraet_id` AS startgeraet,
|
||||||
tabt.`abteilung_id` AS abt_id
|
tabt.`abteilung_id` AS abt_id
|
||||||
FROM $tableTeilnehmende t
|
FROM $db_tabelle_teilnehmende t
|
||||||
LEFT JOIN $tableProgramme p ON p.programm = t.programm
|
LEFT JOIN $db_tabelle_kategorien p ON p.programm = t.programm
|
||||||
INNER JOIN $tableTeilnehmendeAbt tabt ON tabt.`turnerin_id` = t.`id`
|
INNER JOIN $db_tabelle_teilnehmende_gruppen tabt ON tabt.`turnerin_id` = t.`id`
|
||||||
INNER JOIN $tableAbt abt ON abt.`id` = tabt.`abteilung_id`
|
INNER JOIN $db_tabelle_gruppen abt ON abt.`id` = tabt.`abteilung_id`
|
||||||
WHERE (t.bezahlt = 2 OR t.bezahltoverride = 5) AND abt.`order_index` = ?");
|
WHERE (t.bezahlt = 4 OR t.bezahltoverride = 4) AND abt.`order_index` = ?");
|
||||||
|
|
||||||
$stmt->bind_param("i", $abt);
|
$stmt->bind_param("i", $abt);
|
||||||
} elseif ($type === 'programm' && $slug !== null) {
|
} elseif ($type === 'programm' && $slug !== null) {
|
||||||
@@ -134,9 +128,9 @@ if ($is_live) {
|
|||||||
u.verein,
|
u.verein,
|
||||||
u.geburtsdatum,
|
u.geburtsdatum,
|
||||||
p.id AS programm_id
|
p.id AS programm_id
|
||||||
FROM $tableTeilnehmende u
|
FROM $db_tabelle_teilnehmende u
|
||||||
LEFT JOIN $tableProgramme p ON p.programm = u.programm
|
LEFT JOIN $db_tabelle_kategorien p ON p.programm = u.programm
|
||||||
WHERE (u.bezahlt = 2 OR u.bezahltoverride = 5) AND u.programm = ?");
|
WHERE (u.bezahlt = 4 OR u.bezahltoverride = 4) AND u.programm = ?");
|
||||||
|
|
||||||
$stmt->bind_param("s", $validated_programm_name);
|
$stmt->bind_param("s", $validated_programm_name);
|
||||||
} else {
|
} else {
|
||||||
@@ -148,9 +142,9 @@ if ($is_live) {
|
|||||||
u.verein,
|
u.verein,
|
||||||
u.geburtsdatum,
|
u.geburtsdatum,
|
||||||
p.id AS programm_id
|
p.id AS programm_id
|
||||||
FROM $tableTeilnehmende u
|
FROM $db_tabelle_teilnehmende u
|
||||||
LEFT JOIN $tableProgramme p ON p.programm = u.programm
|
LEFT JOIN $db_tabelle_kategorien p ON p.programm = u.programm
|
||||||
WHERE (u.bezahlt = 2 OR u.bezahltoverride = 5)");
|
WHERE (u.bezahlt = 4 OR u.bezahltoverride = 4)");
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
@@ -166,12 +160,14 @@ foreach ($tures as $s_tures) {
|
|||||||
$indexedTures[$s_tures['programm_id']][$s_tures['id']] = $s_tures;
|
$indexedTures[$s_tures['programm_id']][$s_tures['id']] = $s_tures;
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $guest->prepare("SELECT `note_bezeichnung_id`, `geraet_id`, `person_id`, `run_number`, `public_value`
|
$current_wk_id = (int) db_get_variable($guest, $db_tabelle_var, ['current_wk_id']) ?: 0;
|
||||||
FROM $tableNoten
|
|
||||||
WHERE `is_public` = 1
|
|
||||||
AND `jahr` = ?");
|
|
||||||
|
|
||||||
$stmt->bind_param("i", $current_year);
|
$stmt = $guest->prepare("SELECT `note_bezeichnung_id`, `geraet_id`, `person_id`, `run_number`, `public_value`
|
||||||
|
FROM $db_tabelle_wertungen
|
||||||
|
WHERE `is_public` = 1
|
||||||
|
AND `wk_id` = ?");
|
||||||
|
|
||||||
|
$stmt->bind_param("i", $current_wk_id);
|
||||||
|
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
@@ -182,7 +178,7 @@ $stmt->close();
|
|||||||
|
|
||||||
$indexedNoten = [];
|
$indexedNoten = [];
|
||||||
|
|
||||||
$stmt = $guest->prepare("SELECT `id`, `geraete_json`, `pro_geraet`, `anzahl_laeufe_json`, `name`, `default_value` FROM $tableNotenBezeichnungen");
|
$stmt = $guest->prepare("SELECT `id`, `geraete_json`, `pro_geraet`, `anzahl_laeufe_json`, `name`, `default_value` FROM $db_tabelle_wertungstypen");
|
||||||
|
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
@@ -191,13 +187,13 @@ $notenConfig = $result->fetch_all(MYSQLI_ASSOC);
|
|||||||
|
|
||||||
$stmt->close();
|
$stmt->close();
|
||||||
|
|
||||||
$disciplines = db_select($guest, $tableGeraete, '*', '', [], 'start_index ASC');
|
$disciplines = db_select($guest, $db_tabelle_disziplinen, '*', '', [], 'start_index ASC');
|
||||||
|
|
||||||
$indexedNotenConfig = array_column($notenConfig, null, 'id');
|
$indexedNotenConfig = array_column($notenConfig, null, 'id');
|
||||||
|
|
||||||
$rangNote = intval(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rangNote']));
|
$rangNote = intval(db_get_var($guest, "SELECT `value` FROM $db_tabelle_var WHERE `name` = ?", ['rangNote']));
|
||||||
|
|
||||||
$orderBestRang = db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['orderBestRang']);
|
$orderBestRang = db_get_var($guest, "SELECT `value` FROM $db_tabelle_var WHERE `name` = ?", ['orderBestRang']);
|
||||||
|
|
||||||
$okValuesOrderBestRang = ["ASC", "DESC"];
|
$okValuesOrderBestRang = ["ASC", "DESC"];
|
||||||
|
|
||||||
@@ -217,12 +213,13 @@ $disciplinesExtended = array_merge(
|
|||||||
$arrayIndexedNoten = [];
|
$arrayIndexedNoten = [];
|
||||||
|
|
||||||
|
|
||||||
$wkName = db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['wkName']);
|
$wkName = db_get_var($guest, "SELECT `value` FROM $db_tabelle_var WHERE `name` = ?", ['wkName']);
|
||||||
|
|
||||||
$grouped = [];
|
$grouped = [];
|
||||||
|
|
||||||
function groop_by_key($array, $array_key, bool $int = false): array
|
function groop_by_key($array, $array_key, bool $int = false): array
|
||||||
{
|
{
|
||||||
|
$grouped = [];
|
||||||
if ($int) {
|
if ($int) {
|
||||||
foreach ($array as $entry) {
|
foreach ($array as $entry) {
|
||||||
$key = (int) $entry[$array_key];
|
$key = (int) $entry[$array_key];
|
||||||
@@ -439,192 +436,207 @@ function constructSingleValueSpanPublic($token, $type, $row, $selected_geraet_id
|
|||||||
<link rel="stylesheet" href="/intern/css/custom-msg-display.css">
|
<link rel="stylesheet" href="/intern/css/custom-msg-display.css">
|
||||||
<link href="/files/fonts/fonts.css" rel="stylesheet">
|
<link href="/files/fonts/fonts.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="/css/custom-display-editor-required-css.css">
|
<link rel="stylesheet" href="/css/custom-display-editor-required-css.css">
|
||||||
|
<link rel="stylesheet" href="/css/rank-live.css">
|
||||||
|
<link rel="stylesheet" href="/css/header.css">
|
||||||
<script src="/intern/js/jquery/jquery-3.7.1.min.js"></script>
|
<script src="/intern/js/jquery/jquery-3.7.1.min.js"></script>
|
||||||
<script src="/intern/js/custom-msg-display.js"></script>
|
<script src="/intern/js/custom-msg-display.js"></script>
|
||||||
|
<script src="/js/header.js"></script>
|
||||||
<title>RankLive - <?= htmlspecialchars($wkName) ?></title>
|
<title>RankLive - <?= htmlspecialchars($wkName) ?></title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<?php if (count($indexedTures) < 1) : ?>
|
<section class="bgSection">
|
||||||
<h3>Noch keine Daten verfügbar</h3>
|
<?php include $baseDir . '/../scripts/public-elements/rankLive-header.php'; ?>
|
||||||
<?php endif; ?>
|
<?php if (count($indexedTures) < 1) : ?>
|
||||||
<div class="allAbtContainer">
|
<h3>Noch keine Daten verfügbar</h3>
|
||||||
<?php foreach ($grouped as $entry_key => $entries_group):
|
<?php exit; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="headerWrapper">
|
||||||
|
<h2 class="headerMain">RankLive</h2>
|
||||||
|
<?php if ($is_live) : ?>
|
||||||
|
<h4>Gruppe: <?= (int) $abt ?></h4>
|
||||||
|
<h4>Rotation: <?= (int) $akt_subabt ?></h4>
|
||||||
|
<?php elseif ($type === 'programm' && $slug !== null) : ?>
|
||||||
|
<h4><?= htmlspecialchars($validated_programm_name) ?></h4>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div class="allAbtContainer">
|
||||||
|
<?php foreach ($grouped as $entry_key => $entries_group):
|
||||||
|
|
||||||
$table_data_geraet_label = '';
|
$table_data_geraet_label = '';
|
||||||
$extra_table_classes = '';
|
$extra_table_classes = '';
|
||||||
|
|
||||||
if ($is_live) {
|
if ($is_live) {
|
||||||
$entries_with_calculated_index = [];
|
$entries_with_calculated_index = [];
|
||||||
|
|
||||||
// Optional: Vorab-Caching der MAX-Indices, um DB-Abfragen im Loop zu vermeiden
|
// Optional: Vorab-Caching der MAX-Indices, um DB-Abfragen im Loop zu vermeiden
|
||||||
// (Nur aktivieren, wenn $entries_group sehr groß ist und Performance-Probleme auftreten)
|
// (Nur aktivieren, wenn $entries_group sehr groß ist und Performance-Probleme auftreten)
|
||||||
$maxStartIndexCache = [];
|
$maxStartIndexCache = [];
|
||||||
|
|
||||||
foreach ($entries_group as $row) {
|
foreach ($entries_group as $row) {
|
||||||
$old_geraet_id = $row['startgeraet'];
|
$old_geraet_id = $row['startgeraet'];
|
||||||
|
|
||||||
$old_geraet_index = $indexed_array_geraete_id_index[$row['startgeraet']];
|
$old_geraet_index = $indexed_array_geraete_id_index[$row['startgeraet']];
|
||||||
$shifted_geraet_index = $old_geraet_index + $akt_subabt - 1;
|
$shifted_geraet_index = $old_geraet_index + $akt_subabt - 1;
|
||||||
|
if ($shifted_geraet_index > $max_subabt) {
|
||||||
|
$shifted_geraet_index -= $max_subabt;
|
||||||
|
}
|
||||||
|
$row['startgeraet'] = $indexed_array_geraete_index_id[$shifted_geraet_index];
|
||||||
|
|
||||||
|
$rohstartindex = intval($row['start_index']);
|
||||||
|
$abtId = intval($row['abt_id']);
|
||||||
|
|
||||||
|
// Hole maxStartIndex (mit Caching-Logik optional)
|
||||||
|
if (!isset($maxStartIndexCache["$abtId-$old_geraet_id "])) {
|
||||||
|
$maxStartIndexCache["$abtId-$old_geraet_id"] = db_get_var($guest, "SELECT COUNT(*) FROM `$db_tabelle_teilnehmende_gruppen` WHERE abteilung_id = ? AND geraet_id = ?", [$abtId, $old_geraet_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$maxstartindex = $maxStartIndexCache["$abtId-$old_geraet_id"];
|
||||||
|
|
||||||
|
// Sicherheit: Vermeiden Sie Division durch Null oder Modulo durch 0
|
||||||
|
if ($maxstartindex < 1) {
|
||||||
|
$maxstartindex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The order shift depends on the number of rotations the group has made
|
||||||
|
$rotation_shift = $akt_subabt - 1;
|
||||||
|
|
||||||
|
$calculedstartindex = $rohstartindex - $rotation_shift;
|
||||||
|
|
||||||
|
// Sicherstellen, dass das Ergebnis positiv ist (PHP Modulo kann negative Ergebnisse liefern)
|
||||||
|
// Wenn das Ergebnis negativ ist, addieren wir maxstartindex
|
||||||
|
if ($calculedstartindex < 1) {
|
||||||
|
$calculedstartindex += $maxstartindex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kopie der Zeile erstellen, um Originaldaten nicht zu verändern
|
||||||
|
$row['calculedstartindex'] = $calculedstartindex;
|
||||||
|
$entries_with_calculated_index[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sortieren nach dem berechneten Startindex
|
||||||
|
usort($entries_with_calculated_index, fn($a, $b) => $a['calculedstartindex'] <=> $b['calculedstartindex']);
|
||||||
|
|
||||||
|
$entries_to_display = $entries_with_calculated_index;
|
||||||
|
|
||||||
|
$shifted_geraet_index = $indexed_array_geraete_id_index[$entry_key] + $akt_subabt - 1;
|
||||||
if ($shifted_geraet_index > $max_subabt) {
|
if ($shifted_geraet_index > $max_subabt) {
|
||||||
$shifted_geraet_index -= $max_subabt;
|
$shifted_geraet_index -= $max_subabt;
|
||||||
}
|
}
|
||||||
$row['startgeraet'] = $indexed_array_geraete_index_id[$shifted_geraet_index];
|
$shifted_geraet_id = $indexed_array_geraete_index_id[$shifted_geraet_index];
|
||||||
|
|
||||||
$rohstartindex = intval($row['start_index']);
|
$table_data_geraet_label = ' data-geraet-index="' . $shifted_geraet_index . '"';
|
||||||
$abtId = intval($row['abt_id']);
|
$extra_table_classes = ' shiftedGeraetTable';
|
||||||
|
} else {
|
||||||
// Hole maxStartIndex (mit Caching-Logik optional)
|
$entries_to_display = $entries_group;
|
||||||
if (!isset($maxStartIndexCache["$abtId-$old_geraet_id "])) {
|
|
||||||
$maxStartIndexCache["$abtId-$old_geraet_id"] = db_get_var($guest, "SELECT COUNT(*) FROM `$tableTeilnehmendeAbt` WHERE abteilung_id = ? AND geraet_id = ?", [$abtId, $old_geraet_id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$maxstartindex = $maxStartIndexCache["$abtId-$old_geraet_id"];
|
|
||||||
|
|
||||||
// Sicherheit: Vermeiden Sie Division durch Null oder Modulo durch 0
|
|
||||||
if ($maxstartindex < 1) {
|
|
||||||
$maxstartindex = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The order shift depends on the number of rotations the group has made
|
|
||||||
$rotation_shift = $akt_subabt - 1;
|
|
||||||
|
|
||||||
$calculedstartindex = $rohstartindex - $rotation_shift;
|
|
||||||
|
|
||||||
// Sicherstellen, dass das Ergebnis positiv ist (PHP Modulo kann negative Ergebnisse liefern)
|
|
||||||
// Wenn das Ergebnis negativ ist, addieren wir maxstartindex
|
|
||||||
if ($calculedstartindex < 1) {
|
|
||||||
$calculedstartindex += $maxstartindex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kopie der Zeile erstellen, um Originaldaten nicht zu verändern
|
|
||||||
$row['calculedstartindex'] = $calculedstartindex;
|
|
||||||
$entries_with_calculated_index[] = $row;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sortieren nach dem berechneten Startindex
|
foreach ($entries_to_display as $row):
|
||||||
usort($entries_with_calculated_index, fn($a, $b) => $a['calculedstartindex'] <=> $b['calculedstartindex']);
|
$rangNotenArray[$entry_key][$row['id']] = isset($notenIndexed[$row['id']][0][$rangNote][1]) ? floatval($notenIndexed[$row['id']][0][$rangNote][1]) : null;
|
||||||
|
foreach ($disciplinesExtended as $discipline):
|
||||||
|
foreach ($notenConfig as $snC):
|
||||||
|
|
||||||
$entries_to_display = $entries_with_calculated_index;
|
if (intval($snC['pro_geraet']) === 1 && intval($discipline['id']) === 0) {
|
||||||
|
|
||||||
$shifted_geraet_index = $indexed_array_geraete_id_index[$entry_key] + $akt_subabt - 1;
|
|
||||||
if ($shifted_geraet_index > $max_subabt) {
|
|
||||||
$shifted_geraet_index -= $max_subabt;
|
|
||||||
}
|
|
||||||
$shifted_geraet_id = $indexed_array_geraete_index_id[$shifted_geraet_index];
|
|
||||||
|
|
||||||
$table_data_geraet_label = ' data-geraet-index="' . $shifted_geraet_index . '"';
|
|
||||||
$extra_table_classes = ' shiftedGeraetTable';
|
|
||||||
} else {
|
|
||||||
$entries_to_display = $entries_group;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($entries_to_display as $row):
|
|
||||||
$rangNotenArray[$entry_key][$row['id']] = isset($notenIndexed[$row['id']][0][$rangNote][1]) ? floatval($notenIndexed[$row['id']][0][$rangNote][1]) : null;
|
|
||||||
foreach ($disciplinesExtended as $discipline):
|
|
||||||
foreach ($notenConfig as $snC):
|
|
||||||
|
|
||||||
if (intval($snC['pro_geraet']) === 1 && intval($discipline['id']) === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intval($snC['pro_geraet']) !== 1) {
|
|
||||||
$allowedGeraete = !empty($snC['geraete_json']) ? json_decode($snC['geraete_json'], true) : [];
|
|
||||||
if (!in_array($discipline['id'], $allowedGeraete)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$runsJSON = !empty($snC['anzahl_laeufe_json']) ? json_decode($snC['anzahl_laeufe_json'], true) : [];
|
if (intval($snC['pro_geraet']) !== 1) {
|
||||||
|
$allowedGeraete = !empty($snC['geraete_json']) ? json_decode($snC['geraete_json'], true) : [];
|
||||||
|
if (!in_array($discipline['id'], $allowedGeraete)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$runs = $runsJSON[$discipline['id']][$entry_key] ?? $runsJSON[$discipline['id']]['all'] ?? $runsJSON["default"] ?? 1;
|
$runsJSON = !empty($snC['anzahl_laeufe_json']) ? json_decode($snC['anzahl_laeufe_json'], true) : [];
|
||||||
|
|
||||||
for ($r = 1; $r <= $runs; $r++):
|
$runs = $runsJSON[$discipline['id']][$entry_key] ?? $runsJSON[$discipline['id']]['all'] ?? $runsJSON["default"] ?? 1;
|
||||||
$note = $notenIndexed[$row['id']][$discipline['id']][$snC['id']][$r] ?? null;
|
|
||||||
$normalizedNote = ($note !== null) ? number_format($note, $snC['nullstellen'] ?? 2) : null;
|
|
||||||
|
|
||||||
$arrayIndexedNoten[intval($row['id'])][intval($discipline['id'])][intval($snC['id'])][intval($r)] = ["value" => $normalizedNote];
|
for ($r = 1; $r <= $runs; $r++):
|
||||||
endfor;
|
$note = $notenIndexed[$row['id']][$discipline['id']][$snC['id']][$r] ?? null;
|
||||||
|
$normalizedNote = ($note !== null) ? number_format($note, $snC['nullstellen'] ?? 2) : null;
|
||||||
|
|
||||||
|
$arrayIndexedNoten[intval($row['id'])][intval($discipline['id'])][intval($snC['id'])][intval($r)] = ["value" => $normalizedNote];
|
||||||
|
endfor;
|
||||||
|
endforeach;
|
||||||
endforeach;
|
endforeach;
|
||||||
endforeach;
|
endforeach; ?>
|
||||||
endforeach; ?>
|
<div class="singleAbtDiv<?= $extra_table_classes ?>" <?= $table_data_geraet_label ?>>
|
||||||
<div class="singleAbtDiv<?= $extra_table_classes ?>" <?= $table_data_geraet_label ?>>
|
<?php if (!($type === 'programm' && $slug !== null)) : ?>
|
||||||
<?php if ($is_live) : ?>
|
<h2 class="headerAbt"><?= ($is_live) ? $indexedArrayGeraete[$shifted_geraet_id] ?? '' : $entry_key ?? '' ?></h2>
|
||||||
<h3><?= $indexedArrayGeraete[$shifted_geraet_id] ?? '' ?></h3>
|
<?php endif; ?>
|
||||||
<?php else: ?>
|
<?php $geraet_id_for_live_view = $is_live ? $shifted_geraet_id : 0 ?>
|
||||||
<h3><?= $entry_key ?? '' ?></h3>
|
<div class="tableWraper">
|
||||||
<?php endif; ?>
|
<table class="customDisplayEditorTable">
|
||||||
<?php $geraet_id_for_live_view = $is_live ? $shifted_geraet_id : 0 ?>
|
<thead>
|
||||||
<div class="scoreboard-container">
|
<tr>
|
||||||
<table class="customDisplayEditorTable">
|
<?php foreach ($headers as $header_data): ?>
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<?php foreach ($headers as $header_data): ?>
|
|
||||||
<?php
|
|
||||||
$min_display_width = ($header_data['minDisplayWidth'] ?? 1) - 1;
|
|
||||||
|
|
||||||
$class = "";
|
|
||||||
|
|
||||||
if ($min_display_width > 0) {
|
|
||||||
$class = 'displayOver' . $min_display_width . 'px';
|
|
||||||
$display_over_array[] = $min_display_width;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
|
|
||||||
<th class="<?= $class ?>">
|
|
||||||
<?= htmlspecialchars($header_data['title']) ?>
|
|
||||||
</th>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody <?= !$is_live ? 'data-programm-id="'. $entry_key .'"' : ''?>>
|
|
||||||
<?php foreach ($entries_to_display as $row): ?>
|
|
||||||
<tr data-person-id="<?= $row['id'] ?>">
|
|
||||||
<?php foreach ($bodyColumns as $ind => $columnTokens): ?>
|
|
||||||
<?php
|
<?php
|
||||||
$class = '';
|
$min_display_width = ($header_data['minDisplayWidth'] ?? 1) - 1;
|
||||||
|
|
||||||
if (isset($headers[$ind]['minDisplayWidth']) && $headers[$ind]['minDisplayWidth'] > 1) {
|
$class = "";
|
||||||
$min_display_width = ($headers[$ind]['minDisplayWidth'] ?? 1) - 1;
|
|
||||||
|
|
||||||
|
if ($min_display_width > 0) {
|
||||||
$class = 'displayOver' . $min_display_width . 'px';
|
$class = 'displayOver' . $min_display_width . 'px';
|
||||||
$display_over_array[] = $min_display_width;
|
$display_over_array[] = $min_display_width;
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<td class=<?= $class ?>>
|
|
||||||
<span class="tdSpan">
|
|
||||||
|
|
||||||
<?php
|
<th class="<?= $class ?>">
|
||||||
foreach ($columnTokens as $token):
|
<?= htmlspecialchars($header_data['title']) ?>
|
||||||
$type = $token['type'] ?? '';
|
</th>
|
||||||
if ($type === 'layoutEls') :
|
|
||||||
$column = $token['column'];
|
|
||||||
$content = $token['content'] ?? []; ?>
|
|
||||||
<div class="<?= $column ?>LayoutEl">
|
|
||||||
<?php foreach ($content as $layout_row) : ?>
|
|
||||||
<span class="<?= $column ?>LayoutElRow">
|
|
||||||
<?php foreach ($layout_row as $el) :
|
|
||||||
constructSingleValueSpanPublic($el, $el['type'], $row, $geraet_id_for_live_view);
|
|
||||||
endforeach; ?>
|
|
||||||
</span>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
<?php else:
|
|
||||||
constructSingleValueSpanPublic($token, $type, $row, $geraet_id_for_live_view);
|
|
||||||
endif;
|
|
||||||
endforeach; ?>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
</thead>
|
||||||
</tbody>
|
<tbody <?= !$is_live ? 'data-programm-id="'. $entry_key .'"' : ''?>>
|
||||||
|
<?php foreach ($entries_to_display as $row): ?>
|
||||||
|
<tr data-person-id="<?= $row['id'] ?>">
|
||||||
|
<?php foreach ($bodyColumns as $ind => $columnTokens): ?>
|
||||||
|
<?php
|
||||||
|
$class = '';
|
||||||
|
|
||||||
</table>
|
if (isset($headers[$ind]['minDisplayWidth']) && $headers[$ind]['minDisplayWidth'] > 1) {
|
||||||
|
$min_display_width = ($headers[$ind]['minDisplayWidth'] ?? 1) - 1;
|
||||||
|
|
||||||
|
$class = 'displayOver' . $min_display_width . 'px';
|
||||||
|
$display_over_array[] = $min_display_width;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<td class=<?= $class ?>>
|
||||||
|
<span class="tdSpan">
|
||||||
|
|
||||||
|
<?php
|
||||||
|
foreach ($columnTokens as $token):
|
||||||
|
$type = $token['type'] ?? '';
|
||||||
|
if ($type === 'layoutEls') :
|
||||||
|
$column = $token['column'];
|
||||||
|
$content = $token['content'] ?? []; ?>
|
||||||
|
<div class="<?= $column ?>LayoutEl">
|
||||||
|
<?php foreach ($content as $layout_row) : ?>
|
||||||
|
<span class="<?= $column ?>LayoutElRow">
|
||||||
|
<?php foreach ($layout_row as $el) :
|
||||||
|
constructSingleValueSpanPublic($el, $el['type'], $row, $geraet_id_for_live_view);
|
||||||
|
endforeach; ?>
|
||||||
|
</span>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php else:
|
||||||
|
constructSingleValueSpanPublic($token, $type, $row, $geraet_id_for_live_view);
|
||||||
|
endif;
|
||||||
|
endforeach; ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<?php endforeach; ?>
|
||||||
<?php endforeach; ?>
|
</div>
|
||||||
</div>
|
<div class="tableCutter"></div>
|
||||||
|
</section>
|
||||||
<style>
|
<style>
|
||||||
<?php
|
<?php
|
||||||
$unique_display_over_array = array_unique($display_over_array);
|
$unique_display_over_array = array_unique($display_over_array);
|
||||||
@@ -643,7 +655,7 @@ function constructSingleValueSpanPublic($token, $type, $row, $selected_geraet_id
|
|||||||
window.FREIGABE = "<?= $selectedFreigabeId; ?>";
|
window.FREIGABE = "<?= $selectedFreigabeId; ?>";
|
||||||
window.CSDR_TOKEN = "<?= $csrf_token; ?>";
|
window.CSDR_TOKEN = "<?= $csrf_token; ?>";
|
||||||
window.WS_ACCESS_TOKEN = "<?= generateWSReadToken('rankLive', $selectedFreigabeId) ?>";
|
window.WS_ACCESS_TOKEN = "<?= generateWSReadToken('rankLive', $selectedFreigabeId) ?>";
|
||||||
window.AKTUELLES_JAHR = "<?= $current_year ?>";
|
window.AKTUELLES_JAHR = "<?= $current_wk_id ?>";
|
||||||
window.RANG_NOTEN_ARRAY = '<?= json_encode($rangNotenArray, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>';
|
window.RANG_NOTEN_ARRAY = '<?= json_encode($rangNotenArray, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>';
|
||||||
window.RANG_SORT = "<?= $orderBestRang ?>";
|
window.RANG_SORT = "<?= $orderBestRang ?>";
|
||||||
window.RANG_NOTE_ID = <?= $rangNote ?>;
|
window.RANG_NOTE_ID = <?= $rangNote ?>;
|
||||||
+39
-12
@@ -1,19 +1,48 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
--sb-bg: #f8fafc;
|
--sb-bg: #f8fafc;
|
||||||
--sb-card-bg: #ffffff;
|
--sb-card-bg: #ffffff;
|
||||||
--sb-text-main: #36454F;
|
--sb-text-main: #36454F;
|
||||||
--sb-text-muted: #64748b;
|
--sb-text-muted: #64748b;
|
||||||
--sb-primary: #1e40af; /* Deep athletic blue */
|
|
||||||
--sb-accent: #2563eb; /* Vivid score blue */
|
|
||||||
--sb-border: #e2e8f0;
|
--sb-border: #e2e8f0;
|
||||||
--sb-hover: #f3f3f3;
|
--sb-hover: #f3f3f3;
|
||||||
--sb-radius: 12px;
|
--sb-radius: 12px;
|
||||||
--font-numeric: 'Courier New', Courier, monospace; /* Clean alignment for scores */
|
|
||||||
|
/* Newly extracted color variables */
|
||||||
|
--sb-th-text: #1e293b;
|
||||||
|
--sb-th-border: #1e293b;
|
||||||
|
--sb-th-bg: #ffffff;
|
||||||
|
--sb-tr-even-bg: #f8f8f860;
|
||||||
|
--sb-changeable-bg: rgba(37, 99, 235, 0.02);
|
||||||
|
--sb-flash-highlight: #00ff99; /* Green flash for updates */
|
||||||
|
|
||||||
|
--font-numeric: 'Courier New', Courier, monospace;
|
||||||
--padding-td: clamp(3px, 2.5vh, 12px) clamp(4px, 2vw, 20px);
|
--padding-td: clamp(3px, 2.5vh, 12px) clamp(4px, 2vw, 20px);
|
||||||
--padding-th: clamp(2px, 1.5vh, 6px) clamp(4px, 2vw, 20px);
|
--padding-th: clamp(2px, 1.5vh, 6px) clamp(4px, 2vw, 20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark){
|
||||||
|
:root {
|
||||||
|
--sb-bg: #121212; /* Dark main background */
|
||||||
|
--sb-card-bg: #1a1a1a; /* Dark card surfaces */
|
||||||
|
--sb-text-main: #f5f5f5; /* Light grey/white text */
|
||||||
|
--sb-text-muted: #a0a0a0; /* Muted secondary text */
|
||||||
|
--sb-border: #2d2d2d; /* Soft dark border line */
|
||||||
|
--sb-hover: #252525; /* Subtle highlight on row hover */
|
||||||
|
--sb-radius: 12px;
|
||||||
|
|
||||||
|
/* Dark mode specific variables */
|
||||||
|
--sb-th-text: #ffffff; /* Crisp white headers */
|
||||||
|
--sb-th-border: #3d3d3d; /* Distinct header separation */
|
||||||
|
--sb-th-bg: #121212; /* Matches dark body background */
|
||||||
|
--sb-tr-even-bg: #1a1a1a40; /* Very soft zebra striping */
|
||||||
|
--sb-changeable-bg: rgba(240, 163, 236, 0.05); /* Soft background using the pink/purple accent */
|
||||||
|
--sb-flash-highlight: #39ff14; /* Vibrant neon green for live updates on dark backgrounds */
|
||||||
|
|
||||||
|
--font-numeric: 'Courier New', Courier, monospace;
|
||||||
|
--padding-td: clamp(3px, 2.5vh, 12px) clamp(4px, 2vw, 20px);
|
||||||
|
--padding-th: clamp(2px, 1.5vh, 6px) clamp(4px, 2vw, 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Base Table Styling */
|
/* Base Table Styling */
|
||||||
.customDisplayEditorTable {
|
.customDisplayEditorTable {
|
||||||
@@ -27,15 +56,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.customDisplayEditorTable th {
|
.customDisplayEditorTable th {
|
||||||
color: #1e293b;
|
color: var(--sb-th-text);
|
||||||
border-bottom: solid 1px #1e293b;
|
border-bottom: solid 1px var(--sb-th-border);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
padding: var(--padding-th);
|
padding: var(--padding-th);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
background-color: #fff;
|
background-color: var(--sb-th-bg);
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
@@ -54,25 +83,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.customDisplayEditorTable tbody > tr:nth-child(even) td {
|
.customDisplayEditorTable tbody > tr:nth-child(even) td {
|
||||||
background-color: #f8f8f8;
|
background-color: var(--sb-tr-even-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.customDisplayEditorTable tbody > tr:hover td {
|
.customDisplayEditorTable tbody > tr:hover td {
|
||||||
background-color: var(--sb-hover) !important;
|
background-color: var(--sb-hover) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.customDisplayEditorTable td {
|
.customDisplayEditorTable td {
|
||||||
color: var(--sb-text-main);
|
color: var(--sb-text-main);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.customDisplayEditorTable td.changebleValue {
|
.customDisplayEditorTable td.changebleValue {
|
||||||
font-family: var(--font-numeric);
|
font-family: var(--font-numeric);
|
||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
color: var(--sb-text-main);
|
color: var(--sb-text-main);
|
||||||
background-color: rgb(37 99 235 / 0.02);
|
background-color: var(--sb-changeable-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.customDisplayEditorTable tr:last-child td {
|
.customDisplayEditorTable tr:last-child td {
|
||||||
@@ -84,7 +111,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes flashHighlight {
|
@keyframes flashHighlight {
|
||||||
0% { color: #00ff99; }
|
0% { color: var(--sb-flash-highlight); }
|
||||||
100% { color: var(--sb-text-main); }
|
100% { color: var(--sb-text-main); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,315 @@
|
|||||||
|
:root {
|
||||||
|
--header-bg: #f8fcffcc;
|
||||||
|
--header-border: 1px solid #ffffff4d;
|
||||||
|
--header-shadow: 0 8px 32px rgba(0, 0, 0, 0.05);
|
||||||
|
--header-top: 40px;
|
||||||
|
--header-clamp: clamp(0.5px, 0.225vw, 1px);
|
||||||
|
--accent: #391c36;
|
||||||
|
--menu-box-shadows: rgba(0, 0, 0, 0.1);
|
||||||
|
--menu-dropdown-bg: #fff;
|
||||||
|
--dropdown-el-hover: #f0f0f0;
|
||||||
|
--sidebar-dropdown: rgba(255, 255, 255, 0.4);
|
||||||
|
--sidebar-dropdown-shadow: rgba(0, 0, 0, 0.02);
|
||||||
|
--sidebar-dropdown-el-hover: rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--header-bg: #000000cc; /* Dark, semi-transparent background with blur support */
|
||||||
|
--header-border: 1px solid #ffffff10; /* Subtle, soft border for dark interfaces */
|
||||||
|
--header-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); /* Stronger shadow to stand out against dark backgrounds */
|
||||||
|
--header-top: 40px;
|
||||||
|
--header-clamp: clamp(0.5px, 0.225vw, 1px);
|
||||||
|
--accent: #f9d8f7; /* Brightened, vibrant purple/pink accent to pop on dark backgrounds */
|
||||||
|
--menu-box-shadows: rgba(0, 0, 0, 0.5); /* Deeper shadow for menu items */
|
||||||
|
--menu-dropdown-bg: #1a1a1a; /* Dark solid background for dropdowns */
|
||||||
|
--dropdown-el-hover: #2a2a2a; /* Slightly lighter dark tone for hover states */
|
||||||
|
--sidebar-dropdown: rgba(30, 30, 30, 0.6); /* Semi-transparent dark background for sidebar dropdowns */
|
||||||
|
--sidebar-dropdown-shadow: rgba(0, 0, 0, 0.2);
|
||||||
|
--sidebar-dropdown-el-hover: rgba(255, 255, 255, 0.08); /* Soft white highlight on hover */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
position: fixed;
|
||||||
|
top: calc(var(--header-clamp) * 40);
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: calc(100% - 2 * var(--header-clamp) * 40);
|
||||||
|
max-width: var(--max-width-content);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: calc(var(--header-clamp) * 24);
|
||||||
|
padding: calc(var(--header-clamp) * 16) calc(var(--header-clamp) * 40);
|
||||||
|
background-color: var(--header-bg);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
border: var(--heder-border);
|
||||||
|
box-shadow: var(--header-shadow);
|
||||||
|
z-index: 100;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rankLiveHeaderTitle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 24px;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.menuLinksDiv {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLinksDiv>a,
|
||||||
|
.menuLinksDiv>div>span {
|
||||||
|
padding: 8px 16px;
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.25;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLinksDiv .menu-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLinksDiv>div {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLinksDiv>a::after,
|
||||||
|
.menuLinksDiv>div>span::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
right: 16px;
|
||||||
|
bottom: 2px;
|
||||||
|
height: 2px;
|
||||||
|
background-color: var(--accent);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLinksDiv>div>span {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLinksDiv>div>span>svg.menu-chevron {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLinksDiv>div>span:hover>svg.menu-chevron,
|
||||||
|
.menuLinksDiv>div.open>span>svg.menu-chevron {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLinksDiv>a:hover::after,
|
||||||
|
.menuLinksDiv>div>span:hover::after,
|
||||||
|
.menuLinksDiv>div.open>span::after {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLinksDiv .dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background-color: var(--menu-dropdown-bg);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px var(--menu-box-shadows);
|
||||||
|
padding: 8px 0;
|
||||||
|
display: none;
|
||||||
|
min-width: 160px;
|
||||||
|
z-index: 101;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLinksDiv .dropdown::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -15px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 15px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLinksDiv .dropdown a {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 16px;
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown a:hover, .dropdown form:hover {
|
||||||
|
background-color: var(--dropdown-el-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: var(--header-bg);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
-webkit-backdrop-filter: blur(16px);
|
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.06);
|
||||||
|
padding: calc(var(--header-clamp) * 60) 24px 32px 24px;
|
||||||
|
gap: 8px;
|
||||||
|
width: 288px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar>a,
|
||||||
|
.sidebar>div>.menu-item {
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar>a:hover,
|
||||||
|
.sidebar>a:active,
|
||||||
|
.sidebar>div>.menu-item:hover,
|
||||||
|
.sidebar>div.open>.menu-item {
|
||||||
|
background-color: rgba(0, 0, 0, 0.014);
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .menu-icon {
|
||||||
|
margin-right: 14px;
|
||||||
|
opacity: 0.85;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar>div>.menu-item>.menu-chevron {
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar>div.open>.menu-item>.menu-chevron {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .dropdown {
|
||||||
|
background-color: var(--sidebar-dropdown);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: inset 0 2px 8px var(--sidebar-dropdown-shadow);
|
||||||
|
padding: 8px;
|
||||||
|
margin: 4px 16px 8px 16px;
|
||||||
|
display: none;
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .dropdown a, .sidebar button {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 16px;
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .dropdown a:hover {
|
||||||
|
background-color: var(--sidebar-dropdown-el-hover);
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar a,
|
||||||
|
.sidebar>div {
|
||||||
|
-webkit-tap-highlight-color: transparent !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 920px) {
|
||||||
|
|
||||||
|
.burgerMenuDiv {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: calc(var(--header-clamp) * 30);
|
||||||
|
height: calc(var(--header-clamp) * 21);
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 99;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.burgerMenuLine {
|
||||||
|
height: calc(var(--header-clamp) * 3);
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--accent);
|
||||||
|
border-radius: calc(var(--header-clamp) * 3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.burgerMenuDiv.active .burgerMenuLine:nth-child(1) {
|
||||||
|
transform: translateY(calc(var(--header-clamp) * 9)) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.burgerMenuDiv.active .burgerMenuLine:nth-child(2) {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.burgerMenuDiv.active .burgerMenuLine:nth-child(3) {
|
||||||
|
transform: translateY(calc(var(--header-clamp) * -9)) rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
height: 100vh;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: all 0.5s cubic-bezier(0.25, 1, 0.5, 1);
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.active {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLinksDiv {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 921px) {
|
||||||
|
.burgerMenuDiv {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
:root {
|
||||||
|
--main: #2d2726;
|
||||||
|
--bg: #FDFDFD;
|
||||||
|
--border-subtle: #d4d7e1;
|
||||||
|
--text-main: #191919;
|
||||||
|
--text-muted: #5e5e5e;
|
||||||
|
--paddingSite: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--main: #f0f0f0; /* Flipped to a bright off-white for main accents/highlights */
|
||||||
|
--bg: #121212; /* Dark background (replacing the near-white #FDFDFD) */
|
||||||
|
--border-subtle: #2d2d2d; /* Soft, dark border that won't clip harshly against the background */
|
||||||
|
--text-main: #f5f5f5; /* Light grey/white for high readability */
|
||||||
|
--text-muted: #a0a0a0; /* Mid-tone grey for secondary text */
|
||||||
|
--paddingSite: 20px; /* Kept identical for layout consistency */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- BASE & TYPOGRAPHY --- */
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
width: 100vw;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text-main);
|
||||||
|
overflow-x: hidden;
|
||||||
|
overscroll-behavior: none;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgSection {
|
||||||
|
position: relative;
|
||||||
|
width: 100vw;
|
||||||
|
margin: 100px auto 80px auto;
|
||||||
|
padding: var(--paddingSite);
|
||||||
|
max-width: 1800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.bgSection {
|
||||||
|
margin-top: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerWrapper {
|
||||||
|
margin: 40px 0;
|
||||||
|
color: var(--main);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerMain {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
font-weight: 200;
|
||||||
|
font-size: clamp(34px, 6vw, 3.5rem);
|
||||||
|
letter-spacing: 12px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerWrapper > h4 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerAbt {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--main);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableCutter {
|
||||||
|
height: 120px;
|
||||||
|
z-index: 3;
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableWraper {
|
||||||
|
margin: 20px 0 80px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customDisplayEditorTable th {
|
||||||
|
top: 120px !important;
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ require_once $baseDir . '/../scripts/db/db-verbindung-script-guest.php';
|
|||||||
require_once $baseDir . '/../scripts/db/db-functions.php';
|
require_once $baseDir . '/../scripts/db/db-functions.php';
|
||||||
require_once $baseDir . '/../scripts/db/db-tables.php';
|
require_once $baseDir . '/../scripts/db/db-tables.php';
|
||||||
|
|
||||||
$stmt = $guest->prepare("SELECT `name`, `id` FROM $tableGeraete WHERE `audiofile` = '1' ORDER BY start_index ASC");
|
$stmt = $guest->prepare("SELECT `name`, `id` FROM $db_tabelle_disziplinen WHERE `audiofile` = '1' ORDER BY start_index ASC");
|
||||||
|
|
||||||
if (!$stmt->execute()) {
|
if (!$stmt->execute()) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
@@ -34,7 +34,7 @@ require_once $baseDir . '/../scripts/db/db-verbindung-script-guest.php';
|
|||||||
require_once $baseDir . '/../scripts/db/db-functions.php';
|
require_once $baseDir . '/../scripts/db/db-functions.php';
|
||||||
require_once $baseDir . '/../scripts/db/db-tables.php';
|
require_once $baseDir . '/../scripts/db/db-tables.php';
|
||||||
|
|
||||||
$stmt = $guest->prepare("SELECT `name`, `id` FROM $tableGeraete ORDER BY start_index ASC");
|
$stmt = $guest->prepare("SELECT `name`, `id` FROM $db_tabelle_disziplinen ORDER BY start_index ASC");
|
||||||
|
|
||||||
if (!$stmt->execute()) {
|
if (!$stmt->execute()) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
@@ -84,7 +84,7 @@ $keysNeeded = [
|
|||||||
|
|
||||||
$placeholders = implode(',', array_fill(0, count($keysNeeded), '?'));
|
$placeholders = implode(',', array_fill(0, count($keysNeeded), '?'));
|
||||||
|
|
||||||
$query = "SELECT `name`, `value` FROM $tableVar WHERE `name` IN ($placeholders)";
|
$query = "SELECT `name`, `value` FROM $db_tabelle_var WHERE `name` IN ($placeholders)";
|
||||||
|
|
||||||
$stmt = $guest->prepare($query);
|
$stmt = $guest->prepare($query);
|
||||||
$params = str_repeat("s", count($keysNeeded));
|
$params = str_repeat("s", count($keysNeeded));
|
||||||
Vendored
BIN
Binary file not shown.
@@ -153,11 +153,11 @@
|
|||||||
background: #9e253d;
|
background: #9e253d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blackBtn:hover {
|
.confirmImportantInputDiv button:hover {
|
||||||
transform: scale(1.02);
|
transform: scale(1.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
.blackBtn:active {
|
.confirmImportantInputDiv button:active {
|
||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ body.einstellungen strong {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.twoColumDiv > div {
|
.twoColumDiv>div {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
@@ -263,12 +263,12 @@ body.einstellungen strong {
|
|||||||
backdrop-filter: blur(16px);
|
backdrop-filter: blur(16px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.containerDiv + .containerDiv,
|
.containerDiv+.containerDiv,
|
||||||
.firstDiv + .secondDiv {
|
.firstDiv+.secondDiv {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.twoColumDiv .containerDiv + .containerDiv {
|
.twoColumDiv .containerDiv+.containerDiv {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +323,8 @@ body.einstellungen strong {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsRow > span:not(.light), .settingsRow > div > span:not(.light) {
|
.settingsRow>span:not(.light),
|
||||||
|
.settingsRow>div>span:not(.light) {
|
||||||
color: var(--ui-text-soft);
|
color: var(--ui-text-soft);
|
||||||
font-size: 0.92rem;
|
font-size: 0.92rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -415,7 +416,7 @@ input[type="file"]::file-selector-button:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input[data-coloris] {
|
input[data-coloris] {
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Checkbox switch ---------- */
|
/* ---------- Checkbox switch ---------- */
|
||||||
@@ -466,16 +467,16 @@ input[data-coloris] {
|
|||||||
transition: transform 180ms ease;
|
transition: transform 180ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox input:checked + .checkbox-ui {
|
.checkbox input:checked+.checkbox-ui {
|
||||||
background: var(--ui-primary);
|
background: var(--ui-primary);
|
||||||
border-color: var(--ui-primary);
|
border-color: var(--ui-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox input:checked + .checkbox-ui::after {
|
.checkbox input:checked+.checkbox-ui::after {
|
||||||
transform: translate(18px, -50%);
|
transform: translate(18px, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox input:focus-visible + .checkbox-ui {
|
.checkbox input:focus-visible+.checkbox-ui {
|
||||||
box-shadow: 0 0 0 4px var(--ui-focus);
|
box-shadow: 0 0 0 4px var(--ui-focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,10 +545,7 @@ button:not(.blackBtn),
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Destructive icon buttons */
|
/* Destructive icon buttons */
|
||||||
.deleteGeraete,
|
.deleteBtn {
|
||||||
.deleteZeitplan,
|
|
||||||
.deleteZeitplanType,
|
|
||||||
.deleteNoten {
|
|
||||||
width: 38px;
|
width: 38px;
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
@@ -556,21 +554,16 @@ button:not(.blackBtn),
|
|||||||
border: 1px solid transparent !important;
|
border: 1px solid transparent !important;
|
||||||
border-radius: 999px !important;
|
border-radius: 999px !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
|
transition: background 0.3s ease, border-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deleteGeraete:hover,
|
.deleteBtn:hover {
|
||||||
.deleteZeitplan:hover,
|
|
||||||
.deleteZeitplanType:hover,
|
|
||||||
.deleteNoten:hover {
|
|
||||||
background: var(--ui-danger-soft) !important;
|
background: var(--ui-danger-soft) !important;
|
||||||
border-color: color-mix(in srgb, var(--ui-danger) 25%, transparent) !important;
|
border-color: color-mix(in srgb, var(--ui-danger) 25%, transparent) !important;
|
||||||
transform: none !important;
|
transform: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deleteGeraete svg,
|
.deleteBtn svg {
|
||||||
.deleteZeitplan svg,
|
|
||||||
.deleteZeitplanType svg,
|
|
||||||
.deleteNoten svg {
|
|
||||||
stroke: currentColor;
|
stroke: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,7 +583,7 @@ button:not(.blackBtn),
|
|||||||
margin-top: 1.1rem;
|
margin-top: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gridWKVSConfig > div {
|
.gridWKVSConfig>div {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: color-mix(in srgb, var(--ui-bg-soft) 72%, transparent);
|
background: color-mix(in srgb, var(--ui-bg-soft) 72%, transparent);
|
||||||
@@ -703,8 +696,9 @@ button:not(.blackBtn),
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wkvsTabelle .checkbox {
|
.wkvsTabelle .checkbox {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Custom select compatibility ---------- */
|
/* ---------- Custom select compatibility ---------- */
|
||||||
|
|
||||||
.customSelect {
|
.customSelect {
|
||||||
@@ -712,8 +706,10 @@ button:not(.blackBtn),
|
|||||||
min-width: 220px;
|
min-width: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wkvsTabelle .selectTrigger, .wkvsTabelle .selectTriggerBulk, .paddingNormal.selectTrigger {
|
.wkvsTabelle .selectTrigger,
|
||||||
padding: 0.5rem 0.65rem !important;
|
.wkvsTabelle .selectTriggerBulk,
|
||||||
|
.paddingNormal.selectTrigger {
|
||||||
|
padding: 0.5rem 0.65rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectTriggerBulk {
|
.selectTriggerBulk {
|
||||||
@@ -768,7 +764,7 @@ button:not(.blackBtn),
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.containerImages > div {
|
.containerImages>div {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
transition: opacity 160ms ease, transform 160ms ease;
|
transition: opacity 160ms ease, transform 160ms ease;
|
||||||
}
|
}
|
||||||
@@ -797,7 +793,7 @@ button:not(.blackBtn),
|
|||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.containerImages label > span {
|
.containerImages label>span {
|
||||||
color: var(--ui-text);
|
color: var(--ui-text);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
@@ -816,8 +812,8 @@ button:not(.blackBtn),
|
|||||||
border: 1px solid var(--ui-border-soft);
|
border: 1px solid var(--ui-border-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
#imgIcon + img,
|
#imgIcon+img,
|
||||||
#imgLogo_normal + img {
|
#imgLogo_normal+img {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
}
|
}
|
||||||
@@ -869,9 +865,9 @@ button:not(.blackBtn),
|
|||||||
}
|
}
|
||||||
|
|
||||||
.displayDiv.displayDivSettings {
|
.displayDiv.displayDivSettings {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 5/3;
|
aspect-ratio: 5/3;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.displayDiv .scaled {
|
.displayDiv .scaled {
|
||||||
@@ -941,14 +937,15 @@ button:not(.blackBtn),
|
|||||||
/* ---------- Responsive ---------- */
|
/* ---------- Responsive ---------- */
|
||||||
|
|
||||||
@media (max-width: 1180px) {
|
@media (max-width: 1180px) {
|
||||||
.twoColumDiv > div,
|
|
||||||
|
.twoColumDiv>div,
|
||||||
.settingsGrid,
|
.settingsGrid,
|
||||||
.gridWKVSConfig,
|
.gridWKVSConfig,
|
||||||
.oneOneGridDisplay {
|
.oneOneGridDisplay {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.twoColumDiv .containerDiv + .containerDiv {
|
.twoColumDiv .containerDiv+.containerDiv {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -983,7 +980,8 @@ button:not(.blackBtn),
|
|||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsRow > span, .settingsRow > div > span {
|
.settingsRow>span,
|
||||||
|
.settingsRow>div>span {
|
||||||
font-size: 0.88rem;
|
font-size: 0.88rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1052,6 +1050,7 @@ button:not(.blackBtn),
|
|||||||
/* ---------- Reduced motion ---------- */
|
/* ---------- Reduced motion ---------- */
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
|
||||||
*,
|
*,
|
||||||
*::before,
|
*::before,
|
||||||
*::after {
|
*::after {
|
||||||
@@ -1061,3 +1060,76 @@ button:not(.blackBtn),
|
|||||||
animation-iteration-count: 1 !important;
|
animation-iteration-count: 1 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.statusSpan {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusSpan::before {
|
||||||
|
content: "";
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusSpan.activeWk {
|
||||||
|
background-color: #ecfdf5;
|
||||||
|
color: #059669;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusSpan.activeWk::before {
|
||||||
|
background-color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusSpan.activeWk:hover {
|
||||||
|
background-color: #d1fae5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusSpan.inactiveWk {
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusSpan.inactiveWk::before {
|
||||||
|
background-color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusSpan.inactiveWk:hover {
|
||||||
|
background-color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activateSpan {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
background-color: #2564eb;
|
||||||
|
color: #fff;
|
||||||
|
/* Modern, sleek blue */
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid #3b82f6;
|
||||||
|
padding: 6px 12px;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
user-select: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover and active states for a premium, responsive feel */
|
||||||
|
.activateSpan:hover {
|
||||||
|
background-color: #1d4ed8;
|
||||||
|
border-color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activateSpan:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
/* Subtle click shrink effect */
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user