🎉 Fix for JOBS_UNDEFINED and LOG_EXPORT issues, updated documentation 📚 in backend/docs.
This commit is contained in:
parent
45d8d46556
commit
5ee854cbc6
Binary file not shown.
@ -5842,6 +5842,84 @@ def api_logs():
|
||||
'error': f'Fehler beim Abrufen der Log-Daten: {str(e)}'
|
||||
}), 500
|
||||
|
||||
@app.route('/api/admin/logs/export', methods=['GET'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def export_admin_logs():
|
||||
"""
|
||||
Exportiert System-Logs als ZIP-Datei
|
||||
|
||||
Sammelt alle verfügbaren Log-Dateien und komprimiert sie in eine herunterladbare ZIP-Datei
|
||||
"""
|
||||
try:
|
||||
import os
|
||||
import zipfile
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
# Temporäre ZIP-Datei erstellen
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
zip_filename = f"myp_logs_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
|
||||
zip_path = os.path.join(temp_dir, zip_filename)
|
||||
|
||||
log_dir = os.path.join(os.path.dirname(__file__), 'logs')
|
||||
|
||||
# Prüfen ob Log-Verzeichnis existiert
|
||||
if not os.path.exists(log_dir):
|
||||
app_logger.warning(f"Log-Verzeichnis nicht gefunden: {log_dir}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"message": "Log-Verzeichnis nicht gefunden"
|
||||
}), 404
|
||||
|
||||
# ZIP-Datei erstellen und Log-Dateien hinzufügen
|
||||
files_added = 0
|
||||
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||
for root, dirs, files in os.walk(log_dir):
|
||||
for file in files:
|
||||
if file.endswith('.log'):
|
||||
file_path = os.path.join(root, file)
|
||||
try:
|
||||
# Relativen Pfad für Archiv erstellen
|
||||
arcname = os.path.relpath(file_path, log_dir)
|
||||
zipf.write(file_path, arcname)
|
||||
files_added += 1
|
||||
app_logger.debug(f"Log-Datei hinzugefügt: {arcname}")
|
||||
except Exception as file_error:
|
||||
app_logger.warning(f"Fehler beim Hinzufügen der Datei {file_path}: {str(file_error)}")
|
||||
continue
|
||||
|
||||
# Prüfen ob Dateien hinzugefügt wurden
|
||||
if files_added == 0:
|
||||
# Leere ZIP-Datei löschen
|
||||
try:
|
||||
os.remove(zip_path)
|
||||
os.rmdir(temp_dir)
|
||||
except:
|
||||
pass
|
||||
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"message": "Keine Log-Dateien zum Exportieren gefunden"
|
||||
}), 404
|
||||
|
||||
app_logger.info(f"System-Logs exportiert: {files_added} Dateien in {zip_filename}")
|
||||
|
||||
# ZIP-Datei als Download senden
|
||||
return send_file(
|
||||
zip_path,
|
||||
as_attachment=True,
|
||||
download_name=zip_filename,
|
||||
mimetype='application/zip'
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
app_logger.error(f"Fehler beim Exportieren der Logs: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"message": f"Fehler beim Exportieren: {str(e)}"
|
||||
}), 500
|
||||
|
||||
# ===== FEHLENDE ADMIN API-ENDPUNKTE =====
|
||||
|
||||
@app.route("/api/admin/database/status", methods=['GET'])
|
||||
|
Binary file not shown.
1
backend/docs/JOBS_UNDEFINED_FIX.md
Normal file
1
backend/docs/JOBS_UNDEFINED_FIX.md
Normal file
@ -0,0 +1 @@
|
||||
|
1
backend/docs/LOG_EXPORT_FIX.md
Normal file
1
backend/docs/LOG_EXPORT_FIX.md
Normal file
@ -0,0 +1 @@
|
||||
|
@ -9,3 +9,7 @@
|
||||
2025-06-01 03:30:47 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert
|
||||
2025-06-01 03:49:33 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert
|
||||
2025-06-01 03:50:54 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert
|
||||
2025-06-01 03:39:15 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert
|
||||
2025-06-01 03:42:25 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert
|
||||
2025-06-01 03:42:33 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert
|
||||
2025-06-01 04:01:30 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert
|
||||
|
@ -351,3 +351,48 @@ WHERE users.id = ?
|
||||
2025-06-01 03:51:05 - [app] app - [INFO] INFO - Admin-Check für Funktion api_admin_stats_live: User authenticated: True, User ID: 1, Is Admin: True
|
||||
2025-06-01 03:51:06 - [app] app - [WARNING] WARNING - System-Performance-Metriken nicht verfügbar: argument 1 (impossible<bad format char>)
|
||||
2025-06-01 03:51:06 - [app] app - [INFO] INFO - Admin-Check für Funktion api_logs: User authenticated: True, User ID: 1, Is Admin: True
|
||||
2025-06-01 03:39:14 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: /mnt/database/myp.db
|
||||
2025-06-01 03:39:15 - [app] app - [INFO] INFO - SQLite für Produktionsumgebung konfiguriert (WAL-Modus, Cache, Optimierungen)
|
||||
2025-06-01 03:42:25 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: /mnt/database/myp.db
|
||||
2025-06-01 03:42:26 - [app] app - [INFO] INFO - SQLite für Produktionsumgebung konfiguriert (WAL-Modus, Cache, Optimierungen)
|
||||
2025-06-01 03:42:26 - [app] app - [INFO] INFO - ✅ Timeout Force-Quit Manager geladen
|
||||
2025-06-01 03:42:26 - [app] app - [INFO] INFO - ✅ Zentraler Shutdown-Manager initialisiert
|
||||
2025-06-01 03:42:26 - [app] app - [INFO] INFO - 🔄 Starte Datenbank-Setup und Migrationen...
|
||||
2025-06-01 03:42:26 - [app] app - [INFO] INFO - Datenbank mit Optimierungen initialisiert
|
||||
2025-06-01 03:42:26 - [app] app - [INFO] INFO - ✅ JobOrder-Tabelle bereits vorhanden
|
||||
2025-06-01 03:42:26 - [app] app - [INFO] INFO - Admin-Benutzer admin (admin@mercedes-benz.com) existiert bereits. Passwort wurde zurückgesetzt.
|
||||
2025-06-01 03:42:26 - [app] app - [INFO] INFO - ✅ Datenbank-Setup und Migrationen erfolgreich abgeschlossen
|
||||
2025-06-01 03:42:26 - [app] app - [INFO] INFO - 🖨️ Starte automatische Steckdosen-Initialisierung...
|
||||
2025-06-01 03:42:26 - [app] app - [INFO] INFO - ℹ️ Keine Drucker zur Initialisierung gefunden
|
||||
2025-06-01 03:42:26 - [app] app - [INFO] INFO - ✅ Printer Queue Manager erfolgreich gestartet
|
||||
2025-06-01 03:42:27 - [app] app - [INFO] INFO - Job-Scheduler gestartet
|
||||
2025-06-01 03:42:27 - [app] app - [INFO] INFO - Starte HTTPS-Server auf 0.0.0.0:443
|
||||
2025-06-01 03:42:33 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: /mnt/database/myp.db
|
||||
2025-06-01 03:42:34 - [app] app - [INFO] INFO - SQLite für Produktionsumgebung konfiguriert (WAL-Modus, Cache, Optimierungen)
|
||||
2025-06-01 03:42:34 - [app] app - [INFO] INFO - ✅ Timeout Force-Quit Manager geladen
|
||||
2025-06-01 03:42:34 - [app] app - [INFO] INFO - ✅ Zentraler Shutdown-Manager initialisiert
|
||||
2025-06-01 03:42:34 - [app] app - [INFO] INFO - 🔄 Starte Datenbank-Setup und Migrationen...
|
||||
2025-06-01 03:42:34 - [app] app - [INFO] INFO - Datenbank mit Optimierungen initialisiert
|
||||
2025-06-01 03:42:34 - [app] app - [INFO] INFO - ✅ JobOrder-Tabelle bereits vorhanden
|
||||
2025-06-01 03:42:34 - [app] app - [INFO] INFO - Admin-Benutzer admin (admin@mercedes-benz.com) existiert bereits. Passwort wurde zurückgesetzt.
|
||||
2025-06-01 03:42:34 - [app] app - [INFO] INFO - ✅ Datenbank-Setup und Migrationen erfolgreich abgeschlossen
|
||||
2025-06-01 03:42:34 - [app] app - [INFO] INFO - 🖨️ Starte automatische Steckdosen-Initialisierung...
|
||||
2025-06-01 03:42:34 - [app] app - [INFO] INFO - ℹ️ Keine Drucker zur Initialisierung gefunden
|
||||
2025-06-01 03:42:34 - [app] app - [INFO] INFO - ✅ Printer Queue Manager erfolgreich gestartet
|
||||
2025-06-01 03:42:34 - [app] app - [INFO] INFO - Job-Scheduler gestartet
|
||||
2025-06-01 03:42:34 - [app] app - [INFO] INFO - Starte HTTPS-Server auf 0.0.0.0:443
|
||||
2025-06-01 04:01:29 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - SQLite für Produktionsumgebung konfiguriert (WAL-Modus, Cache, Optimierungen)
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - ✅ Timeout Force-Quit Manager geladen
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - ✅ Zentraler Shutdown-Manager initialisiert
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - 🔄 Starte Datenbank-Setup und Migrationen...
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - Datenbank mit Optimierungen initialisiert
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - ✅ JobOrder-Tabelle bereits vorhanden
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - Admin-Benutzer admin (admin@mercedes-benz.com) existiert bereits. Passwort wurde zurückgesetzt.
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - ✅ Datenbank-Setup und Migrationen erfolgreich abgeschlossen
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - 🖨️ Starte automatische Steckdosen-Initialisierung...
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - ℹ️ Keine Drucker zur Initialisierung gefunden
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - 🔄 Debug-Modus: Queue Manager deaktiviert für Entwicklung
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - Job-Scheduler gestartet
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - Starte Debug-Server auf 0.0.0.0:5000 (HTTP)
|
||||
2025-06-01 04:01:31 - [app] app - [INFO] INFO - Windows-Debug-Modus: Auto-Reload deaktiviert
|
||||
|
@ -9,3 +9,7 @@
|
||||
2025-06-01 03:30:47 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation)
|
||||
2025-06-01 03:49:33 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation)
|
||||
2025-06-01 03:50:54 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation)
|
||||
2025-06-01 03:39:15 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation)
|
||||
2025-06-01 03:42:25 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation)
|
||||
2025-06-01 03:42:33 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation)
|
||||
2025-06-01 04:01:29 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation)
|
||||
|
@ -4,3 +4,4 @@
|
||||
2025-06-01 03:43:14 - [calendar] calendar - [INFO] INFO - 📅 Kalender-Events abgerufen: 0 Einträge für Zeitraum 2025-06-01 00:00:00 bis 2025-06-08 00:00:00
|
||||
2025-06-01 03:49:50 - [calendar] calendar - [INFO] INFO - 📅 Kalender-Events abgerufen: 0 Einträge für Zeitraum 2025-06-01 00:00:00 bis 2025-06-08 00:00:00
|
||||
2025-06-01 03:50:49 - [calendar] calendar - [INFO] INFO - 📅 Kalender-Events abgerufen: 0 Einträge für Zeitraum 2025-06-01 00:00:00 bis 2025-06-08 00:00:00
|
||||
2025-06-01 04:01:49 - [calendar] calendar - [INFO] INFO - 📅 Kalender-Events abgerufen: 0 Einträge für Zeitraum 2025-06-01 00:00:00 bis 2025-06-08 00:00:00
|
||||
|
@ -32,3 +32,16 @@
|
||||
2025-06-01 03:50:55 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet
|
||||
2025-06-01 03:50:55 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server wird mit threading initialisiert (eventlet-Fallback)
|
||||
2025-06-01 03:50:55 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server initialisiert (async_mode: threading)
|
||||
2025-06-01 03:39:15 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet
|
||||
2025-06-01 03:42:26 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet
|
||||
2025-06-01 03:42:26 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet
|
||||
2025-06-01 03:42:26 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server wird mit threading initialisiert (eventlet-Fallback)
|
||||
2025-06-01 03:42:26 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server initialisiert (async_mode: threading)
|
||||
2025-06-01 03:42:34 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet
|
||||
2025-06-01 03:42:34 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet
|
||||
2025-06-01 03:42:34 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server wird mit threading initialisiert (eventlet-Fallback)
|
||||
2025-06-01 03:42:34 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server initialisiert (async_mode: threading)
|
||||
2025-06-01 04:01:31 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet
|
||||
2025-06-01 04:01:31 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet
|
||||
2025-06-01 04:01:31 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server wird mit threading initialisiert (eventlet-Fallback)
|
||||
2025-06-01 04:01:31 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server initialisiert (async_mode: threading)
|
||||
|
@ -9,3 +9,7 @@
|
||||
2025-06-01 03:30:47 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet
|
||||
2025-06-01 03:49:33 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet
|
||||
2025-06-01 03:50:54 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet
|
||||
2025-06-01 03:39:15 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet
|
||||
2025-06-01 03:42:25 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet
|
||||
2025-06-01 03:42:33 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet
|
||||
2025-06-01 04:01:29 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet
|
||||
|
@ -8,3 +8,6 @@
|
||||
2025-06-01 03:43:03 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
|
||||
2025-06-01 03:49:34 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
|
||||
2025-06-01 03:50:55 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
|
||||
2025-06-01 03:42:26 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
|
||||
2025-06-01 03:42:34 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
|
||||
2025-06-01 04:01:31 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
|
||||
|
@ -39,3 +39,4 @@
|
||||
2025-06-01 03:49:46 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 0 von 0 (Seite 1)
|
||||
2025-06-01 03:50:44 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 0 von 0 (Seite 1)
|
||||
2025-06-01 03:50:53 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 0 von 0 (Seite 1)
|
||||
2025-06-01 04:01:40 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 0 von 0 (Seite 1)
|
||||
|
@ -16,3 +16,9 @@
|
||||
2025-06-01 03:49:34 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
|
||||
2025-06-01 03:50:55 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
|
||||
2025-06-01 03:50:55 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
|
||||
2025-06-01 03:42:26 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
|
||||
2025-06-01 03:42:26 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
|
||||
2025-06-01 03:42:34 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
|
||||
2025-06-01 03:42:34 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
|
||||
2025-06-01 04:01:31 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
|
||||
2025-06-01 04:01:31 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
|
||||
|
@ -16,3 +16,9 @@
|
||||
2025-06-01 03:49:34 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
|
||||
2025-06-01 03:50:55 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
|
||||
2025-06-01 03:50:55 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
|
||||
2025-06-01 03:42:26 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
|
||||
2025-06-01 03:42:26 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
|
||||
2025-06-01 03:42:34 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
|
||||
2025-06-01 03:42:34 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
|
||||
2025-06-01 04:01:31 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
|
||||
2025-06-01 04:01:31 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
|
||||
|
@ -6,3 +6,6 @@
|
||||
2025-06-01 03:43:03 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert
|
||||
2025-06-01 03:49:34 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert
|
||||
2025-06-01 03:50:55 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert
|
||||
2025-06-01 03:42:26 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert
|
||||
2025-06-01 03:42:34 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert
|
||||
2025-06-01 04:01:31 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert
|
||||
|
@ -319,3 +319,46 @@
|
||||
2025-06-01 03:51:08 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 3/6: 192.168.0.100
|
||||
2025-06-01 03:51:14 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 4/6: 192.168.0.101
|
||||
2025-06-01 03:51:20 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 5/6: 192.168.0.102
|
||||
2025-06-01 03:39:15 - [printer_monitor] printer_monitor - [INFO] INFO - 🖨️ Drucker-Monitor initialisiert
|
||||
2025-06-01 03:39:15 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
|
||||
2025-06-01 03:42:25 - [printer_monitor] printer_monitor - [INFO] INFO - 🖨️ Drucker-Monitor initialisiert
|
||||
2025-06-01 03:42:25 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
|
||||
2025-06-01 03:42:26 - [printer_monitor] printer_monitor - [INFO] INFO - 🚀 Starte Steckdosen-Initialisierung beim Programmstart...
|
||||
2025-06-01 03:42:26 - [printer_monitor] printer_monitor - [WARNING] WARNING - ⚠️ Keine aktiven Drucker zur Initialisierung gefunden
|
||||
2025-06-01 03:42:27 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Starte automatische Tapo-Steckdosenerkennung...
|
||||
2025-06-01 03:42:27 - [printer_monitor] printer_monitor - [INFO] INFO - 🔄 Teste 6 Standard-IPs aus der Konfiguration
|
||||
2025-06-01 03:42:27 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 1/6: 192.168.0.103
|
||||
2025-06-01 03:42:33 - [printer_monitor] printer_monitor - [INFO] INFO - 🖨️ Drucker-Monitor initialisiert
|
||||
2025-06-01 03:42:33 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
|
||||
2025-06-01 03:42:34 - [printer_monitor] printer_monitor - [INFO] INFO - 🚀 Starte Steckdosen-Initialisierung beim Programmstart...
|
||||
2025-06-01 03:42:34 - [printer_monitor] printer_monitor - [WARNING] WARNING - ⚠️ Keine aktiven Drucker zur Initialisierung gefunden
|
||||
2025-06-01 03:42:35 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Starte automatische Tapo-Steckdosenerkennung...
|
||||
2025-06-01 03:42:35 - [printer_monitor] printer_monitor - [INFO] INFO - 🔄 Teste 6 Standard-IPs aus der Konfiguration
|
||||
2025-06-01 03:42:35 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 1/6: 192.168.0.103
|
||||
2025-06-01 03:42:44 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 2/6: 192.168.0.104
|
||||
2025-06-01 03:42:51 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 3/6: 192.168.0.100
|
||||
2025-06-01 03:42:59 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 4/6: 192.168.0.101
|
||||
2025-06-01 03:43:06 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 5/6: 192.168.0.102
|
||||
2025-06-01 03:43:13 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 6/6: 192.168.0.105
|
||||
2025-06-01 03:43:20 - [printer_monitor] printer_monitor - [INFO] INFO - ✅ Steckdosen-Erkennung abgeschlossen: 0/6 Steckdosen gefunden in 44.2s
|
||||
2025-06-01 04:01:29 - [printer_monitor] printer_monitor - [INFO] INFO - 🖨️ Drucker-Monitor initialisiert
|
||||
2025-06-01 04:01:29 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
|
||||
2025-06-01 04:01:31 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Starte automatische Tapo-Steckdosenerkennung...
|
||||
2025-06-01 04:01:31 - [printer_monitor] printer_monitor - [INFO] INFO - 🔄 Teste 6 Standard-IPs aus der Konfiguration
|
||||
2025-06-01 04:01:31 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 1/6: 192.168.0.103
|
||||
2025-06-01 04:01:31 - [printer_monitor] printer_monitor - [INFO] INFO - 🚀 Starte Steckdosen-Initialisierung beim Programmstart...
|
||||
2025-06-01 04:01:31 - [printer_monitor] printer_monitor - [WARNING] WARNING - ⚠️ Keine aktiven Drucker zur Initialisierung gefunden
|
||||
2025-06-01 04:01:37 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 2/6: 192.168.0.104
|
||||
2025-06-01 04:01:38 - [printer_monitor] printer_monitor - [INFO] INFO - 🔄 Aktualisiere Live-Druckerstatus...
|
||||
2025-06-01 04:01:38 - [printer_monitor] printer_monitor - [INFO] INFO - ℹ️ Keine aktiven Drucker gefunden
|
||||
2025-06-01 04:01:38 - [printer_monitor] printer_monitor - [INFO] INFO - 🔄 Aktualisiere Live-Druckerstatus...
|
||||
2025-06-01 04:01:38 - [printer_monitor] printer_monitor - [INFO] INFO - ℹ️ Keine aktiven Drucker gefunden
|
||||
2025-06-01 04:01:42 - [printer_monitor] printer_monitor - [INFO] INFO - 🔄 Aktualisiere Live-Druckerstatus...
|
||||
2025-06-01 04:01:42 - [printer_monitor] printer_monitor - [INFO] INFO - ℹ️ Keine aktiven Drucker gefunden
|
||||
2025-06-01 04:01:42 - [printer_monitor] printer_monitor - [INFO] INFO - 🔄 Aktualisiere Live-Druckerstatus...
|
||||
2025-06-01 04:01:42 - [printer_monitor] printer_monitor - [INFO] INFO - ℹ️ Keine aktiven Drucker gefunden
|
||||
2025-06-01 04:01:43 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 3/6: 192.168.0.100
|
||||
2025-06-01 04:01:49 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 4/6: 192.168.0.101
|
||||
2025-06-01 04:01:55 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 5/6: 192.168.0.102
|
||||
2025-06-01 04:02:01 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 6/6: 192.168.0.105
|
||||
2025-06-01 04:02:07 - [printer_monitor] printer_monitor - [INFO] INFO - ✅ Steckdosen-Erkennung abgeschlossen: 0/6 Steckdosen gefunden in 36.0s
|
||||
|
@ -3073,3 +3073,12 @@
|
||||
2025-06-01 03:51:04 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||
2025-06-01 03:51:04 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||
2025-06-01 03:51:04 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 11.94ms
|
||||
2025-06-01 04:01:38 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||
2025-06-01 04:01:38 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||
2025-06-01 04:01:38 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 6.07ms
|
||||
2025-06-01 04:01:40 - [printers] printers - [INFO] INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check)
|
||||
2025-06-01 04:01:42 - [printers] printers - [INFO] INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check)
|
||||
2025-06-01 04:01:42 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||
2025-06-01 04:01:42 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||
2025-06-01 04:01:42 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 6.22ms
|
||||
2025-06-01 04:01:42 - [printers] printers - [INFO] INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check)
|
||||
|
@ -20,3 +20,25 @@
|
||||
2025-06-01 03:51:23 - [queue_manager] queue_manager - [INFO] INFO - 🛑 Shutdown-Signal empfangen - beende Monitor-Loop
|
||||
2025-06-01 03:51:23 - [queue_manager] queue_manager - [INFO] INFO - 🔚 Monitor-Loop beendet
|
||||
2025-06-01 03:51:23 - [queue_manager] queue_manager - [INFO] INFO - ✅ Queue-Manager erfolgreich gestoppt
|
||||
2025-06-01 03:42:26 - [queue_manager] queue_manager - [INFO] INFO - 🚀 Initialisiere neuen Queue-Manager...
|
||||
2025-06-01 03:42:26 - [queue_manager] queue_manager - [INFO] INFO - 🔄 Zentrale Shutdown-Verwaltung erkannt - deaktiviere lokale Signal-Handler
|
||||
2025-06-01 03:42:26 - [queue_manager] queue_manager - [INFO] INFO - 🚀 Starte Printer Queue Manager...
|
||||
2025-06-01 03:42:26 - [queue_manager] queue_manager - [INFO] INFO - 🔄 Queue-Überwachung gestartet (Intervall: 120 Sekunden)
|
||||
2025-06-01 03:42:26 - [queue_manager] queue_manager - [INFO] INFO - ✅ Printer Queue Manager gestartet
|
||||
2025-06-01 03:42:26 - [queue_manager] queue_manager - [INFO] INFO - ✅ Queue-Manager erfolgreich gestartet
|
||||
2025-06-01 03:42:31 - [queue_manager] queue_manager - [INFO] INFO - 🔄 Stoppe Queue-Manager...
|
||||
2025-06-01 03:42:31 - [queue_manager] queue_manager - [INFO] INFO - ⏳ Warte auf Monitor-Thread...
|
||||
2025-06-01 03:42:31 - [queue_manager] queue_manager - [INFO] INFO - 🛑 Shutdown-Signal empfangen - beende Monitor-Loop
|
||||
2025-06-01 03:42:31 - [queue_manager] queue_manager - [INFO] INFO - 🔚 Monitor-Loop beendet
|
||||
2025-06-01 03:42:31 - [queue_manager] queue_manager - [INFO] INFO - ✅ Queue-Manager erfolgreich gestoppt
|
||||
2025-06-01 03:42:34 - [queue_manager] queue_manager - [INFO] INFO - 🚀 Initialisiere neuen Queue-Manager...
|
||||
2025-06-01 03:42:34 - [queue_manager] queue_manager - [INFO] INFO - 🔄 Zentrale Shutdown-Verwaltung erkannt - deaktiviere lokale Signal-Handler
|
||||
2025-06-01 03:42:34 - [queue_manager] queue_manager - [INFO] INFO - 🚀 Starte Printer Queue Manager...
|
||||
2025-06-01 03:42:34 - [queue_manager] queue_manager - [INFO] INFO - 🔄 Queue-Überwachung gestartet (Intervall: 120 Sekunden)
|
||||
2025-06-01 03:42:34 - [queue_manager] queue_manager - [INFO] INFO - ✅ Printer Queue Manager gestartet
|
||||
2025-06-01 03:42:34 - [queue_manager] queue_manager - [INFO] INFO - ✅ Queue-Manager erfolgreich gestartet
|
||||
2025-06-01 03:43:52 - [queue_manager] queue_manager - [INFO] INFO - 🔄 Stoppe Queue-Manager...
|
||||
2025-06-01 03:43:52 - [queue_manager] queue_manager - [INFO] INFO - ⏳ Warte auf Monitor-Thread...
|
||||
2025-06-01 03:43:52 - [queue_manager] queue_manager - [INFO] INFO - 🛑 Shutdown-Signal empfangen - beende Monitor-Loop
|
||||
2025-06-01 03:43:52 - [queue_manager] queue_manager - [INFO] INFO - 🔚 Monitor-Loop beendet
|
||||
2025-06-01 03:43:52 - [queue_manager] queue_manager - [INFO] INFO - ✅ Queue-Manager erfolgreich gestoppt
|
||||
|
@ -2847,3 +2847,13 @@
|
||||
2025-06-01 03:50:54 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||
2025-06-01 03:50:55 - [scheduler] scheduler - [INFO] INFO - Scheduler-Thread gestartet
|
||||
2025-06-01 03:50:55 - [scheduler] scheduler - [INFO] INFO - Scheduler gestartet
|
||||
2025-06-01 03:39:15 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||
2025-06-01 03:42:25 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||
2025-06-01 03:42:27 - [scheduler] scheduler - [INFO] INFO - Scheduler-Thread gestartet
|
||||
2025-06-01 03:42:27 - [scheduler] scheduler - [INFO] INFO - Scheduler gestartet
|
||||
2025-06-01 03:42:33 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||
2025-06-01 03:42:34 - [scheduler] scheduler - [INFO] INFO - Scheduler-Thread gestartet
|
||||
2025-06-01 03:42:34 - [scheduler] scheduler - [INFO] INFO - Scheduler gestartet
|
||||
2025-06-01 04:01:29 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||
2025-06-01 04:01:31 - [scheduler] scheduler - [INFO] INFO - Scheduler-Thread gestartet
|
||||
2025-06-01 04:01:31 - [scheduler] scheduler - [INFO] INFO - Scheduler gestartet
|
||||
|
@ -6,3 +6,6 @@
|
||||
2025-06-01 03:43:03 - [security] security - [INFO] INFO - 🔒 Security System initialisiert
|
||||
2025-06-01 03:49:34 - [security] security - [INFO] INFO - 🔒 Security System initialisiert
|
||||
2025-06-01 03:50:55 - [security] security - [INFO] INFO - 🔒 Security System initialisiert
|
||||
2025-06-01 03:42:26 - [security] security - [INFO] INFO - 🔒 Security System initialisiert
|
||||
2025-06-01 03:42:34 - [security] security - [INFO] INFO - 🔒 Security System initialisiert
|
||||
2025-06-01 04:01:31 - [security] security - [INFO] INFO - 🔒 Security System initialisiert
|
||||
|
@ -39,3 +39,6 @@
|
||||
2025-06-01 03:43:03 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert
|
||||
2025-06-01 03:49:34 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert
|
||||
2025-06-01 03:50:55 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert
|
||||
2025-06-01 03:42:26 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert
|
||||
2025-06-01 03:42:34 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert
|
||||
2025-06-01 04:01:31 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert
|
||||
|
@ -70,3 +70,26 @@
|
||||
2025-06-01 03:50:55 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert
|
||||
2025-06-01 03:50:55 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert
|
||||
2025-06-01 03:50:55 - [startup] startup - [INFO] INFO - ==================================================
|
||||
2025-06-01 03:42:26 - [startup] startup - [INFO] INFO - ==================================================
|
||||
2025-06-01 03:42:26 - [startup] startup - [INFO] INFO - 🚀 MYP Platform Backend wird gestartet...
|
||||
2025-06-01 03:42:26 - [startup] startup - [INFO] INFO - 🐍 Python Version: 3.11.2 (main, Apr 28 2025, 14:11:48) [GCC 12.2.0]
|
||||
2025-06-01 03:42:26 - [startup] startup - [INFO] INFO - 💻 Betriebssystem: posix (linux)
|
||||
2025-06-01 03:42:26 - [startup] startup - [INFO] INFO - 📁 Arbeitsverzeichnis: /mnt
|
||||
2025-06-01 03:42:26 - [startup] startup - [INFO] INFO - ⏰ Startzeit: 2025-06-01T03:42:26.267864
|
||||
2025-06-01 03:42:26 - [startup] startup - [INFO] INFO - ==================================================
|
||||
2025-06-01 03:42:34 - [startup] startup - [INFO] INFO - ==================================================
|
||||
2025-06-01 03:42:34 - [startup] startup - [INFO] INFO - 🚀 MYP Platform Backend wird gestartet...
|
||||
2025-06-01 03:42:34 - [startup] startup - [INFO] INFO - 🐍 Python Version: 3.11.2 (main, Apr 28 2025, 14:11:48) [GCC 12.2.0]
|
||||
2025-06-01 03:42:34 - [startup] startup - [INFO] INFO - 💻 Betriebssystem: posix (linux)
|
||||
2025-06-01 03:42:34 - [startup] startup - [INFO] INFO - 📁 Arbeitsverzeichnis: /mnt
|
||||
2025-06-01 03:42:34 - [startup] startup - [INFO] INFO - ⏰ Startzeit: 2025-06-01T03:42:34.257182
|
||||
2025-06-01 03:42:34 - [startup] startup - [INFO] INFO - ==================================================
|
||||
2025-06-01 04:01:31 - [startup] startup - [INFO] INFO - ==================================================
|
||||
2025-06-01 04:01:31 - [startup] startup - [INFO] INFO - 🚀 MYP Platform Backend wird gestartet...
|
||||
2025-06-01 04:01:31 - [startup] startup - [INFO] INFO - 🐍 Python Version: 3.13.3 (tags/v3.13.3:6280bb5, Apr 8 2025, 14:47:33) [MSC v.1943 64 bit (AMD64)]
|
||||
2025-06-01 04:01:31 - [startup] startup - [INFO] INFO - 💻 Betriebssystem: nt (win32)
|
||||
2025-06-01 04:01:31 - [startup] startup - [INFO] INFO - 📁 Arbeitsverzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend
|
||||
2025-06-01 04:01:31 - [startup] startup - [INFO] INFO - ⏰ Startzeit: 2025-06-01T04:01:31.569736
|
||||
2025-06-01 04:01:31 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert
|
||||
2025-06-01 04:01:31 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert
|
||||
2025-06-01 04:01:31 - [startup] startup - [INFO] INFO - ==================================================
|
||||
|
@ -46,3 +46,7 @@
|
||||
2025-06-01 03:50:54 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
|
||||
2025-06-01 03:50:54 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet
|
||||
2025-06-01 03:50:54 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
|
||||
2025-06-01 04:01:29 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an...
|
||||
2025-06-01 04:01:29 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
|
||||
2025-06-01 04:01:29 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet
|
||||
2025-06-01 04:01:29 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
|
||||
|
@ -1,9 +1,10 @@
|
||||
# MYP Platform - Benötigte Python Dependencies
|
||||
# Nur tatsächlich verwendete Pakete
|
||||
# MYP Platform - Python Dependencies
|
||||
# Aktualisiert: 2025-01-12
|
||||
# Kompatibel mit Python 3.8+
|
||||
|
||||
# ===== CORE FRAMEWORK =====
|
||||
Flask
|
||||
Werkzeug
|
||||
Flask>=2.3.0,<3.0.0
|
||||
Werkzeug>=2.3.0,<3.0.0
|
||||
|
||||
# ===== FLASK EXTENSIONS =====
|
||||
Flask-Login
|
||||
@ -13,10 +14,10 @@ WTForms
|
||||
Flask-CORS
|
||||
|
||||
# ===== DATABASE =====
|
||||
SQLAlchemy
|
||||
SQLAlchemy>=2.0.0,<3.0.0
|
||||
|
||||
# ===== SECURITY =====
|
||||
cryptography
|
||||
cryptography>=41.0.0
|
||||
bcrypt
|
||||
PyJWT
|
||||
itsdangerous
|
||||
@ -41,7 +42,7 @@ APScheduler
|
||||
# ===== GIS & LOCATION =====
|
||||
geocoder
|
||||
|
||||
# ===== DATA PROCESSING =====
|
||||
# ===== DATA PROCESSING & EXPORT =====
|
||||
openpyxl
|
||||
xlsxwriter
|
||||
pandas
|
||||
@ -49,37 +50,49 @@ chardet
|
||||
python-magic
|
||||
python-magic-bin; sys_platform == "win32"
|
||||
|
||||
# ===== EMAIL =====
|
||||
# ===== EMAIL & VALIDATION =====
|
||||
email-validator
|
||||
|
||||
# ===== IMAGE PROCESSING =====
|
||||
Pillow
|
||||
qrcode
|
||||
qrcode[pil]
|
||||
|
||||
# ===== PDF GENERATION =====
|
||||
# ===== PDF & REPORT GENERATION =====
|
||||
reportlab
|
||||
weasyprint
|
||||
|
||||
# ===== DATE/TIME =====
|
||||
# ===== DATE/TIME HANDLING =====
|
||||
python-dateutil
|
||||
pytz
|
||||
|
||||
# ===== LOGGING =====
|
||||
# ===== LOGGING & MONITORING =====
|
||||
colorlog
|
||||
|
||||
# ===== SYSTEM MONITORING =====
|
||||
psutil
|
||||
|
||||
# ===== FILE WATCHING =====
|
||||
# ===== FILE SYSTEM OPERATIONS =====
|
||||
watchdog
|
||||
Send2Trash
|
||||
|
||||
# ===== VALIDATION =====
|
||||
# ===== DATA VALIDATION =====
|
||||
cerberus
|
||||
marshmallow
|
||||
validators
|
||||
|
||||
# ===== UTILITIES =====
|
||||
python-slugify
|
||||
click
|
||||
humanize
|
||||
python-dotenv
|
||||
|
||||
# ===== NETWORK & API =====
|
||||
ping3
|
||||
netifaces
|
||||
|
||||
# ===== CACHING =====
|
||||
cachelib
|
||||
|
||||
# ===== COMPRESSION =====
|
||||
py7zr
|
||||
|
||||
# ===== WINDOWS COMPATIBILITY =====
|
||||
pywin32; sys_platform == "win32"
|
||||
@ -93,5 +106,20 @@ RPi.GPIO; sys_platform == "linux"
|
||||
gunicorn; sys_platform != "win32"
|
||||
waitress
|
||||
|
||||
# ===== DEVELOPMENT =====
|
||||
python-dotenv
|
||||
# ===== TESTING & DEVELOPMENT =====
|
||||
pytest
|
||||
pytest-flask
|
||||
pytest-cov
|
||||
coverage
|
||||
|
||||
# ===== CODE QUALITY =====
|
||||
flake8
|
||||
black
|
||||
isort
|
||||
|
||||
# ===== OPTIONAL PERFORMANCE ENHANCEMENTS =====
|
||||
# Uncomment for better performance:
|
||||
# uwsgi; sys_platform != "win32"
|
||||
# gevent
|
||||
# redis
|
||||
# celery
|
||||
|
@ -1055,48 +1055,792 @@
|
||||
|
||||
/* Benachrichtigungen */
|
||||
.notification {
|
||||
@apply fixed top-4 right-4 max-w-md p-4 rounded-lg shadow-lg transform translate-x-full opacity-0 transition-all duration-300 z-50 bg-white dark:bg-slate-800 border-l-4;
|
||||
@apply fixed top-4 right-4 max-w-md p-4 rounded-2xl shadow-2xl transform translate-x-full opacity-0 transition-all duration-500 z-50;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
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.25);
|
||||
box-shadow:
|
||||
0 32px 64px rgba(0, 0, 0, 0.25),
|
||||
0 12px 24px 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: notification-slide-in 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
|
||||
.dark .notification {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(40px) saturate(180%) brightness(120%) contrast(115%);
|
||||
-webkit-backdrop-filter: blur(40px) saturate(180%) brightness(120%) contrast(115%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
box-shadow:
|
||||
0 32px 64px rgba(0, 0, 0, 0.6),
|
||||
0 12px 24px 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);
|
||||
}
|
||||
|
||||
.notification.show {
|
||||
@apply translate-x-0 opacity-100;
|
||||
}
|
||||
|
||||
|
||||
.notification:hover {
|
||||
transform: translateY(-2px) scale(1.02);
|
||||
box-shadow:
|
||||
0 40px 80px rgba(0, 0, 0, 0.3),
|
||||
0 16px 32px rgba(0, 0, 0, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.5),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.dark .notification:hover {
|
||||
box-shadow:
|
||||
0 40px 80px rgba(0, 0, 0, 0.7),
|
||||
0 16px 32px rgba(0, 0, 0, 0.5),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.notification-success {
|
||||
@apply border-green-500;
|
||||
@apply text-green-100;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(34, 197, 94, 0.25) 0%,
|
||||
rgba(134, 239, 172, 0.18) 50%,
|
||||
rgba(34, 197, 94, 0.12) 100%);
|
||||
border: 1px solid rgba(34, 197, 94, 0.4);
|
||||
box-shadow:
|
||||
0 32px 64px rgba(34, 197, 94, 0.2),
|
||||
0 12px 24px rgba(34, 197, 94, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4),
|
||||
0 0 0 1px rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.notification-error {
|
||||
@apply border-red-500;
|
||||
@apply text-red-100;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(239, 68, 68, 0.25) 0%,
|
||||
rgba(252, 165, 165, 0.18) 50%,
|
||||
rgba(239, 68, 68, 0.12) 100%);
|
||||
border: 1px solid rgba(239, 68, 68, 0.4);
|
||||
box-shadow:
|
||||
0 32px 64px rgba(239, 68, 68, 0.2),
|
||||
0 12px 24px rgba(239, 68, 68, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4),
|
||||
0 0 0 1px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.notification-warning {
|
||||
@apply border-yellow-500;
|
||||
@apply text-yellow-100;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(245, 158, 11, 0.25) 0%,
|
||||
rgba(252, 211, 77, 0.18) 50%,
|
||||
rgba(245, 158, 11, 0.12) 100%);
|
||||
border: 1px solid rgba(245, 158, 11, 0.4);
|
||||
box-shadow:
|
||||
0 32px 64px rgba(245, 158, 11, 0.2),
|
||||
0 12px 24px rgba(245, 158, 11, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4),
|
||||
0 0 0 1px rgba(245, 158, 11, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.notification-info {
|
||||
@apply border-blue-500;
|
||||
@apply text-blue-100;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(59, 130, 246, 0.25) 0%,
|
||||
rgba(147, 197, 253, 0.18) 50%,
|
||||
rgba(59, 130, 246, 0.12) 100%);
|
||||
border: 1px solid rgba(59, 130, 246, 0.4);
|
||||
box-shadow:
|
||||
0 32px 64px rgba(59, 130, 246, 0.2),
|
||||
0 12px 24px rgba(59, 130, 246, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4),
|
||||
0 0 0 1px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
|
||||
/* Toast-Benachrichtigungen - Einheitlich */
|
||||
.toast-notification {
|
||||
@apply fixed z-50 p-4 rounded-2xl shadow-2xl transform transition-all duration-500 text-sm font-medium;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
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.25);
|
||||
box-shadow:
|
||||
0 32px 64px rgba(0, 0, 0, 0.25),
|
||||
0 12px 24px 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);
|
||||
}
|
||||
|
||||
.dark .toast-notification {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(40px) saturate(180%) brightness(120%) contrast(115%);
|
||||
-webkit-backdrop-filter: blur(40px) saturate(180%) brightness(120%) contrast(115%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
box-shadow:
|
||||
0 32px 64px rgba(0, 0, 0, 0.6),
|
||||
0 12px 24px 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);
|
||||
}
|
||||
|
||||
/* Alert-Benachrichtigungen - Verbessert */
|
||||
.alert {
|
||||
@apply p-4 rounded-lg border mb-4;
|
||||
@apply p-6 rounded-2xl border mb-6 shadow-2xl;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
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.25);
|
||||
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);
|
||||
animation: alert-fade-in 0.5s ease-out;
|
||||
}
|
||||
|
||||
|
||||
.dark .alert {
|
||||
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);
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
@apply bg-green-50 border-green-500 text-green-800 dark:bg-green-900/30 dark:text-green-200;
|
||||
@apply text-green-900 dark:text-green-100;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(34, 197, 94, 0.15) 0%,
|
||||
rgba(134, 239, 172, 0.1) 50%,
|
||||
rgba(34, 197, 94, 0.08) 100%);
|
||||
border: 1px solid rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.alert-error {
|
||||
@apply bg-red-50 border-red-500 text-red-800 dark:bg-red-900/30 dark:text-red-200;
|
||||
@apply text-red-900 dark:text-red-100;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(239, 68, 68, 0.15) 0%,
|
||||
rgba(252, 165, 165, 0.1) 50%,
|
||||
rgba(239, 68, 68, 0.08) 100%);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.alert-warning {
|
||||
@apply bg-yellow-50 border-yellow-500 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-200;
|
||||
@apply text-yellow-900 dark:text-yellow-100;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(245, 158, 11, 0.15) 0%,
|
||||
rgba(252, 211, 77, 0.1) 50%,
|
||||
rgba(245, 158, 11, 0.08) 100%);
|
||||
border: 1px solid rgba(245, 158, 11, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.alert-info {
|
||||
@apply bg-blue-50 border-blue-500 text-blue-800 dark:bg-blue-900/30 dark:text-blue-200;
|
||||
@apply text-blue-900 dark:text-blue-100;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(59, 130, 246, 0.15) 0%,
|
||||
rgba(147, 197, 253, 0.1) 50%,
|
||||
rgba(59, 130, 246, 0.08) 100%);
|
||||
border: 1px solid rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
/* Browser-Notification-Container */
|
||||
.browser-notification {
|
||||
@apply fixed top-4 left-4 max-w-sm p-4 rounded-2xl shadow-2xl z-50;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
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.25);
|
||||
box-shadow:
|
||||
0 32px 64px rgba(0, 0, 0, 0.25),
|
||||
0 12px 24px 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: notification-slide-left 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.dark .browser-notification {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(40px) saturate(180%) brightness(120%) contrast(115%);
|
||||
-webkit-backdrop-filter: blur(40px) saturate(180%) brightness(120%) contrast(115%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
box-shadow:
|
||||
0 32px 64px rgba(0, 0, 0, 0.6),
|
||||
0 12px 24px 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);
|
||||
}
|
||||
|
||||
/* Notification-Animationen */
|
||||
@keyframes notification-slide-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%) translateY(-20px) scale(0.9);
|
||||
backdrop-filter: blur(0px);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
transform: translateX(20px) translateY(-10px) scale(1.05);
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0) translateY(0) scale(1);
|
||||
backdrop-filter: blur(40px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes notification-slide-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateX(0) translateY(0) scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%) translateY(-20px) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes notification-slide-left {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(-100%) translateY(-20px) scale(0.9);
|
||||
backdrop-filter: blur(0px);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
transform: translateX(-20px) translateY(-10px) scale(1.05);
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0) translateY(0) scale(1);
|
||||
backdrop-filter: blur(40px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes alert-fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px) scale(0.95);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.notification.hiding {
|
||||
animation: notification-slide-out 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||
}
|
||||
|
||||
/* Notification-Icons mit Glassmorphism */
|
||||
.notification-icon {
|
||||
@apply flex items-center justify-center w-8 h-8 rounded-full mr-3 flex-shrink-0;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow:
|
||||
0 8px 16px rgba(0, 0, 0, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
@apply flex-1;
|
||||
}
|
||||
|
||||
.notification-title {
|
||||
@apply font-semibold text-sm mb-1;
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
@apply text-sm opacity-90;
|
||||
}
|
||||
|
||||
.notification-close {
|
||||
@apply ml-3 p-1 rounded-lg opacity-70 hover:opacity-100 transition-opacity;
|
||||
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);
|
||||
}
|
||||
|
||||
.notification-close:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Multiple Notifications Container */
|
||||
.notifications-container {
|
||||
@apply fixed top-4 right-4 z-50 space-y-3 max-w-md;
|
||||
}
|
||||
|
||||
.notifications-container-left {
|
||||
@apply fixed top-4 left-4 z-50 space-y-3 max-w-sm;
|
||||
}
|
||||
|
||||
/* Flash-Message-Verbesserungen - Vereinheitlicht */
|
||||
.flash-message-light {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(255, 255, 255, 0.95) 0%,
|
||||
rgba(248, 250, 252, 0.9) 100%);
|
||||
backdrop-filter: blur(32px) saturate(200%) brightness(120%);
|
||||
-webkit-backdrop-filter: blur(32px) saturate(200%) brightness(120%);
|
||||
border: 1px solid rgba(226, 232, 240, 0.6);
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.1),
|
||||
0 12px 24px rgba(0, 115, 206, 0.05),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.flash-message-light.success {
|
||||
border-left: 4px solid #10b981;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(236, 253, 245, 0.95) 0%,
|
||||
rgba(209, 250, 229, 0.9) 100%);
|
||||
}
|
||||
|
||||
.flash-message-light.error {
|
||||
border-left: 4px solid #ef4444;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(254, 242, 242, 0.95) 0%,
|
||||
rgba(252, 165, 165, 0.9) 100%);
|
||||
}
|
||||
|
||||
.flash-message-light.warning {
|
||||
border-left: 4px solid #fbbf24;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(255, 251, 235, 0.95) 0%,
|
||||
rgba(254, 243, 199, 0.9) 100%);
|
||||
}
|
||||
|
||||
.flash-message-light.info {
|
||||
border-left: 4px solid #3b82f6;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(239, 246, 255, 0.95) 0%,
|
||||
rgba(219, 234, 254, 0.9) 100%);
|
||||
}
|
||||
|
||||
/* Premium Table Styles */
|
||||
.table-enhanced {
|
||||
background: var(--gradient-card);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: var(--card-radius);
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 4px 20px var(--color-shadow),
|
||||
0 2px 8px rgba(0, 115, 206, 0.04),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.table-enhanced th {
|
||||
background: linear-gradient(135deg,
|
||||
var(--color-bg-secondary) 0%,
|
||||
var(--color-bg-tertiary) 100%);
|
||||
color: var(--color-text-primary);
|
||||
font-weight: 600;
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.table-enhanced th::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg,
|
||||
transparent 0%,
|
||||
var(--color-border-secondary) 50%,
|
||||
transparent 100%);
|
||||
}
|
||||
|
||||
.table-enhanced td {
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
color: var(--color-text-secondary);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.table-enhanced tbody tr:hover {
|
||||
background: var(--color-bg-secondary);
|
||||
transform: scale(1.002);
|
||||
}
|
||||
|
||||
.dark .table-enhanced {
|
||||
background: rgba(10, 10, 10, 0.8);
|
||||
border-color: var(--color-border-primary);
|
||||
}
|
||||
|
||||
.dark .table-enhanced th {
|
||||
background: rgba(26, 26, 26, 0.8);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.dark .table-enhanced tbody tr:hover {
|
||||
background: rgba(26, 26, 26, 0.6);
|
||||
}
|
||||
|
||||
/* Premium Modal Styles */
|
||||
.modal-enhanced {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(255, 255, 255, 0.98) 0%,
|
||||
rgba(248, 250, 252, 0.95) 50%,
|
||||
rgba(255, 255, 255, 0.98) 100%);
|
||||
backdrop-filter: blur(32px) saturate(220%) brightness(120%);
|
||||
-webkit-backdrop-filter: blur(32px) saturate(220%) brightness(120%);
|
||||
border: 1px solid rgba(226, 232, 240, 0.7);
|
||||
border-radius: 1.5rem;
|
||||
box-shadow:
|
||||
0 50px 100px rgba(0, 0, 0, 0.15),
|
||||
0 20px 40px rgba(0, 115, 206, 0.08),
|
||||
inset 0 2px 0 rgba(255, 255, 255, 0.9);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-enhanced::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg,
|
||||
transparent 0%,
|
||||
rgba(226, 232, 240, 0.8) 50%,
|
||||
transparent 100%);
|
||||
}
|
||||
|
||||
.dark .modal-enhanced {
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
border-color: rgba(42, 42, 42, 0.7);
|
||||
box-shadow:
|
||||
0 50px 100px rgba(0, 0, 0, 0.5),
|
||||
inset 0 2px 0 rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Enhanced Status Badges */
|
||||
.status-badge-enhanced {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
border-radius: 9999px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.status-badge-enhanced::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||
transition: left 0.5s ease;
|
||||
}
|
||||
|
||||
.status-badge-enhanced:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.status-online-enhanced {
|
||||
background: linear-gradient(135deg, #ecfdf5 0%, #a7f3d0 100%);
|
||||
color: #065f46;
|
||||
border-color: rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.status-offline-enhanced {
|
||||
background: linear-gradient(135deg, #fef2f2 0%, #fca5a5 100%);
|
||||
color: #991b1b;
|
||||
border-color: rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.status-printing-enhanced {
|
||||
background: linear-gradient(135deg, #eff6ff 0%, #bfdbfe 100%);
|
||||
color: #1e40af;
|
||||
border-color: rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
/* Dark Mode Toggle - Premium Design */
|
||||
.dark-mode-toggle-new {
|
||||
position: relative;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 9999px;
|
||||
padding: 0.625rem;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background: linear-gradient(135deg,
|
||||
rgba(248, 250, 252, 0.9) 0%,
|
||||
rgba(241, 245, 249, 0.8) 100%);
|
||||
border: 1px solid rgba(226, 232, 240, 0.7);
|
||||
box-shadow:
|
||||
0 4px 12px rgba(0, 0, 0, 0.06),
|
||||
0 2px 4px rgba(0, 115, 206, 0.04),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
color: var(--color-text-secondary);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.dark-mode-toggle-new:hover {
|
||||
transform: translateY(-2px) scale(1.05);
|
||||
background: linear-gradient(135deg,
|
||||
rgba(248, 250, 252, 0.95) 0%,
|
||||
rgba(241, 245, 249, 0.85) 100%);
|
||||
box-shadow:
|
||||
0 8px 20px rgba(0, 0, 0, 0.1),
|
||||
0 4px 8px rgba(0, 115, 206, 0.08),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.dark-mode-toggle-new:active {
|
||||
transform: translateY(-1px) scale(0.98);
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
|
||||
.dark .dark-mode-toggle-new {
|
||||
background: rgba(10, 10, 10, 0.8);
|
||||
border: 1px solid rgba(42, 42, 42, 0.6);
|
||||
box-shadow:
|
||||
0 4px 12px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .dark-mode-toggle-new:hover {
|
||||
background: rgba(10, 10, 10, 0.9);
|
||||
box-shadow:
|
||||
0 8px 20px rgba(0, 0, 0, 0.4),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
/* Icon-Animation für Dark Mode Toggle */
|
||||
.dark-mode-toggle-new .sun-icon,
|
||||
.dark-mode-toggle-new .moon-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.dark-mode-toggle-new .sun-icon:not(.hidden) {
|
||||
animation: icon-appear 0.5s cubic-bezier(0.25, 1, 0.5, 1) forwards;
|
||||
}
|
||||
|
||||
.dark-mode-toggle-new .moon-icon:not(.hidden) {
|
||||
animation: icon-appear 0.5s cubic-bezier(0.25, 1, 0.5, 1) forwards;
|
||||
}
|
||||
|
||||
@keyframes icon-appear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%) scale(0.5) rotate(-20deg);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%) 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 - Premium Design */
|
||||
.user-menu-button-new {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: 0.75rem;
|
||||
padding: 0.5rem;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background: linear-gradient(135deg,
|
||||
rgba(248, 250, 252, 0.8) 0%,
|
||||
rgba(241, 245, 249, 0.7) 100%);
|
||||
border: 1px solid rgba(226, 232, 240, 0.6);
|
||||
box-shadow:
|
||||
0 2px 8px rgba(0, 0, 0, 0.05),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.user-menu-button-new:hover {
|
||||
transform: translateY(-1px);
|
||||
background: linear-gradient(135deg,
|
||||
rgba(248, 250, 252, 0.9) 0%,
|
||||
rgba(241, 245, 249, 0.8) 100%);
|
||||
box-shadow:
|
||||
0 4px 12px rgba(0, 0, 0, 0.08),
|
||||
0 2px 4px rgba(0, 115, 206, 0.04),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.dark .user-menu-button-new {
|
||||
background: rgba(10, 10, 10, 0.7);
|
||||
border-color: rgba(42, 42, 42, 0.6);
|
||||
box-shadow:
|
||||
0 2px 8px rgba(0, 0, 0, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.dark .user-menu-button-new:hover {
|
||||
background: rgba(10, 10, 10, 0.8);
|
||||
box-shadow:
|
||||
0 4px 12px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Enhanced Hover Effects */
|
||||
.hover-lift-enhanced {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.hover-lift-enhanced:hover {
|
||||
transform: translateY(-3px) scale(1.01);
|
||||
box-shadow:
|
||||
0 12px 30px var(--color-shadow-strong),
|
||||
0 6px 15px var(--color-shadow-accent);
|
||||
}
|
||||
|
||||
.dark .hover-lift-enhanced:hover {
|
||||
box-shadow: 0 12px 30px var(--color-shadow);
|
||||
}
|
||||
|
||||
/* Smooth Scrollbar for Light Mode */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--color-bg-secondary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(180deg,
|
||||
var(--color-border-secondary) 0%,
|
||||
var(--color-border-primary) 100%);
|
||||
border-radius: 4px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(180deg,
|
||||
var(--color-accent) 0%,
|
||||
var(--color-accent-hover) 100%);
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-track {
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background: var(--color-border-primary);
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background: #60a5fa;
|
||||
}
|
||||
|
||||
/* Loading States */
|
||||
.loading-enhanced {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.loading-enhanced::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg,
|
||||
transparent,
|
||||
rgba(0, 115, 206, 0.1),
|
||||
transparent);
|
||||
animation: loading-shimmer 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes loading-shimmer {
|
||||
0% { left: -100%; }
|
||||
100% { left: 100%; }
|
||||
}
|
||||
|
||||
/* Focus States for Accessibility */
|
||||
.focus-enhanced:focus {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
box-shadow:
|
||||
0 0 0 4px rgba(0, 115, 206, 0.15),
|
||||
0 4px 12px var(--color-shadow-accent);
|
||||
}
|
||||
|
||||
.dark .focus-enhanced:focus {
|
||||
outline-color: #60a5fa;
|
||||
box-shadow:
|
||||
0 0 0 4px rgba(96, 165, 250, 0.15),
|
||||
0 4px 12px rgba(96, 165, 250, 0.2);
|
||||
}
|
||||
|
||||
/* Responsive Design Enhancements */
|
||||
@media (max-width: 768px) {
|
||||
.card-enhanced {
|
||||
padding: 1rem;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.btn-enhanced {
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.modal-enhanced {
|
||||
border-radius: 1rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.dark-mode-toggle-new {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reduced Motion Support */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
transition: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* High Contrast Mode Support */
|
||||
@media (prefers-contrast: high) {
|
||||
:root {
|
||||
--color-shadow: rgba(0, 0, 0, 0.2);
|
||||
--color-shadow-strong: rgba(0, 0, 0, 0.3);
|
||||
--color-border-primary: #000000;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-border-primary: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
2
backend/static/css/tailwind.min.css
vendored
2
backend/static/css/tailwind.min.css
vendored
File diff suppressed because one or more lines are too long
1012
backend/static/js/glassmorphism-notifications.js
Normal file
1012
backend/static/js/glassmorphism-notifications.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -181,7 +181,7 @@ window.refreshStats = async function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Jobs-Refresh-Funktion
|
||||
* Jobs-Refresh-Funktion - VERBESSERT mit umfassenden Null-Checks
|
||||
*/
|
||||
window.refreshJobs = async function() {
|
||||
const refreshButton = document.getElementById('refresh-button');
|
||||
@ -194,14 +194,27 @@ window.refreshJobs = async function() {
|
||||
}
|
||||
|
||||
try {
|
||||
// Jobs-Daten neu laden mit verbesserter Fehlerbehandlung
|
||||
if (typeof window.jobManager !== 'undefined' && window.jobManager && window.jobManager.loadJobs) {
|
||||
console.log('🔄 Starte Jobs-Refresh...');
|
||||
|
||||
// Mehrstufige Manager-Prüfung mit erweiterten Sicherheitschecks
|
||||
let refreshSuccess = false;
|
||||
|
||||
if (typeof window.jobManager !== 'undefined' &&
|
||||
window.jobManager &&
|
||||
typeof window.jobManager.loadJobs === 'function') {
|
||||
console.log('📝 Verwende window.jobManager.loadJobs()');
|
||||
await window.jobManager.loadJobs();
|
||||
} else if (typeof jobManager !== 'undefined' && jobManager && jobManager.loadJobs) {
|
||||
refreshSuccess = true;
|
||||
} else if (typeof jobManager !== 'undefined' &&
|
||||
jobManager &&
|
||||
typeof jobManager.loadJobs === 'function') {
|
||||
console.log('📝 Verwende lokalen jobManager.loadJobs()');
|
||||
await jobManager.loadJobs();
|
||||
refreshSuccess = true;
|
||||
} else {
|
||||
// Fallback: API-Aufruf direkt
|
||||
// Fallback: Direkter API-Aufruf mit verbesserter Fehlerbehandlung
|
||||
console.log('📝 JobManager nicht verfügbar - verwende direkten API-Aufruf');
|
||||
|
||||
const response = await fetch('/api/jobs', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -214,28 +227,128 @@ window.refreshJobs = async function() {
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('📝 Jobs erfolgreich über API geladen:', data);
|
||||
console.log('📝 API-Response erhalten:', data);
|
||||
|
||||
// Wenn JobsContainer vorhanden ist, versuche die Jobs zu rendern
|
||||
const jobsContainer = document.querySelector('.jobs-container, #jobs-container, .job-grid');
|
||||
if (jobsContainer && data.jobs) {
|
||||
// Einfache Jobs-Darstellung als Fallback
|
||||
jobsContainer.innerHTML = data.jobs.map(job => `
|
||||
<div class="job-card p-4 border rounded-lg">
|
||||
<h3 class="font-semibold">${job.filename || 'Unbekannter Job'}</h3>
|
||||
<p class="text-sm text-gray-600">Status: ${job.status || 'Unbekannt'}</p>
|
||||
</div>
|
||||
`).join('');
|
||||
// VERBESSERTE Datenvalidierung
|
||||
if (!data || typeof data !== 'object') {
|
||||
throw new Error('Ungültige API-Response: Keine Daten erhalten');
|
||||
}
|
||||
|
||||
// Sichere Jobs-Extraktion
|
||||
let jobs = [];
|
||||
if (Array.isArray(data.jobs)) {
|
||||
jobs = data.jobs;
|
||||
} else if (Array.isArray(data)) {
|
||||
jobs = data;
|
||||
} else if (data.success && Array.isArray(data.data)) {
|
||||
jobs = data.data;
|
||||
} else {
|
||||
console.warn('⚠️ Keine Jobs-Array in API-Response gefunden:', data);
|
||||
jobs = [];
|
||||
}
|
||||
|
||||
console.log(`📝 ${jobs.length} Jobs aus API extrahiert:`, jobs);
|
||||
|
||||
// Container-Updates mit Fallback-Rendering
|
||||
const jobsContainers = [
|
||||
'.jobs-container',
|
||||
'#jobs-container',
|
||||
'.job-grid',
|
||||
'#jobs-list',
|
||||
'#jobs-grid'
|
||||
];
|
||||
|
||||
let containerFound = false;
|
||||
for (const selector of jobsContainers) {
|
||||
const container = document.querySelector(selector);
|
||||
if (container) {
|
||||
containerFound = true;
|
||||
console.log(`📝 Container gefunden: ${selector}`);
|
||||
|
||||
if (jobs.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center py-12">
|
||||
<div class="text-gray-400 dark:text-gray-600 text-6xl mb-4">📭</div>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">Keine Jobs vorhanden</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">Es wurden noch keine Druckaufträge erstellt.</p>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// Sichere Job-Darstellung mit Null-Checks
|
||||
const jobCards = jobs.map(job => {
|
||||
if (!job || typeof job !== 'object') {
|
||||
console.warn('⚠️ Ungültiges Job-Objekt übersprungen:', job);
|
||||
return '';
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="job-card p-4 border rounded-lg bg-white dark:bg-slate-800 shadow-sm hover:shadow-md transition-shadow">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white mb-2">
|
||||
${job.filename || job.title || job.name || 'Unbekannter Job'}
|
||||
</h3>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 space-y-1">
|
||||
<p><span class="font-medium">ID:</span> ${job.id || 'N/A'}</p>
|
||||
<p><span class="font-medium">Status:</span> ${job.status || 'Unbekannt'}</p>
|
||||
${job.printer_name ? `<p><span class="font-medium">Drucker:</span> ${job.printer_name}</p>` : ''}
|
||||
${job.created_at ? `<p><span class="font-medium">Erstellt:</span> ${new Date(job.created_at).toLocaleDateString('de-DE')}</p>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).filter(card => card !== '').join('');
|
||||
|
||||
container.innerHTML = jobCards || `
|
||||
<div class="text-center py-8">
|
||||
<p class="text-gray-500 dark:text-gray-400">Keine gültigen Jobs zum Anzeigen</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!containerFound) {
|
||||
console.warn('⚠️ Kein Jobs-Container gefunden. Verfügbare Container:', jobsContainers);
|
||||
}
|
||||
|
||||
refreshSuccess = true;
|
||||
}
|
||||
|
||||
if (refreshSuccess) {
|
||||
showToast('✅ Druckaufträge erfolgreich aktualisiert', 'success');
|
||||
}
|
||||
|
||||
showToast('✅ Druckaufträge erfolgreich aktualisiert', 'success');
|
||||
} catch (error) {
|
||||
console.error('Jobs-Refresh Fehler:', error);
|
||||
const errorMessage = error.message === 'undefined' ?
|
||||
'Jobs-Manager nicht verfügbar' :
|
||||
error.message || 'Unbekannter Fehler';
|
||||
showToast(`❌ Fehler beim Laden der Jobs: ${errorMessage}`, 'error');
|
||||
console.error('❌ Jobs-Refresh Fehler:', error);
|
||||
|
||||
// Intelligente Fehlermeldung basierend auf dem Fehlertyp
|
||||
let errorMessage;
|
||||
if (error.message.includes('undefined')) {
|
||||
errorMessage = 'Jobs-Daten nicht verfügbar';
|
||||
} else if (error.message.includes('fetch')) {
|
||||
errorMessage = 'Netzwerkfehler beim Laden der Jobs';
|
||||
} else if (error.message.includes('API')) {
|
||||
errorMessage = 'Server-Fehler beim Laden der Jobs';
|
||||
} else {
|
||||
errorMessage = error.message || 'Unbekannter Fehler beim Laden der Jobs';
|
||||
}
|
||||
|
||||
showToast(`❌ Fehler: ${errorMessage}`, 'error');
|
||||
|
||||
// Fallback-Container mit Fehleranzeige
|
||||
const container = document.querySelector('.jobs-container, #jobs-container, .job-grid, #jobs-list');
|
||||
if (container) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center py-12">
|
||||
<div class="text-red-400 dark:text-red-600 text-6xl mb-4">⚠️</div>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">Fehler beim Laden</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 mb-4">${errorMessage}</p>
|
||||
<button onclick="refreshJobs()" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
|
||||
Erneut versuchen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (refreshButton) {
|
||||
refreshButton.disabled = false;
|
||||
|
@ -382,6 +382,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// VERBESSERTE SICHERHEITSCHECKS
|
||||
if (!Array.isArray(this.jobs)) {
|
||||
console.warn('⚠️ this.jobs ist kein Array:', this.jobs);
|
||||
this.jobs = [];
|
||||
}
|
||||
|
||||
if (this.jobs.length === 0) {
|
||||
jobsList.innerHTML = `
|
||||
<div class="text-center py-12">
|
||||
@ -393,10 +399,32 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const jobsHTML = this.jobs.map(job => this.renderJobCard(job)).join('');
|
||||
jobsList.innerHTML = jobsHTML;
|
||||
|
||||
console.log(`📋 ${this.jobs.length} Jobs gerendert`);
|
||||
try {
|
||||
const jobsHTML = this.jobs.map(job => {
|
||||
// Sicherstellen, dass job ein gültiges Objekt ist
|
||||
if (!job || typeof job !== 'object') {
|
||||
console.warn('⚠️ Ungültiges Job-Objekt:', job);
|
||||
return '';
|
||||
}
|
||||
return this.renderJobCard(job);
|
||||
}).filter(html => html !== '').join('');
|
||||
|
||||
jobsList.innerHTML = jobsHTML;
|
||||
|
||||
console.log(`📋 ${this.jobs.length} Jobs gerendert`);
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler beim Rendern der Jobs:', error);
|
||||
jobsList.innerHTML = `
|
||||
<div class="text-center py-12">
|
||||
<div class="text-red-400 dark:text-red-600 text-6xl mb-4">⚠️</div>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">Fehler beim Laden</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">Es gab einen Fehler beim Darstellen der Jobs.</p>
|
||||
<button onclick="window.jobManager.loadJobs()" class="mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
|
||||
Erneut versuchen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
307
backend/static/js/jobs-safety-fix.js
Normal file
307
backend/static/js/jobs-safety-fix.js
Normal file
@ -0,0 +1,307 @@
|
||||
/**
|
||||
* Jobs Safety Fix - Mercedes-Benz MYP Platform
|
||||
* Umfassende Lösung für "jobs undefined" Probleme
|
||||
* Version: 1.0.0
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
console.log('🛡️ Jobs Safety Fix wird geladen...');
|
||||
|
||||
/**
|
||||
* Globale Jobs-Variable sicher initialisieren
|
||||
*/
|
||||
if (typeof window.jobsData === 'undefined') {
|
||||
window.jobsData = [];
|
||||
}
|
||||
|
||||
if (typeof window.filteredJobs === 'undefined') {
|
||||
window.filteredJobs = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sichere Jobs-Funktionen
|
||||
*/
|
||||
window.safeJobsOperations = {
|
||||
/**
|
||||
* Jobs sicher abrufen
|
||||
*/
|
||||
getJobs: function() {
|
||||
if (window.jobManager && Array.isArray(window.jobManager.jobs)) {
|
||||
return window.jobManager.jobs;
|
||||
}
|
||||
if (Array.isArray(window.jobsData)) {
|
||||
return window.jobsData;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Gefilterte Jobs sicher abrufen
|
||||
*/
|
||||
getFilteredJobs: function() {
|
||||
if (Array.isArray(window.filteredJobs)) {
|
||||
return window.filteredJobs;
|
||||
}
|
||||
return this.getJobs();
|
||||
},
|
||||
|
||||
/**
|
||||
* Jobs sicher setzen
|
||||
*/
|
||||
setJobs: function(jobs) {
|
||||
if (!Array.isArray(jobs)) {
|
||||
console.warn('⚠️ Jobs ist kein Array, konvertiere zu leerem Array');
|
||||
jobs = [];
|
||||
}
|
||||
|
||||
if (window.jobManager) {
|
||||
window.jobManager.jobs = jobs;
|
||||
}
|
||||
window.jobsData = jobs;
|
||||
|
||||
console.log(`✅ ${jobs.length} Jobs sicher gesetzt`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Job sicher finden
|
||||
*/
|
||||
findJob: function(jobId) {
|
||||
const jobs = this.getJobs();
|
||||
return jobs.find(job => job && job.id && job.id.toString() === jobId.toString()) || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Jobs sicher filtern
|
||||
*/
|
||||
filterJobs: function(filterFn) {
|
||||
const jobs = this.getJobs();
|
||||
if (typeof filterFn !== 'function') {
|
||||
return jobs;
|
||||
}
|
||||
|
||||
try {
|
||||
return jobs.filter(job => {
|
||||
if (!job || typeof job !== 'object') {
|
||||
return false;
|
||||
}
|
||||
return filterFn(job);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler beim Filtern der Jobs:', error);
|
||||
return jobs;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* JobManager Sicherheitsprüfungen
|
||||
*/
|
||||
function ensureJobManagerSafety() {
|
||||
if (typeof window.jobManager !== 'undefined' && window.jobManager) {
|
||||
// Sicherstellen, dass jobs ein Array ist
|
||||
if (!Array.isArray(window.jobManager.jobs)) {
|
||||
console.warn('⚠️ JobManager.jobs ist kein Array, korrigiere...');
|
||||
window.jobManager.jobs = [];
|
||||
}
|
||||
|
||||
// Originale loadJobs Methode wrappen
|
||||
if (window.jobManager.loadJobs && typeof window.jobManager.loadJobs === 'function') {
|
||||
const originalLoadJobs = window.jobManager.loadJobs;
|
||||
window.jobManager.loadJobs = async function(...args) {
|
||||
try {
|
||||
await originalLoadJobs.apply(this, args);
|
||||
// Nach dem Laden sicherstellen, dass jobs ein Array ist
|
||||
if (!Array.isArray(this.jobs)) {
|
||||
this.jobs = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler beim sicheren Laden der Jobs:', error);
|
||||
this.jobs = [];
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Originale renderJobs Methode wrappen
|
||||
if (window.jobManager.renderJobs && typeof window.jobManager.renderJobs === 'function') {
|
||||
const originalRenderJobs = window.jobManager.renderJobs;
|
||||
window.jobManager.renderJobs = function(...args) {
|
||||
try {
|
||||
// Sicherstellen, dass jobs ein Array ist vor dem Rendern
|
||||
if (!Array.isArray(this.jobs)) {
|
||||
console.warn('⚠️ Jobs ist kein Array vor dem Rendern, korrigiere...');
|
||||
this.jobs = [];
|
||||
}
|
||||
return originalRenderJobs.apply(this, args);
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler beim sicheren Rendern der Jobs:', error);
|
||||
// Fallback-Rendering
|
||||
const jobsList = document.getElementById('jobs-list');
|
||||
if (jobsList) {
|
||||
jobsList.innerHTML = `
|
||||
<div class="text-center py-12">
|
||||
<div class="text-red-400 dark:text-red-600 text-6xl mb-4">⚠️</div>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">Renderingfehler</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 mb-4">Jobs konnten nicht angezeigt werden</p>
|
||||
<button onclick="window.jobManager.loadJobs()" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
|
||||
Erneut laden
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Globale Variablen-Überwachung
|
||||
*/
|
||||
function setupGlobalVariableWatching() {
|
||||
// jobsData überwachen
|
||||
let _jobsData = [];
|
||||
Object.defineProperty(window, 'jobsData', {
|
||||
get: function() {
|
||||
return _jobsData;
|
||||
},
|
||||
set: function(value) {
|
||||
if (!Array.isArray(value)) {
|
||||
console.warn('⚠️ Versuche jobsData mit Non-Array zu setzen:', value);
|
||||
_jobsData = [];
|
||||
} else {
|
||||
_jobsData = value;
|
||||
}
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
// filteredJobs überwachen
|
||||
let _filteredJobs = [];
|
||||
Object.defineProperty(window, 'filteredJobs', {
|
||||
get: function() {
|
||||
return _filteredJobs;
|
||||
},
|
||||
set: function(value) {
|
||||
if (!Array.isArray(value)) {
|
||||
console.warn('⚠️ Versuche filteredJobs mit Non-Array zu setzen:', value);
|
||||
_filteredJobs = [];
|
||||
} else {
|
||||
_filteredJobs = value;
|
||||
}
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* API-Response Validator
|
||||
*/
|
||||
window.validateJobsResponse = function(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
console.warn('⚠️ Ungültige API-Response:', data);
|
||||
return { jobs: [], total: 0 };
|
||||
}
|
||||
|
||||
let jobs = [];
|
||||
|
||||
// Verschiedene Response-Formate unterstützen
|
||||
if (Array.isArray(data.jobs)) {
|
||||
jobs = data.jobs;
|
||||
} else if (Array.isArray(data.data)) {
|
||||
jobs = data.data;
|
||||
} else if (Array.isArray(data)) {
|
||||
jobs = data;
|
||||
} else if (data.success && Array.isArray(data.jobs)) {
|
||||
jobs = data.jobs;
|
||||
}
|
||||
|
||||
// Jobs validieren
|
||||
jobs = jobs.filter(job => {
|
||||
if (!job || typeof job !== 'object') {
|
||||
console.warn('⚠️ Ungültiges Job-Objekt gefiltert:', job);
|
||||
return false;
|
||||
}
|
||||
if (!job.id) {
|
||||
console.warn('⚠️ Job ohne ID gefiltert:', job);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
console.log(`✅ ${jobs.length} Jobs validiert`);
|
||||
return {
|
||||
jobs: jobs,
|
||||
total: jobs.length,
|
||||
current_page: data.current_page || 1,
|
||||
total_pages: data.total_pages || 1
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Fehlerbehandlung für undefined Jobs
|
||||
*/
|
||||
window.addEventListener('error', function(e) {
|
||||
if (e.message && e.message.includes('jobs') && e.message.includes('undefined')) {
|
||||
console.log('🛡️ Jobs undefined Fehler abgefangen:', e.message);
|
||||
|
||||
// Versuche Jobs zu reparieren
|
||||
window.safeJobsOperations.setJobs([]);
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Promise rejection handler für Jobs
|
||||
*/
|
||||
window.addEventListener('unhandledrejection', function(e) {
|
||||
if (e.reason && e.reason.message && e.reason.message.includes('jobs')) {
|
||||
console.log('🛡️ Jobs Promise rejection abgefangen:', e.reason);
|
||||
|
||||
// Versuche Jobs zu reparieren
|
||||
window.safeJobsOperations.setJobs([]);
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DOM bereit - Setup starten
|
||||
*/
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
setupGlobalVariableWatching();
|
||||
ensureJobManagerSafety();
|
||||
});
|
||||
} else {
|
||||
setupGlobalVariableWatching();
|
||||
ensureJobManagerSafety();
|
||||
}
|
||||
|
||||
/**
|
||||
* Periodische Sicherheitsprüfung
|
||||
*/
|
||||
setInterval(function() {
|
||||
ensureJobManagerSafety();
|
||||
|
||||
// Prüfe ob globale Jobs-Variablen noch Arrays sind
|
||||
if (!Array.isArray(window.jobsData)) {
|
||||
console.warn('⚠️ jobsData ist kein Array mehr, repariere...');
|
||||
window.jobsData = [];
|
||||
}
|
||||
|
||||
if (!Array.isArray(window.filteredJobs)) {
|
||||
console.warn('⚠️ filteredJobs ist kein Array mehr, repariere...');
|
||||
window.filteredJobs = [];
|
||||
}
|
||||
}, 10000); // Alle 10 Sekunden
|
||||
|
||||
console.log('✅ Jobs Safety Fix erfolgreich geladen');
|
||||
|
||||
})();
|
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Benachrichtigungssystem für die MYP 3D-Druck Platform
|
||||
* Verwaltet die Anzeige und Interaktion mit Benachrichtigungen
|
||||
* Modernes Benachrichtigungssystem für die MYP 3D-Druck Platform
|
||||
* Verwaltet einheitliche Glassmorphism-Benachrichtigungen aller Art
|
||||
*/
|
||||
|
||||
class NotificationManager {
|
||||
class ModernNotificationManager {
|
||||
constructor() {
|
||||
this.notificationToggle = document.getElementById('notificationToggle');
|
||||
this.notificationDropdown = document.getElementById('notificationDropdown');
|
||||
@ -13,11 +13,14 @@ class NotificationManager {
|
||||
|
||||
this.isOpen = false;
|
||||
this.notifications = [];
|
||||
this.activeToasts = new Map();
|
||||
this.toastCounter = 0;
|
||||
|
||||
// CSRF-Token aus Meta-Tag holen
|
||||
this.csrfToken = this.getCSRFToken();
|
||||
|
||||
this.init();
|
||||
this.setupGlobalNotificationSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -307,9 +310,358 @@ class NotificationManager {
|
||||
console.error('Fehler beim Markieren aller als gelesen:', error);
|
||||
}
|
||||
}
|
||||
|
||||
setupGlobalNotificationSystem() {
|
||||
// Globale Funktionen für einheitliche Benachrichtigungen
|
||||
window.showFlashMessage = this.showGlassToast.bind(this);
|
||||
window.showToast = this.showGlassToast.bind(this);
|
||||
window.showNotification = this.showGlassToast.bind(this);
|
||||
window.showSuccessMessage = (message) => this.showGlassToast(message, 'success');
|
||||
window.showErrorMessage = (message) => this.showGlassToast(message, 'error');
|
||||
window.showWarningMessage = (message) => this.showGlassToast(message, 'warning');
|
||||
window.showInfoMessage = (message) => this.showGlassToast(message, 'info');
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt eine moderne Glassmorphism-Toast-Benachrichtigung
|
||||
*/
|
||||
showGlassToast(message, type = 'info', duration = 5000, options = {}) {
|
||||
// DND-Check
|
||||
if (window.dndManager && window.dndManager.isEnabled) {
|
||||
window.dndManager.suppressNotification(message, type);
|
||||
return;
|
||||
}
|
||||
|
||||
const toastId = `toast-${++this.toastCounter}`;
|
||||
|
||||
// Toast-Element erstellen
|
||||
const toast = document.createElement('div');
|
||||
toast.id = toastId;
|
||||
toast.className = `glass-toast notification notification-${type} show`;
|
||||
|
||||
// Icon basierend auf Typ
|
||||
const iconMap = {
|
||||
success: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
</svg>`,
|
||||
error: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>`,
|
||||
warning: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"/>
|
||||
</svg>`,
|
||||
info: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>`
|
||||
};
|
||||
|
||||
toast.innerHTML = `
|
||||
<div class="flex items-center">
|
||||
<div class="notification-icon">
|
||||
${iconMap[type] || iconMap.info}
|
||||
</div>
|
||||
<div class="notification-content">
|
||||
${options.title ? `<div class="notification-title">${options.title}</div>` : ''}
|
||||
<div class="notification-message">${message}</div>
|
||||
</div>
|
||||
<button class="notification-close" onclick="modernNotificationManager.closeToast('${toastId}')">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Container für Toasts erstellen falls nicht vorhanden
|
||||
let container = document.getElementById('toast-container');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = 'toast-container';
|
||||
container.className = 'notifications-container';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
// Position berechnen basierend auf existierenden Toasts
|
||||
const existingToasts = container.children.length;
|
||||
toast.style.top = `${1 + existingToasts * 5}rem`;
|
||||
|
||||
container.appendChild(toast);
|
||||
this.activeToasts.set(toastId, toast);
|
||||
|
||||
// Hover-Effekt pausiert Auto-Close
|
||||
let isPaused = false;
|
||||
let timeoutId;
|
||||
|
||||
const startTimer = () => {
|
||||
if (!isPaused && duration > 0) {
|
||||
timeoutId = setTimeout(() => {
|
||||
this.closeToast(toastId);
|
||||
}, duration);
|
||||
}
|
||||
};
|
||||
|
||||
toast.addEventListener('mouseenter', () => {
|
||||
isPaused = true;
|
||||
clearTimeout(timeoutId);
|
||||
toast.style.transform = 'translateY(-2px) scale(1.02)';
|
||||
});
|
||||
|
||||
toast.addEventListener('mouseleave', () => {
|
||||
isPaused = false;
|
||||
toast.style.transform = 'translateY(0) scale(1)';
|
||||
startTimer();
|
||||
});
|
||||
|
||||
// Initial timer starten
|
||||
setTimeout(startTimer, 100);
|
||||
|
||||
// Sound-Effekt (optional)
|
||||
if (options.playSound !== false) {
|
||||
this.playNotificationSound(type);
|
||||
}
|
||||
|
||||
return toastId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schließt eine spezifische Toast-Benachrichtigung
|
||||
*/
|
||||
closeToast(toastId) {
|
||||
const toast = this.activeToasts.get(toastId);
|
||||
if (!toast) return;
|
||||
|
||||
toast.classList.add('hiding');
|
||||
|
||||
setTimeout(() => {
|
||||
if (toast.parentNode) {
|
||||
toast.parentNode.removeChild(toast);
|
||||
}
|
||||
this.activeToasts.delete(toastId);
|
||||
this.repositionToasts();
|
||||
}, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Repositioniert alle aktiven Toasts
|
||||
*/
|
||||
repositionToasts() {
|
||||
let index = 0;
|
||||
this.activeToasts.forEach((toast) => {
|
||||
if (toast.parentNode) {
|
||||
toast.style.top = `${1 + index * 5}rem`;
|
||||
index++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Spielt einen Benachrichtigungston ab
|
||||
*/
|
||||
playNotificationSound(type) {
|
||||
try {
|
||||
// Nur wenn AudioContext verfügbar ist
|
||||
if (typeof AudioContext !== 'undefined' || typeof webkitAudioContext !== 'undefined') {
|
||||
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
const oscillator = audioContext.createOscillator();
|
||||
const gainNode = audioContext.createGain();
|
||||
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
|
||||
// Verschiedene Töne für verschiedene Typen
|
||||
const frequencies = {
|
||||
success: [523.25, 659.25, 783.99], // C-E-G Akkord
|
||||
error: [440, 415.3], // A-Ab
|
||||
warning: [493.88, 523.25], // B-C
|
||||
info: [523.25] // C
|
||||
};
|
||||
|
||||
const freq = frequencies[type] || frequencies.info;
|
||||
|
||||
freq.forEach((f, i) => {
|
||||
setTimeout(() => {
|
||||
const osc = audioContext.createOscillator();
|
||||
const gain = audioContext.createGain();
|
||||
|
||||
osc.connect(gain);
|
||||
gain.connect(audioContext.destination);
|
||||
|
||||
osc.frequency.setValueAtTime(f, audioContext.currentTime);
|
||||
gain.gain.setValueAtTime(0.1, audioContext.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
|
||||
|
||||
osc.start(audioContext.currentTime);
|
||||
osc.stop(audioContext.currentTime + 0.1);
|
||||
}, i * 100);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// Silent fail - Audio nicht verfügbar
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt Browser-Benachrichtigung (falls berechtigt)
|
||||
*/
|
||||
showBrowserNotification(title, message, options = {}) {
|
||||
if ('Notification' in window) {
|
||||
if (Notification.permission === 'granted') {
|
||||
const notification = new Notification(title, {
|
||||
body: message,
|
||||
icon: options.icon || '/static/icons/static/icons/notification-icon.png',
|
||||
badge: options.badge || '/static/icons/static/icons/badge-icon.png',
|
||||
tag: options.tag || 'myp-notification',
|
||||
requireInteraction: options.requireInteraction || false,
|
||||
...options
|
||||
});
|
||||
|
||||
notification.onclick = options.onClick || (() => {
|
||||
window.focus();
|
||||
notification.close();
|
||||
});
|
||||
|
||||
return notification;
|
||||
} else if (Notification.permission === 'default') {
|
||||
Notification.requestPermission().then(permission => {
|
||||
if (permission === 'granted') {
|
||||
this.showBrowserNotification(title, message, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt eine Alert-Benachrichtigung mit Glassmorphism
|
||||
*/
|
||||
showAlert(message, type = 'info', options = {}) {
|
||||
const alertId = `alert-${Date.now()}`;
|
||||
const alert = document.createElement('div');
|
||||
alert.id = alertId;
|
||||
alert.className = `alert alert-${type}`;
|
||||
|
||||
alert.innerHTML = `
|
||||
<div class="flex items-start">
|
||||
<div class="notification-icon mr-3">
|
||||
${this.getIconForType(type)}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
${options.title ? `<h4 class="font-semibold mb-2">${options.title}</h4>` : ''}
|
||||
<p>${message}</p>
|
||||
</div>
|
||||
${options.dismissible !== false ? `
|
||||
<button class="ml-3 p-1 rounded-lg opacity-70 hover:opacity-100 transition-opacity" onclick="document.getElementById('${alertId}').remove()">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Alert in Container einfügen
|
||||
const container = options.container || document.querySelector('.flash-messages') || document.body;
|
||||
container.appendChild(alert);
|
||||
|
||||
// Auto-dismiss
|
||||
if (options.autoDismiss !== false) {
|
||||
setTimeout(() => {
|
||||
if (alert.parentNode) {
|
||||
alert.style.opacity = '0';
|
||||
alert.style.transform = 'translateY(-20px)';
|
||||
setTimeout(() => alert.remove(), 300);
|
||||
}
|
||||
}, options.duration || 7000);
|
||||
}
|
||||
|
||||
return alertId;
|
||||
}
|
||||
|
||||
getIconForType(type) {
|
||||
const icons = {
|
||||
success: `<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
</svg>`,
|
||||
error: `<svg class="w-5 h-5 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>`,
|
||||
warning: `<svg class="w-5 h-5 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"/>
|
||||
</svg>`,
|
||||
info: `<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>`
|
||||
};
|
||||
return icons[type] || icons.info;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisierung nach DOM-Load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new NotificationManager();
|
||||
});
|
||||
// Legacy NotificationManager für Rückwärtskompatibilität
|
||||
class NotificationManager extends ModernNotificationManager {
|
||||
constructor() {
|
||||
super();
|
||||
console.warn('NotificationManager ist deprecated. Verwenden Sie ModernNotificationManager.');
|
||||
}
|
||||
}
|
||||
|
||||
// Globale Instanz erstellen
|
||||
const modernNotificationManager = new ModernNotificationManager();
|
||||
|
||||
// Für Rückwärtskompatibilität
|
||||
if (typeof window !== 'undefined') {
|
||||
window.notificationManager = modernNotificationManager;
|
||||
window.modernNotificationManager = modernNotificationManager;
|
||||
}
|
||||
|
||||
// CSS für glassmorphe Toasts hinzufügen
|
||||
const toastStyles = `
|
||||
<style>
|
||||
.glass-toast {
|
||||
position: relative;
|
||||
transform: translateX(0) translateY(0) scale(1);
|
||||
animation: toast-slide-in 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.glass-toast:hover {
|
||||
transform: translateY(-2px) scale(1.02) !important;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
@keyframes toast-slide-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%) translateY(-20px) scale(0.9);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
transform: translateX(20px) translateY(-10px) scale(1.05);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0) translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.notification-item.unread {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(59, 130, 246, 0.08) 0%,
|
||||
rgba(147, 197, 253, 0.05) 100%);
|
||||
border-left: 3px solid rgb(59, 130, 246);
|
||||
}
|
||||
|
||||
.dark .notification-item.unread {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(59, 130, 246, 0.15) 0%,
|
||||
rgba(147, 197, 253, 0.08) 100%);
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
// Styles zur Seite hinzufügen
|
||||
if (typeof document !== 'undefined' && !document.getElementById('toast-styles')) {
|
||||
const styleElement = document.createElement('div');
|
||||
styleElement.id = 'toast-styles';
|
||||
styleElement.innerHTML = toastStyles;
|
||||
document.head.appendChild(styleElement);
|
||||
}
|
@ -115,11 +115,16 @@
|
||||
if (messageType === 'danger') messageType = 'error';
|
||||
|
||||
// Nachricht über das moderne Glassmorphism-System anzeigen
|
||||
if (typeof showFlashMessage === 'function') {
|
||||
// Kleine Verzögerung für bessere UX
|
||||
if (typeof showToast === 'function') {
|
||||
// Kleine Verzögerung für bessere UX und schöne Glassmorphism-Animationen
|
||||
setTimeout(() => {
|
||||
showFlashMessage(message, messageType, 6000);
|
||||
}, i * 200); // Nachrichten gestaffelt anzeigen
|
||||
showToast(message, messageType, 6000, {
|
||||
title: messageType === 'success' ? 'Erfolgreich' :
|
||||
messageType === 'error' ? 'Fehler' :
|
||||
messageType === 'warning' ? 'Warnung' : 'Information',
|
||||
playSound: true
|
||||
});
|
||||
}, i * 250); // Nachrichten gestaffelt anzeigen
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -769,39 +774,82 @@
|
||||
<script src="{{ url_for('static', filename='js/auto-logout.js') }}"></script>
|
||||
{% endif %}
|
||||
|
||||
<!-- Glassmorphism Notification System - Modernisiert -->
|
||||
<script src="{{ url_for('static', filename='js/glassmorphism-notifications.js') }}"></script>
|
||||
|
||||
<!-- Additional JavaScript Functions -->
|
||||
<script>
|
||||
/**
|
||||
* Logout-Handler für sicheres Abmelden
|
||||
*/
|
||||
function handleLogout() {
|
||||
// Bestätigung abfragen
|
||||
if (confirm('Möchten Sie sich wirklich abmelden?')) {
|
||||
// Loading-Animation anzeigen
|
||||
document.body.style.opacity = '0.7';
|
||||
document.body.style.pointerEvents = 'none';
|
||||
|
||||
// CSRF-Token aus Meta-Tag holen
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]');
|
||||
|
||||
// Logout-Formular erstellen und absenden
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '{{ url_for("auth_logout") }}';
|
||||
form.style.display = 'none';
|
||||
|
||||
// CSRF-Token hinzufügen falls verfügbar
|
||||
if (csrfToken) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'csrf_token';
|
||||
input.value = csrfToken.getAttribute('content');
|
||||
form.appendChild(input);
|
||||
// Verwende das moderne Glassmorphism-Bestätigungssystem
|
||||
if (typeof showConfirmationToast === 'function') {
|
||||
showConfirmationToast(
|
||||
'Möchten Sie sich wirklich abmelden?',
|
||||
() => {
|
||||
// Loading-Animation anzeigen
|
||||
document.body.style.opacity = '0.7';
|
||||
document.body.style.pointerEvents = 'none';
|
||||
|
||||
// CSRF-Token aus Meta-Tag holen
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]');
|
||||
|
||||
// Logout-Formular erstellen und absenden
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '{{ url_for("auth_logout") }}';
|
||||
form.style.display = 'none';
|
||||
|
||||
// CSRF-Token hinzufügen falls verfügbar
|
||||
if (csrfToken) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'csrf_token';
|
||||
input.value = csrfToken.getAttribute('content');
|
||||
form.appendChild(input);
|
||||
}
|
||||
|
||||
// Formular absenden
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
},
|
||||
null,
|
||||
{
|
||||
title: 'Abmeldung bestätigen',
|
||||
confirmText: 'Abmelden',
|
||||
cancelText: 'Abbrechen'
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Fallback für den Fall, dass das Glassmorphism-System nicht verfügbar ist
|
||||
if (confirm('Möchten Sie sich wirklich abmelden?')) {
|
||||
// Loading-Animation anzeigen
|
||||
document.body.style.opacity = '0.7';
|
||||
document.body.style.pointerEvents = 'none';
|
||||
|
||||
// CSRF-Token aus Meta-Tag holen
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]');
|
||||
|
||||
// Logout-Formular erstellen und absenden
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '{{ url_for("auth_logout") }}';
|
||||
form.style.display = 'none';
|
||||
|
||||
// CSRF-Token hinzufügen falls verfügbar
|
||||
if (csrfToken) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'csrf_token';
|
||||
input.value = csrfToken.getAttribute('content');
|
||||
form.appendChild(input);
|
||||
}
|
||||
|
||||
// Formular absenden
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
// Formular absenden
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
@ -832,11 +880,16 @@
|
||||
if (messageType === 'danger') messageType = 'error';
|
||||
|
||||
// Nachricht über das moderne Glassmorphism-System anzeigen
|
||||
if (typeof showFlashMessage === 'function') {
|
||||
// Kleine Verzögerung für bessere UX
|
||||
if (typeof showToast === 'function') {
|
||||
// Kleine Verzögerung für bessere UX und schöne Glassmorphism-Animationen
|
||||
setTimeout(() => {
|
||||
showFlashMessage(message, messageType, 6000);
|
||||
}, i * 200); // Nachrichten gestaffelt anzeigen
|
||||
showToast(message, messageType, 6000, {
|
||||
title: messageType === 'success' ? 'Erfolgreich' :
|
||||
messageType === 'error' ? 'Fehler' :
|
||||
messageType === 'warning' ? 'Warnung' : 'Information',
|
||||
playSound: true
|
||||
});
|
||||
}, i * 250); // Nachrichten gestaffelt anzeigen
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1029,8 +1082,11 @@
|
||||
? 'Nicht stören aktiviert - Benachrichtigungen werden unterdrückt'
|
||||
: 'Nicht stören deaktiviert - Benachrichtigungen sind wieder aktiv';
|
||||
|
||||
if (typeof showFlashMessage === 'function') {
|
||||
showFlashMessage(message, 'info', 3000);
|
||||
if (typeof showToast === 'function') {
|
||||
showToast(message, 'info', 3000, {
|
||||
title: 'Information',
|
||||
playSound: true
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`🔕 DND ${this.isEnabled ? 'aktiviert' : 'deaktiviert'}`);
|
||||
@ -1078,15 +1134,23 @@
|
||||
const count = this.suppressedMessages.length;
|
||||
const summaryMessage = `${count} Benachrichtigung${count > 1 ? 'en' : ''} während "Nicht stören" erhalten`;
|
||||
|
||||
if (typeof showFlashMessage === 'function') {
|
||||
showFlashMessage(summaryMessage, 'info', 4000);
|
||||
if (typeof showToast === 'function') {
|
||||
showToast(summaryMessage, 'info', 4000, {
|
||||
title: 'Information',
|
||||
playSound: true
|
||||
});
|
||||
}
|
||||
|
||||
// Optional: Alle einzelnen Nachrichten anzeigen (mit Verzögerung)
|
||||
this.suppressedMessages.forEach((item, index) => {
|
||||
setTimeout(() => {
|
||||
if (typeof showFlashMessage === 'function') {
|
||||
showFlashMessage(item.message, item.type, 3000);
|
||||
if (typeof showToast === 'function') {
|
||||
showToast(item.message, item.type, 3000, {
|
||||
title: item.type === 'success' ? 'Erfolgreich' :
|
||||
item.type === 'error' ? 'Fehler' :
|
||||
item.type === 'warning' ? 'Warnung' : 'Information',
|
||||
playSound: true
|
||||
});
|
||||
}
|
||||
}, (index + 1) * 500);
|
||||
});
|
||||
@ -1107,15 +1171,15 @@
|
||||
}
|
||||
|
||||
integrateWithNotificationSystem() {
|
||||
// Überschreibe showFlashMessage wenn verfügbar
|
||||
if (typeof window.showFlashMessage === 'function') {
|
||||
const originalShowFlashMessage = window.showFlashMessage;
|
||||
window.showFlashMessage = (message, type, duration) => {
|
||||
// Überschreibe showToast wenn verfügbar
|
||||
if (typeof window.showToast === 'function') {
|
||||
const originalShowToast = window.showToast;
|
||||
window.showToast = (message, type, duration, options) => {
|
||||
if (this.shouldSuppressNotification(message, type)) {
|
||||
console.log(`🔕 Benachrichtigung unterdrückt: ${message}`);
|
||||
return;
|
||||
}
|
||||
return originalShowFlashMessage(message, type, duration);
|
||||
return originalShowToast(message, type, duration, options);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
BIN
backend/utils/__pycache__/advanced_tables.cpython-311.pyc
Normal file
BIN
backend/utils/__pycache__/advanced_tables.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/utils/__pycache__/email_notification.cpython-311.pyc
Normal file
BIN
backend/utils/__pycache__/email_notification.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/utils/__pycache__/maintenance_system.cpython-311.pyc
Normal file
BIN
backend/utils/__pycache__/maintenance_system.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/utils/__pycache__/multi_location_system.cpython-311.pyc
Normal file
BIN
backend/utils/__pycache__/multi_location_system.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/utils/__pycache__/realtime_dashboard.cpython-311.pyc
Normal file
BIN
backend/utils/__pycache__/realtime_dashboard.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/utils/__pycache__/shutdown_manager.cpython-311.pyc
Normal file
BIN
backend/utils/__pycache__/shutdown_manager.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/utils/__pycache__/timer_manager.cpython-311.pyc
Normal file
BIN
backend/utils/__pycache__/timer_manager.cpython-311.pyc
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user