Compare commits

...
Author SHA1 Message Date
bruno f163b9b5ea Automated merge from dev to live 2026-07-16 23:51:11 +02:00
bruno 30e27dae3c Automated merge from main to dev 2026-07-16 23:51:08 +02:00
Tower 448bffd2f5 add logout logging 2026-07-16 23:50:59 +02:00
Tower afc73cb030 Merge branch 'dev' into live 2026-07-16 19:21:42 +02:00
Tower 6459129e1a Merge branch 'main' into dev 2026-07-16 19:21:31 +02:00
Tower db392d915d change new server ip environment 2026-07-16 19:20:27 +02:00
bruno 8ecf7d1f6e Automated merge from dev to live 2026-07-14 19:07:32 +02:00
bruno 54cb7e98ad Automated merge from main to dev 2026-07-14 19:07:30 +02:00
Tower 584f168074 enable html in email 2026-07-14 19:07:01 +02:00
Tower 0e80de4c26 Merge branch 'dev' into live 2026-07-13 20:41:54 +02:00
Tower 75802de142 Merge branch 'main' into dev 2026-07-13 20:41:33 +02:00
Tower ce0d16c1ed add and delete connections 2026-07-13 20:41:23 +02:00
Bruno Eckhardt a73caf0bab Merge branch 'dev' into live 2026-07-13 14:49:50 +02:00
Bruno Eckhardt c5a7addd20 Merge branch 'main' into dev 2026-07-13 14:49:41 +02:00
Bruno Eckhardt b1416aa933 Merge branch 'domain-umzug' 2026-07-13 14:49:30 +02:00
Bruno Eckhardt c8f4f47439 change domain name 2026-07-13 14:47:33 +02:00
Bruno Eckhardt 6798a60d83 Merge branch 'dev' into live 2026-07-13 14:21:58 +02:00
Bruno Eckhardt 4f76da54f3 Merge branch 'main' into dev 2026-07-13 14:21:37 +02:00
Bruno Eckhardt 86c20a63bf add new DB connection 2026-07-13 14:21:24 +02:00
Bruno Eckhardt 13a98f2880 Automated merge from dev to live 2026-07-13 13:33:27 +02:00
Bruno Eckhardt cba34c4329 Automated merge from main to dev 2026-07-13 13:33:18 +02:00
Bruno Eckhardt 1fba004b9b Merge branch 'auto-merge' 2026-07-13 13:32:11 +02:00
Bruno Eckhardt 9622e35ca3 fixes 2026-07-13 13:32:01 +02:00
Bruno Eckhardt c173ea0c18 Automated merge from dev to live 2026-07-13 13:06:52 +02:00
Bruno Eckhardt 82ac74a7ee Automated merge from main to dev 2026-07-13 13:06:43 +02:00
Bruno Eckhardt 2748349060 Merge branch 'auto-merge' 2026-07-13 13:02:52 +02:00
Bruno Eckhardt ebbb6551f7 delete comments 2026-07-13 13:02:40 +02:00
Bruno Eckhardt a0914b60a6 Automated merge from dev to live 2026-07-13 12:35:55 +02:00
Bruno Eckhardt 0541557ae9 Automated merge from main to dev 2026-07-13 12:35:47 +02:00
Bruno Eckhardt 992ac5eb6a Merge branch 'auto-merge' 2026-07-13 12:35:11 +02:00
Bruno Eckhardt d37937f4d8 add form fixes 2026-07-13 12:34:59 +02:00
Bruno Eckhardt e98dbdc77b Automated merge from dev to live 2026-07-13 12:32:44 +02:00
Bruno Eckhardt aa4829343e Automated merge from main to dev 2026-07-13 12:32:36 +02:00
Bruno Eckhardt 523400ac0e Merge branch 'auto-merge' 2026-07-13 12:31:02 +02:00
Bruno Eckhardt 2064546b19 add auto merge script 2026-07-13 12:30:48 +02:00
Bruno Eckhardt 7d8a891798 Merge branch 'dev' into live 2026-07-13 10:46:24 +02:00
Bruno Eckhardt ddbf5b697f Merge branch 'main' into dev 2026-07-13 10:46:18 +02:00
Bruno Eckhardt d71e2c1e2d api controller fixes 2026-07-13 10:45:32 +02:00
Bruno Eckhardt 643be91c57 Merge branch 'dev' into live 2026-07-08 09:41:07 +02:00
Bruno Eckhardt f1b58a3399 Merge branch 'main' into dev 2026-07-08 09:40:54 +02:00
Bruno Eckhardt 62dadd175c Merge branch 'vergabe-service' 2026-07-08 09:40:41 +02:00
Bruno Eckhardt 3c9eb7091f feat: search persons and onjects 2026-07-08 09:40:32 +02:00
Bruno Eckhardt 7abcac5f04 feat: autofill and archive deleted things 2026-07-08 09:31:20 +02:00
Bruno Eckhardt 61ada9f2e3 Merge branch 'dev' into live 2026-07-08 08:59:44 +02:00
Bruno Eckhardt 5759f999c5 Merge branch 'main' into dev 2026-07-08 08:59:32 +02:00
Bruno Eckhardt 7d03579d31 Merge branch 'vergabe-service' 2026-07-08 08:59:19 +02:00
Bruno Eckhardt 742c5d5b56 feat: autofill 2026-07-08 08:59:03 +02:00
Bruno Eckhardt 20e0e09305 Merge branch 'dev' into live 2026-07-08 08:16:57 +02:00
Bruno Eckhardt d8a0121ac2 Merge branch 'main' into dev 2026-07-08 08:16:50 +02:00
Bruno Eckhardt 0952d6de13 Merge branch 'berufsschulberichte' 2026-07-08 08:15:57 +02:00
Tower 5a9a08421f new berufsschulreport service 2026-07-07 22:13:12 +02:00
Tower 733fc7dc12 Merge branch 'dev' into live 2026-07-07 19:20:36 +02:00
Tower 99f4f8a746 Merge branch 'main' into dev 2026-07-07 19:20:24 +02:00
Tower ffcc8821ed update 2026-07-07 19:19:57 +02:00
Bruno Eckhardt 72f188e239 raw form 2026-07-07 15:44:07 +02:00
Bruno Eckhardt 81ff082d71 Merge branch 'dev' into live 2026-07-07 14:28:05 +02:00
Bruno Eckhardt 3b7453563d Merge branch 'main' into dev 2026-07-07 14:27:52 +02:00
Bruno Eckhardt 6ec159f75c fix email 2026-07-07 14:27:37 +02:00
Bruno Eckhardt 9b1dfa8441 Merge branch 'dev' into live 2026-07-07 10:08:57 +02:00
Bruno Eckhardt 2f285f284b Merge branch 'main' into dev 2026-07-07 10:08:46 +02:00
Bruno Eckhardt d6e70a08eb Merge branch 'termin-service' 2026-07-07 10:08:34 +02:00
Bruno Eckhardt 52446cc019 fixes 2026-07-07 10:07:22 +02:00
Bruno Eckhardt fe5979d4d0 Merge branch 'dev' into live 2026-07-07 09:07:50 +02:00
Bruno Eckhardt 950fcd6f3b Merge branch 'main' into dev 2026-07-07 09:07:39 +02:00
Bruno Eckhardt 10472843a7 Merge branch 'termin-service' 2026-07-07 09:07:27 +02:00
Bruno Eckhardt 771f9e7bf6 fix UTF8 formating and right timestamp in DB when updated 2026-07-07 09:05:38 +02:00
Tower 3843677903 Merge branch 'dev' into live 2026-07-06 22:30:50 +02:00
Tower ca867e91d0 Merge branch 'main' into dev 2026-07-06 22:30:45 +02:00
Tower a066c59576 client ip changes 2026-07-06 22:30:24 +02:00
Tower ad763caaf5 Merge branch 'dev' into live 2026-07-06 22:26:56 +02:00
Tower 9ca5e7617c Merge branch 'main' into dev 2026-07-06 22:26:50 +02:00
Tower 388f9b8f0d add termin mailing 2026-07-06 22:26:26 +02:00
Tower 37871de69f Merge branch 'dev' into live 2026-07-06 21:52:19 +02:00
Tower 0431257a44 Merge branch 'main' into dev 2026-07-06 21:52:03 +02:00
Tower b08857a4cd delete comments and change some words 2026-07-06 21:51:40 +02:00
Tower 6d68fbc25d fix format and delete option 2026-07-06 20:39:21 +02:00
Bruno Eckhardt 33e4388b20 Merge branch 'dev' into live 2026-07-06 15:51:42 +02:00
Bruno Eckhardt 5479d0e364 Merge branch 'main' into dev 2026-07-06 15:51:36 +02:00
Bruno Eckhardt dceb02a3de fixes 2026-07-06 15:51:27 +02:00
Bruno Eckhardt 206d56b10c Merge branch 'dev' into live 2026-07-06 15:18:31 +02:00
Bruno Eckhardt f0f7c16abc Merge branch 'main' into dev 2026-07-06 15:18:25 +02:00
Bruno Eckhardt cd325270f0 add new calender / termine service 2026-07-06 15:18:11 +02:00
Bruno Eckhardt 859f67ca02 Merge branch 'dev' into live 2026-07-06 14:33:49 +02:00
Bruno Eckhardt 69199c78eb Merge branch 'main' into dev 2026-07-06 14:33:41 +02:00
Bruno Eckhardt efa9584baf hotfix 2026-07-06 14:33:32 +02:00
Bruno Eckhardt e0e9bca81a Merge branch 'dev' into live 2026-07-06 14:25:43 +02:00
Bruno Eckhardt 232cc27ce3 Merge branch 'main' into dev 2026-07-06 14:25:25 +02:00
Bruno Eckhardt ac202be4a7 add includes 2026-07-06 14:25:06 +02:00
Bruno Eckhardt d727452dd8 Merge branch 'dev' into live 2026-07-06 14:22:17 +02:00
Bruno Eckhardt 636f6b5c37 Merge branch 'main' into dev 2026-07-06 14:22:06 +02:00
Bruno Eckhardt 24e11e14a3 fix logout error logging 2026-07-06 14:21:53 +02:00
Bruno Eckhardt 62844653d2 Merge branch 'dev' into live 2026-07-06 14:13:54 +02:00
Bruno Eckhardt 88a95d28e3 Merge branch 'main' into dev 2026-07-06 14:13:46 +02:00
Bruno Eckhardt 5305fcabb6 logout fix 2026-07-06 14:13:33 +02:00
Bruno Eckhardt 14e01891a2 Merge branch 'dev' into live 2026-07-06 13:50:26 +02:00
Bruno Eckhardt 4cadcf263d Merge branch 'main' into dev 2026-07-06 13:50:21 +02:00
Bruno Eckhardt 3f7c840c39 style fixes 2026-07-06 13:50:12 +02:00
Bruno Eckhardt 1dfc6c9ac5 Merge branch 'dev' into live 2026-07-06 13:42:29 +02:00
Bruno Eckhardt 4e43f234c2 Merge branch 'main' into dev 2026-07-06 13:42:22 +02:00
Bruno Eckhardt 378c462b1c new einkaufslisten service 2026-07-06 13:40:55 +02:00
Bruno Eckhardt 671688f37d Merge branch 'dev' into live 2026-07-06 08:47:47 +02:00
Bruno Eckhardt 1eff0a54a6 Merge branch 'main' into dev 2026-07-06 08:47:42 +02:00
Bruno Eckhardt 5f1aa1c6fc mobile optimizing 2026-07-06 08:47:33 +02:00
Bruno Eckhardt 5ea0308b83 mobile optimizing 2026-07-06 08:46:30 +02:00
Bruno Eckhardt 859c1563fb Merge branch 'dev' into live 2026-07-06 08:41:00 +02:00
Bruno Eckhardt 9c1c86a7e7 Merge branch 'main' into dev 2026-07-06 08:40:53 +02:00
Bruno Eckhardt 5ac4fa4ac6 add showing entries and delete option 2026-07-06 08:40:00 +02:00
Bruno Eckhardt 3eb451069b delete wrong letter 2026-07-06 08:22:44 +02:00
Tower d6232f6418 Merge branch 'DEV' into LIVE 2026-07-05 19:13:32 +02:00
Tower 878b691ab1 Merge branch 'main' into DEV 2026-07-05 19:13:20 +02:00
Tower caf62ea701 fix logout 2026-07-05 19:13:08 +02:00
Tower a7368eb18f Merge branch 'DEV' into LIVE 2026-07-05 19:07:18 +02:00
Tower 38adea29cf Merge branch 'main' into DEV 2026-07-05 19:07:07 +02:00
Tower 82fe6e85d8 mobile css fixes 2026-07-05 19:06:04 +02:00
Tower f6fbc8404c Merge branch 'DEV' into LIVE 2026-07-01 20:10:15 +02:00
Tower e45fecf6ff Merge branch 'main' into DEV 2026-07-01 20:09:58 +02:00
Tower 40c26e625b add new vergabe service for papa 2026-07-01 16:39:56 +02:00
Bruno Eckhardt 5dce558640 Merge branch 'DEV' into LIVE 2026-05-20 12:33:38 +02:00
Bruno Eckhardt ffb6f38af0 Merge branch 'main' into DEV 2026-05-20 12:33:26 +02:00
Bruno Eckhardt 2901a5e362 add create and save new api token 2026-05-20 12:31:20 +02:00
Tower 2231d65465 fixed user_rights control, when new service is created rights are setted for bruno 2026-05-19 21:55:55 +02:00
Tower 4558a717fa new rightscontroller for new service creating 2026-05-19 21:55:14 +02:00
Tower 3e54b917ca Merge branch 'DEV' into LIVE 2026-05-19 19:50:24 +02:00
Tower 4756d0586f Merge branch 'main' into DEV 2026-05-19 19:50:14 +02:00
Bruno Eckhardt b2b4ac2124 new endpoint for workreports and moved reports to schoolreports 2026-05-19 15:13:53 +02:00
Bruno Eckhardt 86a1eecd8d Merge branch 'DEV' into LIVE 2026-05-19 10:38:34 +02:00
Bruno Eckhardt 9dcf0ced19 Merge branch 'main' into DEV 2026-05-19 10:38:20 +02:00
Bruno Eckhardt f7fd4ed183 request saves 2026-05-19 10:37:45 +02:00
Bruno Eckhardt c05f9f721c Merge branch 'DEV' into LIVE 2026-05-19 10:11:25 +02:00
Bruno Eckhardt 04c89fb676 Merge branch 'main' into DEV 2026-05-19 10:11:14 +02:00
Bruno Eckhardt a73b2ec13f fixes and new api entpoints for lessons and reports 2026-05-19 10:10:43 +02:00
Bruno Eckhardt 5160bbc5a5 Merge branch 'DEV' into LIVE 2026-05-19 07:17:41 +02:00
Bruno Eckhardt e9550b9a05 Merge branch 'main' into DEV 2026-05-19 07:17:31 +02:00
Bruno Eckhardt dce0a0caed htaccess 2026-05-19 07:17:17 +02:00
Bruno Eckhardt c5aabcf5d1 Merge branch 'DEV' into LIVE 2026-05-11 14:17:44 +02:00
Bruno Eckhardt e13ac6c503 Merge branch 'main' into DEV 2026-05-11 14:17:15 +02:00
Bruno Eckhardt bcaaac8a19 Merge branch 'DEV' into LIVE 2026-05-11 13:18:35 +02:00
Bruno Eckhardt e2ee377e35 Merge branch 'main' into DEV 2026-05-11 13:18:23 +02:00
Bruno Eckhardt 57813e3309 Merge branch 'DEV' into LIVE 2026-05-11 13:14:55 +02:00
Bruno Eckhardt 9eb06a3a16 Merge branch 'main' into DEV 2026-05-11 13:14:46 +02:00
Bruno Eckhardt a920fe99a8 Merge branch 'DEV' into LIVE 2026-05-11 13:09:39 +02:00
Bruno Eckhardt 0776ae8dd0 Merge branch 'main' into DEV 2026-05-11 13:09:30 +02:00
Bruno Eckhardt 21fa9145cb Merge branch 'DEV' into LIVE 2026-05-11 12:55:32 +02:00
Bruno Eckhardt c350f4c776 Merge branch 'main' into DEV 2026-05-11 12:55:21 +02:00
Tower fd5f3093c6 Merge branch 'DEV' into LIVE 2026-04-10 21:44:08 +02:00
Tower c47723ec00 Merge branch 'main' into DEV 2026-04-10 21:43:42 +02:00
Tower 737ea6e962 Merge branch 'DEV' into LIVE 2026-04-08 16:54:39 +02:00
Tower 0e5cdcd725 Merge branch 'main' into DEV 2026-04-08 16:54:24 +02:00
Bruno Eckhardt 739fda06e7 db fix 2026-04-08 16:36:53 +02:00
Bruno Eckhardt 3d170a38d8 Merge branch 'main' into DEV 2026-04-08 16:35:47 +02:00
Bruno Eckhardt 03febbc877 fix 2026-03-27 06:57:33 +01:00
Bruno Eckhardt bdc41bbd48 Merge branch 'main' into LIVE 2026-03-27 06:56:36 +01:00
Bruno Eckhardt 1f3e8228d1 fix 2026-03-26 08:13:59 +01:00
Bruno Eckhardt dccec7642b Merge branch 'DEV' into LIVE
# Conflicts:
#	WEBROOT/controller/DatabaseConnect.php
2026-03-26 08:13:20 +01:00
Bruno Eckhardt 6f74a11a79 Merge branch 'main' into DEV 2026-03-26 08:11:59 +01:00
Bruno Eckhardt ea51b1808a Merge branch 'main' into DEV 2026-03-25 08:47:16 +01:00
Bruno Eckhardt 2eacb68fd6 Merge branch 'main' into LIVE 2026-03-25 08:47:05 +01:00
Bruno Eckhardt 8c53e850c1 Merge branch 'main' into LIVE 2026-03-25 07:46:45 +01:00
Bruno Eckhardt 0d9b9e7b07 Merge branch 'main' into DEV 2026-03-25 07:46:23 +01:00
Bruno Eckhardt 9b6060d39f Merge branch 'main' into DEV 2026-03-23 15:45:43 +01:00
Bruno Eckhardt 01d6c69b3e Merge branch 'main' into LIVE 2026-03-23 15:45:26 +01:00
Bruno Eckhardt 7c94592b2c Merge branch 'main' into LIVE 2026-03-23 10:48:08 +01:00
Bruno Eckhardt 883a1fd76f Merge branch 'main' into DEV 2026-03-23 10:37:03 +01:00
Bruno Eckhardt dd08ec793a Merge branch 'main' into DEV 2026-03-23 08:58:33 +01:00
Bruno Eckhardt 24799dffbe Merge branch 'main' into LIVE 2026-03-23 08:58:26 +01:00
Bruno Eckhardt 34f464f442 dev db 2026-03-23 08:20:25 +01:00
Bruno Eckhardt d1464d874f Merge branch 'main' into LIVE 2026-03-23 08:19:28 +01:00
Bruno Eckhardt 8f5a616dd8 live db 2026-03-23 08:18:54 +01:00
32 changed files with 2599 additions and 486 deletions

