feat: Erweiterung der Druckerstatusüberprüfung durch Implementierung einer Steckdosenabfrage für Drucker. Verbesserung der Benutzeroberfläche mit optimierten Dark Mode-Elementen und neuen Statusanzeigen für Drucker. Anpassungen in den Templates zur Unterstützung dynamischer Statusinformationen und zur Verbesserung der Benutzererfahrung. Aktualisierung der CSS-Styles für ein ansprechenderes Design und bessere Benutzerinteraktion.
This commit is contained in:
parent
e9071c7b57
commit
d81149229a
@ -1110,10 +1110,10 @@ def kiosk_restart_system():
|
||||
|
||||
def check_printer_status(ip_address: str, timeout: int = 7) -> Tuple[str, bool]:
|
||||
"""
|
||||
Überprüft den Status eines Druckers über Ping mit Timeout.
|
||||
Überprüft den Status eines Druckers über Steckdosenabfrage mit Timeout.
|
||||
|
||||
Args:
|
||||
ip_address: IP-Adresse des Druckers
|
||||
ip_address: IP-Adresse der Drucker-Steckdose
|
||||
timeout: Timeout in Sekunden (Standard: 7)
|
||||
|
||||
Returns:
|
||||
@ -1132,7 +1132,7 @@ def check_printer_status(ip_address: str, timeout: int = 7) -> Tuple[str, bool]:
|
||||
printers_logger.warning(f"Ungültige IP-Adresse: {ip_address}")
|
||||
return "offline", False
|
||||
|
||||
# Windows-spezifischer Ping-Befehl mit Timeout
|
||||
# Zuerst prüfen, ob die Steckdose erreichbar ist
|
||||
if os.name == 'nt': # Windows
|
||||
cmd = ['ping', '-n', '1', '-w', str(timeout * 1000), ip_address.strip()]
|
||||
else: # Unix/Linux/macOS
|
||||
@ -1150,19 +1150,66 @@ def check_printer_status(ip_address: str, timeout: int = 7) -> Tuple[str, bool]:
|
||||
timeout=timeout + 2 # Zusätzlicher Timeout für subprocess
|
||||
)
|
||||
|
||||
# Erfolgreicher Ping (Return Code 0)
|
||||
if result.returncode == 0:
|
||||
printers_logger.debug(f"Ping erfolgreich für {ip_address}")
|
||||
return "online", True
|
||||
else:
|
||||
# Wenn Steckdose nicht erreichbar ist, ist der Drucker offline
|
||||
if result.returncode != 0:
|
||||
printers_logger.debug(f"Ping fehlgeschlagen für {ip_address} (Return Code: {result.returncode})")
|
||||
return "offline", False
|
||||
|
||||
# Jetzt den tatsächlichen Steckdosenstatus abfragen
|
||||
db_session = get_db_session()
|
||||
printer = db_session.query(Printer).filter(Printer.plug_ip == ip_address).first()
|
||||
|
||||
if not printer:
|
||||
printers_logger.warning(f"Kein Drucker mit Steckdosen-IP {ip_address} gefunden")
|
||||
db_session.close()
|
||||
return "offline", False
|
||||
|
||||
# Smart Plug Status prüfen
|
||||
import requests
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
# Standardwerte aus der Datenbank verwenden
|
||||
username = printer.plug_username
|
||||
password = printer.plug_password
|
||||
|
||||
try:
|
||||
# Für TP-Link Smart Plugs oder kompatible Steckdosen
|
||||
auth = (username, password)
|
||||
response = requests.get(f"http://{ip_address}/status", auth=auth, timeout=timeout)
|
||||
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
status_data = response.json()
|
||||
# Überprüfen ob die Steckdose eingeschaltet ist
|
||||
if 'system' in status_data and 'get_sysinfo' in status_data['system']:
|
||||
if status_data['system']['get_sysinfo'].get('relay_state') == 1:
|
||||
printers_logger.debug(f"Steckdose {ip_address} ist eingeschaltet")
|
||||
db_session.close()
|
||||
return "online", True
|
||||
except (ValueError, KeyError) as e:
|
||||
printers_logger.debug(f"Fehler beim Parsen der Steckdosen-Antwort: {str(e)}")
|
||||
|
||||
# Zweiter Versuch mit einfacher GET-Anfrage
|
||||
response = requests.get(f"http://{ip_address}", auth=auth, timeout=timeout)
|
||||
if response.status_code == 200:
|
||||
printers_logger.debug(f"Steckdose {ip_address} antwortet auf HTTP-Anfrage")
|
||||
# Wenn wir hier ankommen, ist die Steckdose online, aber wir wissen nicht sicher, ob sie eingeschaltet ist
|
||||
# Da wir nur die Verfügbarkeit prüfen, nehmen wir an, dass sie aktiv ist, wenn sie antwortet
|
||||
db_session.close()
|
||||
return "online", True
|
||||
|
||||
except RequestException as e:
|
||||
printers_logger.debug(f"Fehler bei HTTP-Anfrage an Steckdose {ip_address}: {str(e)}")
|
||||
|
||||
# Wenn beide API-Anfragen fehlschlagen, können wir annehmen, dass die Steckdose nicht eingeschaltet ist
|
||||
db_session.close()
|
||||
return "offline", False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
printers_logger.warning(f"Ping-Timeout für Drucker {ip_address} nach {timeout} Sekunden")
|
||||
return "offline", False
|
||||
except Exception as e:
|
||||
printers_logger.error(f"Fehler beim Ping für Drucker {ip_address}: {str(e)}")
|
||||
printers_logger.error(f"Fehler beim Status-Check für Drucker {ip_address}: {str(e)}")
|
||||
return "offline", False
|
||||
|
||||
def check_multiple_printers_status(printers: List[Dict], timeout: int = 7) -> Dict[int, Tuple[str, bool]]:
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -20,22 +20,28 @@
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* Dark Mode Farben - Dunkler gemacht */
|
||||
--color-bg-primary: #0a0f1a; /* Dunkler als vorher */
|
||||
--color-bg-secondary: #131c2e; /* Dunkler als vorher */
|
||||
--color-text-primary: #f8fafc;
|
||||
/* Dark Mode Farben - Noch dunkler und eleganter */
|
||||
--color-bg-primary: #000000; /* Tiefes Schwarz */
|
||||
--color-bg-secondary: #0a0a0a; /* Sehr dunkles Grau */
|
||||
--color-text-primary: #ffffff;
|
||||
--color-text-secondary: #e2e8f0;
|
||||
--color-text-muted: #94a3b8;
|
||||
--color-border-primary: #1e293b; /* Angepasst */
|
||||
--color-accent: #f8fafc; /* Weiß statt Indigo */
|
||||
--color-accent-hover: #e2e8f0;
|
||||
--color-accent-text: #0f172a;
|
||||
--color-shadow: rgba(0, 0, 0, 0.5); /* Dunklerer Schatten */
|
||||
--color-border-primary: #1a1a1a; /* Dunkler Rahmen */
|
||||
--color-accent: #ffffff; /* Reines Weiß */
|
||||
--color-accent-hover: #f0f0f0;
|
||||
--color-accent-text: #000000;
|
||||
--color-shadow: rgba(0, 0, 0, 0.8); /* Sehr dunkler Schatten */
|
||||
--mb-black: #000000; /* Mercedes-Benz Schwarz */
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-white dark:bg-[#050505] text-slate-900 dark:text-slate-200 transition-colors duration-300;
|
||||
@apply bg-white dark:bg-black text-slate-900 dark:text-white transition-colors duration-300;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.dark body {
|
||||
background: linear-gradient(135deg, #000000 0%, #0a0a0a 50%, #000000 100%);
|
||||
}
|
||||
|
||||
/* Navbar Styles - Glassmorphism ohne überlagerte Hintergründe */
|
||||
@ -407,25 +413,98 @@
|
||||
}
|
||||
|
||||
.dark .mercedes-background::before {
|
||||
opacity: 0.02; /* Leicht reduziert für dunkleren Hintergrund */
|
||||
filter: invert(1);
|
||||
opacity: 0.015; /* Sehr subtil für eleganten Dark Mode */
|
||||
filter: invert(1) brightness(0.3);
|
||||
background-size: 150px 150px; /* Größere Sterne für bessere Sichtbarkeit */
|
||||
}
|
||||
|
||||
/* Monochrome Button Styles */
|
||||
@layer components {
|
||||
/* Buttons im Light Mode Schwarz statt Blau */
|
||||
/* Buttons mit verstärktem Glassmorphism */
|
||||
.btn-primary {
|
||||
@apply bg-black/80 hover:bg-gray-800/80 dark:bg-white/80 dark:hover:bg-gray-200/80 text-white dark:text-slate-900 px-4 py-2 rounded-lg transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 shadow-xl backdrop-blur-lg;
|
||||
backdrop-filter: blur(16px) saturate(150%) brightness(110%);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(150%) brightness(110%);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
@apply text-white dark:text-slate-900 px-4 py-2 rounded-lg transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 shadow-2xl hover:-translate-y-0.5;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: blur(20px) saturate(150%) brightness(110%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(150%) brightness(110%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 0, 0, 0.3),
|
||||
0 8px 16px rgba(0, 0, 0, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
backdrop-filter: blur(25px) saturate(180%) brightness(120%);
|
||||
-webkit-backdrop-filter: blur(25px) saturate(180%) brightness(120%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.4),
|
||||
0 10px 20px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.dark .btn-primary {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 0, 0, 0.2),
|
||||
0 8px 16px rgba(0, 0, 0, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8),
|
||||
0 0 0 1px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.dark .btn-primary:hover {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.3),
|
||||
0 10px 20px rgba(0, 0, 0, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply bg-white/70 hover:bg-gray-100/70 dark:bg-slate-800/70 dark:hover:bg-slate-700/70 text-slate-900 dark:text-white border border-gray-300/60 dark:border-slate-700/60 px-4 py-2 rounded-lg transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2 shadow-xl backdrop-blur-lg;
|
||||
backdrop-filter: blur(16px) saturate(150%) brightness(110%);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(150%) brightness(110%);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
@apply text-slate-900 dark:text-white px-4 py-2 rounded-lg transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2 shadow-2xl hover:-translate-y-0.5;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
backdrop-filter: blur(20px) saturate(150%) brightness(110%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(150%) brightness(110%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 0, 0, 0.15),
|
||||
0 8px 16px rgba(0, 0, 0, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.5),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
backdrop-filter: blur(25px) saturate(180%) brightness(120%);
|
||||
-webkit-backdrop-filter: blur(25px) saturate(180%) brightness(120%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.2),
|
||||
0 10px 20px rgba(0, 0, 0, 0.15),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.dark .btn-secondary {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 0, 0, 0.3),
|
||||
0 8px 16px rgba(0, 0, 0, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.dark .btn-secondary:hover {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.4),
|
||||
0 10px 20px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
@ -435,23 +514,59 @@
|
||||
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Glassmorphism Card mit abgerundeten Ecken */
|
||||
/* Glassmorphism Card mit abgerundeten Ecken - Verstärkt */
|
||||
.glass-card {
|
||||
@apply bg-white/70 dark:bg-black/70 backdrop-blur-xl border border-gray-200/60 dark:border-slate-700/40 rounded-xl p-6 shadow-xl transition-all duration-300;
|
||||
backdrop-filter: blur(20px) saturate(180%) brightness(110%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(180%) brightness(110%);
|
||||
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
@apply rounded-xl p-6 shadow-2xl transition-all duration-300;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
backdrop-filter: blur(30px) saturate(200%) brightness(120%) contrast(110%);
|
||||
-webkit-backdrop-filter: blur(30px) saturate(200%) brightness(120%) contrast(110%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.15),
|
||||
0 8px 16px rgba(0, 0, 0, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
border-radius: var(--card-radius);
|
||||
}
|
||||
|
||||
.dark .glass-card {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(30px) saturate(180%) brightness(110%) contrast(120%);
|
||||
-webkit-backdrop-filter: blur(30px) saturate(180%) brightness(110%) contrast(120%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.4),
|
||||
0 8px 16px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.15),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Dashboard Cards mit schwarzem Hintergrund */
|
||||
/* Dashboard Cards mit verstärktem Glassmorphism */
|
||||
.dashboard-card {
|
||||
@apply bg-white/60 dark:bg-black/80 backdrop-blur-2xl border border-gray-200/70 dark:border-slate-700/20 rounded-xl p-6 shadow-xl transition-all duration-300;
|
||||
backdrop-filter: blur(24px) saturate(200%) brightness(120%);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(200%) brightness(120%);
|
||||
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
@apply rounded-xl p-6 shadow-2xl transition-all duration-300 hover:-translate-y-1;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
backdrop-filter: blur(35px) saturate(200%) brightness(125%) contrast(115%);
|
||||
-webkit-backdrop-filter: blur(35px) saturate(200%) brightness(125%) contrast(115%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.15),
|
||||
0 8px 16px rgba(0, 0, 0, 0.08),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.25),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
border-radius: var(--card-radius);
|
||||
}
|
||||
|
||||
.dark .dashboard-card {
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
backdrop-filter: blur(35px) saturate(180%) brightness(115%) contrast(125%);
|
||||
-webkit-backdrop-filter: blur(35px) saturate(180%) brightness(115%) contrast(125%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.5),
|
||||
0 8px 16px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.12),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Navigation Styles */
|
||||
.nav-link {
|
||||
@ -498,8 +613,26 @@
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
@apply flex items-center justify-center space-x-1 md:space-x-3 lg:space-x-6 p-2 rounded-full;
|
||||
background: transparent;
|
||||
@apply flex items-center justify-center space-x-1 md:space-x-3 lg:space-x-6 p-3 mx-4 rounded-2xl border;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
backdrop-filter: blur(20px) saturate(150%) brightness(110%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(150%) brightness(110%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow:
|
||||
0 4px 16px rgba(0, 0, 0, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.dark .navbar-menu {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(20px) saturate(150%) brightness(110%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(150%) brightness(110%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
box-shadow:
|
||||
0 4px 16px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.navbar-button {
|
||||
@ -529,21 +662,81 @@
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
@apply flex items-center space-x-2 px-4 py-2.5 text-slate-700 dark:text-slate-300 rounded-full hover:bg-white/50 dark:hover:bg-slate-800/50 hover:text-slate-900 dark:hover:text-white transition-all duration-300 hover:shadow-md;
|
||||
@apply flex items-center space-x-2 px-4 py-2.5 text-slate-700 dark:text-slate-300 rounded-xl transition-all duration-300;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
@apply text-slate-900 dark:text-white;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
backdrop-filter: blur(15px) saturate(150%);
|
||||
-webkit-backdrop-filter: blur(15px) saturate(150%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.dark .menu-item {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.dark .menu-item:hover {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.menu-item.active {
|
||||
@apply bg-white/60 dark:bg-black/60 text-slate-900 dark:text-white font-medium shadow-sm backdrop-blur-sm;
|
||||
@apply text-slate-900 dark:text-white font-medium;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
backdrop-filter: blur(20px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
box-shadow:
|
||||
0 4px 16px rgba(0, 0, 0, 0.15),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.dark .menu-item.active {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow:
|
||||
0 4px 16px rgba(0, 0, 0, 0.4),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* Dropdown Styles */
|
||||
/* Dropdown Styles - Verstärktes Glassmorphism */
|
||||
.user-dropdown {
|
||||
@apply absolute right-0 mt-2 w-64 bg-white/70 dark:bg-black/80 backdrop-blur-2xl border border-gray-200/70 dark:border-slate-700/20 rounded-xl shadow-2xl z-50 overflow-hidden;
|
||||
backdrop-filter: blur(24px) saturate(200%) brightness(120%);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(200%) brightness(120%);
|
||||
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
@apply absolute right-0 mt-2 w-64 rounded-xl shadow-2xl z-50 overflow-hidden;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(40px) saturate(200%) brightness(130%) contrast(110%);
|
||||
-webkit-backdrop-filter: blur(40px) saturate(200%) brightness(130%) contrast(110%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.25),
|
||||
0 8px 16px rgba(0, 0, 0, 0.15),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
animation: fadeIn 0.2s ease-out forwards;
|
||||
}
|
||||
|
||||
.dark .user-dropdown {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(40px) saturate(180%) brightness(120%) contrast(120%);
|
||||
-webkit-backdrop-filter: blur(40px) saturate(180%) brightness(120%) contrast(120%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.6),
|
||||
0 8px 16px rgba(0, 0, 0, 0.4),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.dropdown-header {
|
||||
@apply flex items-center p-4 border-b border-gray-200/80 dark:border-slate-700/30;
|
||||
@ -581,30 +774,299 @@
|
||||
width: 100% !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
@apply bg-white/60 dark:bg-black/70;
|
||||
backdrop-filter: blur(24px) saturate(200%);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(200%);
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.08);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
/* Dark Mode Toggle - Schwarz statt Blau im Light Mode */
|
||||
.dark-mode-toggle {
|
||||
@apply p-3 rounded-full bg-black/80 hover:bg-gray-800/80 dark:bg-white/80 dark:hover:bg-gray-200/80 text-white dark:text-slate-900 transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-gray-500 shadow-xl;
|
||||
backdrop-filter: blur(12px) saturate(150%);
|
||||
-webkit-backdrop-filter: blur(12px) saturate(150%);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
min-width: 42px;
|
||||
min-height: 42px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.dark .navbar {
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Animation für Dropdown */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
/* Neue Navbar-Komponenten */
|
||||
.navbar-menu-new {
|
||||
@apply flex items-center justify-center space-x-3 md:space-x-2 lg:space-x-1 xl:space-x-2;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.dropdown-animation {
|
||||
animation: fadeIn 0.2s ease-out forwards;
|
||||
}
|
||||
.navbar-menu-new::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
@apply flex items-center space-x-2 px-3 py-2 rounded-lg text-sm font-medium transition-all duration-300;
|
||||
color: rgba(15, 23, 42, 0.8);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.dark .nav-item {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
@apply transform -translate-y-0.5;
|
||||
color: rgba(15, 23, 42, 1);
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow:
|
||||
0 10px 20px rgba(0, 0, 0, 0.05),
|
||||
0 2px 6px rgba(0, 0, 0, 0.04),
|
||||
0 0 1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dark .nav-item:hover {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow:
|
||||
0 10px 20px rgba(0, 0, 0, 0.2),
|
||||
0 2px 6px rgba(0, 0, 0, 0.15),
|
||||
0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: rgba(15, 23, 42, 1);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
box-shadow:
|
||||
0 10px 25px rgba(0, 0, 0, 0.08),
|
||||
0 3px 8px rgba(0, 0, 0, 0.06),
|
||||
0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dark .nav-item.active {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
box-shadow:
|
||||
0 10px 25px rgba(0, 0, 0, 0.3),
|
||||
0 3px 8px rgba(0, 0, 0, 0.2),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
/* Dark Mode Toggle - Neues Design */
|
||||
.dark-mode-toggle-new {
|
||||
@apply relative p-2.5 rounded-full flex items-center justify-center transition-all duration-300 cursor-pointer;
|
||||
background: rgba(241, 245, 249, 0.8);
|
||||
border: 1px solid rgba(255, 255, 255, 0.7);
|
||||
box-shadow:
|
||||
0 2px 10px rgba(0, 0, 0, 0.05),
|
||||
0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
color: #334155;
|
||||
z-index: 100; /* Stellt sicher, dass der Button über anderen Elementen liegt */
|
||||
}
|
||||
|
||||
.dark-mode-toggle-new:hover {
|
||||
@apply transform -translate-y-0.5;
|
||||
background: rgba(241, 245, 249, 0.9);
|
||||
box-shadow:
|
||||
0 10px 20px rgba(0, 0, 0, 0.08),
|
||||
0 2px 6px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.dark-mode-toggle-new:active {
|
||||
@apply transform scale-95;
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
|
||||
.dark .dark-mode-toggle-new {
|
||||
background: rgba(30, 41, 59, 0.8);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow:
|
||||
0 2px 10px rgba(0, 0, 0, 0.2),
|
||||
0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.dark .dark-mode-toggle-new:hover {
|
||||
background: rgba(30, 41, 59, 0.9);
|
||||
box-shadow:
|
||||
0 10px 20px rgba(0, 0, 0, 0.2),
|
||||
0 2px 6px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Icon-Animation */
|
||||
.dark-mode-toggle-new .sun-icon,
|
||||
.dark-mode-toggle-new .moon-icon {
|
||||
@apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transition-all duration-300;
|
||||
}
|
||||
|
||||
.dark-mode-toggle-new .sun-icon:not(.hidden) {
|
||||
animation: spin-in 0.5s cubic-bezier(0.25, 1, 0.5, 1) forwards;
|
||||
}
|
||||
|
||||
.dark-mode-toggle-new .moon-icon:not(.hidden) {
|
||||
animation: spin-in 0.5s cubic-bezier(0.25, 1, 0.5, 1) forwards;
|
||||
}
|
||||
|
||||
@keyframes spin-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(10px) scale(0.7) rotate(20deg);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1) rotate(0);
|
||||
}
|
||||
}
|
||||
|
||||
.dark .sun-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dark .moon-icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sun-icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.moon-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* User Menu Button - Neues Design */
|
||||
.user-menu-button-new {
|
||||
@apply flex items-center space-x-2 rounded-lg p-1.5 transition-all duration-300;
|
||||
background: rgba(241, 245, 249, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
box-shadow:
|
||||
0 2px 10px rgba(0, 0, 0, 0.04),
|
||||
0 1px 2px rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.user-menu-button-new:hover {
|
||||
@apply transform -translate-y-0.5;
|
||||
background: rgba(241, 245, 249, 0.8);
|
||||
box-shadow:
|
||||
0 10px 20px rgba(0, 0, 0, 0.06),
|
||||
0 2px 6px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.dark .user-menu-button-new {
|
||||
background: rgba(30, 41, 59, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
box-shadow:
|
||||
0 2px 10px rgba(0, 0, 0, 0.15),
|
||||
0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dark .user-menu-button-new:hover {
|
||||
background: rgba(30, 41, 59, 0.8);
|
||||
box-shadow:
|
||||
0 10px 20px rgba(0, 0, 0, 0.15),
|
||||
0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* User Avatar - Neues Design */
|
||||
.user-avatar-new {
|
||||
@apply h-8 w-8 rounded-full flex items-center justify-center text-white font-semibold text-sm shadow-md transition-all duration-300;
|
||||
background: linear-gradient(135deg, #000000, #333333);
|
||||
box-shadow:
|
||||
0 2px 6px rgba(0, 0, 0, 0.2),
|
||||
0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dark .user-avatar-new {
|
||||
background: linear-gradient(135deg, #f8fafc, #e2e8f0);
|
||||
color: #0f172a;
|
||||
box-shadow:
|
||||
0 2px 6px rgba(0, 0, 0, 0.3),
|
||||
0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Login Button - Neues Design */
|
||||
.login-button-new {
|
||||
@apply flex items-center px-4 py-2 rounded-lg text-sm font-medium shadow-sm transition-all duration-300;
|
||||
background: #000000;
|
||||
color: #ffffff;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow:
|
||||
0 2px 10px rgba(0, 0, 0, 0.1),
|
||||
0 1px 2px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.login-button-new:hover {
|
||||
@apply transform -translate-y-0.5;
|
||||
background: #333333;
|
||||
box-shadow:
|
||||
0 10px 20px rgba(0, 0, 0, 0.15),
|
||||
0 3px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dark .login-button-new {
|
||||
background: #ffffff;
|
||||
color: #000000;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow:
|
||||
0 2px 10px rgba(0, 0, 0, 0.2),
|
||||
0 1px 2px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.dark .login-button-new:hover {
|
||||
background: #f1f5f9;
|
||||
box-shadow:
|
||||
0 10px 20px rgba(0, 0, 0, 0.25),
|
||||
0 3px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Mobile Menu - Neues Design */
|
||||
.mobile-menu-new {
|
||||
@apply w-full overflow-hidden transition-all duration-300 z-40;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(24px);
|
||||
-webkit-backdrop-filter: blur(24px);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
|
||||
border-bottom: 1px solid rgba(241, 245, 249, 0.8);
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.mobile-menu-new.open {
|
||||
max-height: 500px;
|
||||
opacity: 1;
|
||||
border-bottom: 1px solid rgba(241, 245, 249, 0.8);
|
||||
}
|
||||
|
||||
.dark .mobile-menu-new {
|
||||
background: rgba(15, 23, 42, 0.8);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||
border-bottom: 1px solid rgba(30, 41, 59, 0.8);
|
||||
}
|
||||
|
||||
.mobile-nav-item {
|
||||
@apply flex items-center space-x-3 px-4 py-3 rounded-lg text-slate-800 dark:text-slate-200 transition-all duration-300;
|
||||
}
|
||||
|
||||
.mobile-nav-item:hover {
|
||||
background: rgba(241, 245, 249, 0.8);
|
||||
}
|
||||
|
||||
.dark .mobile-nav-item:hover {
|
||||
background: rgba(30, 41, 59, 0.6);
|
||||
}
|
||||
|
||||
.mobile-nav-item.active {
|
||||
background: rgba(241, 245, 249, 0.9);
|
||||
color: #000000;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dark .mobile-nav-item.active {
|
||||
background: rgba(30, 41, 59, 0.8);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Dashboard Stat Cards mit schwarzem Hintergrund im Dark Mode */
|
||||
.mb-stat-card {
|
||||
@ -634,12 +1096,30 @@
|
||||
border-radius: var(--card-radius);
|
||||
}
|
||||
|
||||
/* Footer Styling */
|
||||
/* Footer Styling - Verstärktes Glassmorphism */
|
||||
footer {
|
||||
@apply bg-white/60 dark:bg-black/60 backdrop-blur-xl border-t border-gray-200/70 dark:border-slate-700/20 transition-all duration-300;
|
||||
backdrop-filter: blur(20px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
||||
box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
@apply transition-all duration-300;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(30px) saturate(180%) brightness(120%);
|
||||
-webkit-backdrop-filter: blur(30px) saturate(180%) brightness(120%);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow:
|
||||
0 -8px 32px rgba(0, 0, 0, 0.1),
|
||||
0 -2px 8px rgba(0, 0, 0, 0.05),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.dark footer {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(30px) saturate(160%) brightness(110%);
|
||||
-webkit-backdrop-filter: blur(30px) saturate(160%) brightness(110%);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow:
|
||||
0 -8px 32px rgba(0, 0, 0, 0.3),
|
||||
0 -2px 8px rgba(0, 0, 0, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
/* Dropdown Pfeil Animation */
|
||||
@ -668,6 +1148,286 @@ footer {
|
||||
}
|
||||
|
||||
.dark .mercedes-star-bg::after {
|
||||
opacity: 0.03;
|
||||
filter: invert(1);
|
||||
}
|
||||
opacity: 0.02;
|
||||
filter: invert(1) brightness(0.4);
|
||||
}
|
||||
|
||||
/* Zusätzliche Glassmorphism-Verbesserungen */
|
||||
.glass-effect {
|
||||
backdrop-filter: blur(20px) saturate(180%) brightness(110%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(180%) brightness(110%);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.dark .glass-effect {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
/* Verbesserte Hover-Effekte für alle interaktiven Elemente */
|
||||
.glass-hover {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.glass-hover:hover {
|
||||
transform: translateY(-2px);
|
||||
backdrop-filter: blur(25px) saturate(200%) brightness(120%);
|
||||
-webkit-backdrop-filter: blur(25px) saturate(200%) brightness(120%);
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 0, 0, 0.15),
|
||||
0 8px 16px rgba(0, 0, 0, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.dark .glass-hover:hover {
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 0, 0, 0.4),
|
||||
0 8px 16px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* Neue verbesserte Drucker-Karten für die Drucker-Ansicht */
|
||||
.printer-card-new {
|
||||
@apply bg-gradient-to-br from-white/90 to-white/70 dark:from-slate-800/90 dark:to-slate-900/70 backdrop-blur-2xl rounded-xl border border-gray-200/70 dark:border-slate-700/30 p-5 shadow-2xl transition-all duration-300 hover:-translate-y-1 relative overflow-hidden;
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 0, 0, 0.08),
|
||||
0 10px 20px rgba(0, 0, 0, 0.06),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
border-radius: var(--card-radius, 1rem);
|
||||
}
|
||||
|
||||
.dark .printer-card-new {
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 0, 0, 0.4),
|
||||
0 10px 20px rgba(0, 0, 0, 0.3),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Online Drucker-Karten mit stärkerem visuellen Unterschied */
|
||||
.printer-card-new.online {
|
||||
@apply bg-gradient-to-br from-green-50/90 to-emerald-50/80 dark:from-green-900/30 dark:to-emerald-900/20 border-green-200 dark:border-green-700/50;
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 122, 85, 0.08),
|
||||
0 10px 20px rgba(0, 122, 85, 0.06),
|
||||
0 0 0 1px rgba(209, 250, 229, 0.4);
|
||||
}
|
||||
|
||||
.dark .printer-card-new.online {
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 0, 0, 0.3),
|
||||
0 10px 20px rgba(0, 0, 0, 0.2),
|
||||
0 0 0 1px rgba(16, 185, 129, 0.2);
|
||||
}
|
||||
|
||||
/* Status-Badge mit verbesserten Styles */
|
||||
.status-badge-new {
|
||||
@apply px-2.5 py-1 rounded-full text-xs font-medium inline-flex items-center space-x-1 shadow-sm;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.dark .status-badge-new {
|
||||
background: rgba(30, 41, 59, 0.7);
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.status-badge-new.online {
|
||||
@apply bg-green-100/90 text-green-800 dark:bg-green-900/60 dark:text-green-300;
|
||||
}
|
||||
|
||||
.status-badge-new.offline {
|
||||
@apply bg-red-100/90 text-red-800 dark:bg-red-900/60 dark:text-red-300;
|
||||
}
|
||||
|
||||
/* Verbesserte Filterleiste */
|
||||
.filter-bar-new {
|
||||
@apply bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-lg p-1.5 border border-gray-200/60 dark:border-slate-700/30 shadow-xl;
|
||||
box-shadow:
|
||||
0 10px 25px rgba(0, 0, 0, 0.05),
|
||||
0 5px 10px rgba(0, 0, 0, 0.03),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.dark .filter-bar-new {
|
||||
box-shadow:
|
||||
0 10px 25px rgba(0, 0, 0, 0.2),
|
||||
0 5px 10px rgba(0, 0, 0, 0.15),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.filter-btn-new {
|
||||
@apply px-3.5 py-2 text-sm rounded-md transition-all duration-300 font-medium;
|
||||
}
|
||||
|
||||
.filter-btn-new.active {
|
||||
@apply bg-black text-white dark:bg-white dark:text-slate-900 shadow-md;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dark .filter-btn-new.active {
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Verbesserte Aktionsschaltflächen */
|
||||
.action-btn-new {
|
||||
@apply flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg font-medium text-sm transition-all duration-300 shadow-md hover:-translate-y-0.5;
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.action-btn-new.primary {
|
||||
@apply bg-indigo-600 hover:bg-indigo-700 text-white dark:bg-indigo-600 dark:hover:bg-indigo-500;
|
||||
box-shadow: 0 5px 15px rgba(79, 70, 229, 0.2);
|
||||
}
|
||||
|
||||
.dark .action-btn-new.primary {
|
||||
box-shadow: 0 5px 15px rgba(79, 70, 229, 0.3);
|
||||
}
|
||||
|
||||
.action-btn-new.success {
|
||||
@apply bg-green-600 hover:bg-green-700 text-white dark:bg-green-600 dark:hover:bg-green-500;
|
||||
box-shadow: 0 5px 15px rgba(16, 185, 129, 0.2);
|
||||
}
|
||||
|
||||
.dark .action-btn-new.success {
|
||||
box-shadow: 0 5px 15px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.action-btn-new.danger {
|
||||
@apply bg-red-600 hover:bg-red-700 text-white dark:bg-red-600 dark:hover:bg-red-500;
|
||||
box-shadow: 0 5px 15px rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
|
||||
.dark .action-btn-new.danger {
|
||||
box-shadow: 0 5px 15px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
/* Informationszeilen in Drucker-Karten */
|
||||
.printer-info-row {
|
||||
@apply flex items-center gap-2 text-xs sm:text-sm text-slate-700 dark:text-slate-300 mb-1.5;
|
||||
}
|
||||
|
||||
.printer-info-icon {
|
||||
@apply w-3.5 h-3.5 sm:w-4 sm:h-4 text-slate-500 dark:text-slate-400 flex-shrink-0;
|
||||
}
|
||||
|
||||
/* Online-Indikator mit Pulseffekt */
|
||||
.online-indicator {
|
||||
@apply absolute top-2.5 right-2.5 w-3 h-3 bg-green-500 rounded-full shadow-lg;
|
||||
box-shadow: 0 0 0 rgba(16, 185, 129, 0.6);
|
||||
animation: pulse-ring 2s cubic-bezier(0.455, 0.03, 0.515, 0.955) infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-ring {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.6);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 6px rgba(16, 185, 129, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header mit verbesserten Status-Anzeigen */
|
||||
.status-overview-new {
|
||||
@apply flex flex-wrap gap-3 text-xs sm:text-sm p-3 bg-white/60 dark:bg-slate-800/60 backdrop-blur-xl rounded-lg border border-gray-200/60 dark:border-slate-700/30 shadow-lg;
|
||||
box-shadow:
|
||||
0 10px 25px rgba(0, 0, 0, 0.04),
|
||||
0 5px 10px rgba(0, 0, 0, 0.02),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.dark .status-overview-new {
|
||||
box-shadow:
|
||||
0 10px 25px rgba(0, 0, 0, 0.15),
|
||||
0 5px 10px rgba(0, 0, 0, 0.1),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
@apply w-2.5 h-2.5 rounded-full;
|
||||
}
|
||||
|
||||
.status-dot.online {
|
||||
@apply bg-green-500;
|
||||
animation: pulse-dot 2s cubic-bezier(0.455, 0.03, 0.515, 0.955) infinite;
|
||||
}
|
||||
|
||||
.status-dot.offline {
|
||||
@apply bg-red-500;
|
||||
}
|
||||
|
||||
@keyframes pulse-dot {
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.95);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Verbesserte Modals mit Glassmorphism */
|
||||
.modal-new {
|
||||
@apply fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm;
|
||||
}
|
||||
|
||||
.modal-content-new {
|
||||
@apply bg-white/90 dark:bg-slate-800/90 backdrop-blur-2xl rounded-2xl p-6 w-full max-w-md shadow-2xl border border-gray-200/60 dark:border-slate-700/30 transform transition-all duration-300;
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.15),
|
||||
0 15px 30px rgba(0, 0, 0, 0.1),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
animation: modal-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.dark .modal-content-new {
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.4),
|
||||
0 15px 30px rgba(0, 0, 0, 0.3),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
@keyframes modal-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(10px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Verbesserte Input-Felder für Modals */
|
||||
.input-new {
|
||||
@apply w-full px-3 py-2.5 bg-white/70 dark:bg-slate-700/70 border border-gray-300/60 dark:border-slate-600/60 rounded-lg text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-black dark:focus:ring-white focus:border-transparent transition-all duration-200 backdrop-blur-lg;
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.dark .input-new {
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Verbesserte Form-Labels */
|
||||
.form-label-new {
|
||||
@apply block mb-2 text-sm font-medium text-slate-900 dark:text-white;
|
||||
}
|
2
backend/app/static/css/tailwind.min.css
vendored
2
backend/app/static/css/tailwind.min.css
vendored
File diff suppressed because one or more lines are too long
159
backend/app/static/js/dark-mode-fix.js
Normal file
159
backend/app/static/js/dark-mode-fix.js
Normal file
@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Dark Mode Toggle Fix
|
||||
* Diese Datei stellt sicher, dass der Dark Mode Toggle Button korrekt funktioniert
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Dark Mode Toggle Button
|
||||
const darkModeToggle = document.getElementById('darkModeToggle');
|
||||
const html = document.documentElement;
|
||||
|
||||
// Local Storage Key
|
||||
const STORAGE_KEY = 'myp-dark-mode';
|
||||
|
||||
/**
|
||||
* Aktuellen Dark Mode Status aus Local Storage oder Systemeinstellung abrufen
|
||||
*/
|
||||
function isDarkMode() {
|
||||
const savedMode = localStorage.getItem(STORAGE_KEY);
|
||||
if (savedMode !== null) {
|
||||
return savedMode === 'true';
|
||||
}
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Icons im Toggle-Button aktualisieren
|
||||
*/
|
||||
function updateIcons(isDark) {
|
||||
// Finde die Icons im Button - versuche sowohl direkte Kinder als auch verschachtelte
|
||||
let sunIcon = darkModeToggle.querySelector('.sun-icon');
|
||||
let moonIcon = darkModeToggle.querySelector('.moon-icon');
|
||||
|
||||
// Wenn Icons nicht gefunden wurden, erstelle sie neu
|
||||
if (!sunIcon || !moonIcon) {
|
||||
console.warn('Icons nicht gefunden - erzeuge neue Icons');
|
||||
|
||||
// Entferne vorhandene Inhalte im Button
|
||||
darkModeToggle.innerHTML = '';
|
||||
|
||||
// Neue Icons erstellen
|
||||
sunIcon = document.createElement('svg');
|
||||
sunIcon.className = 'w-5 h-5 sm:w-5 sm:h-5 sun-icon';
|
||||
sunIcon.setAttribute('fill', 'none');
|
||||
sunIcon.setAttribute('stroke', 'currentColor');
|
||||
sunIcon.setAttribute('viewBox', '0 0 24 24');
|
||||
sunIcon.setAttribute('aria-hidden', 'true');
|
||||
sunIcon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />';
|
||||
|
||||
moonIcon = document.createElement('svg');
|
||||
moonIcon.className = 'w-5 h-5 sm:w-5 sm:h-5 moon-icon hidden';
|
||||
moonIcon.setAttribute('fill', 'none');
|
||||
moonIcon.setAttribute('stroke', 'currentColor');
|
||||
moonIcon.setAttribute('viewBox', '0 0 24 24');
|
||||
moonIcon.setAttribute('aria-hidden', 'true');
|
||||
moonIcon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />';
|
||||
|
||||
darkModeToggle.appendChild(sunIcon);
|
||||
darkModeToggle.appendChild(moonIcon);
|
||||
}
|
||||
|
||||
// Icons entsprechend dem Dark Mode Status anzeigen/verbergen
|
||||
if (isDark) {
|
||||
sunIcon.classList.add('hidden');
|
||||
moonIcon.classList.remove('hidden');
|
||||
} else {
|
||||
sunIcon.classList.remove('hidden');
|
||||
moonIcon.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dark Mode aktivieren/deaktivieren
|
||||
*/
|
||||
function setDarkMode(enable) {
|
||||
console.log(`Setze Dark Mode auf: ${enable ? 'Aktiviert' : 'Deaktiviert'}`);
|
||||
|
||||
if (enable) {
|
||||
html.classList.add('dark');
|
||||
html.setAttribute('data-theme', 'dark');
|
||||
html.style.colorScheme = 'dark';
|
||||
|
||||
if (darkModeToggle) {
|
||||
darkModeToggle.setAttribute('aria-pressed', 'true');
|
||||
darkModeToggle.setAttribute('title', 'Light Mode aktivieren');
|
||||
// Button-Icons aktualisieren
|
||||
updateIcons(true);
|
||||
}
|
||||
} else {
|
||||
html.classList.remove('dark');
|
||||
html.setAttribute('data-theme', 'light');
|
||||
html.style.colorScheme = 'light';
|
||||
|
||||
if (darkModeToggle) {
|
||||
darkModeToggle.setAttribute('aria-pressed', 'false');
|
||||
darkModeToggle.setAttribute('title', 'Dark Mode aktivieren');
|
||||
// Button-Icons aktualisieren
|
||||
updateIcons(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Einstellung im Local Storage speichern
|
||||
localStorage.setItem(STORAGE_KEY, enable.toString());
|
||||
|
||||
// ThemeColor Meta-Tag aktualisieren
|
||||
const metaThemeColor = document.getElementById('metaThemeColor');
|
||||
if (metaThemeColor) {
|
||||
metaThemeColor.setAttribute('content', enable ? '#000000' : '#ffffff');
|
||||
}
|
||||
|
||||
// Event für andere Komponenten auslösen
|
||||
window.dispatchEvent(new CustomEvent('darkModeChanged', {
|
||||
detail: { isDark: enable }
|
||||
}));
|
||||
}
|
||||
|
||||
// Toggle Dark Mode Funktion
|
||||
function toggleDarkMode() {
|
||||
const currentMode = isDarkMode();
|
||||
setDarkMode(!currentMode);
|
||||
}
|
||||
|
||||
// Event Listener für Toggle Button
|
||||
if (darkModeToggle) {
|
||||
// Vorherige Event-Listener entfernen, um Duplikate zu vermeiden
|
||||
const newDarkModeToggle = darkModeToggle.cloneNode(true);
|
||||
darkModeToggle.parentNode.replaceChild(newDarkModeToggle, darkModeToggle);
|
||||
|
||||
// Neuen Event-Listener hinzufügen
|
||||
newDarkModeToggle.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // Verhindere Bubbling
|
||||
toggleDarkMode();
|
||||
});
|
||||
|
||||
// Aktualisiere die Variable auf das neue Element
|
||||
darkModeToggle = newDarkModeToggle;
|
||||
|
||||
// Initialen Status setzen
|
||||
const isDark = isDarkMode();
|
||||
setDarkMode(isDark);
|
||||
|
||||
console.log('Dark Mode Toggle Button erfolgreich initialisiert');
|
||||
} else {
|
||||
console.error('Dark Mode Toggle Button konnte nicht gefunden werden!');
|
||||
}
|
||||
|
||||
// Tastaturkürzel: Strg+Shift+D für Dark Mode Toggle
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.ctrlKey && e.shiftKey && e.key === 'D') {
|
||||
toggleDarkMode();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Direkte Verfügbarkeit der Funktionen im globalen Bereich
|
||||
window.toggleDarkMode = toggleDarkMode;
|
||||
window.isDarkMode = isDarkMode;
|
||||
window.setDarkMode = setDarkMode;
|
||||
});
|
@ -79,7 +79,7 @@
|
||||
}, 100);
|
||||
|
||||
// Erfolgsmeldung in die Konsole
|
||||
console.log(`Dark Mode ${enable ? 'aktiviert' : 'deaktiviert'}`);
|
||||
console.log(`${enable ? '🌙' : '☀️'} ${enable ? 'Dark Mode aktiviert - Augenschonender Modus aktiv' : 'Light Mode aktiviert - Heller Modus aktiv'}`);
|
||||
}
|
||||
|
||||
// Aktualisiert das Theme-Color Meta-Tag
|
||||
@ -117,35 +117,38 @@
|
||||
darkModeToggle.setAttribute('aria-pressed', isDark.toString());
|
||||
darkModeToggle.title = isDark ? "Light Mode aktivieren" : "Dark Mode aktivieren";
|
||||
|
||||
// Stile aktualisieren mit Tailwind-Klassen
|
||||
if (isDark) {
|
||||
darkModeToggle.classList.remove('bg-indigo-600', 'hover:bg-indigo-700');
|
||||
darkModeToggle.classList.add('bg-slate-800', 'hover:bg-slate-700', 'text-amber-400');
|
||||
darkModeToggle.setAttribute('data-tooltip', 'Light Mode aktivieren');
|
||||
} else {
|
||||
darkModeToggle.classList.remove('bg-slate-800', 'hover:bg-slate-700', 'text-amber-400');
|
||||
darkModeToggle.classList.add('bg-indigo-600', 'hover:bg-indigo-700');
|
||||
darkModeToggle.setAttribute('data-tooltip', 'Dark Mode aktivieren');
|
||||
}
|
||||
// Icons aktualisieren
|
||||
const sunIcon = darkModeToggle.querySelector('.sun-icon');
|
||||
const moonIcon = darkModeToggle.querySelector('.moon-icon');
|
||||
|
||||
// Icon aktualisieren - ohne innerHTML für CSP-Kompatibilität
|
||||
const icon = darkModeToggle.querySelector('svg');
|
||||
if (icon) {
|
||||
// Animationsklasse hinzufügen
|
||||
icon.classList.add('animate-spin-once');
|
||||
|
||||
// Nach Animation wieder entfernen
|
||||
setTimeout(() => {
|
||||
icon.classList.remove('animate-spin-once');
|
||||
}, 300);
|
||||
|
||||
const pathElement = icon.querySelector('path');
|
||||
if (pathElement) {
|
||||
// Sonnen- oder Mond-Symbol
|
||||
if (isDark) {
|
||||
pathElement.setAttribute("d", "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z");
|
||||
} else {
|
||||
pathElement.setAttribute("d", "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z");
|
||||
if (sunIcon && moonIcon) {
|
||||
if (isDark) {
|
||||
sunIcon.classList.add('hidden');
|
||||
moonIcon.classList.remove('hidden');
|
||||
} else {
|
||||
sunIcon.classList.remove('hidden');
|
||||
moonIcon.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
// Fallback für ältere Implementierung mit einem Icon
|
||||
const icon = darkModeToggle.querySelector('svg');
|
||||
if (icon) {
|
||||
// Animationsklasse hinzufügen
|
||||
icon.classList.add('animate-spin-once');
|
||||
|
||||
// Nach Animation wieder entfernen
|
||||
setTimeout(() => {
|
||||
icon.classList.remove('animate-spin-once');
|
||||
}, 300);
|
||||
|
||||
const pathElement = icon.querySelector('path');
|
||||
if (pathElement) {
|
||||
// Sonnen- oder Mond-Symbol
|
||||
if (isDark) {
|
||||
pathElement.setAttribute("d", "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z");
|
||||
} else {
|
||||
pathElement.setAttribute("d", "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,6 +161,7 @@
|
||||
|
||||
// Wenn kein Toggle existiert, erstelle einen
|
||||
if (!darkModeToggle) {
|
||||
console.log('🔧 Dark Mode Toggle nicht gefunden - erstelle automatisch einen neuen Button');
|
||||
createDarkModeToggle();
|
||||
}
|
||||
|
||||
@ -165,6 +169,7 @@
|
||||
if (darkModeToggle) {
|
||||
darkModeToggle.addEventListener('click', function() {
|
||||
const isDark = !shouldUseDarkMode();
|
||||
console.log(`👆 Dark Mode Toggle: Wechsel zu ${isDark ? '🌙 dunkel' : '☀️ hell'} angefordert`);
|
||||
setDarkMode(isDark);
|
||||
});
|
||||
}
|
||||
@ -173,6 +178,7 @@
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.ctrlKey && e.shiftKey && e.key === 'D') {
|
||||
const isDark = !shouldUseDarkMode();
|
||||
console.log(`⌨️ Tastenkombination STRG+SHIFT+D erkannt: Wechsel zu ${isDark ? '🌙 dunkel' : '☀️ hell'}`);
|
||||
setDarkMode(isDark);
|
||||
e.preventDefault();
|
||||
}
|
||||
@ -186,6 +192,7 @@
|
||||
darkModeMediaQuery.addEventListener('change', function(e) {
|
||||
// Nur anwenden, wenn keine benutzerdefinierte Einstellung gespeichert ist
|
||||
if (localStorage.getItem(STORAGE_KEY) === null) {
|
||||
console.log(`🖥️ Systemeinstellung geändert: ${e.matches ? '🌙 dunkel' : '☀️ hell'}`);
|
||||
setDarkMode(e.matches);
|
||||
}
|
||||
});
|
||||
@ -193,22 +200,27 @@
|
||||
// Fallback für ältere Browser
|
||||
darkModeMediaQuery.addListener(function(e) {
|
||||
if (localStorage.getItem(STORAGE_KEY) === null) {
|
||||
console.log(`🖥️ Systemeinstellung geändert (Legacy-Browser): ${e.matches ? '🌙 dunkel' : '☀️ hell'}`);
|
||||
setDarkMode(e.matches);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialer Zustand
|
||||
setDarkMode(shouldUseDarkMode());
|
||||
const initialState = shouldUseDarkMode();
|
||||
console.log(`🔍 Ermittelter Ausgangszustand: ${initialState ? '🌙 Dark Mode' : '☀️ Light Mode'}`);
|
||||
setDarkMode(initialState);
|
||||
|
||||
// Animation für den korrekten Modus hinzufügen
|
||||
const animClass = shouldUseDarkMode() ? 'dark-mode-transition' : 'light-mode-transition';
|
||||
const animClass = initialState ? 'dark-mode-transition' : 'light-mode-transition';
|
||||
document.body.classList.add(animClass);
|
||||
|
||||
// Animation entfernen nach Abschluss
|
||||
setTimeout(() => {
|
||||
document.body.classList.remove(animClass);
|
||||
}, 300);
|
||||
|
||||
console.log('🚀 Dark Mode Handler erfolgreich initialisiert');
|
||||
}
|
||||
|
||||
// Erstellt ein Toggle-Element, falls keines existiert
|
||||
@ -218,46 +230,61 @@
|
||||
const nav = document.querySelector('nav');
|
||||
const container = document.querySelector('.dark-mode-container') || header || nav;
|
||||
|
||||
if (!container) return;
|
||||
if (!container) {
|
||||
console.error('⚠️ Kein geeigneter Container für Dark Mode Toggle gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle-Button erstellen
|
||||
darkModeToggle = document.createElement('button');
|
||||
darkModeToggle.id = 'darkModeToggle';
|
||||
darkModeToggle.className = 'p-2 sm:p-3 rounded-full bg-indigo-600 text-white transition-all duration-300';
|
||||
darkModeToggle.className = 'dark-mode-toggle-new';
|
||||
darkModeToggle.setAttribute('aria-label', 'Dark Mode umschalten');
|
||||
darkModeToggle.setAttribute('title', 'Dark Mode aktivieren');
|
||||
darkModeToggle.setAttribute('data-tooltip', 'Dark Mode aktivieren');
|
||||
darkModeToggle.setAttribute('data-action', 'toggle-dark-mode');
|
||||
|
||||
// SVG-Icon erstellen (ohne innerHTML für Content Security Policy)
|
||||
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.setAttribute("class", "w-4 h-4 sm:w-5 sm:h-5");
|
||||
svg.setAttribute("fill", "none");
|
||||
svg.setAttribute("stroke", "currentColor");
|
||||
svg.setAttribute("viewBox", "0 0 24 24");
|
||||
// Sonnen-Icon erstellen
|
||||
const sunIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
sunIcon.setAttribute("class", "w-5 h-5 sm:w-5 sm:h-5 sun-icon");
|
||||
sunIcon.setAttribute("fill", "none");
|
||||
sunIcon.setAttribute("stroke", "currentColor");
|
||||
sunIcon.setAttribute("viewBox", "0 0 24 24");
|
||||
sunIcon.setAttribute("aria-hidden", "true");
|
||||
|
||||
// Path für das Icon
|
||||
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
path.setAttribute("stroke-linecap", "round");
|
||||
path.setAttribute("stroke-linejoin", "round");
|
||||
path.setAttribute("stroke-width", "2");
|
||||
path.setAttribute("d", "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z");
|
||||
const sunPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
sunPath.setAttribute("stroke-linecap", "round");
|
||||
sunPath.setAttribute("stroke-linejoin", "round");
|
||||
sunPath.setAttribute("stroke-width", "2");
|
||||
sunPath.setAttribute("d", "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z");
|
||||
|
||||
// Mond-Icon erstellen
|
||||
const moonIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
moonIcon.setAttribute("class", "w-5 h-5 sm:w-5 sm:h-5 moon-icon hidden");
|
||||
moonIcon.setAttribute("fill", "none");
|
||||
moonIcon.setAttribute("stroke", "currentColor");
|
||||
moonIcon.setAttribute("viewBox", "0 0 24 24");
|
||||
moonIcon.setAttribute("aria-hidden", "true");
|
||||
|
||||
const moonPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
moonPath.setAttribute("stroke-linecap", "round");
|
||||
moonPath.setAttribute("stroke-linejoin", "round");
|
||||
moonPath.setAttribute("stroke-width", "2");
|
||||
moonPath.setAttribute("d", "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z");
|
||||
|
||||
// Elemente zusammenfügen
|
||||
svg.appendChild(path);
|
||||
darkModeToggle.appendChild(svg);
|
||||
|
||||
// Screenreader-Text hinzufügen
|
||||
const srText = document.createElement('span');
|
||||
srText.className = 'sr-only';
|
||||
srText.textContent = 'Dark Mode umschalten';
|
||||
darkModeToggle.appendChild(srText);
|
||||
sunIcon.appendChild(sunPath);
|
||||
moonIcon.appendChild(moonPath);
|
||||
darkModeToggle.appendChild(sunIcon);
|
||||
darkModeToggle.appendChild(moonIcon);
|
||||
|
||||
// Zum Container hinzufügen
|
||||
container.appendChild(darkModeToggle);
|
||||
console.log('✅ Dark Mode Toggle Button erfolgreich erstellt und zur Benutzeroberfläche hinzugefügt');
|
||||
}
|
||||
|
||||
// Sofort Dark/Light Mode anwenden (vor DOMContentLoaded)
|
||||
const isDark = shouldUseDarkMode();
|
||||
console.log(`🏃♂️ Sofortige Anwendung: ${isDark ? '🌙 Dark Mode' : '☀️ Light Mode'} (vor DOM-Ladung)`);
|
||||
setDarkMode(isDark);
|
||||
})();
|
||||
|
||||
@ -275,4 +302,5 @@ if (!document.querySelector('style#dark-mode-animations')) {
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(styleTag);
|
||||
console.log('💫 Animations-Styles für Dark Mode Toggle hinzugefügt');
|
||||
}
|
@ -18,7 +18,8 @@
|
||||
constructor() {
|
||||
// DOM-Elemente
|
||||
this.darkModeToggle = document.getElementById('darkModeToggle');
|
||||
this.darkModeIcon = this.darkModeToggle ? this.darkModeToggle.querySelector('svg') : null;
|
||||
this.sunIcon = this.darkModeToggle ? this.darkModeToggle.querySelector('.sun-icon') : null;
|
||||
this.moonIcon = this.darkModeToggle ? this.darkModeToggle.querySelector('.moon-icon') : null;
|
||||
this.html = document.documentElement;
|
||||
|
||||
// Local Storage Key
|
||||
@ -51,6 +52,10 @@
|
||||
this.updateDarkModeIcon(true);
|
||||
this.darkModeToggle.setAttribute('aria-pressed', 'true');
|
||||
this.darkModeToggle.setAttribute('title', 'Light Mode aktivieren');
|
||||
if (this.sunIcon && this.moonIcon) {
|
||||
this.sunIcon.classList.add('hidden');
|
||||
this.moonIcon.classList.remove('hidden');
|
||||
}
|
||||
} else {
|
||||
this.html.classList.remove('dark');
|
||||
this.html.setAttribute('data-theme', 'light');
|
||||
@ -58,6 +63,10 @@
|
||||
this.updateDarkModeIcon(false);
|
||||
this.darkModeToggle.setAttribute('aria-pressed', 'false');
|
||||
this.darkModeToggle.setAttribute('title', 'Dark Mode aktivieren');
|
||||
if (this.sunIcon && this.moonIcon) {
|
||||
this.sunIcon.classList.remove('hidden');
|
||||
this.moonIcon.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Einstellung im Local Storage speichern
|
||||
@ -74,7 +83,7 @@
|
||||
detail: { isDark: enable }
|
||||
}));
|
||||
|
||||
console.log('Dark Mode set to:', enable);
|
||||
console.log(`${enable ? '🌙' : '☀️'} Design umgeschaltet auf: ${enable ? 'Dark Mode' : 'Light Mode'}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,18 +91,17 @@
|
||||
* @param {boolean} isDark - Ob Dark Mode aktiv ist
|
||||
*/
|
||||
updateDarkModeIcon(isDark) {
|
||||
if (!this.darkModeIcon) return;
|
||||
if (!this.darkModeToggle) return;
|
||||
|
||||
if (isDark) {
|
||||
// Sonne anzeigen (für Wechsel zum Light Mode)
|
||||
this.darkModeIcon.innerHTML = `
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
`;
|
||||
} else {
|
||||
// Mond anzeigen (für Wechsel zum Dark Mode)
|
||||
this.darkModeIcon.innerHTML = `
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||||
`;
|
||||
// Stellen sicher, dass die Icons korrekt angezeigt werden
|
||||
if (this.sunIcon && this.moonIcon) {
|
||||
if (isDark) {
|
||||
this.sunIcon.classList.add('hidden');
|
||||
this.moonIcon.classList.remove('hidden');
|
||||
} else {
|
||||
this.sunIcon.classList.remove('hidden');
|
||||
this.moonIcon.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,15 +110,15 @@
|
||||
*/
|
||||
init() {
|
||||
if (!this.darkModeToggle) {
|
||||
console.error('Dark Mode Toggle Button nicht gefunden!');
|
||||
console.error('⚠️ Dark Mode Toggle Button nicht gefunden! UI-Komponente nicht verfügbar.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Dark Mode Manager initialisiert');
|
||||
console.log('🚀 Dark Mode Manager erfolgreich initialisiert');
|
||||
|
||||
// Event Listener für den Dark Mode Toggle Button
|
||||
this.darkModeToggle.addEventListener('click', () => {
|
||||
console.log('Dark Mode Toggle geklickt');
|
||||
console.log('👆 Dark Mode Toggle Button angeklickt');
|
||||
const newDarkModeState = !this.isDarkMode();
|
||||
this.setDarkMode(newDarkModeState);
|
||||
});
|
||||
@ -118,7 +126,7 @@
|
||||
// Alternative Event-Listener für Buttons mit data-action
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.closest('[data-action="toggle-dark-mode"]')) {
|
||||
console.log('Dark Mode Toggle über data-action geklickt');
|
||||
console.log('👆 Dark Mode Toggle über data-action aktiviert');
|
||||
const newDarkModeState = !this.isDarkMode();
|
||||
this.setDarkMode(newDarkModeState);
|
||||
}
|
||||
@ -144,11 +152,111 @@
|
||||
|
||||
// Initialisierung: Aktuellen Status setzen
|
||||
const isDark = this.isDarkMode();
|
||||
console.log('Initial Dark Mode Status:', isDark);
|
||||
console.log(`🔍 Initialer Dark Mode Status: ${isDark ? '🌙 aktiviert' : '☀️ deaktiviert'}`);
|
||||
this.setDarkMode(isDark);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mobile Menu Manager
|
||||
*/
|
||||
class MobileMenuManager {
|
||||
constructor() {
|
||||
this.mobileMenuToggle = document.getElementById('mobileMenuToggle');
|
||||
this.mobileMenu = document.getElementById('mobileMenu');
|
||||
this.isOpen = false;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.mobileMenuToggle || !this.mobileMenu) {
|
||||
console.log('ℹ️ Mobile Menu Komponenten nicht gefunden - Feature deaktiviert');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📱 Mobile Menu Manager erfolgreich initialisiert');
|
||||
|
||||
// Event Listener für den Mobile Menu Toggle Button
|
||||
this.mobileMenuToggle.addEventListener('click', () => {
|
||||
this.toggleMenu();
|
||||
});
|
||||
|
||||
// Event Listener für Klicks außerhalb des Menüs
|
||||
document.addEventListener('click', (e) => {
|
||||
if (this.isOpen && !this.mobileMenuToggle.contains(e.target) && !this.mobileMenu.contains(e.target)) {
|
||||
this.closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Event Listener für das Schließen bei Klick auf einen Menüpunkt
|
||||
this.mobileMenu.querySelectorAll('a').forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
this.closeMenu();
|
||||
});
|
||||
});
|
||||
|
||||
// Beim Scrollen das Menü schließen
|
||||
window.addEventListener('scroll', () => {
|
||||
if (this.isOpen && window.scrollY > 50) {
|
||||
this.closeMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toggleMenu() {
|
||||
if (this.isOpen) {
|
||||
this.closeMenu();
|
||||
} else {
|
||||
this.openMenu();
|
||||
}
|
||||
}
|
||||
|
||||
openMenu() {
|
||||
if (!this.mobileMenu || this.isOpen) return;
|
||||
|
||||
this.mobileMenu.classList.remove('hidden');
|
||||
this.isOpen = true;
|
||||
this.mobileMenuToggle.setAttribute('aria-expanded', 'true');
|
||||
|
||||
// Icon ändern
|
||||
const menuIcon = this.mobileMenuToggle.querySelector('svg');
|
||||
if (menuIcon) {
|
||||
menuIcon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>';
|
||||
}
|
||||
|
||||
// Animation - kleine Verzögerung für sanfte Animation
|
||||
setTimeout(() => {
|
||||
this.mobileMenu.classList.add('open');
|
||||
}, 10);
|
||||
|
||||
// Body-Scroll verhindern (optional)
|
||||
// document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
closeMenu() {
|
||||
if (!this.mobileMenu || !this.isOpen) return;
|
||||
|
||||
this.mobileMenu.classList.remove('open');
|
||||
this.isOpen = false;
|
||||
this.mobileMenuToggle.setAttribute('aria-expanded', 'false');
|
||||
|
||||
// Icon zurücksetzen
|
||||
const menuIcon = this.mobileMenuToggle.querySelector('svg');
|
||||
if (menuIcon) {
|
||||
menuIcon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>';
|
||||
}
|
||||
|
||||
// Nach der Animation ausblenden
|
||||
setTimeout(() => {
|
||||
this.mobileMenu.classList.add('hidden');
|
||||
}, 300);
|
||||
|
||||
// Body-Scroll wiederherstellen
|
||||
// document.body.style.overflow = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User Dropdown Manager
|
||||
*/
|
||||
@ -992,6 +1100,9 @@
|
||||
// Dark Mode Manager
|
||||
window.MYP.UI.darkMode = new DarkModeManager();
|
||||
|
||||
// Mobile Menu Manager
|
||||
window.MYP.UI.mobileMenu = new MobileMenuManager();
|
||||
|
||||
// User Dropdown Manager
|
||||
window.MYP.UI.userDropdown = new UserDropdownManager();
|
||||
|
||||
@ -1025,7 +1136,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
console.log('MYP UI Components initialized successfully');
|
||||
console.log('✅ MYP UI Components erfolgreich initialisiert - Benutzeroberfläche bereit');
|
||||
});
|
||||
|
||||
// Globale Variable für Toast-Funktion
|
||||
|
@ -63,6 +63,28 @@
|
||||
setTimeout(function() {
|
||||
document.documentElement.classList.remove('disable-transitions');
|
||||
}, 10);
|
||||
|
||||
// Diese Funktion wird nach dem DOM-Laden ausgeführt
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Dark Mode Toggle Button Icons aktualisieren
|
||||
const darkModeToggle = document.getElementById('darkModeToggle');
|
||||
if (darkModeToggle) {
|
||||
const sunIcon = darkModeToggle.querySelector('.sun-icon');
|
||||
const moonIcon = darkModeToggle.querySelector('.moon-icon');
|
||||
|
||||
if (isDark) {
|
||||
darkModeToggle.setAttribute('aria-pressed', 'true');
|
||||
darkModeToggle.setAttribute('title', 'Light Mode aktivieren');
|
||||
if (sunIcon) sunIcon.classList.add('hidden');
|
||||
if (moonIcon) moonIcon.classList.remove('hidden');
|
||||
} else {
|
||||
darkModeToggle.setAttribute('aria-pressed', 'false');
|
||||
darkModeToggle.setAttribute('title', 'Dark Mode aktivieren');
|
||||
if (sunIcon) sunIcon.classList.remove('hidden');
|
||||
if (moonIcon) moonIcon.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Disable Transitions Styling -->
|
||||
@ -76,17 +98,17 @@
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body class="min-h-screen flex flex-col bg-white text-slate-900 dark:bg-[#050505] dark:text-slate-200 transition-colors duration-300 text-base mercedes-background">
|
||||
<body class="min-h-screen flex flex-col bg-white text-slate-900 dark:bg-black dark:text-white transition-colors duration-300 text-base mercedes-background">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar">
|
||||
<div class="max-w-7xl mx-auto px-2 sm:px-4 lg:px-6">
|
||||
<div class="flex items-center justify-between h-14 sm:h-16 lg:h-20">
|
||||
<div class="max-w-7xl mx-auto px-3 sm:px-4 lg:px-6">
|
||||
<div class="flex items-center justify-between h-16 sm:h-18 lg:h-20">
|
||||
<!-- Brand Section -->
|
||||
<div class="flex-shrink-0">
|
||||
<a href="{{ url_for('dashboard') }}" class="navbar-brand" aria-label="Zur Startseite">
|
||||
<!-- Mercedes-Benz Logo -->
|
||||
<div class="w-8 h-8 sm:w-10 sm:h-10 lg:w-12 lg:h-12 transition-all duration-300 group-hover:rotate-12">
|
||||
<div class="w-9 h-9 sm:w-10 sm:h-10 lg:w-12 lg:h-12 transition-all duration-300 group-hover:rotate-12">
|
||||
<svg class="w-full h-full text-slate-900 dark:text-white transition-colors duration-300" fill="currentColor" viewBox="0 0 80 80" aria-hidden="true">
|
||||
<path d="M58.6,4.5C53,1.6,46.7,0,40,0c-6.7,0-13,1.6-18.6,4.5v0C8.7,11.2,0,24.6,0,40c0,15.4,8.7,28.8,21.5,35.5
|
||||
C27,78.3,33.3,80,40,80c6.7,0,12.9-1.7,18.5-4.6C71.3,68.8,80,55.4,80,40C80,24.6,71.3,11.2,58.6,4.5z M4,40
|
||||
@ -104,11 +126,11 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Desktop Navigation Menu -->
|
||||
<div class="hidden lg:flex flex-1 justify-center px-8">
|
||||
<nav class="navbar-menu" role="navigation" aria-label="Hauptnavigation">
|
||||
<!-- Desktop Navigation Menu - überarbeitetes Design -->
|
||||
<div class="hidden lg:flex flex-1 justify-center">
|
||||
<nav class="navbar-menu-new" role="navigation" aria-label="Hauptnavigation">
|
||||
<a href="{{ url_for('dashboard') }}"
|
||||
class="menu-item {{ 'text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-500' if request.endpoint == 'dashboard' else '' }}">
|
||||
class="nav-item {{ 'active' if request.endpoint == 'dashboard' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"/>
|
||||
</svg>
|
||||
@ -116,15 +138,15 @@
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('printers_page') }}"
|
||||
class="menu-item {{ 'text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-500' if request.endpoint == 'printers_page' else '' }}">
|
||||
<svg class="w-8 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
class="nav-item {{ 'active' if request.endpoint == 'printers_page' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/>
|
||||
</svg>
|
||||
<span>3D-Drucker</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('jobs_page') }}"
|
||||
class="menu-item {{ 'text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-500' if request.endpoint == 'jobs_page' else '' }}">
|
||||
class="nav-item {{ 'active' if request.endpoint == 'jobs_page' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
|
||||
</svg>
|
||||
@ -132,7 +154,7 @@
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('stats_page') }}"
|
||||
class="menu-item {{ 'text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-500' if request.endpoint == 'stats_page' else '' }}">
|
||||
class="nav-item {{ 'active' if request.endpoint == 'stats_page' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
||||
</svg>
|
||||
@ -141,7 +163,7 @@
|
||||
|
||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||
<a href="{{ url_for('admin_page') }}"
|
||||
class="menu-item {{ 'text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-500' if request.endpoint == 'admin_page' else '' }}">
|
||||
class="nav-item {{ 'active' if request.endpoint == 'admin_page' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
@ -152,47 +174,60 @@
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Right Side Controls -->
|
||||
<!-- Rechte Seite der Navbar -->
|
||||
<div class="flex items-center space-x-2 sm:space-x-3">
|
||||
<!-- Dark Mode Toggle -->
|
||||
<!-- Mobile Menu Toggle (neu) -->
|
||||
<button
|
||||
id="mobileMenuToggle"
|
||||
class="lg:hidden p-2 rounded-full text-slate-700 dark:text-slate-300 hover:bg-slate-100/80 dark:hover:bg-slate-800/50"
|
||||
aria-label="Menü öffnen"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Dark Mode Toggle - neues Design -->
|
||||
<button
|
||||
id="darkModeToggle"
|
||||
class="dark-mode-toggle p-2 sm:p-3"
|
||||
class="dark-mode-toggle-new"
|
||||
aria-label="Dark Mode umschalten"
|
||||
aria-pressed="false"
|
||||
data-action="toggle-dark-mode"
|
||||
title="Dark Mode aktivieren"
|
||||
>
|
||||
<svg class="w-6 h-6 sm:w-7 sm:h-7 transition-all duration-300 transform" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<svg class="w-5 h-5 sm:w-5 sm:h-5 sun-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
<svg class="w-5 h-5 sm:w-5 sm:h-5 moon-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<!-- User Profile Dropdown -->
|
||||
<!-- User Profile Dropdown - neues Design -->
|
||||
<div class="relative" id="user-menu-container">
|
||||
<button
|
||||
id="user-menu-button"
|
||||
class="flex items-center space-x-2 text-sm font-medium rounded-lg p-1.5 transition-all duration-300 hover:bg-blue-50 dark:hover:bg-blue-950"
|
||||
class="user-menu-button-new"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-label="Benutzermenu öffnen"
|
||||
>
|
||||
<!-- Profile Avatar -->
|
||||
<div class="h-8 w-8 rounded-full flex items-center justify-center bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 font-medium transition-colors duration-300">
|
||||
<div class="user-avatar-new">
|
||||
{{ current_user.email[0].upper() if current_user.email else 'U' }}
|
||||
</div>
|
||||
<!-- User Info (hidden on mobile) -->
|
||||
<div class="hidden md:block text-left ml-2">
|
||||
<div class="text-sm font-medium text-slate-900 dark:text-white transition-colors duration-300">{{ current_user.email.split('@')[0] if current_user.email else 'Benutzer' }}</div>
|
||||
<div class="text-sm font-semibold text-slate-900 dark:text-white transition-colors duration-300">{{ current_user.email.split('@')[0] if current_user.email else 'Benutzer' }}</div>
|
||||
<div class="text-xs text-slate-600 dark:text-slate-400 transition-colors duration-300">Mercedes-Benz Mitarbeiter</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<!-- Login Button -->
|
||||
<!-- Login Button - neues Design -->
|
||||
<a href="{{ url_for('login') }}"
|
||||
class="bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium shadow-sm hover:shadow-md transition-all duration-300 flex items-center">
|
||||
class="login-button-new">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
|
||||
</svg>
|
||||
@ -203,6 +238,54 @@
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Mobile Menu (neu) -->
|
||||
<div id="mobileMenu" class="mobile-menu-new hidden lg:hidden">
|
||||
<nav class="flex flex-col space-y-1 px-3 py-4" role="navigation" aria-label="Mobile Navigation">
|
||||
<a href="{{ url_for('dashboard') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'dashboard' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"/>
|
||||
</svg>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('printers_page') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'printers_page' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/>
|
||||
</svg>
|
||||
<span>3D-Drucker</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('jobs_page') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'jobs_page' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
|
||||
</svg>
|
||||
<span>Druckaufträge</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('stats_page') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'stats_page' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
||||
</svg>
|
||||
<span>Statistiken</span>
|
||||
</a>
|
||||
|
||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||
<a href="{{ url_for('admin_page') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'admin_page' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
</svg>
|
||||
<span>Administration</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main id="main-content" class="flex-grow max-w-7xl w-full mx-auto px-3 sm:px-6 lg:px-8 py-4 sm:py-8">
|
||||
@ -288,6 +371,7 @@
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script src="{{ url_for('static', filename='js/ui-components.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/dark-mode-fix.js') }}"></script>
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
|
@ -35,14 +35,23 @@
|
||||
<option value="">Drucker auswählen...</option>
|
||||
<!-- Wird durch JavaScript gefüllt -->
|
||||
</select>
|
||||
<div id="printer-status-info" class="mt-2 text-center">
|
||||
<!-- Status-Info wird dynamisch gefüllt -->
|
||||
</div>
|
||||
<div id="printer-status-warning" class="mt-2 hidden">
|
||||
<div class="flex items-center p-3 bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded-lg">
|
||||
<svg class="w-5 h-5 text-orange-500 mr-2 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-orange-800 dark:text-orange-200">Offline-Drucker ausgewählt</p>
|
||||
<p class="text-orange-700 dark:text-orange-300">Der Job wird erstellt, startet aber erst, wenn der Drucker online geht.</p>
|
||||
<div class="flex items-center p-4 bg-red-50 dark:bg-red-900/20 border-2 border-red-400 dark:border-red-700 rounded-lg shadow-sm">
|
||||
<div class="flex-shrink-0 w-10 h-10 mr-3 flex items-center justify-center rounded-full bg-red-100 dark:bg-red-800">
|
||||
<svg class="w-6 h-6 text-red-600 dark:text-red-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-lg font-bold text-red-800 dark:text-red-200">ACHTUNG: Offline-Drucker ausgewählt!</p>
|
||||
<p class="text-sm text-red-700 dark:text-red-300 mt-1">
|
||||
Dieser Drucker ist derzeit <span class="font-bold">NICHT VERFÜGBAR</span>.
|
||||
Der Job wird in der Warteschlange gespeichert und erst gestartet,
|
||||
wenn der Drucker wieder online ist.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -213,20 +222,49 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
loadPrinters();
|
||||
loadActiveJobs();
|
||||
|
||||
// Event-Listener für Drucker-Auswahl (Offline-Warnung)
|
||||
// Event-Listener für Drucker-Auswahl (Status-Anzeige)
|
||||
const printerSelect = document.getElementById('printer_id');
|
||||
const statusWarning = document.getElementById('printer-status-warning');
|
||||
const statusInfo = document.getElementById('printer-status-info');
|
||||
|
||||
if (printerSelect && statusWarning) {
|
||||
if (printerSelect && statusWarning && statusInfo) {
|
||||
printerSelect.addEventListener('change', function() {
|
||||
const selectedOption = this.options[this.selectedIndex];
|
||||
|
||||
if (selectedOption && selectedOption.getAttribute('data-offline') === 'true') {
|
||||
statusWarning.classList.remove('hidden');
|
||||
} else {
|
||||
if (!selectedOption || selectedOption.value === "") {
|
||||
// Keine Auswahl
|
||||
statusWarning.classList.add('hidden');
|
||||
statusInfo.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Status des ausgewählten Druckers bestimmen
|
||||
const isOffline = selectedOption.getAttribute('data-offline') === 'true';
|
||||
const printerName = selectedOption.textContent.split('(')[0].trim().replace(/🟢|🔴/g, '').trim();
|
||||
|
||||
if (isOffline) {
|
||||
// Offline-Drucker: Deutliche Warnung anzeigen
|
||||
statusWarning.classList.remove('hidden');
|
||||
statusInfo.innerHTML = `
|
||||
<div class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800 border border-red-200">
|
||||
<span class="w-2 h-2 mr-1 bg-red-500 rounded-full"></span>
|
||||
${printerName} ist OFFLINE
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// Online-Drucker: Positive Statusmeldung
|
||||
statusWarning.classList.add('hidden');
|
||||
statusInfo.innerHTML = `
|
||||
<div class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 border border-green-200">
|
||||
<span class="w-2 h-2 mr-1 bg-green-500 rounded-full animate-pulse"></span>
|
||||
${printerName} ist ONLINE und bereit
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
// Initial auslösen, um den Status der Vorauswahl anzuzeigen
|
||||
printerSelect.dispatchEvent(new Event('change'));
|
||||
}
|
||||
|
||||
// Formulare initialisieren
|
||||
@ -342,25 +380,50 @@ function populatePrinterSelect(printers, onlineOnly = false) {
|
||||
// Bei gleichem Online-Status nach Name sortieren
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
// Zähler für Online- und Offline-Drucker
|
||||
let onlineCount = 0;
|
||||
let offlineCount = 0;
|
||||
|
||||
// Optionsgruppen für bessere visuelle Trennung erstellen
|
||||
const onlineGroup = document.createElement('optgroup');
|
||||
onlineGroup.label = "── ONLINE DRUCKER ──";
|
||||
onlineGroup.style.fontWeight = 'bold';
|
||||
onlineGroup.style.color = '#047857'; // Dunkelgrün
|
||||
|
||||
const offlineGroup = document.createElement('optgroup');
|
||||
offlineGroup.label = "── OFFLINE DRUCKER ──";
|
||||
offlineGroup.style.fontWeight = 'bold';
|
||||
offlineGroup.style.color = '#b91c1c'; // Dunkelrot
|
||||
|
||||
// Drucker in entsprechende Gruppen einsortieren
|
||||
sortedPrinters.forEach(printer => {
|
||||
const option = document.createElement('option');
|
||||
option.value = printer.id;
|
||||
|
||||
// Status-Indikator bestimmen
|
||||
const isOnline = printer.status === 'available' || printer.is_online || printer.active;
|
||||
let statusIcon, statusText;
|
||||
let statusIcon, statusText, statusClass;
|
||||
|
||||
if (isOnline) {
|
||||
statusIcon = '🟢';
|
||||
statusText = 'Online';
|
||||
option.style.color = '#059669'; // Grün für online
|
||||
statusText = 'ONLINE';
|
||||
statusClass = 'online';
|
||||
option.style.backgroundColor = 'rgba(4, 120, 87, 0.1)'; // Leicht grüner Hintergrund
|
||||
option.style.color = '#047857'; // Grün für online
|
||||
option.style.fontWeight = '500';
|
||||
onlineCount++;
|
||||
} else {
|
||||
statusIcon = '🔴';
|
||||
statusText = 'Offline';
|
||||
option.style.color = '#dc2626'; // Rot für offline
|
||||
statusText = 'OFFLINE';
|
||||
statusClass = 'offline';
|
||||
option.style.backgroundColor = 'rgba(185, 28, 28, 0.05)'; // Sehr leicht roter Hintergrund
|
||||
option.style.color = '#b91c1c'; // Rot für offline
|
||||
option.style.fontStyle = 'italic';
|
||||
option.style.fontWeight = '400';
|
||||
// Offline-Drucker NICHT deaktivieren, aber kennzeichnen
|
||||
option.setAttribute('data-offline', 'true');
|
||||
offlineCount++;
|
||||
}
|
||||
|
||||
// Letzter Check-Zeitstempel
|
||||
@ -384,18 +447,52 @@ function populatePrinterSelect(printers, onlineOnly = false) {
|
||||
// Tooltip für zusätzliche Informationen
|
||||
option.title = `Standort: ${printer.location || 'Unbekannt'}\nIP: ${printer.plug_ip || printer.ip_address || 'Unbekannt'}\nStatus: ${statusText}${lastChecked}`;
|
||||
|
||||
printerSelect.appendChild(option);
|
||||
// Zu entsprechender Gruppe hinzufügen
|
||||
if (isOnline) {
|
||||
onlineGroup.appendChild(option);
|
||||
} else {
|
||||
offlineGroup.appendChild(option);
|
||||
}
|
||||
});
|
||||
|
||||
// Hinweis für Offline-Drucker
|
||||
const offlineCount = printers.filter(p => !(p.status === 'available' || p.is_online || p.active)).length;
|
||||
// Status-Info als erste Option hinzufügen
|
||||
const statusOption = document.createElement('option');
|
||||
statusOption.disabled = true;
|
||||
statusOption.className = 'status-summary';
|
||||
statusOption.style.backgroundColor = '#f3f4f6';
|
||||
statusOption.style.fontWeight = 'bold';
|
||||
statusOption.style.textAlign = 'center';
|
||||
statusOption.style.padding = '4px';
|
||||
statusOption.style.marginBottom = '4px';
|
||||
statusOption.style.borderBottom = '1px solid #d1d5db';
|
||||
|
||||
// Deutliche Status-Zusammenfassung
|
||||
if (onlineCount > 0) {
|
||||
statusOption.textContent = `✅ ${onlineCount} von ${printers.length} Drucker ONLINE`;
|
||||
statusOption.style.color = '#047857'; // Grün
|
||||
} else {
|
||||
statusOption.textContent = `⚠️ ACHTUNG: Alle ${printers.length} Drucker OFFLINE`;
|
||||
statusOption.style.color = '#b91c1c'; // Rot
|
||||
}
|
||||
printerSelect.appendChild(statusOption);
|
||||
|
||||
// Gruppen zum Select hinzufügen
|
||||
if (onlineCount > 0) {
|
||||
printerSelect.appendChild(onlineGroup);
|
||||
}
|
||||
|
||||
if (offlineCount > 0) {
|
||||
const infoOption = document.createElement('option');
|
||||
infoOption.disabled = true;
|
||||
infoOption.textContent = `--- ${offlineCount} Drucker offline (nicht verfügbar) ---`;
|
||||
infoOption.style.fontStyle = 'italic';
|
||||
infoOption.style.color = '#6b7280';
|
||||
printerSelect.appendChild(infoOption);
|
||||
printerSelect.appendChild(offlineGroup);
|
||||
}
|
||||
|
||||
// Wenn online Drucker verfügbar, den ersten online Drucker vorauswählen
|
||||
if (onlineCount > 0 && onlineGroup.firstChild) {
|
||||
onlineGroup.firstChild.selected = true;
|
||||
document.getElementById('printer-status-warning').classList.add('hidden');
|
||||
} else if (offlineCount > 0 && offlineGroup.firstChild) {
|
||||
// Sonst den ersten offline Drucker vorauswählen und Warnung anzeigen
|
||||
offlineGroup.firstChild.selected = true;
|
||||
document.getElementById('printer-status-warning').classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
@ -442,11 +539,22 @@ function loadPrintersWithLiveStatus() {
|
||||
|
||||
populatePrinterSelect(printers, false);
|
||||
|
||||
const onlineCount = printers.filter(p => p.status === 'available' || p.is_online || p.active).length;
|
||||
// Verwende die vom Backend bereitgestellten Werte
|
||||
const onlineCount = data.online_count || 0;
|
||||
const totalCount = data.count || printers.length;
|
||||
|
||||
if (onlineCount > 0) {
|
||||
showNotification(`${onlineCount} von ${printers.length} Drucker online (Live-Check)`, 'success');
|
||||
if (onlineCount === totalCount) {
|
||||
// Alle Drucker online
|
||||
showNotification(`✅ OPTIMAL: Alle ${totalCount} Drucker sind ONLINE und BEREIT`, 'success');
|
||||
} else {
|
||||
// Einige Drucker online
|
||||
const offlineCount = totalCount - onlineCount;
|
||||
showNotification(`⚠️ ${onlineCount} von ${totalCount} Drucker ONLINE | ${offlineCount} Drucker OFFLINE`, 'success');
|
||||
}
|
||||
} else {
|
||||
showNotification(`${printers.length} Drucker gefunden, aber alle offline`, 'warning');
|
||||
// Kein Drucker online
|
||||
showNotification(`❌ ACHTUNG: Alle ${totalCount} Drucker sind OFFLINE - Reservierte Jobs werden in Warteschlange gespeichert`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
@ -556,23 +664,29 @@ function initNewJobForm() {
|
||||
// Prüfen, ob ein Offline-Drucker ausgewählt wurde
|
||||
const selectedOption = document.querySelector(`#printer_id option[value="${printer_id}"]`);
|
||||
if (selectedOption && selectedOption.getAttribute('data-offline') === 'true') {
|
||||
const printerName = selectedOption.textContent.split(' (')[0].replace('🔴 ', '');
|
||||
const printerName = selectedOption.textContent.split(' (')[0].replace(/🔴\s*/g, '').trim();
|
||||
|
||||
// Deutlichere Warnung mit einem ausführlicheren Dialog
|
||||
const confirmOffline = confirm(
|
||||
`⚠️ WARNUNG: Offline-Drucker ausgewählt!\n\n` +
|
||||
`Der Drucker "${printerName}" ist derzeit OFFLINE.\n\n` +
|
||||
`Wenn Sie fortfahren:\n` +
|
||||
`• Der Job wird als "Wartend auf Drucker" markiert\n` +
|
||||
`• Sie erhalten eine Benachrichtigung, wenn der Drucker online geht\n` +
|
||||
`• Der Job startet automatisch, sobald der Drucker verfügbar ist\n\n` +
|
||||
`Möchten Sie trotzdem fortfahren?`
|
||||
`⛔ ACHTUNG: OFFLINE-DRUCKER AUSGEWÄHLT! ⛔\n` +
|
||||
`---------------------------------------------\n\n` +
|
||||
`Der Drucker "${printerName}" ist DERZEIT NICHT VERFÜGBAR!\n\n` +
|
||||
`Wenn Sie trotzdem fortfahren:\n\n` +
|
||||
`✓ Ihr Job wird in der WARTESCHLANGE gespeichert\n` +
|
||||
`✓ Der System-Status wird regelmäßig überprüft\n` +
|
||||
`✓ Sie erhalten eine BENACHRICHTIGUNG, sobald der Drucker online geht\n` +
|
||||
`✓ Der Job startet AUTOMATISCH, wenn der Drucker verfügbar wird\n\n` +
|
||||
`---------------------------------------------\n` +
|
||||
`Möchten Sie TROTZDEM mit diesem Offline-Drucker fortfahren?`
|
||||
);
|
||||
|
||||
if (!confirmOffline) {
|
||||
showNotification('Job-Erstellung abgebrochen. Bitte wählen Sie einen Online-Drucker oder warten Sie, bis der gewünschte Drucker verfügbar ist.', 'info');
|
||||
showNotification('Job-Erstellung abgebrochen - Bitte wählen Sie einen ONLINE-Drucker für sofortigen Start', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
showNotification(`Job für Offline-Drucker "${printerName}" wird erstellt. Sie werden benachrichtigt, wenn der Drucker online geht.`, 'warning');
|
||||
// Spezielle Benachrichtigung für Offline-Drucker-Jobs
|
||||
showNotification(`⏳ Job für OFFLINE-Drucker "${printerName}" wird in Warteschlange erstellt. Sie werden benachrichtigt, wenn der Drucker verfügbar wird.`, 'warning');
|
||||
}
|
||||
|
||||
// Startzeit in ISO-Format konvertieren
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,12 @@
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
from typing import Dict
|
||||
import sys
|
||||
import time
|
||||
import platform
|
||||
import socket
|
||||
from typing import Dict, Optional, Any
|
||||
from datetime import datetime
|
||||
from config.settings import (
|
||||
LOG_DIR, LOG_SUBDIRS, LOG_LEVEL, LOG_FORMAT, LOG_DATE_FORMAT,
|
||||
get_log_file, ensure_log_directories
|
||||
@ -10,25 +15,163 @@ from config.settings import (
|
||||
# Dictionary zur Speicherung der konfigurierten Logger
|
||||
_loggers: Dict[str, logging.Logger] = {}
|
||||
|
||||
def setup_logging():
|
||||
"""Initialisiert das Logging-System und erstellt alle erforderlichen Verzeichnisse."""
|
||||
# ANSI-Farbcodes für Log-Level
|
||||
ANSI_COLORS = {
|
||||
'RESET': '\033[0m',
|
||||
'BOLD': '\033[1m',
|
||||
'BLACK': '\033[30m',
|
||||
'RED': '\033[31m',
|
||||
'GREEN': '\033[32m',
|
||||
'YELLOW': '\033[33m',
|
||||
'BLUE': '\033[34m',
|
||||
'MAGENTA': '\033[35m',
|
||||
'CYAN': '\033[36m',
|
||||
'WHITE': '\033[37m',
|
||||
'BG_RED': '\033[41m',
|
||||
'BG_GREEN': '\033[42m',
|
||||
'BG_YELLOW': '\033[43m',
|
||||
'BG_BLUE': '\033[44m'
|
||||
}
|
||||
|
||||
# Emojis für verschiedene Log-Level und Kategorien
|
||||
LOG_EMOJIS = {
|
||||
'DEBUG': '🔍',
|
||||
'INFO': 'ℹ️',
|
||||
'WARNING': '⚠️',
|
||||
'ERROR': '❌',
|
||||
'CRITICAL': '🔥',
|
||||
'app': '🖥️',
|
||||
'scheduler': '⏱️',
|
||||
'auth': '🔐',
|
||||
'jobs': '🖨️',
|
||||
'printers': '🔧',
|
||||
'errors': '💥',
|
||||
'user': '👤',
|
||||
'kiosk': '📺'
|
||||
}
|
||||
|
||||
# Prüfen, ob das Terminal ANSI-Farben unterstützt
|
||||
def supports_color() -> bool:
|
||||
"""Prüft, ob das Terminal ANSI-Farben unterstützt."""
|
||||
if os.name == 'nt':
|
||||
try:
|
||||
import ctypes
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
# Aktiviere VT100-Unterstützung unter Windows
|
||||
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
return sys.stdout.isatty()
|
||||
|
||||
USE_COLORS = supports_color()
|
||||
|
||||
class ColoredFormatter(logging.Formatter):
|
||||
"""Formatter, der Farben und Emojis für Logs hinzufügt."""
|
||||
|
||||
level_colors = {
|
||||
'DEBUG': ANSI_COLORS['CYAN'],
|
||||
'INFO': ANSI_COLORS['GREEN'],
|
||||
'WARNING': ANSI_COLORS['YELLOW'],
|
||||
'ERROR': ANSI_COLORS['RED'],
|
||||
'CRITICAL': ANSI_COLORS['BG_RED'] + ANSI_COLORS['WHITE'] + ANSI_COLORS['BOLD']
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
# Basis-Format erstellen
|
||||
log_fmt = LOG_FORMAT
|
||||
date_fmt = LOG_DATE_FORMAT
|
||||
|
||||
# Emoji dem Level und der Kategorie hinzufügen
|
||||
level_name = record.levelname
|
||||
category_name = record.name.split('.')[-1] if '.' in record.name else record.name
|
||||
|
||||
level_emoji = LOG_EMOJIS.get(level_name, '')
|
||||
category_emoji = LOG_EMOJIS.get(category_name, '')
|
||||
|
||||
# Record-Objekt modifizieren (aber temporär)
|
||||
original_levelname = record.levelname
|
||||
original_name = record.name
|
||||
|
||||
# Emojis hinzufügen
|
||||
record.levelname = f"{level_emoji} {level_name}"
|
||||
record.name = f"{category_emoji} {category_name}"
|
||||
|
||||
# Farbe hinzufügen wenn unterstützt
|
||||
if USE_COLORS:
|
||||
level_color = self.level_colors.get(original_levelname, ANSI_COLORS['RESET'])
|
||||
record.levelname = f"{level_color}{record.levelname}{ANSI_COLORS['RESET']}"
|
||||
record.name = f"{ANSI_COLORS['BOLD']}{record.name}{ANSI_COLORS['RESET']}"
|
||||
|
||||
# Formatieren
|
||||
result = super().format(record)
|
||||
|
||||
# Originale Werte wiederherstellen
|
||||
record.levelname = original_levelname
|
||||
record.name = original_name
|
||||
|
||||
return result
|
||||
|
||||
class DebugInfoFilter(logging.Filter):
|
||||
"""Filter, der Debug-Informationen zu jedem Log-Eintrag hinzufügt."""
|
||||
|
||||
def __init__(self, add_hostname=True, add_process_info=True):
|
||||
super().__init__()
|
||||
self.add_hostname = add_hostname
|
||||
self.add_process_info = add_process_info
|
||||
self.hostname = socket.gethostname() if add_hostname else None
|
||||
self.pid = os.getpid() if add_process_info else None
|
||||
|
||||
def filter(self, record):
|
||||
# Debug-Informationen hinzufügen
|
||||
if self.add_hostname and not hasattr(record, 'hostname'):
|
||||
record.hostname = self.hostname
|
||||
|
||||
if self.add_process_info and not hasattr(record, 'pid'):
|
||||
record.pid = self.pid
|
||||
|
||||
# Zusätzliche Infos für DEBUG-Level
|
||||
if record.levelno == logging.DEBUG:
|
||||
# Funktionsname und Zeilennummer hervorheben
|
||||
if USE_COLORS:
|
||||
record.funcName = f"{ANSI_COLORS['CYAN']}{record.funcName}{ANSI_COLORS['RESET']}"
|
||||
record.lineno = f"{ANSI_COLORS['CYAN']}{record.lineno}{ANSI_COLORS['RESET']}"
|
||||
|
||||
return True
|
||||
|
||||
def setup_logging(debug_mode: bool = False):
|
||||
"""
|
||||
Initialisiert das Logging-System und erstellt alle erforderlichen Verzeichnisse.
|
||||
|
||||
Args:
|
||||
debug_mode: Wenn True, wird das Log-Level auf DEBUG gesetzt
|
||||
"""
|
||||
ensure_log_directories()
|
||||
|
||||
# Log-Level festlegen
|
||||
log_level = logging.DEBUG if debug_mode else getattr(logging, LOG_LEVEL)
|
||||
|
||||
# Root-Logger konfigurieren
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(getattr(logging, LOG_LEVEL))
|
||||
root_logger.setLevel(log_level)
|
||||
|
||||
# Alle Handler entfernen
|
||||
for handler in root_logger.handlers[:]:
|
||||
root_logger.removeHandler(handler)
|
||||
|
||||
# Formatter erstellen
|
||||
formatter = logging.Formatter(LOG_FORMAT, LOG_DATE_FORMAT)
|
||||
# Formatter erstellen (mit und ohne Farben)
|
||||
colored_formatter = ColoredFormatter(LOG_FORMAT, LOG_DATE_FORMAT)
|
||||
file_formatter = logging.Formatter(LOG_FORMAT, LOG_DATE_FORMAT)
|
||||
|
||||
# Filter für zusätzliche Debug-Informationen
|
||||
debug_filter = DebugInfoFilter()
|
||||
|
||||
# Console Handler für alle Logs
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(getattr(logging, LOG_LEVEL))
|
||||
console_handler.setFormatter(formatter)
|
||||
console_handler.setLevel(log_level)
|
||||
console_handler.setFormatter(colored_formatter)
|
||||
console_handler.addFilter(debug_filter)
|
||||
root_logger.addHandler(console_handler)
|
||||
|
||||
# File Handler für allgemeine App-Logs
|
||||
@ -36,9 +179,13 @@ def setup_logging():
|
||||
app_handler = logging.handlers.RotatingFileHandler(
|
||||
app_log_file, maxBytes=10*1024*1024, backupCount=5
|
||||
)
|
||||
app_handler.setLevel(getattr(logging, LOG_LEVEL))
|
||||
app_handler.setFormatter(formatter)
|
||||
app_handler.setLevel(log_level)
|
||||
app_handler.setFormatter(file_formatter)
|
||||
root_logger.addHandler(app_handler)
|
||||
|
||||
# Wenn Debug-Modus aktiv, Konfiguration loggen
|
||||
if debug_mode:
|
||||
root_logger.debug(f"🐞 Debug-Modus aktiviert - Ausführliche Logs werden generiert")
|
||||
|
||||
def get_logger(category: str) -> logging.Logger:
|
||||
"""
|
||||
@ -60,13 +207,18 @@ def get_logger(category: str) -> logging.Logger:
|
||||
# Verhindere doppelte Logs durch Parent-Logger
|
||||
logger.propagate = False
|
||||
|
||||
# Formatter erstellen
|
||||
formatter = logging.Formatter(LOG_FORMAT, LOG_DATE_FORMAT)
|
||||
# Formatter erstellen (mit und ohne Farben)
|
||||
colored_formatter = ColoredFormatter(LOG_FORMAT, LOG_DATE_FORMAT)
|
||||
file_formatter = logging.Formatter(LOG_FORMAT, LOG_DATE_FORMAT)
|
||||
|
||||
# Filter für zusätzliche Debug-Informationen
|
||||
debug_filter = DebugInfoFilter()
|
||||
|
||||
# Console Handler
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(getattr(logging, LOG_LEVEL))
|
||||
console_handler.setFormatter(formatter)
|
||||
console_handler.setFormatter(colored_formatter)
|
||||
console_handler.addFilter(debug_filter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# File Handler für spezifische Kategorie
|
||||
@ -75,7 +227,7 @@ def get_logger(category: str) -> logging.Logger:
|
||||
log_file, maxBytes=10*1024*1024, backupCount=5
|
||||
)
|
||||
file_handler.setLevel(getattr(logging, LOG_LEVEL))
|
||||
file_handler.setFormatter(formatter)
|
||||
file_handler.setFormatter(file_formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Error-Logs zusätzlich in errors.log schreiben
|
||||
@ -85,7 +237,7 @@ def get_logger(category: str) -> logging.Logger:
|
||||
error_log_file, maxBytes=10*1024*1024, backupCount=5
|
||||
)
|
||||
error_handler.setLevel(logging.ERROR)
|
||||
error_handler.setFormatter(formatter)
|
||||
error_handler.setFormatter(file_formatter)
|
||||
logger.addHandler(error_handler)
|
||||
|
||||
_loggers[category] = logger
|
||||
@ -95,7 +247,100 @@ def log_startup_info():
|
||||
"""Loggt Startup-Informationen."""
|
||||
app_logger = get_logger("app")
|
||||
app_logger.info("=" * 50)
|
||||
app_logger.info("MYP (Manage Your Printers) wird gestartet...")
|
||||
app_logger.info(f"Log-Verzeichnis: {LOG_DIR}")
|
||||
app_logger.info(f"Log-Level: {LOG_LEVEL}")
|
||||
app_logger.info("=" * 50)
|
||||
app_logger.info("🚀 MYP (Manage Your Printers) wird gestartet...")
|
||||
app_logger.info(f"📂 Log-Verzeichnis: {LOG_DIR}")
|
||||
app_logger.info(f"📊 Log-Level: {LOG_LEVEL}")
|
||||
app_logger.info(f"💻 Betriebssystem: {platform.system()} {platform.release()}")
|
||||
app_logger.info(f"🌐 Hostname: {socket.gethostname()}")
|
||||
app_logger.info(f"📅 Startzeit: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}")
|
||||
app_logger.info("=" * 50)
|
||||
|
||||
# Hilfsfunktionen für das Debugging
|
||||
|
||||
def debug_request(logger: logging.Logger, request):
|
||||
"""
|
||||
Loggt detaillierte Informationen über eine HTTP-Anfrage.
|
||||
|
||||
Args:
|
||||
logger: Logger-Instanz
|
||||
request: Flask-Request-Objekt
|
||||
"""
|
||||
if logger.level > logging.DEBUG:
|
||||
return
|
||||
|
||||
logger.debug(f"🌐 HTTP-Anfrage: {request.method} {request.path}")
|
||||
logger.debug(f"📡 Remote-Adresse: {request.remote_addr}")
|
||||
logger.debug(f"🧩 Inhaltstyp: {request.content_type}")
|
||||
|
||||
# Nur relevante Headers ausgeben
|
||||
important_headers = ['User-Agent', 'Referer', 'X-Forwarded-For', 'Authorization']
|
||||
headers = {k: v for k, v in request.headers.items() if k in important_headers}
|
||||
if headers:
|
||||
logger.debug(f"📋 Wichtige Headers: {headers}")
|
||||
|
||||
# Request-Parameter (max. 1000 Zeichen)
|
||||
if request.args:
|
||||
args_str = str(request.args)
|
||||
if len(args_str) > 1000:
|
||||
args_str = args_str[:997] + "..."
|
||||
logger.debug(f"🔍 URL-Parameter: {args_str}")
|
||||
|
||||
def debug_response(logger: logging.Logger, response, duration_ms: float = None):
|
||||
"""
|
||||
Loggt detaillierte Informationen über eine HTTP-Antwort.
|
||||
|
||||
Args:
|
||||
logger: Logger-Instanz
|
||||
response: Flask-Response-Objekt
|
||||
duration_ms: Verarbeitungsdauer in Millisekunden (optional)
|
||||
"""
|
||||
if logger.level > logging.DEBUG:
|
||||
return
|
||||
|
||||
status_emoji = "✅" if response.status_code < 400 else "❌"
|
||||
logger.debug(f"{status_emoji} HTTP-Antwort: {response.status_code}")
|
||||
|
||||
if duration_ms is not None:
|
||||
logger.debug(f"⏱️ Verarbeitungsdauer: {duration_ms:.2f} ms")
|
||||
|
||||
content_length = response.content_length or 0
|
||||
if content_length > 0:
|
||||
size_str = f"{content_length / 1024:.1f} KB" if content_length > 1024 else f"{content_length} Bytes"
|
||||
logger.debug(f"📦 Antwortgröße: {size_str}")
|
||||
|
||||
def measure_execution_time(func=None, logger=None, task_name=None):
|
||||
"""
|
||||
Dekorator, der die Ausführungszeit einer Funktion misst und loggt.
|
||||
|
||||
Args:
|
||||
func: Die zu dekorierende Funktion
|
||||
logger: Logger-Instanz (optional)
|
||||
task_name: Name der Aufgabe für das Logging (optional)
|
||||
|
||||
Returns:
|
||||
Dekorierte Funktion
|
||||
"""
|
||||
from functools import wraps
|
||||
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
start_time = time.time()
|
||||
result = f(*args, **kwargs)
|
||||
end_time = time.time()
|
||||
|
||||
duration_ms = (end_time - start_time) * 1000
|
||||
name = task_name or f.__name__
|
||||
|
||||
if logger:
|
||||
if duration_ms > 1000: # Länger als 1 Sekunde
|
||||
logger.warning(f"⏱️ Langsame Ausführung: {name} - {duration_ms:.2f} ms")
|
||||
else:
|
||||
logger.debug(f"⏱️ Ausführungszeit: {name} - {duration_ms:.2f} ms")
|
||||
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
if func:
|
||||
return decorator(func)
|
||||
return decorator
|
Loading…
x
Reference in New Issue
Block a user