No files matched your search

+1 -1
View File
@@ -5,7 +5,7 @@
} }
], ],
"settings": { "settings": {
"workbench.colorTheme": "Default Dark Modern", "workbench.colorTheme": "Dark Modern",
"workbench.colorCustomizations": { "workbench.colorCustomizations": {
"activityBar.background": "#ff000075", "activityBar.background": "#ff000075",
"activityBar.foreground": "#000000", "activityBar.foreground": "#000000",
+14
View File
@@ -3,3 +3,17 @@ RewriteEngine On
RewriteRule ^(Commands|config_backups|controller|modules)($|/.*) - [F,L,NC] RewriteRule ^(Commands|config_backups|controller|modules)($|/.*) - [F,L,NC]
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
# -----------------------------------
# .php entfernen
# -----------------------------------
# browser requestet datei.php -> redirect zu /datei
RewriteCond %{THE_REQUEST} \s/+(.+?)\.php[\s?] [NC]
RewriteRule ^ /%1 [R=301,L]
# /datei -> datei.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^(.+)$ $1.php [L]
+2 -2
View File
@@ -1,7 +1,7 @@
<?php <?php
// loginTest.php // loginTest.php
$loginUrl = "https://backend.blackpi.ipv64.net/login.php"; // URL zu deinem Login-Script $loginUrl = "https://backend.mrhossa.de/login.php"; // URL zu deinem Login-Script
// Testfälle // Testfälle
$tests = [ $tests = [
@@ -51,4 +51,4 @@ foreach ($tests as $test) {
} else { } else {
echo "[FAIL] {$test['description']}\n"; echo "[FAIL] {$test['description']}\n";
} }
} }
+76
View File
@@ -0,0 +1,76 @@
<?php
include_once(__DIR__ . "/../controller/DatabaseController.php");
include_once(__DIR__ . "/../modules/Mail.php");
date_default_timezone_set("Europe/Berlin");
$jetzt = new DateTime();
$heute = $jetzt->format("Y-m-d");
// Alle heutigen Termine laden
$termine = DatabaseShow(
"Termine",
"*",
"DATUM = ? AND ERINNERUNG_AM IS NULL",
$heute
);
foreach ($termine as $termin) {
$terminZeit = new DateTime($termin["DATUM"] . " " . $termin["ZEIT"]);
$sekundenBisTermin = $terminZeit->getTimestamp() - $jetzt->getTimestamp();
// Nur Termine innerhalb der nächsten 30 Minuten
if ($sekundenBisTermin > 0 && $sekundenBisTermin <= 1800) {
// Benutzer laden
$benutzer = DatabaseShow(
"Benutzer",
"*",
"NAME = ?",
$termin["ERSTELLT_VON"]
);
if (empty($benutzer)) {
continue;
}
$benutzer = $benutzer[0];
if (empty($benutzer["EMAIL"])) {
continue;
}
$subject = "Terminerinnerung";
$message = "Hallo {$benutzer["NAME"]},\n\n";
$message .= "Du hast in weniger als 30 Minuten einen Termin.\n\n";
$message .= "Termin: {$termin["NAME"]}\n";
$message .= "Datum: {$termin["DATUM"]}\n";
$message .= "Uhrzeit: {$termin["ZEIT"]}\n";
if (!empty($termin["NOTIZ"])) {
$notizClean = mb_convert_encoding($termin["NOTIZ"], "UTF-8", "auto");
$message .= "Notiz: {$notizClean}\n";
}
sendMail(
$subject,
$message,
$_SERVER['SCRIPT_NAME'] ?? 'CLI/Unbekannt',
true,
$benutzer["EMAIL"],
);
print_r("{$benutzer["EMAIL"]}\n{$message}");
DatabaseUpdate(
"Termine",
"ERINNERUNG_AM = ?",
"ID = ?",
date('d-m-Y H:i:s'),
$termin["ID"]
);
}
}
@@ -1,7 +1,8 @@
<?php <?php
include_once '../controller/DatabaseController.php'; include_once( __DIR__ . '/../../controller/DatabaseController.php');
include_once '../controller/DataController.php'; include_once( __DIR__ . '/../../controller/DataController.php');
include_once __DIR__ . '/../modules/Mail.php'; include_once( __DIR__ . '/../../controller/ApiController.php');
include_once(__DIR__ . '/../../modules/Mail.php');
header("Content-Type: application/json"); header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Origin: *");
@@ -13,9 +14,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit(); exit();
} }
// $validTokens = [
// "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e242",
// ];
$validTokens = DatabaseShow( $validTokens = DatabaseShow(
"API_Credentials", "API_Credentials",
"TOKEN" "TOKEN"
@@ -60,6 +58,8 @@ if (!in_array($token, $validTokens)) {
exit; exit;
} }
$data = null;
if (in_array($token, $validTokens)) { if (in_array($token, $validTokens)) {
DatabaseInsert( DatabaseInsert(
"API_LOGS", "API_LOGS",
@@ -1,6 +1,6 @@
<?php <?php
include_once '../controller/DatabaseController.php'; include_once( __DIR__ . '/../../controller/DatabaseController.php' );
include_once '../controller/DataController.php'; include_once( __DIR__ . '/../../controller/DataController.php' );
header("Content-Type: application/json"); header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Origin: *");
@@ -110,4 +110,4 @@ DatabaseInsert(
$json $json
); );
echo $json; echo $json;
+37
View File
@@ -0,0 +1,37 @@
<?php
include_once(__DIR__ . '/../../controller/DatabaseController.php');
include_once(__DIR__ . '/../../controller/DataController.php');
include_once(__DIR__ . '/../../controller/ApiController.php');
header('Content-Type: application/json');
$api = (new ApiController());
if ($api->urlAuth($_GET['token']) == true ) {
if (isset($_GET['day'])) {
$day = $_GET['day'];
$data = DatabaseShowWithFK(
"Stundenplan",
$Stundenplan,
"ZEITEN, $day",
"$day IS NOT NULL AND $day != '' AND $day != '(NULL)' ORDER BY ZEITEN ASC"
);
} else {
$data = DatabaseShowWithFK(
"Stundenplan",
$Stundenplan,
"ZEITEN, MO, DI, MO, DO, FR",
);
}
echo $api->jsonSuccess($data);
$api->requestSave();
}
/**
* http://localhost:9000/api/v2/lessons.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3
* http://localhost:9000/api/v2/lessons.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3&day=FR
*/
+37
View File
@@ -0,0 +1,37 @@
<?php
include_once(__DIR__ . '/../../controller/DatabaseController.php');
include_once(__DIR__ . '/../../controller/DataController.php');
include_once(__DIR__ . '/../../controller/ApiController.php');
header('Content-Type: application/json');
$api = (new ApiController());
if ($api->urlAuth($_GET['token']) == true ) {
if (isset($_GET['date'])) {
$date = $_GET['date'];
$data = DatabaseShowWithFK(
"Berufsschulberichte",
$Berufsschulberichte,
"*",
"DATUM = ?",
$date
);
} else {
$data = DatabaseShowWithFK(
"Berufsschulberichte",
$Berufsschulberichte,
);
}
echo $api->jsonSuccess($data);
$api->requestSave();
}
/**
* http://localhost:9000/api/v2/schoolReports.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3
* http://localhost:9000/api/v2/schoolReports.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3&date=2025-09-09
*/
+36
View File
@@ -0,0 +1,36 @@
<?php
include_once(__DIR__ . '/../../controller/DatabaseController.php');
include_once(__DIR__ . '/../../controller/DataController.php');
include_once(__DIR__ . '/../../controller/ApiController.php');
header('Content-Type: application/json');
$api = (new ApiController());
if ($api->urlAuth($_GET['token']) == true ) {
if (isset($_GET['date'])) {
$date = $_GET['date'];
$data = DatabaseShow(
"Firmenberichte",
"DATUM, PROZESS_1, PROZESS_2, PROZESS_3, PROZESS_4, BEGINN, ENDE_PUBLIC",
"DATUM = ?",
$date
);
} else {
$data = DatabaseShow(
"Firmenberichte",
"DATUM, PROZESS_1, PROZESS_2, PROZESS_3, PROZESS_4, BEGINN, ENDE_PUBLIC",
);
}
echo $api->jsonSuccess($data);
$api->requestSave();
}
/**
* http://localhost:9000/api/v2/workReports.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3
* http://localhost:9000/api/v2/workReports.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3&date=2025-09-09
*/
+154 -119
View File
@@ -1,42 +1,47 @@
<?php <?php
$superuser = array ( $superuser = array(
'admin' => 'admin' =>
array ( array(
'password' => 'bBHofurUJWKzPE8Z:', 'password' => 'bBHofurUJWKzPE8Z:',
'active' => true, 'active' => true,
), ),
); );
$serviceGroups = array ( $serviceGroups = array(
'Info' => 'Info' =>
array ( array(
'label' => 'Home', 'label' => 'Home',
'defaultOpen' => true, 'defaultOpen' => true,
'services' => 'services' =>
array ( array(
0 => 'home_info', 0 => 'home_info',
1 => 'links', 1 => 'vergabe',
2 => 'links',
3 => 'einkaufsliste',
4 => 'termine',
), ),
), ),
'Ausbildung' => 'Ausbildung' =>
array ( array(
'label' => 'Ausbildung', 'label' => 'Ausbildung',
'defaultOpen' => true, 'defaultOpen' => true,
'services' => 'services' =>
array ( array(
0 => 'print_static', 0 => 'print_static',
1 => 'zeiterfassung', 1 => 'zeiterfassung',
2 => 'berichte_arbeit_gen', 2 => 'berichte_arbeit_gen',
3 => 'berichte', 3 => 'berichte',
4 => 'firmenberichte',
5 => 'berufsschulberichte',
), ),
), ),
'Logs' => 'Logs' =>
array ( array(
'label' => 'Webserver', 'label' => 'Webserver',
'defaultOpen' => true, 'defaultOpen' => false,
'services' => 'services' =>
array ( array(
0 => 'public_request_logs', 0 => 'public_request_logs',
1 => 'public_request_chart', 1 => 'public_request_chart',
2 => 'backend_request_logs', 2 => 'backend_request_logs',
@@ -44,24 +49,24 @@ $serviceGroups = array (
4 => 'personal_request_logs', 4 => 'personal_request_logs',
), ),
), ),
'System' => 'System' =>
array ( array(
'label' => 'Verwaltung', 'label' => 'Verwaltung',
'defaultOpen' => false, 'defaultOpen' => false,
'services' => 'services' =>
array ( array(
0 => 'data_info', 0 => 'data_info',
1 => 'services_control', 1 => 'services_control',
2 => 'user_control', 2 => 'user_control',
3 => 'variablen_control', 3 => 'variablen_control',
), ),
), ),
'Test' => 'Test' =>
array ( array(
'label' => 'Dev / Test', 'label' => 'Dev / Test',
'defaultOpen' => false, 'defaultOpen' => false,
'services' => 'services' =>
array ( array(
0 => 'api_tester', 0 => 'api_tester',
1 => 'weather_chart', 1 => 'weather_chart',
2 => 'tabellen', 2 => 'tabellen',
@@ -70,309 +75,339 @@ $serviceGroups = array (
), ),
); );
$services = array ( $services = array(
'home_info' => 'home_info' =>
array ( array(
'name' => 'Home', 'name' => 'Home',
'active' => true, 'active' => true,
'adminOnly' => false, 'adminOnly' => false,
), ),
'print_static' => 'vergabe' =>
array ( array(
'name' => 'Vergabe',
'active' => true,
'adminOnly' => false,
),
'print_static' =>
array(
'name' => 'Berichte Drucken', 'name' => 'Berichte Drucken',
'active' => true, 'active' => true,
'adminOnly' => false, 'adminOnly' => false,
), ),
'api_tester' => 'api_tester' =>
array ( array(
'name' => 'API Testing', 'name' => 'API Testing',
'active' => true, 'active' => true,
'adminOnly' => true, 'adminOnly' => true,
), ),
'weather_chart' => 'weather_chart' =>
array ( array(
'name' => 'Wetter Statistik', 'name' => 'Wetter Statistik',
'active' => true, 'active' => true,
'adminOnly' => true, 'adminOnly' => true,
), ),
'data_info' => 'data_info' =>
array ( array(
'name' => 'Data Info', 'name' => 'Data Info',
'active' => true, 'active' => true,
'adminOnly' => true, 'adminOnly' => true,
), ),
'links' => 'links' =>
array ( array(
'name' => 'Links', 'name' => 'Links',
'active' => true, 'active' => true,
'adminOnly' => false, 'adminOnly' => false,
), ),
'public_request_logs' => 'public_request_logs' =>
array ( array(
'name' => 'Public Logs', 'name' => 'Public Logs',
'active' => true, 'active' => true,
'adminOnly' => false, 'adminOnly' => false,
), ),
'public_request_chart' => 'public_request_chart' =>
array ( array(
'name' => 'Public Statistik', 'name' => 'Public Statistik',
'active' => true, 'active' => true,
'adminOnly' => false, 'adminOnly' => false,
), ),
'backend_request_logs' => 'backend_request_logs' =>
array ( array(
'name' => 'Backend Logs', 'name' => 'Backend Logs',
'active' => true, 'active' => true,
'adminOnly' => false, 'adminOnly' => false,
), ),
'backend_request_chart' => 'backend_request_chart' =>
array ( array(
'name' => 'Backend Statistik', 'name' => 'Backend Statistik',
'active' => true, 'active' => true,
'adminOnly' => false, 'adminOnly' => false,
), ),
'personal_request_logs' => 'personal_request_logs' =>
array ( array(
'name' => 'Persönliche Logs', 'name' => 'Persönliche Logs',
'active' => true, 'active' => true,
'adminOnly' => true, 'adminOnly' => true,
), ),
'services_control' => 'services_control' =>
array ( array(
'name' => 'Seiten', 'name' => 'Seiten',
'active' => true, 'active' => true,
'adminOnly' => true, 'adminOnly' => true,
), ),
'zeiterfassung' => 'zeiterfassung' =>
array ( array(
'name' => 'Zeit Erfassung', 'name' => 'Zeit Erfassung',
'active' => true, 'active' => true,
'adminOnly' => false, 'adminOnly' => false,
), ),
'berichte_arbeit_gen' => 'berichte_arbeit_gen' =>
array ( array(
'name' => 'Berichte PDF', 'name' => 'Berichte PDF',
'active' => true, 'active' => true,
'adminOnly' => false, 'adminOnly' => false,
), ),
'user_control' => 'user_control' =>
array ( array(
'name' => 'Benutzer', 'name' => 'Benutzer',
'active' => true, 'active' => true,
'adminOnly' => true, 'adminOnly' => true,
), ),
'variablen_control' => 'variablen_control' =>
array ( array(
'name' => 'Variablen', 'name' => 'Variablen',
'active' => true, 'active' => true,
'adminOnly' => true, 'adminOnly' => true,
), ),
'tabellen' => 'tabellen' =>
array ( array(
'name' => 'Tabellen', 'name' => 'Tabellen',
'active' => true, 'active' => true,
'adminOnly' => true, 'adminOnly' => true,
), ),
'berichte' => 'berichte' =>
array ( array(
'name' => 'Berichte Einsehen', 'name' => 'Berichte Einsehen',
'active' => true, 'active' => true,
'adminOnly' => false, 'adminOnly' => false,
), ),
'domains' => 'domains' =>
array ( array(
'name' => 'Domains', 'name' => 'Domains',
'active' => true, 'active' => true,
'adminOnly' => false, 'adminOnly' => false,
), ),
'einkaufsliste' =>
array(
'name' => 'Einkaufsliste',
'active' => true,
'adminOnly' => false,
),
'termine' =>
array(
'name' => 'Termine',
'active' => true,
'adminOnly' => false,
),
'firmenberichte' =>
array(
'name' => 'Firmenberichte',
'active' => true,
'adminOnly' => false,
),
'berufsschulberichte' =>
array(
'name' => 'Berufsschulberichte',
'active' => true,
'adminOnly' => false,
),
); );
$linkGroups = array ( $linkGroups = array(
'info' => 'info' =>
array ( array(
'label' => 'Informationen', 'label' => 'Informationen',
'color' => '#3498db', 'color' => '#3498db',
'defaultOpen' => false, 'defaultOpen' => false,
), ),
'tools' => 'tools' =>
array ( array(
'label' => 'IoT', 'label' => 'IoT',
'color' => '#2ecc71', 'color' => '#2ecc71',
'defaultOpen' => false, 'defaultOpen' => false,
), ),
'admin' => 'admin' =>
array ( array(
'label' => 'Administration', 'label' => 'Administration',
'color' => '#e74c3c', 'color' => '#e74c3c',
'defaultOpen' => false, 'defaultOpen' => false,
), ),
'devices' => 'devices' =>
array ( array(
'label' => 'Geräte', 'label' => 'Geräte',
'color' => '#b7db33', 'color' => '#b7db33',
'defaultOpen' => false, 'defaultOpen' => false,
), ),
); );
$linkButtons = array ( $linkButtons = array(
0 => 0 =>
array ( array(
'label' => 'Gitea', 'label' => 'Gitea',
'url' => 'http://gitea.blackpi.ipv64.net', 'url' => 'http://gitea.mrhossa.de',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'admin', 'group' => 'admin',
), ),
1 => 1 =>
array ( array(
'label' => 'HAProxy', 'label' => 'HAProxy',
'url' => 'http://stats.blackpi.ipv64.net', 'url' => 'http://stats.mrhossa.de',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'admin', 'group' => 'admin',
), ),
2 => 2 =>
array ( array(
'label' => 'Public', 'label' => 'Public',
'url' => 'https://blackpi.ipv64.net', 'url' => 'https://mrhossa.de',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'info', 'group' => 'info',
), ),
3 => 3 =>
array ( array(
'label' => 'NAS-1', 'label' => 'NAS-1',
'url' => 'http://nas-1.blackpi.ipv64.net', 'url' => 'http://nas-1.blackpi.ipv64.net',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'devices', 'group' => 'devices',
), ),
4 => 4 =>
array ( array(
'label' => 'NAS-2', 'label' => 'NAS-2',
'url' => 'http://nas-2.blackpi.ipv64.net', 'url' => 'http://nas-2.blackpi.ipv64.net',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'devices', 'group' => 'devices',
), ),
5 => 5 =>
array ( array(
'label' => 'FritzBox-1', 'label' => 'FritzBox-1',
'url' => 'http://fritzbox-1.blackpi.ipv64.net', 'url' => 'http://fritzbox-1.blackpi.ipv64.net',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'devices', 'group' => 'devices',
), ),
6 => 6 =>
array ( array(
'label' => 'FritzBox-2', 'label' => 'FritzBox-2',
'url' => 'http://fritzbox-2.blackpi.ipv64.net', 'url' => 'http://fritzbox-2.blackpi.ipv64.net',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'devices', 'group' => 'devices',
), ),
7 => 7 =>
array ( array(
'label' => 'GS1910-24', 'label' => 'GS1910-24',
'url' => 'http://switch.blackpi.ipv64.net', 'url' => 'http://switch.blackpi.ipv64.net',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'admin', 'group' => 'admin',
), ),
8 => 8 =>
array ( array(
'label' => 'NWA5121-NI Innen', 'label' => 'NWA5121-NI Innen',
'url' => 'http://ap-1.blackpi.ipv64.net', 'url' => 'http://ap-1.blackpi.ipv64.net',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'devices', 'group' => 'devices',
), ),
9 => 9 =>
array ( array(
'label' => 'NWA5121-NI Aussen', 'label' => 'NWA5121-NI Aussen',
'url' => 'http://ap-2.blackpi.ipv64.net', 'url' => 'http://ap-2.blackpi.ipv64.net',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'devices', 'group' => 'devices',
), ),
10 => 10 =>
array ( array(
'label' => 'ZyWALL 110', 'label' => 'ZyWALL 110',
'url' => 'http://10.233.1248', 'url' => 'http://10.233.1248',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'admin', 'group' => 'admin',
), ),
11 => 11 =>
array ( array(
'label' => 'Ender 3 V3 KE', 'label' => 'Ender 3 V3 KE',
'url' => 'http://10.233.1.52', 'url' => 'http://10.233.1.52',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'tools', 'group' => 'tools',
), ),
12 => 12 =>
array ( array(
'label' => 'Ubuntu NAS', 'label' => 'Ubuntu NAS',
'url' => 'http://10.233.1.32:9000', 'url' => 'http://10.233.1.32:9000',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'tools', 'group' => 'tools',
), ),
13 => 13 =>
array ( array(
'label' => 'Backend', 'label' => 'Backend',
'url' => 'https://backend.blackpi.ipv64.net', 'url' => 'https://backend.blackpi.ipv64.net',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'info', 'group' => 'info',
), ),
14 => 14 =>
array ( array(
'label' => 'Dev Backend', 'label' => 'Dev Backend',
'url' => 'https://dev-backend.blackpi.ipv64.net', 'url' => 'https://dev-backend.blackpi.ipv64.net',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'info', 'group' => 'info',
), ),
15 => 15 =>
array ( array(
'label' => 'Dev Pubic', 'label' => 'Dev Pubic',
'url' => 'https://dev-public.blackpi.ipv64.net', 'url' => 'https://dev-public.blackpi.ipv64.net',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'info', 'group' => 'info',
), ),
16 => 16 =>
array ( array(
'label' => 'Local Backend', 'label' => 'Local Backend',
'url' => 'http://backend.local.ipv64.net', 'url' => 'http://backend.local.ipv64.net',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'info', 'group' => 'info',
), ),
17 => 17 =>
array ( array(
'label' => 'Local Pubic', 'label' => 'Local Pubic',
'url' => 'http://public.local.ipv64.net', 'url' => 'http://public.local.ipv64.net',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'info', 'group' => 'info',
), ),
18 => 18 =>
array ( array(
'label' => 'Proxmox', 'label' => 'Proxmox',
'url' => 'https://100.88.208.86:8006', 'url' => 'https://100.88.208.86:8006',
'newTab' => true, 'newTab' => true,
'adminOnly' => true, 'adminOnly' => true,
'group' => 'devices', 'group' => 'devices',
), ),
19 => 19 =>
array ( array(
'label' => 'Flatnotes', 'label' => 'Flatnotes',
'url' => 'http://10.233.1.130:8080', 'url' => 'http://10.233.1.130:8080',
'newTab' => true, 'newTab' => true,
'adminOnly' => false, 'adminOnly' => false,
'group' => 'tools', 'group' => 'tools',
), ),
); );
+139
View File
@@ -0,0 +1,139 @@
<?php
include_once(__DIR__ . '/DatabaseController.php');
class ApiController
{
/**
* method for pretty jsonsuccess output
*
* @param array $data
* @return string
*/
public function jsonSuccess($data)
{
return json_encode(
[
'status' => 'success',
'data' => $data,
],
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
}
/**
* method for pretty jsonerror output
*
* @param array $error
* @return string
*/
public function jsonError($error)
{
echo json_encode(
[
'status' => 'failed',
'data' => 'none',
'error' => $error
],
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
}
/**
* get authentication key out database
*
* @return array
*/
private function getAuthKey()
{
$validTokens = DatabaseShow(
"API_Credentials",
"TOKEN"
);
return array_column($validTokens, 'TOKEN');
}
/**
* url authentication method via token and optional parameter function
*
* @param string $token
* @param string $function
* @return void
*/
public function urlAuth($token, $function = '')
{
$validTokens = $this->getAuthKey();
if (!in_array($token, $validTokens)) {
http_response_code(401);
$error = json_encode([
"error" => "Unauthorized",
"debug_received_token" => $token,
]);
echo $this->jsonError($error);
DatabaseInsert(
"API_LOGS",
"CLIENT_IP, TOKEN, API, FUNKTION, JSON",
(new getSpecificData())->getClientIP(),
$token,
(new getSpecificData())->getRequestURL(),
$function,
$error
);
errorMail(
"API Login failed",
$error,
true,
);
return false;
}
return true;
}
public function requestSave($function = '')
{
DatabaseInsert(
"API_LOGS",
"CLIENT_IP, TOKEN, API, FUNKTION, JSON",
(new getSpecificData())->getClientIP(),
$_GET['token'],
(new getSpecificData())->getRequestURL(),
$function,
'{"status": "success"}',
);
}
/**
* create new api token
*
*/
private function createToken()
{
return bin2hex(random_bytes(32));
}
/**
* save created api token
*
* @return bool
*/
public function saveToken()
{
try {
DatabaseInsert(
"API_Credentials",
"TOKEN, BENUTZER",
$this->createToken(),
"API-User"
);
} catch (Exception $error) {
return false;
}
return true;
}
}
+12 -34
View File
@@ -108,25 +108,23 @@ class getSpecificData {
} }
public function getServerIP(): string { public function getServerIP(): string {
$ServerIP = $_SERVER['SERVER_ADDR'] ?? 'Unbekannt'; return $_SERVER['SERVER_ADDR'] ?? 'Unbekannt';
if (in_array($ServerIP, ['Unbekannt', '0.0.0.0', '127.0.0.1'])) {
return "Lokal";
} else {
return $ServerIP;
}
} }
public function getClientIP(): string { public function getClientIP(): string
{
if (PHP_SAPI === 'cli') {
return 'CLI';
}
$xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null; $xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null;
if ($xff) { if (!empty($xff)) {
$ips = array_map('trim', explode(',', $xff)); $ips = array_map('trim', explode(',', $xff));
$clientIp = $ips[0]; return $ips[0];
return $clientIp;
} else {
return $_SERVER['REMOTE_ADDR'];
} }
return $_SERVER['REMOTE_ADDR'] ?? 'UNKNOWN';
} }
public function getRequestTime(): string { public function getRequestTime(): string {
@@ -162,27 +160,7 @@ class getSpecificData {
} }
function getServerNode($ServerIP) { function getServerNode($ServerIP) {
$nodes = [ return $ServerIP;
'node1' => '172.30.0.11',
'node1' => '172.30.0.21',
'node2' => '172.30.0.12',
'node2' => '172.30.0.22',
'node3' => '172.30.0.13',
'node3' => '172.30.0.23',
'node4' => '172.30.0.14',
'node4' => '172.30.0.24',
'Lokal' => 'Lokal',
];
foreach ($nodes as $node => $ip) {
if ($ServerIP == $ip) {
return $node;
break;
}
}
} }
function getClientStatus($ClientIP) { function getClientStatus($ClientIP) {
+9 -29
View File
@@ -4,7 +4,8 @@ include_once __DIR__ . '/../modules/Mail.php';
include_once __DIR__ . '/../controller/RoutesController.php'; include_once __DIR__ . '/../controller/RoutesController.php';
include_once 'DataController.php'; include_once 'DataController.php';
function db_connect() { function db_connect()
{
$servers = [ $servers = [
'local' => [ 'local' => [
'hostaddr' => '127.0.0.1', 'hostaddr' => '127.0.0.1',
@@ -12,39 +13,21 @@ function db_connect() {
'password' => 'bBHofurUJWKzPE8Z', 'password' => 'bBHofurUJWKzPE8Z',
'database' => 'DEV', 'database' => 'DEV',
], ],
'vpn' => [ 'live' => [
'hostaddr' => '100.123.136.86', 'hostaddr' => '152.53.159.169',
'username' => 'worker', 'username' => 'worker',
'password' => 'bBHofurUJWKzPE8Z', 'password' => 'bBHofurUJWKzPE8Z',
'database' => 'DEV', 'database' => 'LIVE',
],
'backup' => [
'hostaddr' => '10.233.1.241',
'username' => 'worker',
'password' => 'bBHofurUJWKzPE8Z',
'database' => 'DEV',
], ],
'dev' => [ 'dev' => [
'hostaddr' => '10.233.1.243', 'hostaddr' => '152.53.159.169',
'username' => 'worker', 'username' => 'worker',
'password' => 'bBHofurUJWKzPE8Z', 'password' => 'bBHofurUJWKzPE8Z',
'database' => 'DEV', 'database' => 'DEV',
], ],
'live' => [
'hostaddr' => '10.233.1.244',
'username' => 'worker',
'password' => 'bBHofurUJWKzPE8Z',
'database' => 'DEV',
],
'error' => [
'hostaddr' => '10.233.1.244',
'username' => 'error',
'password' => 'bBHofurUJWKzPE8Z',
'database' => 'DEV',
]
]; ];
$enviroment = 'vpn'; $enviroment = 'live';
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
@@ -59,7 +42,6 @@ function db_connect() {
$conn->set_charset("utf8mb4"); $conn->set_charset("utf8mb4");
return $conn; return $conn;
} catch (mysqli_sql_exception $e) { } catch (mysqli_sql_exception $e) {
errorMail( errorMail(
@@ -67,9 +49,7 @@ function db_connect() {
$e->getMessage(), $e->getMessage(),
false, false,
); );
redirectToWebsite("https://blackpi.ipv64.net"); redirectToWebsite("https://mrhossa.de");
throw $e; throw $e;
} }
} }
?>
+87
View File
@@ -0,0 +1,87 @@
<?php
require_once(__DIR__ . '/DatabaseController.php');
class RightsController {
/**
* save new site with sitename
*
* @param string $sitename
*/
private function saveSite($sitename) {
DatabaseInsert(
"Backend_Seiten",
"DATEINAME",
$sitename
);
}
/**
* get site id by sitename
*
* @param string $sitename
* @return int
*/
private function getSiteID($sitename) {
$id = DatabaseShow(
"Backend_Seiten",
"ID",
"DATEINAME = ?",
$sitename
);
return $id[0]['ID'];
}
/**
* get user id
*
* @param string $name
* @param string $lastname
* @return int
*/
private function getUserID($name, $lastname) {
$id = DatabaseShow(
"Benutzer",
"ID",
"NAME = ? AND NACHNAME = ?",
$name,
$lastname
);
return $id[0]['ID'];
}
/**
* Rights Handler for saving default user is Bruno
*
* @param string $sitename
* @param string $name
* @param string $lastname
* @return string
*/
public function RightsHandler($sitename, $name = 'Bruno', $lastname = 'Eckhardt') {
try {
$sitename = strtolower($sitename);
$this->saveSite($sitename);
DatabaseInsert(
"Benutzer_Berechtigungen",
"BENUTZER_ID, SEITEN_ID",
$this->getUserID($name, $lastname),
$this->getSiteID($sitename)
);
} catch (Exception $error) {
return json_encode(
[
"status" => "failed",
"error" => $error->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
}
return json_encode(
[
"status" => "success",
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
}
}
+26 -25
View File
@@ -52,7 +52,8 @@ if (
$selectedService = $_GET['service']; $selectedService = $_GET['service'];
} }
if (!$selectedService && if (
!$selectedService &&
isset($defaultService) && isset($defaultService) &&
isset($services[$defaultService]) && isset($services[$defaultService]) &&
$services[$defaultService]['active'] $services[$defaultService]['active']
@@ -68,14 +69,14 @@ if (!$selectedService) {
} }
} }
} }
if ($selectedService && !in_array($selectedService, $userRights, true)) { if ($selectedService && !in_array($selectedService, $userRights, true)) {
$selectedService = null; $selectedService = null;
} }
$isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true; $isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
if ((new getSpecificData())->getWebsite() != "backend.blackpi.ipv64.net") { if ((new getSpecificData())->getWebsite() != "backend.mrhossa.de") {
$title = "DEV Backend"; $title = "DEV Backend";
} else { } else {
$title = "Backend"; $title = "Backend";
@@ -101,7 +102,7 @@ if ((new getSpecificData())->getWebsite() != "backend.blackpi.ipv64.net") {
<div class="main"> <div class="main">
<div class="sidebar"> <div class="sidebar">
<?php foreach ($serviceGroups as $groupKey => $group): ?> <?php foreach ($serviceGroups as $groupKey => $group): ?>
<?php <?php
$visibleServices = []; $visibleServices = [];
foreach ($group['services'] as $serviceKey) { foreach ($group['services'] as $serviceKey) {
@@ -115,34 +116,34 @@ if ((new getSpecificData())->getWebsite() != "backend.blackpi.ipv64.net") {
if (empty($visibleServices)) continue; if (empty($visibleServices)) continue;
$isOpen = !empty($group['defaultOpen']) || in_array($selectedService, $visibleServices, true); $isOpen = !empty($group['defaultOpen']) || in_array($selectedService, $visibleServices, true);
?> ?>
<div class="service-group <?= $isOpen ? 'open' : '' ?>" data-group="<?= $groupKey ?>"> <div class="service-group <?= $isOpen ? 'open' : '' ?>" data-group="<?= $groupKey ?>">
<div class="group-title"> <div class="group-title">
<?= htmlspecialchars($group['label']) ?> <?= htmlspecialchars($group['label']) ?>
<span class="arrow">▸</span> <span class="arrow">▸</span>
</div> </div>
<div class="group-services"> <div class="group-services">
<?php foreach ($visibleServices as $serviceKey): ?> <?php foreach ($visibleServices as $serviceKey): ?>
<a href="?service=<?= $serviceKey ?>"> <a href="?service=<?= $serviceKey ?>">
<button class="<?= $selectedService === $serviceKey ? 'active' : '' ?>"> <button class="<?= $selectedService === $serviceKey ? 'active' : '' ?>">
<?= htmlspecialchars($services[$serviceKey]['name']) ?> <?= htmlspecialchars($services[$serviceKey]['name']) ?>
</button> </button>
</a> </a>
<?php endforeach; ?> <?php endforeach; ?>
</div>
</div> </div>
</div>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<div class="content"> <div class="content">
<?php <?php
if ($selectedService) { if ($selectedService) {
include "services/$selectedService.php"; include "services/$selectedService.php";
logging(); logging();
} else { } else {
echo "<p>Kein aktiver Service verfügbar.</p>"; echo "<p>Kein aktiver Service verfügbar.</p>";
} }
?> ?>
</div> </div>
</div> </div>
+26
View File
@@ -0,0 +1,26 @@
<?php
include_once(__DIR__ . '/modules/Mail.php');
include_once(__DIR__ . '/controller/DataController.php');
include_once(__DIR__ . '/controller/LogController.php');
try {
session_start();
logging();
$_SESSION = array();
session_destroy();
http_response_code(200);
exit();
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'An error occurred while logging out.']);
sendMail(
"[ERROR] Logout",
"$e",
(new getSpecificData())->getRequestURL(),
true,
);
exit();
}
+1 -1
View File
@@ -27,7 +27,7 @@ document.querySelectorAll('.group-title').forEach(title => {
const logoutButton = document.getElementById('logoutButton'); const logoutButton = document.getElementById('logoutButton');
logoutButton.addEventListener('click', () => { logoutButton.addEventListener('click', () => {
fetch('services/logout.php', { method: 'POST' }) fetch('logout', { method: 'POST' })
.then(() => { .then(() => {
window.location.href = 'login.php'; window.location.href = 'login.php';
}); });
+12 -6
View File
@@ -3,10 +3,11 @@
include_once __DIR__ . '/../controller/DatabaseController.php'; include_once __DIR__ . '/../controller/DatabaseController.php';
include_once __DIR__ . '/../controller/DataController.php'; include_once __DIR__ . '/../controller/DataController.php';
function sendMail($subject, $message, $url, bool $log) { function sendMail($subject, $message, $url, bool $log, $to = "bruno.eckhardt07@gmail.com")
{
$headers = "From: bruno.eckhardt07@gmail.com\r\n"; $headers = "From: bruno.eckhardt07@gmail.com\r\n";
mail( mail(
"bruno.eckhardt07@gmail.com", $to,
$subject, $subject,
$message, $message,
$headers $headers
@@ -14,21 +15,26 @@ function sendMail($subject, $message, $url, bool $log) {
if ($log == true) { if ($log == true) {
DatabaseInsert( DatabaseInsert(
"Mailing", "Mailing",
"DATUM, SUBJECT, MESSAGE, URL, CLIENT_IP", "DATUM, SUBJECT, MESSAGE, MAIL_AN, URL, CLIENT_IP",
(new getSpecificData())->getRequestTime(), (new getSpecificData())->getRequestTime(),
$subject, $subject,
$message, $message,
$to,
$url, $url,
(new getSpecificData())->getClientIP(), (new getSpecificData())->getClientIP(),
); );
} }
} }
function errorMail($title, $error, bool $log) { function errorMail($title, $error, bool $log)
{
$headers = "From: bruno.eckhardt07@gmail.com\r\n"; $headers = "From: bruno.eckhardt07@gmail.com\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
mail( mail(
"bruno.eckhardt07@gmail.com", "bruno.eckhardt07@gmail.com",
"ERROR: $title", "[ERROR] $title",
$error, $error,
$headers, $headers,
); );
@@ -37,7 +43,7 @@ function errorMail($title, $error, bool $log) {
"Mailing", "Mailing",
"DATUM, SUBJECT, MESSAGE, URL, CLIENT_IP", "DATUM, SUBJECT, MESSAGE, URL, CLIENT_IP",
(new getSpecificData())->getRequestTime(), (new getSpecificData())->getRequestTime(),
"ERROR: $title", "[ERROR] $title",
$error, $error,
(new getSpecificData())->getClientIP(), (new getSpecificData())->getClientIP(),
); );
+437 -112
View File
@@ -1,29 +1,69 @@
<?php <?php
// API Tester Service - Als Modul für index.php // ======================================================
// Dieser Service dient zum manuellen Testen der API-Endpunkte. // API TESTER
// Modul für index.php
// ======================================================
// Basis-Verzeichnisse scannen (Pfad relativ zur index.php) // API Basis-Verzeichnis
$apiDir = __DIR__ . '/../api/'; $apiDir = realpath(__DIR__ . '/../api');
$apiFiles = glob($apiDir . '*.php');
// Alle PHP APIs rekursiv finden
$apiOptions = []; $apiOptions = [];
foreach ($apiFiles as $file) {
$apiOptions[] = 'api/' . basename($file); if ($apiDir !== false) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$apiDir,
RecursiveDirectoryIterator::SKIP_DOTS
)
);
foreach ($iterator as $file) {
if (!$file->isFile()) {
continue;
}
if ($file->getExtension() !== 'php') {
continue;
}
// relativen Pfad erzeugen
$relativePath = str_replace(
$apiDir . DIRECTORY_SEPARATOR,
'',
$file->getPathname()
);
// Windows kompatibel
$relativePath = str_replace('\\', '/', $relativePath);
$apiOptions[] = 'api/' . $relativePath;
}
} }
// Token aus deiner get.php entnommen // sortieren
sort($apiOptions);
// Standard Token
$defaultToken = "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e242"; $defaultToken = "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e242";
?> ?>
<style> <style>
/* Spezifische Styles für den API Tester innerhalb des Content-Bereichs */ /* ======================================================
API TESTER STYLES
====================================================== */
.api-tester-container { .api-tester-container {
width: 100%; width: 100%;
max-width: 900px; max-width: 1000px;
margin: 0 auto; margin: 0 auto;
background-color: var(--card-bg); background-color: var(--card-bg);
padding: 25px; padding: 25px;
border-radius: 10px; border-radius: 12px;
border: 1px solid var(--card-border); border: 1px solid var(--card-border);
box-sizing: border-box;
} }
.api-tester-container h2 { .api-tester-container h2 {
@@ -31,83 +71,8 @@ $defaultToken = "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e24
margin-bottom: 20px; margin-bottom: 20px;
color: var(--btn-bg); color: var(--btn-bg);
border-bottom: 1px solid var(--card-border); border-bottom: 1px solid var(--card-border);
padding-bottom: 10px; padding-bottom: 12px;
display: block; display: block;
/* style.css h2 hat flex, wir wollen hier einfaches block */
}
.tester-form-group {
margin-bottom: 15px;
}
.tester-form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
font-size: 0.9rem;
}
.tester-input {
width: 100%;
padding: 10px;
background-color: var(--input-bg);
border: 1px solid var(--input-border);
color: var(--input-text);
border-radius: 5px;
box-sizing: border-box;
}
.tester-btn-send {
background-color: var(--btn-bg);
color: white;
padding: 12px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
width: 100%;
margin-top: 10px;
}
.tester-btn-send:hover {
opacity: 0.9;
}
#tester-response-container {
margin-top: 25px;
background-color: var(--input-bg);
padding: 15px;
border-radius: 8px;
border: 1px solid var(--card-border);
min-height: 80px;
overflow-x: auto;
}
#tester-output-content {
white-space: pre-wrap;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 0.85rem;
margin: 0;
color: var(--text);
}
.tester-badge {
display: inline-block;
padding: 3px 8px;
border-radius: 4px;
font-size: 0.8rem;
font-weight: bold;
margin-bottom: 10px;
}
.tester-status-200 {
background-color: var(--status-bg);
color: var(--status-text);
}
.tester-status-error {
background-color: #dc3545;
color: white;
} }
.tester-grid { .tester-grid {
@@ -116,140 +81,500 @@ $defaultToken = "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e24
gap: 15px; gap: 15px;
} }
@media (max-width: 600px) { .tester-form-group {
.tester-grid { margin-bottom: 16px;
grid-template-columns: 1fr; }
}
.tester-form-group label {
display: block;
margin-bottom: 6px;
font-weight: bold;
font-size: 0.9rem;
}
.tester-input,
.tester-textarea,
select {
width: 100%;
padding: 10px;
background-color: var(--input-bg);
border: 1px solid var(--input-border);
color: var(--input-text);
border-radius: 6px;
box-sizing: border-box;
font-size: 0.9rem;
}
.tester-textarea {
min-height: 120px;
resize: vertical;
font-family: Consolas, monospace;
}
.tester-btn-send {
width: 100%;
background-color: var(--btn-bg);
color: white;
border: none;
border-radius: 8px;
padding: 13px;
font-weight: bold;
cursor: pointer;
transition: 0.2s;
margin-top: 5px;
}
.tester-btn-send:hover {
opacity: 0.9;
transform: translateY(-1px);
}
#tester-response-container {
margin-top: 25px;
background-color: var(--input-bg);
border: 1px solid var(--card-border);
border-radius: 10px;
padding: 15px;
min-height: 100px;
overflow-x: auto;
}
#tester-output-content {
white-space: pre-wrap;
font-family: Consolas, monospace;
font-size: 0.85rem;
margin: 0;
color: var(--text);
}
.tester-badge {
display: inline-block;
padding: 5px 10px;
border-radius: 5px;
font-size: 0.8rem;
font-weight: bold;
margin-bottom: 12px;
}
.tester-status-200 {
background-color: #198754;
color: white;
}
.tester-status-error {
background-color: #dc3545;
color: white;
}
.tester-status-warning {
background-color: #ffc107;
color: black;
} }
.hidden { .hidden {
display: none !important; display: none !important;
} }
.tester-row {
display: flex;
gap: 10px;
align-items: center;
}
.tester-checkbox {
display: flex;
align-items: center;
gap: 6px;
white-space: nowrap;
margin-top: 8px;
}
.tester-small-btn {
background: transparent;
border: 1px solid var(--input-border);
color: var(--text);
padding: 6px 10px;
border-radius: 6px;
cursor: pointer;
}
.tester-small-btn:hover {
background: rgba(255, 255, 255, 0.05);
}
.tester-response-info {
margin-bottom: 12px;
font-size: 0.85rem;
opacity: 0.8;
}
@media (max-width: 700px) {
.tester-grid {
grid-template-columns: 1fr;
}
.tester-row {
flex-direction: column;
align-items: stretch;
}
}
</style> </style>
<div class="api-tester-container"> <div class="api-tester-container">
<h2>🛠️ API Tester</h2> <h2>🛠️ API Tester</h2>
<!-- API Auswahl -->
<div class="tester-form-group"> <div class="tester-form-group">
<label for="endpoint">API Endpunkt</label> <label for="endpoint">API Endpunkt</label>
<select id="endpoint" class="tester-input" onchange="checkEndpoint()"> <select id="endpoint" class="tester-input" onchange="checkEndpoint()">
<?php foreach ($apiOptions as $opt): ?> <?php foreach ($apiOptions as $opt): ?>
<option value="<?= $opt ?>"><?= $opt ?></option>
<option value="<?= htmlspecialchars($opt) ?>">
<?= htmlspecialchars($opt) ?>
</option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>
<!-- Methode + Function -->
<div class="tester-grid"> <div class="tester-grid">
<div class="tester-form-group"> <div class="tester-form-group">
<label for="method">HTTP Methode</label> <label for="method">HTTP Methode</label>
<select id="method" class="tester-input"> <select id="method" class="tester-input">
<option value="GET">GET</option> <option value="GET">GET</option>
<option value="POST">POST</option> <option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="PATCH">PATCH</option>
<option value="OPTIONS">OPTIONS</option> <option value="OPTIONS">OPTIONS</option>
</select> </select>
</div> </div>
<div class="tester-form-group" id="function-group"> <div class="tester-form-group" id="function-group">
<label for="function-header">Function Header</label> <label for="function-header">Function Header</label>
<input type="text" id="function-header" class="tester-input" placeholder="z.B. IPs, Ueberstunden"> <input type="text" id="function-header" class="tester-input" placeholder="z.B. IPs, Ueberstunden">
</div> </div>
</div> </div>
<!-- Token -->
<div class="tester-form-group"> <div class="tester-form-group">
<label for="token">Authorization Token</label> <label for="token">Authorization Token</label>
<input type="text" id="token" class="tester-input" value="<?= $defaultToken ?>">
<div class="tester-row">
<input type="text" id="token" class="tester-input" value="<?= htmlspecialchars($defaultToken) ?>">
<label class="tester-checkbox">
<input type="checkbox" id="send-token" checked>
Mitsenden
</label>
</div>
</div> </div>
<!-- Eigene Header -->
<div class="tester-form-group">
<label for="custom-headers">
Zusätzliche Header (JSON)
</label>
<textarea id="custom-headers" class="tester-textarea" placeholder='{
"X-Test": "123",
"Client": "Frontend"
}'></textarea>
</div>
<!-- JSON Body -->
<div class="tester-form-group" id="json-body-group">
<label for="json-body">
JSON Body
</label>
<textarea id="json-body" class="tester-textarea" placeholder='{
"test": true
}'></textarea>
</div>
<!-- Datei Upload -->
<div class="tester-form-group hidden" id="file-group"> <div class="tester-form-group hidden" id="file-group">
<label for="files">Dateien (files)</label>
<label for="files">Dateien Upload</label>
<input type="file" id="files" class="tester-input" multiple> <input type="file" id="files" class="tester-input" multiple>
</div> </div>
<button class="tester-btn-send" onclick="sendApiRequest()">Anfrage senden 🚀</button> <!-- Buttons -->
<div class="tester-grid">
<button class="tester-btn-send" onclick="sendApiRequest()">
Anfrage senden 🚀
</button>
<button class="tester-btn-send" onclick="clearResponse()">
Ausgabe leeren 🧹
</button>
</div>
<!-- Ausgabe -->
<div id="tester-response-container"> <div id="tester-response-container">
<div style="text-align: center; color: #888; padding: 10px;">Bereit für Anfragen.</div>
<div style="text-align:center; opacity:0.7; padding:15px;">
Bereit für Anfragen.
</div>
</div> </div>
</div> </div>
<script> <script>
// ======================================================
// CHECK ENDPOINT
// ======================================================
function checkEndpoint() { function checkEndpoint() {
const endpoint = document.getElementById('endpoint').value; const endpoint = document.getElementById('endpoint').value;
const fileGroup = document.getElementById('file-group'); const fileGroup = document.getElementById('file-group');
const functionGroup = document.getElementById('function-group'); const functionGroup = document.getElementById('function-group');
const jsonBodyGroup = document.getElementById('json-body-group');
const methodSelect = document.getElementById('method'); const methodSelect = document.getElementById('method');
// Standard: alles ausblenden // reset
fileGroup.classList.add('hidden'); fileGroup.classList.add('hidden');
functionGroup.classList.remove('hidden'); functionGroup.classList.remove('hidden');
jsonBodyGroup.classList.remove('hidden');
// upload api
if (endpoint.includes('upload.php')) {
if (endpoint === 'api/upload.php') {
// Nur bei exakt api/upload.php Dateien anzeigen
fileGroup.classList.remove('hidden'); fileGroup.classList.remove('hidden');
functionGroup.classList.add('hidden'); jsonBodyGroup.classList.add('hidden');
methodSelect.value = 'POST'; methodSelect.value = 'POST';
} }
if (endpoint === 'api/get.php') { // get api
if (endpoint.includes('get.php')) {
methodSelect.value = 'GET'; methodSelect.value = 'GET';
} }
} }
// ======================================================
// CLEAR RESPONSE
// ======================================================
function clearResponse() {
document.getElementById('tester-response-container').innerHTML = `
<div style="text-align:center; opacity:0.7; padding:15px;">
Ausgabe geleert.
</div>
`;
}
// ======================================================
// SEND REQUEST
// ======================================================
async function sendApiRequest() { async function sendApiRequest() {
const endpoint = document.getElementById('endpoint').value; const endpoint = document.getElementById('endpoint').value;
const method = document.getElementById('method').value; const method = document.getElementById('method').value;
const token = document.getElementById('token').value; const token = document.getElementById('token').value;
const functionHeader = document.getElementById('function-header').value; const functionHeader = document.getElementById('function-header').value;
const customHeadersInput = document.getElementById('custom-headers').value;
const jsonBodyInput = document.getElementById('json-body').value;
const fileInput = document.getElementById('files'); const fileInput = document.getElementById('files');
const sendToken = document.getElementById('send-token').checked;
const responseContainer = document.getElementById('tester-response-container'); const responseContainer = document.getElementById('tester-response-container');
responseContainer.innerHTML = '<div style="text-align: center; padding: 10px;">Lade...</div>'; responseContainer.innerHTML = `
<div style="text-align:center; padding:15px;">
Anfrage läuft...
</div>
`;
let headers = { let headers = {};
'Authorization': token
};
if (endpoint.includes('get.php') && functionHeader) { // Token
if (sendToken && token.trim() !== '') {
headers['Authorization'] = token;
}
// Function Header
if (functionHeader.trim() !== '') {
headers['Function'] = functionHeader; headers['Function'] = functionHeader;
} }
// Custom Headers
if (customHeadersInput.trim() !== '') {
try {
const customHeaders = JSON.parse(customHeadersInput);
headers = {
...headers,
...customHeaders
};
} catch (e) {
responseContainer.innerHTML = `
<div style="color:#dc3545; padding:15px;">
Fehlerhafte Header JSON Struktur.
</div>
`;
return;
}
}
let options = { let options = {
method: method, method: method,
headers: headers headers: headers
}; };
// Upload
if (endpoint.includes('upload.php') && fileInput.files.length > 0) { if (endpoint.includes('upload.php') && fileInput.files.length > 0) {
const formData = new FormData(); const formData = new FormData();
for (let i = 0; i < fileInput.files.length; i++) { for (let i = 0; i < fileInput.files.length; i++) {
formData.append('files[]', fileInput.files[i]); formData.append('files[]', fileInput.files[i]);
} }
options.body = formData; options.body = formData;
} else {
// JSON Body
if (
['POST', 'PUT', 'PATCH', 'DELETE']
.includes(method) &&
jsonBodyInput.trim() !== ''
) {
try {
JSON.parse(jsonBodyInput);
headers['Content-Type'] = 'application/json';
options.body = jsonBodyInput;
} catch (e) {
responseContainer.innerHTML = `
<div style="color:#dc3545; padding:15px;">
Fehlerhafte JSON Body Struktur.
</div>
`;
return;
}
}
} }
const startedAt = performance.now();
try { try {
const response = await fetch(endpoint, options); const response = await fetch(endpoint, options);
const duration = (performance.now() - startedAt).toFixed(0);
const status = response.status; const status = response.status;
const rawText = await response.text(); const rawText = await response.text();
let resultText = rawText; let resultText = rawText;
// JSON hübsch formatieren
try { try {
const json = JSON.parse(rawText); const json = JSON.parse(rawText);
resultText = JSON.stringify(json, null, 2); resultText = JSON.stringify(json, null, 2);
} catch (e) {} } catch (e) {}
const badgeClass = status >= 200 && status < 300 ? 'tester-status-200' : 'tester-status-error'; let badgeClass = 'tester-status-error';
if (status >= 200 && status < 300) {
badgeClass = 'tester-status-200';
} else if (status >= 300 && status < 400) {
badgeClass = 'tester-status-warning';
}
responseContainer.innerHTML = ` responseContainer.innerHTML = `
<div class="tester-badge ${badgeClass}">Status: ${status}</div> <div class="tester-badge ${badgeClass}">
<pre id="tester-output-content"></pre> Status: ${status}
`; </div>
<div class="tester-response-info">
⏱️ ${duration} ms
</div>
<button
class="tester-small-btn"
onclick="copyResponse()"
>
Antwort kopieren
</button>
<pre id="tester-output-content"></pre>
`;
document.getElementById('tester-output-content').textContent = resultText; document.getElementById('tester-output-content').textContent = resultText;
} catch (error) { } catch (error) {
responseContainer.innerHTML = `<div style="color:#dc3545; padding: 10px;">Fehler: ${error.message}</div>`;
responseContainer.innerHTML = `
<div style="color:#dc3545; padding:15px;">
Fehler: ${error.message}
</div>
`;
} }
} }
// Initial check // ======================================================
// COPY RESPONSE
// ======================================================
function copyResponse() {
const content = document.getElementById('tester-output-content');
if (!content) {
return;
}
navigator.clipboard.writeText(content.textContent);
alert('Antwort kopiert.');
}
// Initial
checkEndpoint(); checkEndpoint();
</script> </script>
+58 -58
View File
@@ -1,14 +1,14 @@
<?php <?php
$conn = db_connect(); $conn = db_connect();
$sql = "SELECT DATE(FIRST_REQUEST) as tag, COUNT(*) as anzahl FROM logs WHERE WEBSITE = 'backend.blackpi.ipv64.net' GROUP BY DATE(FIRST_REQUEST) ORDER BY DATE(FIRST_REQUEST)"; $sql = "SELECT DATE(FIRST_REQUEST) as tag, COUNT(*) as anzahl FROM logs WHERE WEBSITE = 'backend.mrhossa.de' GROUP BY DATE(FIRST_REQUEST) ORDER BY DATE(FIRST_REQUEST)";
$result = $conn->query($sql); $result = $conn->query($sql);
$labels = []; $labels = [];
$data = []; $data = [];
if ($result->num_rows > 0) { if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) { while ($row = $result->fetch_assoc()) {
$labels[] = $row['tag']; $labels[] = $row['tag'];
$data[] = (int)$row['anzahl']; $data[] = (int)$row['anzahl'];
} }
@@ -33,71 +33,71 @@ $conn->close();
<canvas id="logChart" width="600" height="400"></canvas> <canvas id="logChart" width="600" height="400"></canvas>
<script> <script>
const ctx = document.getElementById('logChart').getContext('2d'); const ctx = document.getElementById('logChart').getContext('2d');
const logChart = new Chart(ctx, { const logChart = new Chart(ctx, {
type: 'line', type: 'line',
data: { data: {
labels: <?php echo json_encode($labels); ?>, labels: <?php echo json_encode($labels); ?>,
datasets: [{ datasets: [{
label: 'Logs pro Tag', label: 'Logs pro Tag',
data: <?php echo json_encode($data); ?>, data: <?php echo json_encode($data); ?>,
borderColor: 'rgba(75, 192, 192, 1)', borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)', backgroundColor: 'rgba(75, 192, 192, 0.2)',
fill: true, fill: true,
tension: 0 tension: 0
}] }]
}, },
options: { options: {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
plugins: { plugins: {
title: { title: {
display: true, display: true,
text: 'Logs pro Tag' text: 'Logs pro Tag'
}, },
legend: { legend: {
display: true, display: true,
position: 'top' position: 'top'
},
zoom: {
pan: {
enabled: true,
mode: 'x',
}, },
zoom: { zoom: {
wheel: { pan: {
enabled: true, enabled: true,
mode: 'x',
}, },
mode: 'x', zoom: {
} wheel: {
} enabled: true,
}, },
interaction: { mode: 'x',
mode: 'index', }
intersect: false
},
scales: {
x: {
title: {
display: true,
text: 'Datum'
} }
}, },
y: { interaction: {
min: 0, mode: 'index',
max: 100, intersect: false
title: { },
display: true, scales: {
text: 'Anzahl Logs' x: {
title: {
display: true,
text: 'Datum'
}
}, },
ticks: { y: {
stepSize: 20 min: 0,
}, max: 100,
beginAtZero: true title: {
display: true,
text: 'Anzahl Logs'
},
ticks: {
stepSize: 20
},
beginAtZero: true
}
} }
} }
} });
});
</script> </script>
</body> </body>
+24 -24
View File
@@ -40,7 +40,7 @@ if (!$useCustom) {
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs FROM logs
WHERE DATE(FIRST_REQUEST) = CURDATE() WHERE DATE(FIRST_REQUEST) = CURDATE()
AND (WEBSITE = 'backend.blackpi.ipv64.net') AND (WEBSITE = 'backend.mrhossa.de')
AND CLIENT_IP NOT IN ($devips_sql) AND CLIENT_IP NOT IN ($devips_sql)
ORDER BY FIRST_REQUEST DESC ORDER BY FIRST_REQUEST DESC
"; ";
@@ -50,7 +50,7 @@ if (!$useCustom) {
$sql = " $sql = "
SELECT CLIENT_IP, LAND, USER_AGENT, SUM(counter) AS gesamt_counter SELECT CLIENT_IP, LAND, USER_AGENT, SUM(counter) AS gesamt_counter
FROM logs FROM logs
WHERE (WEBSITE = 'backend.blackpi.ipv64.net') WHERE (WEBSITE = 'backend.mrhossa.de')
AND CLIENT_IP NOT IN ($devips_sql) AND CLIENT_IP NOT IN ($devips_sql)
GROUP BY CLIENT_IP GROUP BY CLIENT_IP
ORDER BY gesamt_counter DESC ORDER BY gesamt_counter DESC
@@ -62,7 +62,7 @@ if (!$useCustom) {
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs FROM logs
WHERE `SERVER_IP` = 'node1' WHERE `SERVER_IP` = 'node1'
AND (WEBSITE = 'backend.blackpi.ipv64.net') AND (WEBSITE = 'backend.mrhossa.de')
ORDER BY `FIRST_REQUEST` DESC ORDER BY `FIRST_REQUEST` DESC
"; ";
break; break;
@@ -72,7 +72,7 @@ if (!$useCustom) {
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs FROM logs
WHERE `SERVER_IP` = 'node2' WHERE `SERVER_IP` = 'node2'
AND (WEBSITE = 'backend.blackpi.ipv64.net') AND (WEBSITE = 'backend.mrhossa.de')
ORDER BY `FIRST_REQUEST` DESC ORDER BY `FIRST_REQUEST` DESC
"; ";
break; break;
@@ -82,7 +82,7 @@ if (!$useCustom) {
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs FROM logs
WHERE `SERVER_IP` = 'node3' WHERE `SERVER_IP` = 'node3'
AND (WEBSITE = 'backend.blackpi.ipv64.net') AND (WEBSITE = 'backend.mrhossa.de')
ORDER BY `FIRST_REQUEST` DESC ORDER BY `FIRST_REQUEST` DESC
"; ";
break; break;
@@ -92,7 +92,7 @@ if (!$useCustom) {
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs FROM logs
WHERE `SERVER_IP` = 'node4' WHERE `SERVER_IP` = 'node4'
AND (WEBSITE = 'backend.blackpi.ipv64.net') AND (WEBSITE = 'backend.mrhossa.de')
ORDER BY `FIRST_REQUEST` DESC ORDER BY `FIRST_REQUEST` DESC
"; ";
break; break;
@@ -101,7 +101,7 @@ if (!$useCustom) {
$sql = " $sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs FROM logs
WHERE (WEBSITE = 'backend.blackpi.ipv64.net') WHERE (WEBSITE = 'backend.mrhossa.de')
AND CLIENT_IP NOT IN ($devips_sql) AND CLIENT_IP NOT IN ($devips_sql)
ORDER BY FIRST_REQUEST DESC ORDER BY FIRST_REQUEST DESC
"; ";
@@ -116,7 +116,7 @@ if (!$useCustom) {
<select name="filter" onchange="this.form.submit()"> <select name="filter" onchange="this.form.submit()">
<?php <?php
$options = ['alle','today','node1','node2','node3','node4','counterperip']; $options = ['alle', 'today', 'node1', 'node2', 'node3', 'node4', 'counterperip'];
foreach ($options as $opt) { foreach ($options as $opt) {
$sel = ($filter === $opt) ? 'selected' : ''; $sel = ($filter === $opt) ? 'selected' : '';
echo "<option value='$opt' $sel>$opt</option>"; echo "<option value='$opt' $sel>$opt</option>";
@@ -150,25 +150,25 @@ if ($erg = $conn->query($sql)) {
?> ?>
<?php if (!$daten): ?> <?php if (!$daten): ?>
<b>Keine Daten gefunden</b> <b>Keine Daten gefunden</b>
<?php else: ?> <?php else: ?>
<div style="overflow-x:auto;"> <div style="overflow-x:auto;">
<table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;width:100%;"> <table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;width:100%;">
<tr> <tr>
<?php foreach ($daten[0] as $key => $_): ?> <?php foreach ($daten[0] as $key => $_): ?>
<th><?= htmlspecialchars($key) ?></th> <th><?= htmlspecialchars($key) ?></th>
<?php endforeach; ?> <?php endforeach; ?>
</tr> </tr>
<?php foreach ($daten as $row): ?> <?php foreach ($daten as $row): ?>
<tr> <tr>
<?php foreach ($row as $val): ?> <?php foreach ($row as $val): ?>
<td><?= htmlspecialchars($val ?? '') ?></td> <td><?= htmlspecialchars($val ?? '') ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?> <?php endforeach; ?>
</tr> </table>
<?php endforeach; ?> </div>
</table>
</div>
<?php endif; ?> <?php endif; ?>
+1 -1
View File
@@ -120,7 +120,7 @@ $count = is_array($displayData) ? count($displayData) : 0;
<div class="main"> <div class="main">
<div class="content"> <div class="content">
<h2 style="display: flex; justify-content: space-between; align-items: center;"> <h2 style="display: flex; justify-content: space-between; align-items: center;">
<span>📊 Berichtte-Vorschau:</span> <span>📊 Berichte-Vorschau:</span>
<div> <div>
<a href="?service=<?= $selectedService ?>&mode=fk"> <a href="?service=<?= $selectedService ?>&mode=fk">
+212
View File
@@ -0,0 +1,212 @@
<?php
include_once(__DIR__ . '/../controller/DataController.php');
include_once(__DIR__ . '/../controller/DatabaseController.php');
$alleFaecher = DatabaseShow("Fach", "ID, FACH");
$alleRaeume = DatabaseShow("Raum", "ID, RAUM_NR");
$alleLehrer = DatabaseShow("Lehrer", "ID, NAME");
/*
1 => ["fach_id" => 1, "raum_id" => 5, "lehrer_id" => 8], // LF1
1 => ["fach_id" => 2, "raum_id" => 4, "lehrer_id" => 11], // LF2
1 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
1 => ["fach_id" => 4, "raum_id" => 1, "lehrer_id" => 5], // LF4
6 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
1 => ["fach_id" => 6, "raum_id" => 5, "lehrer_id" => 4], // Sozi
1 => ["fach_id" => 8, "raum_id" => 5, "lehrer_id" => 7], // VWL
1 => ["fach_id" => 9, "raum_id" => 5, "lehrer_id" => 3], // DE
1 => ["fach_id" => 10, "raum_id" => 5, "lehrer_id" => 3], // ENG
*/
$Stundenplan = [
"MO" => [
1 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
2 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
3 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
4 => ["fach_id" => 6, "raum_id" => 5, "lehrer_id" => 4], // Sozi
5 => ["fach_id" => 10, "raum_id" => 5, "lehrer_id" => 3], // Eng
6 => ["fach_id" => 10, "raum_id" => 5, "lehrer_id" => 3], // Eng
7 => ["fach_id" => 2, "raum_id" => 3, "lehrer_id" => 11], // LF2
8 => ["fach_id" => 2, "raum_id" => 3, "lehrer_id" => 11], // LF2
],
"DI" => [
1 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
2 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
3 => ["fach_id" => 5, "raum_id" => 1, "lehrer_id" => 5], // LF5
4 => ["fach_id" => 2, "raum_id" => 3, "lehrer_id" => 6], // LF2
5 => ["fach_id" => 2, "raum_id" => 3, "lehrer_id" => 6], // LF2
],
"MI" => [
1 => ["fach_id" => 8, "raum_id" => 5, "lehrer_id" => 7], // VWL
2 => ["fach_id" => 8, "raum_id" => 5, "lehrer_id" => 7], // VWL
3 => ["fach_id" => 9, "raum_id" => 5, "lehrer_id" => 3], // DE
4 => ["fach_id" => 9, "raum_id" => 5, "lehrer_id" => 3], // DE
5 => ["fach_id" => 2, "raum_id" => 4, "lehrer_id" => 6], // LF2
6 => ["fach_id" => 2, "raum_id" => 4, "lehrer_id" => 6], // LF2
7 => ["fach_id" => 8, "raum_id" => 5, "lehrer_id" => 7], // VWL
],
"DO" => [
1 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
2 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
3 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
4 => ["fach_id" => 6, "raum_id" => 5, "lehrer_id" => 4], // Sozi
5 => ["fach_id" => 6, "raum_id" => 5, "lehrer_id" => 4], // Sozi
6 => ["fach_id" => 4, "raum_id" => 1, "lehrer_id" => 5], // LF4
7 => ["fach_id" => 4, "raum_id" => 1, "lehrer_id" => 5], // LF4
8 => ["fach_id" => 4, "raum_id" => 1, "lehrer_id" => 5], // LF4
],
"FR" => [
1 => ["fach_id" => 1, "raum_id" => 5, "lehrer_id" => 8], // LF1
2 => ["fach_id" => 1, "raum_id" => 5, "lehrer_id" => 8], // LF1
3 => ["fach_id" => 1, "raum_id" => 5, "lehrer_id" => 8], // LF1
4 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
5 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
6 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
]
];
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Berufsschulbericht</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="main">
<div class="content" style="max-width: 1100px; margin: 0 auto; padding: 20px;">
<div class="form-container">
<h3 style="margin-top: 0;">Berufsschulbericht erfassen</h3>
<form method="post">
<div class="form-group" style="max-width: 220px; margin-bottom: 25px;">
<label for="datum">Datum auswählen</label>
<input type="date" name="datum" id="datum" required>
</div>
<div class="vergabe-grid"
style="font-weight: bold; padding-bottom: 8px; margin-bottom: 12px; border-bottom: 1px solid var(--card-border);">
<div>Stunde</div>
<div>Fach</div>
<div>Raum</div>
<div>Lehrer</div>
<div>Inhalt / Thema</div>
</div>
<?php for ($i = 1; $i <= 8; $i++): ?>
<div class="vergabe-grid" style="margin-bottom: 10px; align-items: center;">
<div>
<span class="badge"
style="display: inline-block; min-width: 90px; text-align: center; padding: 8px 12px; font-weight: bold;">Stunde
<?= $i ?></span>
</div>
<div class="form-group">
<select name="stunden[<?= $i ?>][fach_id]" data-stunde="<?= $i ?>" data-type="fach_id">
<option value="">-- Fach --</option>
<?php foreach ($alleFaecher as $row): ?>
<option value="<?= $row['ID'] ?>"><?= htmlspecialchars($row['FACH']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<select name="stunden[<?= $i ?>][raum_id]" data-stunde="<?= $i ?>" data-type="raum_id">
<option value="">-- Raum --</option>
<?php foreach ($alleRaeume as $row): ?>
<option value="<?= $row['ID'] ?>"><?= htmlspecialchars($row['RAUM_NR']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<select name="stunden[<?= $i ?>][lehrer_id]" data-stunde="<?= $i ?>" data-type="lehrer_id">
<option value="">-- Lehrer --</option>
<?php foreach ($alleLehrer as $row): ?>
<option value="<?= $row['ID'] ?>"><?= htmlspecialchars($row['NAME']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<input type="text" name="stunden[<?= $i ?>][inhalt_text]" placeholder="Inhalt der Stunde..."
style="margin: 0; padding: 10px;">
</div>
</div>
<?php endfor; ?>
<div style="margin-top: 25px; text-align: right;">
<button type="submit" class="btn-submit-vergabe"
style="width: 220px; display: inline-flex; font-weight: bold;">
Bericht speichern
</button>
</div>
</form>
</div>
</div>
</div>
<script>
const stundenplan = <?= json_encode($Stundenplan) ?>;
const wochentage = ["SO", "MO", "DI", "MI", "DO", "FR", "SA"];
document.getElementById('datum').addEventListener('change', function() {
const gewaehltesDatum = new Date(this.value);
if (isNaN(gewaehltesDatum)) return;
let tagKey = wochentage[gewaehltesDatum.getDay()];
if (tagKey === "SO" || tagKey === "SA") {
tagKey = "MO";
}
const tagesPlan = stundenplan[tagKey] || {};
document.querySelectorAll('.vergabe-grid select').forEach(select => {
const stunde = select.getAttribute('data-stunde');
const type = select.getAttribute('data-type');
const vordefinierteId = (tagesPlan[stunde] && tagesPlan[stunde][type]) ? tagesPlan[stunde][
type
] : "";
select.value = vordefinierteId;
});
});
</script>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$datum = $_POST['datum'] ?? null;
$werte = [$datum];
for ($i = 1; $i <= 8; $i++) {
$stunde = $_POST['stunden'][$i] ?? [];
$werte[] = !empty($stunde['fach_id']) ? $stunde['fach_id'] : null;
$werte[] = !empty($stunde['lehrer_id']) ? $stunde['lehrer_id'] : null;
$werte[] = !empty($stunde['raum_id']) ? $stunde['raum_id'] : null;
$werte[] = !empty($stunde['inhalt_text']) ? $stunde['inhalt_text'] : null;
}
DatabaseInsert(
"Berufsschulberichte",
"DATUM, FACH_S1, LEHRER_S1, RAUM_S1, INHALT_S1, FACH_S2, LEHRER_S2, RAUM_S2, INHALT_S2, FACH_S3, LEHRER_S3, RAUM_S3, INHALT_S3, FACH_S4, LEHRER_S4, RAUM_S4, INHALT_S4, FACH_S5, LEHRER_S5, RAUM_S5, INHALT_S5, FACH_S6, LEHRER_S6, RAUM_S6, INHALT_S6, FACH_S7, LEHRER_S7, RAUM_S7, INHALT_S7, FACH_S8, LEHRER_S8, RAUM_S8, INHALT_S8",
...$werte
);
echo "<div class='status-msg' style='position: fixed; bottom: 20px; right: 20px; z-index: 10000;'>Bericht gespeichert!</div>";
}
?>
</body>
</html>
+17 -17
View File
@@ -57,9 +57,9 @@ $domains = DatabaseShow("Domains", "*");
</h2> </h2>
<?php if ($statusMsg): ?> <?php if ($statusMsg): ?>
<div class="status-msg"> <div class="status-msg">
<?= htmlspecialchars($statusMsg) ?> <?= htmlspecialchars($statusMsg) ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
@@ -67,27 +67,27 @@ $domains = DatabaseShow("Domains", "*");
<?php foreach ($domains as $domain): ?> <?php foreach ($domains as $domain): ?>
<form method="post" class="admin-card"> <form method="post" class="admin-card">
<input type="hidden" name="id" value="<?= $domain["ID"] ?>"> <input type="hidden" name="id" value="<?= $domain["ID"] ?>">
<label>Subdomain</label> <label>Subdomain</label>
<input type="text" name="subdomain" value="<?= htmlspecialchars($domain["SUBDOMAIN"]) ?>"> <input type="text" name="subdomain" value="<?= htmlspecialchars($domain["SUBDOMAIN"]) ?>">
<div style="margin-top:10px; display:flex; gap:10px;"> <div style="margin-top:10px; display:flex; gap:10px;">
<button type="submit" name="save_domain" class="btn-save"> <button type="submit" name="save_domain" class="btn-save">
Speichern Speichern
</button> </button>
<button type="submit" name="delete_domain" class="btn-delete"> <button type="submit" name="delete_domain" class="btn-delete">
Löschen Löschen
</button> </button>
</div> </div>
</form> </form>
<?php endforeach; ?> <?php endforeach; ?>
@@ -100,7 +100,7 @@ $domains = DatabaseShow("Domains", "*");
<label>Subdomain</label> <label>Subdomain</label>
<input type="text" name="subdomain" placeholder="z.B. kleinerpenis.blackpi.ipv64.net"> <input type="text" name="subdomain" placeholder="z.B. kleinerpenis.mrhossa.de">
<button type="submit" name="add_domain" class="btn-add"> <button type="submit" name="add_domain" class="btn-add">
Domain hinzufügen Domain hinzufügen
+91
View File
@@ -0,0 +1,91 @@
<?php
include_once __DIR__ . '/../controller/DatabaseController.php';
include_once __DIR__ . '/../controller/RoutesController.php';
$view = $_GET['view'] ?? 'show';
$selectedService = $_GET['service'] ?? null;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_entry'])) {
$artikel = $_POST['artikel'];
$menge = $_POST['menge'];
DatabaseInsert(
"Einkaufsliste",
"PRODUKT, MENGE",
$artikel,
$menge
);
redirectToWebsite((new getSpecificData())->getReferer());
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_entry'])) {
$id = $_POST['id'];
DatabaseDelete("Einkaufsliste", "ID = ?", $id);
redirectToWebsite((new getSpecificData())->getReferer());
exit;
}
$entries = DatabaseShow(
"Einkaufsliste"
);
?>
<div>
<div class="vergabe-form">
<form method="POST" class="vergabe-grid">
<div class="form-group">
<label>Artikel</label>
<input type="text" name="artikel" required>
</div>
<div class="form-group">
<label>Menge</label>
<input type="text" name="menge" required>
</div>
<button type="submit" name="add_entry" class="btn-submit-vergabe">
💾 Speichern
</button>
</form>
</div>
<div class="table-container" style="margin-top: 30px;">
<h3>Einkaufsliste</h3>
<?php if (!empty($entries)): ?>
<table class="db-table mobile-responsive-table">
<thead>
<tr>
<th>Artikel</th>
<th>Menge</th>
<th style="width: 100px;">Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($entries as $entry): ?>
<tr>
<td data-label="Artikel"><?= htmlspecialchars($entry['PRODUKT'] ?? '') ?></td>
<td data-label="Menge"><?= htmlspecialchars($entry['MENGE'] ?? '') ?></td>
<td data-label="Aktion">
<form method="POST" onsubmit="return confirm('Möchtest du diesen Eintrag wirklich löschen?');" style="margin: 0;">
<input type="hidden" name="id" value="<?= $entry['ID'] ?? '' ?>">
<button type="submit" name="delete_entry" class="btn-delete">
🗑️ Löschen
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p>Keine Einträge vorhanden.</p>
<?php endif; ?>
</div>
</div>
+97
View File
@@ -0,0 +1,97 @@
<?php
include_once(__DIR__ . '/../controller/DataController.php');
include_once(__DIR__ . '/../controller/DatabaseController.php');
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_bericht'])) {
$prozess1 = $_POST['prozess1'];
$prozess2 = $_POST['prozess2'];
$prozess3 = $_POST['prozess3'];
$prozess4 = $_POST['prozess4'];
$startzeit = $_POST['startzeit'];
$endzeit = $_POST['endzeit'];
$datum = $_POST['datum'];
// IHK konforme zeiten berechnen und Überstunden berechnen
$soll = new DateTime($startzeit);
$soll->modify("+8 hours 30 minutes");
$randomMin = rand(1, 15);
$endePublicDt = clone $soll;
$endePublicDt->modify("+{$randomMin} minutes");
$endePublic = $endePublicDt->format("H:i");
$real = DateTime::createFromFormat("H:i", $endzeit);
$beginnDt = new DateTime($startzeit);
$arbeitsDiff = $beginnDt->diff($real);
$arbeitsMinuten = ($arbeitsDiff->h * 60) + $arbeitsDiff->i;
$sollMinuten = 8 * 60 + 30;
if ($arbeitsMinuten <= $sollMinuten) {
$overtime = 0;
} else {
$diff = $soll->diff($real);
$overtime = ($diff->h * 60) + $diff->i;
}
DatabaseInsert(
"Firmenberichte",
"DATUM, PROZESS_1, PROZESS_2, PROZESS_3, PROZESS_4, BEGINN, ENDE_REAL, ENDE_PUBLIC, OVERTIME",
$datum,
$prozess1,
$prozess2,
$prozess3,
$prozess4,
$startzeit,
$endzeit,
$endePublic,
$overtime
);
redirectToWebsite((new getSpecificData())->getReferer());
exit;
}
?>
<div>
<div class="berichte-container">
<form method="POST" class="berichte-form">
<div class="form-group">
<label>Prozess 1</label>
<input type="text" name="prozess1" required>
</div>
<div class="form-group">
<label>Prozess 2</label>
<input type="text" name="prozess2" required>
</div>
<div class="form-group">
<label>Prozess 3</label>
<input type="text" name="prozess3" required>
</div>
<div class="form-group">
<label>Prozess 4</label>
<input type="text" name="prozess4" required>
</div>
<div class="form-group">
<label>Startzeit</label>
<input type="time" name="startzeit" required>
</div>
<div class="form-group">
<label>Endzeit</label>
<input type="time" name="endzeit" required>
</div>
<div class="form-group">
<label>Datum</label>
<input type="date" name="datum" required>
</div>
<button type="submit" name="add_bericht" class="btn-submit-bericht">
Bericht Speichern
</button>
</form>
</div>
</div>
+45 -45
View File
@@ -35,77 +35,77 @@ if ($customQuery) {
// Standard Queries // Standard Queries
if (!$useCustom) { if (!$useCustom) {
switch ($filter) { switch ($filter) {
case 'today': case 'today':
$sql = " $sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs FROM logs
WHERE CLIENT_IP NOT IN ($devips_sql) WHERE CLIENT_IP NOT IN ($devips_sql)
AND (WEBSITE != 'backend.blackpi.ipv64.net' OR WEBSITE IS NULL) AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
AND DATE(FIRST_REQUEST) = CURDATE() AND DATE(FIRST_REQUEST) = CURDATE()
ORDER BY FIRST_REQUEST DESC ORDER BY FIRST_REQUEST DESC
"; ";
break; break;
case 'counterperip': case 'counterperip':
$sql = " $sql = "
SELECT CLIENT_IP, LAND, USER_AGENT, SUM(counter) AS gesamt_counter SELECT CLIENT_IP, LAND, USER_AGENT, SUM(counter) AS gesamt_counter
FROM logs FROM logs
WHERE CLIENT_IP NOT IN ($devips_sql) WHERE CLIENT_IP NOT IN ($devips_sql)
AND (WEBSITE != 'backend.blackpi.ipv64.net' OR WEBSITE IS NULL) AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
GROUP BY CLIENT_IP GROUP BY CLIENT_IP
ORDER BY gesamt_counter DESC ORDER BY gesamt_counter DESC
"; ";
break; break;
case 'node1': case 'node1':
$sql = " $sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs FROM logs
WHERE `SERVER_IP` = 'node1' WHERE `SERVER_IP` = 'node1'
AND (WEBSITE != 'backend.blackpi.ipv64.net' OR WEBSITE IS NULL) AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
ORDER BY `FIRST_REQUEST` DESC ORDER BY `FIRST_REQUEST` DESC
"; ";
break; break;
case 'node2': case 'node2':
$sql = " $sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs FROM logs
WHERE `SERVER_IP` = 'node2' WHERE `SERVER_IP` = 'node2'
AND (WEBSITE != 'backend.blackpi.ipv64.net' OR WEBSITE IS NULL) AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
ORDER BY `FIRST_REQUEST` DESC ORDER BY `FIRST_REQUEST` DESC
"; ";
break; break;
case 'node3': case 'node3':
$sql = " $sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs FROM logs
WHERE `SERVER_IP` = 'node3' WHERE `SERVER_IP` = 'node3'
AND (WEBSITE != 'backend.blackpi.ipv64.net' OR WEBSITE IS NULL) AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
ORDER BY `FIRST_REQUEST` DESC ORDER BY `FIRST_REQUEST` DESC
"; ";
break; break;
case 'node4': case 'node4':
$sql = " $sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs FROM logs
WHERE `SERVER_IP` = 'node4' WHERE `SERVER_IP` = 'node4'
AND (WEBSITE != 'backend.blackpi.ipv64.net' OR WEBSITE IS NULL) AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
ORDER BY `FIRST_REQUEST` DESC ORDER BY `FIRST_REQUEST` DESC
"; ";
break; break;
default: default:
$sql = " $sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs FROM logs
WHERE CLIENT_IP NOT IN ($devips_sql) WHERE CLIENT_IP NOT IN ($devips_sql)
AND (WEBSITE != 'backend.blackpi.ipv64.net' OR WEBSITE IS NULL) AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
ORDER BY FIRST_REQUEST DESC ORDER BY FIRST_REQUEST DESC
"; ";
} }
} }
?> ?>
@@ -116,7 +116,7 @@ if (!$useCustom) {
<select name="filter" onchange="this.form.submit()"> <select name="filter" onchange="this.form.submit()">
<?php <?php
$options = ['alle','today','node1','node2','node3','node4','counterperip']; $options = ['alle', 'today', 'node1', 'node2', 'node3', 'node4', 'counterperip'];
foreach ($options as $opt) { foreach ($options as $opt) {
$sel = ($filter === $opt) ? 'selected' : ''; $sel = ($filter === $opt) ? 'selected' : '';
echo "<option value='$opt' $sel>$opt</option>"; echo "<option value='$opt' $sel>$opt</option>";
@@ -150,25 +150,25 @@ if ($erg = $conn->query($sql)) {
?> ?>
<?php if (!$daten): ?> <?php if (!$daten): ?>
<b>Keine Daten gefunden</b> <b>Keine Daten gefunden</b>
<?php else: ?> <?php else: ?>
<div style="overflow-x:auto;"> <div style="overflow-x:auto;">
<table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;width:100%;"> <table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;width:100%;">
<tr> <tr>
<?php foreach ($daten[0] as $key => $_): ?> <?php foreach ($daten[0] as $key => $_): ?>
<th><?= htmlspecialchars($key) ?></th> <th><?= htmlspecialchars($key) ?></th>
<?php endforeach; ?> <?php endforeach; ?>
</tr> </tr>
<?php foreach ($daten as $row): ?> <?php foreach ($daten as $row): ?>
<tr> <tr>
<?php foreach ($row as $val): ?> <?php foreach ($row as $val): ?>
<td><?= htmlspecialchars($val ?? '') ?></td> <td><?= htmlspecialchars($val ?? '') ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?> <?php endforeach; ?>
</tr> </table>
<?php endforeach; ?> </div>
</table>
</div>
<?php endif; ?> <?php endif; ?>
+4
View File
@@ -1,5 +1,8 @@
<?php <?php
include_once(__DIR__ . '/../controller/RightsController.php');
include_once(__DIR__ . '/../config.php');
$configFile = 'config.php'; $configFile = 'config.php';
$backupDir = 'config_backups'; $backupDir = 'config_backups';
@@ -47,6 +50,7 @@ if (isset($_POST['save']) && ($_SESSION['logged_in'] ?? false)) {
$serviceToGroupMapping[$s['targetGroup']][] = $finalId; $serviceToGroupMapping[$s['targetGroup']][] = $finalId;
} }
} }
(new RightsController())->RightsHandler($s['name']);
} }
$newServiceGroups = []; $newServiceGroups = [];
+227
View File
@@ -0,0 +1,227 @@
<?php
include_once(__DIR__ . '/../controller/DatabaseController.php');
include_once(__DIR__ . '/../controller/DataController.php');
$msg = "";
$username = $_SESSION['username'] ?? 'Gast';
if (isset($_GET['action']) && $_GET['action'] === 'delete') {
if (isset($_GET['serien_id']) && !empty($_GET['serien_id'])) {
$serienId = $_GET['serien_id'];
DatabaseDelete("Termine", "SERIEN_ID = ?", $serienId);
$msg = "Komplette Terminserie erfolgreich gelöscht!";
} elseif (isset($_GET['id'])) {
$deleteId = (int)$_GET['id'];
DatabaseDelete("Termine", "ID = ?", $deleteId);
$msg = "Termin erfolgreich gelöscht!";
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_termin'])) {
$datum = $_POST['datum'] ?? '';
$zeit = $_POST['zeit'] ?? '';
$name = $_POST['name'] ?? '';
$notiz = $_POST['notiz'] ?? '';
$wiederholung = $_POST['wiederholung'] ?? 'none';
$enddatum = $_POST['enddatum'] ?? '';
if (!empty($datum) && !empty($zeit) && !empty($name)) {
if ($wiederholung !== 'none' && !empty($enddatum)) {
$currentDate = new DateTime($datum);
$endDate = new DateTime($enddatum);
if ($endDate < $currentDate) {
$msg = "Das Enddatum darf nicht vor dem Startdatum liegen.";
} else {
$insertedCount = 0;
$serienId = uniqid('serie_', true);
$intervalMapping = [
'daily' => '+1 day',
'weekly' => '+1 week',
'monthly' => '+1 month'
];
$modifier = $intervalMapping[$wiederholung] ?? null;
if ($modifier) {
while ($currentDate <= $endDate) {
DatabaseInsert(
"Termine",
"DATUM, ZEIT, NAME, NOTIZ, ERSTELLT_VON, SERIEN_ID, INTERVALL, SERIEN_ENDE",
$currentDate->format('Y-m-d'),
$zeit,
$name,
$notiz,
$username,
$serienId,
$wiederholung,
$enddatum
);
$insertedCount++;
$currentDate->modify($modifier);
}
$msg = "Serientermin ($insertedCount Termine) erfolgreich eingetragen!";
}
}
} else {
DatabaseInsert(
"Termine",
"DATUM, ZEIT, NAME, NOTIZ, ERSTELLT_VON",
$datum,
$zeit,
$name,
$notiz,
$username
);
$msg = "Termin erfolgreich eingetragen!";
}
} else {
$msg = "Bitte alle Pflichtfelder ausfüllen.";
}
}
$termine = DatabaseShow(
"Termine",
"MAX(ID) as ID, MIN(DATUM) as DATUM, ZEIT, NAME, NOTIZ, ERSTELLT_VON, SERIEN_ID, INTERVALL, SERIEN_ENDE",
"ERSTELLT_VON = ? GROUP BY IFNULL(SERIEN_ID, ID) ORDER BY DATUM ASC, ZEIT ASC",
$username
);
$intervallNamen = [
'daily' => 'Täglich',
'weekly' => 'Wöchentlich',
'monthly' => 'Monatlich'
];
?>
<?php if (!empty($msg)): ?>
<div class="status-msg"><?= htmlspecialchars($msg) ?></div>
<?php endif; ?>
<div class="form-container">
<h3>📅 Neuen Termin vergeben</h3>
<form method="POST" class="vergabe-form">
<div class="vergabe-grid">
<div class="form-group">
<label for="datum">Start-Datum *</label>
<input type="date" id="datum" name="datum" required>
</div>
<div class="form-group">
<label for="zeit">Uhrzeit *</label>
<input type="time" id="zeit" name="zeit" required
style="width:100%; padding:10px; border-radius:5px; border:1px solid var(--input-border); background:var(--input-bg); color:var(--input-text);">
</div>
<div class="form-group">
<label for="name">Name / Zweck *</label>
<input type="text" id="name" name="name" placeholder="z.B. Kundentermin" required>
</div>
<div class="form-group">
<label for="notiz">Notiz (Optional)</label>
<input type="text" id="notiz" name="notiz" placeholder="Details zum Termin...">
</div>
<div class="form-group">
<label for="wiederholung">Wiederholung</label>
<select id="wiederholung" name="wiederholung" onchange="toggleEnddatum()"
style="width:100%; padding:10px; border-radius:5px; border:1px solid var(--input-border); background:var(--input-bg); color:var(--input-text);">
<option value="none">Keine Wiederholung</option>
<option value="daily">Täglich</option>
<option value="weekly">Wöchentlich</option>
<option value="monthly">Monatlich</option>
</select>
</div>
<div class="form-group" id="enddatum-group" style="display: none;">
<label for="enddatum">Wiederholen bis *</label>
<input type="date" id="enddatum" name="enddatum">
</div>
<button type="submit" name="add_termin" class="btn-submit-vergabe">Speichern</button>
</div>
</form>
</div>
<div class="form-container">
<h3>📋 Aktuelle Terminbelegungen</h3>
<div class="table-container">
<table class="db-table mobile-responsive-table">
<thead>
<tr>
<th>Datum / Turnus</th>
<th>Uhrzeit</th>
<th>Termin / Name</th>
<th>Notiz</th>
<th>Eingetragen von</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php if (empty($termine)): ?>
<tr>
<td colspan="6" style="text-align: center; padding: 20px;">Keine Termine eingetragen.</td>
</tr>
<?php else: ?>
<?php foreach ($termine as $termin): ?>
<?php
$dateFormatted = date('d.m.Y', strtotime($termin['DATUM']));
$timeFormatted = date('H:i', strtotime($termin['ZEIT']));
$istSerie = !empty($termin['SERIEN_ID']);
?>
<tr>
<td data-label="Datum:">
<strong><?= htmlspecialchars($dateFormatted) ?></strong>
<?php if ($istSerie): ?>
<br>
<small style="color: var(--primary-color, #007bff); font-style: italic;">
(<?= $intervallNamen[$termin['INTERVALL']] ?? '' ?>
bis <?= date('d.m.Y', strtotime($termin['SERIEN_ENDE'])) ?>)
</small>
<?php endif; ?>
</td>
<td data-label="Uhrzeit:"><?= htmlspecialchars($timeFormatted) ?></td>
<td data-label="Termin / Name:">
<strong><?= htmlspecialchars($termin['NAME']) ?></strong>
<?php if ($istSerie): ?>
<span
style="font-size: 0.8em; background: #e1f5fe; color: #0288d1; padding: 2px 6px; border-radius: 10px; margin-left: 5px;">Wiederholend</span>
<?php endif; ?>
</td>
<td data-label="Notiz:"><?= htmlspecialchars($termin['NOTIZ'] ?: '-') ?></td>
<td data-label="Von:"><span class="badge"><?= htmlspecialchars($termin['ERSTELLT_VON']) ?></span>
</td>
<td data-label="Aktion:">
<?php if ($istSerie): ?>
<a href="?service=termine&action=delete&serien_id=<?= urlencode($termin['SERIEN_ID']) ?>"
onclick="return confirm('Möchtest du die GESAMTE Terminserie löschen?');">
<button class="btn-delete" style="background-color: #d32f2f;">löschen</button>
</a>
<?php else: ?>
<a href="?service=termine&action=delete&id=<?= $termin['ID'] ?>"
onclick="return confirm('Möchtest du diesen Termin wirklich löschen?');">
<button class="btn-delete">Löschen</button>
</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<script>
function toggleEnddatum() {
var wiederholung = document.getElementById('wiederholung').value;
var enddatumGroup = document.getElementById('enddatum-group');
var enddatumInput = document.getElementById('enddatum');
if (wiederholung !== 'none') {
enddatumGroup.style.display = 'block';
enddatumInput.required = true;
} else {
enddatumGroup.style.display = 'none';
enddatumInput.required = false;
enddatumInput.value = '';
}
}
</script>
+299
View File
@@ -0,0 +1,299 @@
<?php
include_once __DIR__ . '/../controller/DatabaseController.php';
include_once __DIR__ . '/../controller/RoutesController.php';
include_once __DIR__ . '/../modules/ConsoleLog.php';
$conn = db_connect();
$view = $_GET['view'] ?? 'show';
$selectedService = $_GET['service'] ?? null;
// --- Suchfilter auslesen ---
$searchWer = $_GET['search_wer'] ?? '';
$searchWas = $_GET['search_was'] ?? '';
// --- LOGIK: Überstunden-Eintrag (Abbau) speichern ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_entry'])) {
$was = $_POST['was'];
$wer = $_POST['wer'];
$wann = $_POST['wann'];
$rueckgabe = $_POST['rueckgabe'];
DatabaseInsert(
"Vergabe",
"WAS, WER, WANN, RUECKGABE",
$was,
$wer,
$wann,
$rueckgabe
);
redirectToWebsite((new getSpecificData())->getReferer());
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_entry'])) {
$id = $_POST['id'];
ConsoleLog("$id");
DatabaseUpdate(
"Vergabe",
"STATUS = ?",
"ID = ?",
1,
$id
);
redirectToWebsite((new getSpecificData())->getReferer());
exit;
}
// --- Dynamic Query mit kombiniertem Suchfilter ---
$whereClause = "STATUS IS NULL";
if (!empty($searchWer)) {
$whereClause .= " AND WER LIKE '%" . $conn->real_escape_string($searchWer) . "%'";
}
if (!empty($searchWas)) {
$whereClause .= " AND WAS LIKE '%" . $conn->real_escape_string($searchWas) . "%'";
}
$entries = DatabaseShow(
"Vergabe",
"ID, WAS, WER, WANN, RUECKGABE",
$whereClause
);
$searchEntries = DatabaseShow(
"Vergabe",
);
$existingWas = [];
$existingWer = [];
foreach ($searchEntries as $entry) {
if (!empty($entry['WAS'])) $existingWas[$entry['WAS']] = true;
if (!empty($entry['WER'])) $existingWer[$entry['WER']] = true;
}
$existingWas = array_keys($existingWas);
$existingWer = array_keys($existingWer);
?>
<div class="arbeitsberichte-wrapper">
<div class="vergabe-form">
<form method="POST" class="vergabe-grid">
<div class="form-group">
<label>Wann ausgeliehen</label>
<input type="date" name="wann" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="form-group">
<label>Rückgabe</label>
<input type="date" name="rueckgabe" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="form-group autocomplete-wrapper">
<label>An wen</label>
<input type="text" name="wer" id="wer-input" placeholder="Name eingeben" required autocomplete="off">
<div class="autocomplete-suggestions" id="wer-suggestions"></div>
</div>
<div class="form-group autocomplete-wrapper">
<label>Was</label>
<input type="text" name="was" id="was-input" placeholder="Gegenstand eingeben" required autocomplete="off">
<div class="autocomplete-suggestions" id="was-suggestions"></div>
</div>
<button type="submit" name="add_entry" class="btn-submit-vergabe">
Speichern
</button>
</form>
</div>
<div class="vergabe-form" style="margin-top: 30px;">
<form method="GET" action="" class="vergabe-grid" id="search-form">
<input type="hidden" name="view" value="<?= htmlspecialchars($view) ?>">
<?php if ($selectedService): ?>
<input type="hidden" name="service" value="<?= htmlspecialchars($selectedService) ?>">
<?php endif; ?>
<div class="form-group autocomplete-wrapper">
<label for="search-wer-input">Nach Person filtern</label>
<input type="text" name="search_wer" id="search-wer-input" value="<?= htmlspecialchars($searchWer) ?>" placeholder="Name eingeben..." autocomplete="off">
<div class="autocomplete-suggestions" id="search-wer-suggestions"></div>
</div>
<div class="form-group autocomplete-wrapper">
<label for="search-was-input">Nach Gegenstand filtern</label>
<input type="text" name="search_was" id="search-was-input" value="<?= htmlspecialchars($searchWas) ?>" placeholder="Gegenstand eingeben..." autocomplete="off">
<div class="autocomplete-suggestions" id="search-was-suggestions"></div>
</div>
<div style="display: flex; gap: 10px; align-items: flex-end;">
<button type="submit" class="btn-submit-vergabe" style="margin: 0; flex-grow: 1;">
Suchen
</button>
<?php if (!empty($searchWer) || !empty($searchWas)): ?>
<a href="?view=<?= urlencode($view) ?><?= $selectedService ? '&service=' . urlencode($selectedService) : '' ?>" class="btn-delete" style="text-decoration: none; padding: 10px; display: inline-block; text-align: center; white-space: nowrap; margin: 0;">
Zurücksetzen
</a>
<?php endif; ?>
</div>
</form>
</div>
<div class="table-container" style="margin-top: 30px;">
<h3>
<?php
if (!empty($searchWer) && !empty($searchWas)) {
echo "Gefiltert nach: " . htmlspecialchars($searchWer) . " ➔ " . htmlspecialchars($searchWas);
} elseif (!empty($searchWer)) {
echo "Vergaben für: " . htmlspecialchars($searchWer);
} elseif (!empty($searchWas)) {
echo "Ausgeliehene Gegenstände: " . htmlspecialchars($searchWas);
} else {
echo "Aktuelle Vergaben";
}
?>
</h3>
<?php if (!empty($entries)): ?>
<table class="db-table mobile-responsive-table">
<thead>
<tr>
<th>Was</th>
<th>Wer</th>
<th>Wann ausgeliehen</th>
<th>Rückgabe</th>
<th style="width: 100px;">Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($entries as $entry): ?>
<tr>
<td data-label="Was"><?= htmlspecialchars($entry['WAS'] ?? '') ?></td>
<td data-label="Wer"><?= htmlspecialchars($entry['WER'] ?? '') ?></td>
<td data-label="Wann"><?= htmlspecialchars($entry['WANN'] ?? '') ?></td>
<td data-label="Rückgabe"><?= htmlspecialchars($entry['RUECKGABE'] ?? '') ?></td>
<td data-label="Aktion">
<form method="POST" onsubmit="return confirm('Möchtest du diesen Eintrag wirklich löschen?');" style="margin: 0;">
<input type="hidden" name="id" value="<?= $entry['ID'] ?? '' ?>">
<button type="submit" name="delete_entry" class="btn-delete">
Löschen
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p>Keine Einträge vorhanden.</p>
<?php endif; ?>
</div>
</div>
<script>
// Parameter `submitOnSelect` hinzugefügt (Standard: false)
function setupAutocomplete(inputId, suggestionsId, data, submitOnSelect = false) {
const input = document.getElementById(inputId);
const box = document.getElementById(suggestionsId);
if (!input || !box) return;
let items = [];
let activeIndex = -1;
function render(list) {
box.innerHTML = '';
items = list;
activeIndex = -1;
if (list.length === 0) {
box.style.display = 'none';
return;
}
list.forEach((val, i) => {
const div = document.createElement('div');
div.textContent = val;
div.addEventListener('mousedown', (e) => {
e.preventDefault();
selectItem(i);
});
box.appendChild(div);
});
box.style.display = 'block';
}
function updateActive() {
[...box.children].forEach((el, i) => {
el.classList.toggle('active', i === activeIndex);
});
if (activeIndex >= 0) {
box.children[activeIndex].scrollIntoView({
block: 'nearest'
});
}
}
function selectItem(i) {
if (items[i] !== undefined) {
input.value = items[i];
}
box.style.display = 'none';
activeIndex = -1;
// NEU: Wenn gewünscht, schicke das übergeordnete Formular sofort ab
if (submitOnSelect) {
const form = input.closest('form');
if (form) form.submit();
}
}
input.addEventListener('input', () => {
const val = input.value.toLowerCase();
if (val.length === 0) {
box.style.display = 'none';
return;
}
const filtered = data.filter(d => d.toLowerCase().includes(val));
render(filtered);
});
input.addEventListener('keydown', (e) => {
if (box.style.display !== 'block') return;
if (e.key === 'ArrowDown') {
e.preventDefault();
activeIndex = Math.min(activeIndex + 1, items.length - 1);
updateActive();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
activeIndex = Math.max(activeIndex - 1, 0);
updateActive();
} else if (e.key === 'Tab' || e.key === 'Enter') {
if (activeIndex >= 0) {
e.preventDefault();
selectItem(activeIndex);
} else {
box.style.display = 'none';
}
} else if (e.key === 'Escape') {
box.style.display = 'none';
}
});
document.addEventListener('click', (e) => {
if (e.target !== input) {
box.style.display = 'none';
}
});
}
// 1. Normales Autocomplete für das Eintragungs-Formular (schickt nicht automatisch ab)
setupAutocomplete('was-input', 'was-suggestions', <?= json_encode($existingWas) ?>);
setupAutocomplete('wer-input', 'wer-suggestions', <?= json_encode($existingWer) ?>);
// 2. Such-Autocomplete: Hier wird als 4. Parameter `true` übergeben, damit sofort abgesendet wird!
setupAutocomplete('search-wer-input', 'search-wer-suggestions', <?= json_encode($existingWer) ?>, true);
setupAutocomplete('search-was-input', 'search-was-suggestions', <?= json_encode($existingWas) ?>, true);
</script>
+369 -3
View File
@@ -981,7 +981,7 @@ body.dark-mode .info-value {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
margin-top: 20px; margin-top: 20px;
table-layout: fixed; /* table-layout: fixed; */
/* sorgt für feste Spaltenbreite */ /* sorgt für feste Spaltenbreite */
} }
@@ -997,9 +997,9 @@ body.dark-mode .info-value {
border-bottom: 1px solid var(--card-border); border-bottom: 1px solid var(--card-border);
/* Wichtig für gleiche Größe */ /* Wichtig für gleiche Größe */
overflow: hidden; /* overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap; */
} }
.db-table tr:hover { .db-table tr:hover {
@@ -1070,4 +1070,370 @@ select {
/* Dark Mode Pfeil */ /* Dark Mode Pfeil */
body.dark-mode select { body.dark-mode select {
background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg fill='%23ccc' height='20' viewBox='0 0 20 20' width='20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 7l5 5 5-5z'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg fill='%23ccc' height='20' viewBox='0 0 20 20' width='20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 7l5 5 5-5z'/%3E%3C/svg%3E");
}
/* ===== Vergabe Formular ===== */
.vergabe-form {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-left: 5px solid var(--btn-bg);
border-radius: 10px;
padding: 20px;
margin-bottom: 25px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
}
/* Desktop-First Grid Layout */
.vergabe-grid {
display: grid;
/* Nutzt minmax, damit die Datumsfelder auf kleineren Desktop-Monitoren nicht zerquetschen */
grid-template-columns: minmax(140px, 1fr) minmax(140px, 1fr) 1.3fr 2fr auto;
gap: 15px;
align-items: end;
}
.vergabe-grid .form-group {
margin: 0;
}
.vergabe-grid input,
.btn-submit-vergabe {
width: 100%;
/* Wichtig für Responsiveness */
padding: 10px 16px;
font-size: 15px;
border-radius: 5px;
box-sizing: border-box;
}
body.dark-mode input[type="date"] {
color-scheme: dark;
}
/* Dedizierter Speicher-Button Style */
.btn-submit-vergabe {
background: var(--btn-add-bg);
color: white;
border: none;
font-weight: 600;
cursor: pointer;
height: 43px;
/* Gleicht die Höhe der Inputs inklusive Padding/Border perfekt aus */
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s ease, transform 0.1s ease;
}
.btn-submit-vergabe:hover {
opacity: 0.9;
transform: translateY(-1px);
}
.btn-submit-vergabe:active {
transform: scale(0.98);
}
/* ===== Mobile Optimierung (Tablets & Smartphones) ===== */
@media (max-width: 1024px) {
.vergabe-grid {
/* Schaltet auf Mobilgeräten in eine saubere 1-spaltige Liste um */
grid-template-columns: 1fr;
gap: 12px;
/* Schafft vertikalen Abstand zwischen den Feldern */
}
.btn-submit-vergabe {
margin-top: 5px;
height: 48px;
/* Größere Touch-Fläche für Smartphones */
font-size: 1rem;
}
}
/* ===== Globale Mobile Anpassungen ===== */
@media (max-width: 768px) {
/* 1. Haupt-Layout untereinander brechen lassen */
.main {
flex-direction: column;
height: calc(100vh - 60px);
/* Behält die feste Gesamthöhe bei */
overflow-y: auto;
/* Erlaubt das Scrollen der gesamten Seite falls nötig */
}
/* 2. Sidebar nimmt volle Breite ein und passt sich dem Inhalt an */
.sidebar {
width: 100%;
height: auto;
/* Keine feste Höhe mehr auf Mobile */
border-right: none;
border-bottom: 1px solid #333;
/* Trennlinie nach unten statt nach rechts */
overflow-y: visible;
/* Scrollbar hier deaktivieren */
}
/* 3. Inhaltsbereich nimmt den Rest des Platzes ein */
.content {
width: 100%;
height: auto;
overflow-y: visible;
/* Verhindert doppelte Scrollbalken */
padding: 15px 10px;
}
/* 4. Das Vergabe-Formular bricht sauber in eine Spalte um */
.vergabe-grid {
grid-template-columns: 1fr !important;
/* Erzwingt eine Spalte */
gap: 12px;
}
/* 5. Inputs und Buttons nehmen die volle Breite der Box ein */
.vergabe-grid input,
.btn-submit-vergabe,
.vergabe-grid button {
width: 100% !important;
box-sizing: border-box;
}
/* Optional: Header-Buttons (Logout / Light Mode) für Mobile optimieren, falls sie überlappen */
#darkModeToggle,
#logoutButton {
position: static;
/* Entfernt absolute Positionierung im mobilen Header */
margin: 5px;
padding: 6px 12px;
font-size: 0.9rem;
}
header {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 10px;
height: auto;
}
}
/* ===== Mobile Optimierung für die Vergabe-Tabelle ===== */
@media (max-width: 768px) {
/* Verstecke die normale Tabellen-Überschrift */
.mobile-responsive-table thead {
display: none;
}
/* Mache aus der Tabelle ein Block-Layout */
.mobile-responsive-table,
.mobile-responsive-table tbody,
.mobile-responsive-table tr,
.mobile-responsive-table td {
display: block;
width: 100% !important;
}
/* Jede Zeile wird zu einer schicken Card */
.mobile-responsive-table tr {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 8px;
margin-bottom: 15px;
padding: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
/* Einzelne Zellen nebeneinander/untereinander stylen */
.mobile-responsive-table td {
text-align: right !important;
padding: 8px 10px !important;
border-bottom: 1px solid var(--card-border);
position: relative;
white-space: normal !important;
/* Erlaubt Textumbruch auf Handys */
overflow: visible !important;
}
.mobile-responsive-table td:last-child {
border-bottom: none;
padding-top: 12px !important;
}
/* Die Beschriftung (z.B. "Was:") links einblenden */
.mobile-responsive-table td::before {
content: attr(data-label);
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
font-weight: bold;
font-size: 0.9rem;
color: var(--text);
}
/* Lösch-Button auf Mobile über die gesamte Breite strecken für leichtere Bedienung */
.mobile-responsive-table .btn-delete {
width: 100%;
padding: 10px;
font-size: 1rem;
}
}
/* ===== Optimiertes Firmenberichte Erfassungsformular ===== */
.berichte-container {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-top: 4px solid #007bff;
/* Eleganter blauer Akzentstreifen oben statt links */
border-radius: 12px;
padding: 30px;
max-width: 500px;
/* Kompakt und perfekt lesbar auf Monitoren */
margin: 40px auto;
/* Schön zentriert mit Luft nach oben/unten */
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
}
/* Tiefenwirkung für den Dark Mode */
body.dark-mode .berichte-container {
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
background: #161616;
/* Passt sich perfekt deiner CSS-Variable an */
}
.berichte-form {
display: flex;
flex-direction: column;
gap: 20px;
}
.berichte-form .form-group {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
}
.berichte-form .form-group label {
font-size: 0.85rem;
font-weight: 700;
color: var(--text);
text-transform: uppercase;
letter-spacing: 0.8px;
opacity: 0.85;
}
/* Einheitlicher, moderner Look für ALLE Input-Typen */
.berichte-form input[type="text"],
.berichte-form input[type="time"],
.berichte-form input[type="date"] {
width: 100%;
padding: 12px 16px;
background: var(--input-bg);
border: 1px solid var(--input-border);
color: var(--input-text);
border-radius: 8px;
font-size: 0.95rem;
box-sizing: border-box;
transition: border-color 0.2s, box-shadow 0.2s;
}
/* Behebt das Problem mit den grellweißen Zeit/Datumsfeldern im Dark Mode */
body.dark-mode .berichte-form input[type="time"],
body.dark-mode .berichte-form input[type="date"] {
color-scheme: dark;
/* Zwingt den Browser, das native Dropdown dunkel zu machen */
background: #222;
border-color: #444;
color: #fff;
}
/* Fokus-Effekt (weicher blauer Schein) */
.berichte-form input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 4px rgba(0, 123, 255, 0.15);
}
body.dark-mode .berichte-form input:focus {
box-shadow: 0 0 0 4px rgba(0, 123, 255, 0.3);
}
/* Vollbreiter, moderner Absende-Button */
.btn-submit-bericht {
width: 100%;
background: #28a745;
/* Kräftiges Success-Grün */
color: white;
border: none;
padding: 14px;
font-size: 1rem;
font-weight: 600;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin-top: 10px;
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.2);
transition: all 0.2s ease;
}
.btn-submit-bericht:hover {
background: #218838;
transform: translateY(-1px);
box-shadow: 0 6px 15px rgba(40, 167, 69, 0.3);
}
.btn-submit-bericht:active {
transform: scale(0.98);
}
/* Kleine Bildschirme (Smartphones) */
@media (max-width: 600px) {
.berichte-container {
margin: 15px;
padding: 20px;
}
}
.autocomplete-wrapper {
position: relative;
}
.autocomplete-suggestions {
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
background: var(--card-bg);
color: var(--text);
border: 1px solid var(--input-border);
border-top: none;
max-height: 180px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.autocomplete-suggestions div {
padding: 8px 10px;
cursor: pointer;
}
.autocomplete-suggestions div:hover,
.autocomplete-suggestions div.active {
background: rgba(0, 123, 255, 0.15);
}
body.dark-mode .autocomplete-suggestions div:hover,
body.dark-mode .autocomplete-suggestions div.active {
background: rgba(0, 123, 255, 0.25);
} }
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
set -e
echo "Starte automatischen Merge- und Tag-Prozess..."
git config core.filemode false
echo "Aktualisiere lokale Branches vom Remote..."
git fetch --all --quiet
echo "Wechsle zu main..."
git checkout main --quiet
git pull origin main --quiet
echo "Merge: main -> dev..."
git checkout dev --quiet
git pull origin dev --quiet || true
git merge main --no-ff -m "Automated merge from main to dev" --quiet
git push origin dev --quiet
echo "Merge: dev -> live..."
git checkout live --quiet
git pull origin live --quiet || true
git merge dev --no-ff -m "Automated merge from dev to live" --quiet
git push origin live --quiet
TAG_NAME="v$(date +'%Y.%m.%d-%H%M')"
echo " Erstelle Tag: $TAG_NAME..."
git tag -a "$TAG_NAME" -m "Automated release tag $TAG_NAME"
git push origin "$TAG_NAME" --quiet
git checkout main --quiet
echo ""
echo "========================================================"
echo "✅ FERTIG! Alles sauber durchgelaufen."
echo "📦 Neuer Release-Tag gepusht: $TAG_NAME"
echo "========================================================"