🎉 Fix printer monitor complete issue & logs updates 📝

This commit is contained in:
2025-06-12 08:44:29 +02:00
parent 79f4682b20
commit 985bd32d95
30 changed files with 1895 additions and 60 deletions

View File

@ -959,7 +959,8 @@ def api_get_printer_status():
if has_tapo_printers:
try:
from utils.hardware_integration import tapo_controller
from utils.hardware_integration import get_tapo_controller
tapo_controller = get_tapo_controller()
app_logger.info(f"✅ Tapo-Controller erfolgreich importiert: {type(tapo_controller)}")
except Exception as import_error:
app_logger.warning(f"⚠️ Tapo-Controller konnte nicht importiert werden: {str(import_error)}")

Binary file not shown.

View File

@ -1144,7 +1144,7 @@ def export_logs_api():
# ===== API-ENDPUNKTE FÜR SYSTEM-INFORMATIONEN =====
@admin_blueprint.route("/api/system/status", methods=["GET"])
@admin_api_blueprint.route("/system/status", methods=["GET"])
@admin_required
def get_system_status_api():
"""API-Endpunkt für System-Status-Informationen"""
@ -1473,7 +1473,7 @@ def api_admin_plug_schedules_cleanup():
@admin_required
def api_admin_plug_schedules_calendar():
"""
API-Endpoint für Kalender-Daten der Steckdosenschaltzeiten.
API-Endpunkt für Kalender-Daten der Steckdosenschaltzeiten.
Liefert Events für FullCalendar im JSON-Format.
"""
try:
@ -1549,6 +1549,218 @@ def api_admin_plug_schedules_calendar():
admin_logger.error(f"Fehler beim Laden der Kalender-Daten: {str(e)}")
return jsonify([])
@admin_api_blueprint.route('/live-stats', methods=['GET'])
@admin_required
def api_admin_live_stats():
"""
API-Endpunkt für Live-Statistiken des Admin-Dashboards
Liefert aktuelle System-Statistiken für das Dashboard:
- Benutzer-Statistiken
- Drucker-Status
- Job-Statistiken
- System-Performance
"""
try:
with get_cached_session() as db_session:
# Benutzer-Statistiken
total_users = db_session.query(User).count()
active_users = db_session.query(User).filter(User.active == True).count()
admin_users = db_session.query(User).filter(User.role == 'admin').count()
# Drucker-Statistiken
total_printers = db_session.query(Printer).count()
active_printers = db_session.query(Printer).filter(Printer.active == True).count()
online_printers = db_session.query(Printer).filter(
Printer.active == True,
Printer.status == 'online'
).count()
# Job-Statistiken
total_jobs = db_session.query(Job).count()
active_jobs = db_session.query(Job).filter(
Job.status.in_(['pending', 'printing', 'paused'])
).count()
completed_jobs = db_session.query(Job).filter(
Job.status == 'completed'
).count()
failed_jobs = db_session.query(Job).filter(
Job.status == 'failed'
).count()
# Jobs der letzten 24 Stunden
last_24h = datetime.now() - timedelta(hours=24)
jobs_24h = db_session.query(Job).filter(
Job.created_at >= last_24h
).count()
# Jobs der letzten 7 Tage
last_7d = datetime.now() - timedelta(days=7)
jobs_7d = db_session.query(Job).filter(
Job.created_at >= last_7d
).count()
# Steckdosen-Statistiken
plug_logs_24h = db_session.query(PlugStatusLog).filter(
PlugStatusLog.timestamp >= last_24h
).count()
# System-Logs der letzten Stunde
last_hour = datetime.now() - timedelta(hours=1)
system_logs_1h = db_session.query(SystemLog).filter(
SystemLog.timestamp >= last_hour
).count()
# Response-Struktur
stats = {
'users': {
'total': total_users,
'active': active_users,
'admins': admin_users
},
'printers': {
'total': total_printers,
'active': active_printers,
'online': online_printers,
'offline': active_printers - online_printers
},
'jobs': {
'total': total_jobs,
'active': active_jobs,
'completed': completed_jobs,
'failed': failed_jobs,
'last_24h': jobs_24h,
'last_7d': jobs_7d
},
'system': {
'plug_logs_24h': plug_logs_24h,
'system_logs_1h': system_logs_1h,
'uptime': 'Unbekannt' # Könnte später implementiert werden
},
'timestamp': datetime.now().isoformat()
}
admin_api_logger.info(f"Live-Statistiken abgerufen von Admin {current_user.username}")
return jsonify({
'success': True,
'stats': stats,
'message': 'Live-Statistiken erfolgreich geladen'
})
except Exception as e:
admin_api_logger.error(f"Fehler beim Abrufen der Live-Statistiken: {str(e)}")
return jsonify({
'success': False,
'error': 'Fehler beim Laden der Statistiken',
'message': str(e),
'stats': {}
}), 500
@admin_api_blueprint.route('/system/health', methods=['GET'])
@admin_required
def api_admin_system_health():
"""
API-Endpunkt für System-Health-Check
Überprüft verschiedene System-Komponenten:
- Datenbank-Verbindung
- Dateisystem
- Speicherplatz
- Service-Status
"""
try:
health_status = {
'database': 'unknown',
'filesystem': 'unknown',
'storage': {},
'services': {},
'timestamp': datetime.now().isoformat()
}
# Datenbank-Check
try:
with get_cached_session() as db_session:
# Einfacher Query-Test
db_session.execute("SELECT 1")
health_status['database'] = 'healthy'
except Exception as db_error:
health_status['database'] = 'unhealthy'
admin_api_logger.error(f"Datenbank-Health-Check fehlgeschlagen: {str(db_error)}")
# Dateisystem-Check
try:
# Prüfe wichtige Verzeichnisse
important_dirs = [
'backend/uploads',
'backend/database',
'backend/logs'
]
all_accessible = True
for dir_path in important_dirs:
if not os.path.exists(dir_path) or not os.access(dir_path, os.W_OK):
all_accessible = False
break
health_status['filesystem'] = 'healthy' if all_accessible else 'unhealthy'
except Exception as fs_error:
health_status['filesystem'] = 'unhealthy'
admin_api_logger.error(f"Dateisystem-Health-Check fehlgeschlagen: {str(fs_error)}")
# Speicherplatz-Check
try:
statvfs = os.statvfs('.')
total_space = statvfs.f_blocks * statvfs.f_frsize
free_space = statvfs.f_bavail * statvfs.f_frsize
used_space = total_space - free_space
health_status['storage'] = {
'total_gb': round(total_space / (1024**3), 2),
'used_gb': round(used_space / (1024**3), 2),
'free_gb': round(free_space / (1024**3), 2),
'percent_used': round((used_space / total_space) * 100, 1)
}
except Exception as storage_error:
admin_api_logger.error(f"Speicherplatz-Check fehlgeschlagen: {str(storage_error)}")
# Service-Status (vereinfacht)
health_status['services'] = {
'web_server': 'running', # Immer running, da wir antworten
'job_scheduler': 'unknown', # Könnte später implementiert werden
'tapo_controller': 'unknown' # Könnte später implementiert werden
}
# Gesamt-Status berechnen
if health_status['database'] == 'healthy' and health_status['filesystem'] == 'healthy':
overall_status = 'healthy'
elif health_status['database'] == 'unhealthy' or health_status['filesystem'] == 'unhealthy':
overall_status = 'unhealthy'
else:
overall_status = 'degraded'
health_status['overall'] = overall_status
admin_api_logger.info(f"System-Health-Check durchgeführt: {overall_status}")
return jsonify({
'success': True,
'health': health_status,
'message': f'System-Status: {overall_status}'
})
except Exception as e:
admin_api_logger.error(f"Fehler beim System-Health-Check: {str(e)}")
return jsonify({
'success': False,
'error': 'Fehler beim Health-Check',
'message': str(e),
'health': {
'overall': 'error',
'timestamp': datetime.now().isoformat()
}
}), 500
# ===== HELPER FUNCTIONS FOR PLUG SCHEDULES =====
def get_relative_time(timestamp):

View File

@ -73,6 +73,90 @@ def get_live_printer_status():
"message": str(e)
}), 500
@printers_blueprint.route("/status", methods=["GET"])
@login_required
@measure_execution_time(logger=printers_logger, task_name="API-Drucker-Status-Abfrage")
def get_printer_status():
"""
Liefert den aktuellen Status aller Drucker.
Dieser Endpunkt ist kompatibel mit dem Frontend printer_monitor.js
Returns:
JSON mit Status aller Drucker
"""
printers_logger.info(f"🔄 Status-Abfrage von Benutzer {current_user.name} (ID: {current_user.id})")
try:
# Drucker aus Datenbank holen
db_session = get_db_session()
printers = db_session.query(Printer).all()
# Status-Daten für jeden Drucker sammeln
printer_data = []
status_summary = {
'total': len(printers),
'online': 0,
'offline': 0,
'standby': 0,
'unreachable': 0,
'unconfigured': 0
}
for printer in printers:
# Basis-Drucker-Daten
printer_info = {
'id': printer.id,
'name': printer.name,
'model': printer.model,
'location': printer.location,
'status': printer.status or 'offline',
'plug_ip': printer.plug_ip,
'has_plug': bool(printer.plug_ip and printer.plug_username and printer.plug_password),
'last_checked': printer.last_checked.isoformat() if printer.last_checked else None,
'created_at': printer.created_at.isoformat() if printer.created_at else None
}
# Status-Zusammenfassung aktualisieren
status = printer_info['status']
if status in status_summary:
status_summary[status] += 1
else:
status_summary['offline'] += 1
# Aktive Jobs zählen
active_jobs = db_session.query(Job).filter(
Job.printer_id == printer.id,
Job.status.in_(["running", "printing", "active", "scheduled"])
).count()
printer_info['active_jobs'] = active_jobs
printer_info['is_busy'] = active_jobs > 0
printer_data.append(printer_info)
db_session.close()
# Antwort mit Status und Zusammenfassung
response = {
"success": True,
"printers": printer_data,
"summary": status_summary,
"timestamp": datetime.now().isoformat()
}
printers_logger.info(f"✅ Status-Abfrage erfolgreich: {len(printer_data)} Drucker")
return jsonify(response)
except Exception as e:
printers_logger.error(f"❌ Fehler bei Status-Abfrage: {str(e)}")
if 'db_session' in locals():
db_session.close()
return jsonify({
"success": False,
"error": "Fehler bei Abfrage des Druckerstatus",
"message": str(e)
}), 500
@printers_blueprint.route("/control/<int:printer_id>/power", methods=["POST"])
@login_required
@require_permission(Permission.CONTROL_PRINTER) # Verwende die bereits vorhandene Berechtigung

View File

@ -0,0 +1 @@

View File

@ -2,3 +2,12 @@
2025-06-12 08:08:27 - [admin] admin - [INFO] INFO - Admin-Dashboard geladen von admin
2025-06-12 08:31:58 - [admin] admin - [INFO] INFO - Admin-Check für Funktion printers_overview: User authenticated: True, User ID: 1, Is Admin: True
2025-06-12 08:31:58 - [admin] admin - [INFO] INFO - Druckerübersicht geladen von admin
2025-06-12 08:33:35 - [admin] admin - [INFO] INFO - Admin-Check für Funktion admin_dashboard: User authenticated: True, User ID: 1, Is Admin: True
2025-06-12 08:33:35 - [admin] admin - [INFO] INFO - Admin-Dashboard geladen von admin
2025-06-12 08:33:38 - [admin] admin - [INFO] INFO - Admin-Check für Funktion logs_overview: User authenticated: True, User ID: 1, Is Admin: True
2025-06-12 08:33:38 - [admin] admin - [INFO] INFO - Logs-Übersicht geladen von admin
2025-06-12 08:33:39 - [admin] admin - [INFO] INFO - Admin-Check für Funktion get_logs_api: User authenticated: True, User ID: 1, Is Admin: True
2025-06-12 08:33:39 - [admin] admin - [INFO] INFO - Logs abgerufen: 0 Einträge, Level: all
2025-06-12 08:33:40 - [admin] admin - [INFO] INFO - Admin-Check für Funktion get_logs_api: User authenticated: True, User ID: 1, Is Admin: True
2025-06-12 08:33:40 - [admin] admin - [INFO] INFO - Logs abgerufen: 0 Einträge, Level: all
2025-06-12 08:33:42 - [admin] admin - [INFO] INFO - Admin-Check für Funktion guest_requests: User authenticated: True, User ID: 1, Is Admin: True

View File

@ -2024,3 +2024,282 @@ werkzeug.routing.exceptions.BuildError: Could not build url for endpoint 'admin.
2025-06-12 08:32:50 - [app] app - [INFO] INFO - [STARTUP] Starte Job Scheduler...
2025-06-12 08:32:50 - [app] app - [INFO] INFO - [STARTUP] ✅ Job Scheduler gestartet
2025-06-12 08:32:50 - [app] app - [INFO] INFO - [STARTUP] 🌐 Server startet auf http://0.0.0.0:5000
2025-06-12 08:32:51 - [app] app - [WARNING] WARNING - DatabaseCleanupManager nicht verfügbar - Fallback auf Legacy-Cleanup
2025-06-12 08:32:51 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: backend/database/myp.db
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [CONFIG] Erkannte Umgebung: development
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [CONFIG] Production-Modus: False
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [CONFIG] Verwende Development-Konfiguration
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [DEVELOPMENT] Aktiviere Development-Konfiguration
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ MYP Development Environment Konfiguration aktiviert
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ Environment: Development/Testing
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ Debug Mode: True
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ SQL Echo: True
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [STARTUP] 🚀 Starte MYP DEVELOPMENT-Umgebung
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [STARTUP] 🏢 Mercedes-Benz TBA Marienfelde
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [STARTUP] 🔒 Air-Gapped: True
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [STARTUP] Initialisiere Datenbank...
2025-06-12 08:32:53 - [app] app - [INFO] INFO - SQLite für Raspberry Pi optimiert (reduzierte Cache-Größe, SD-Karten I/O)
2025-06-12 08:32:53 - [app] app - [INFO] INFO - Datenbank mit Optimierungen initialisiert
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [STARTUP] ✅ Datenbank initialisiert
2025-06-12 08:32:53 - [app] app - [INFO] INFO - [STARTUP] Prüfe Initial-Admin...
2025-06-12 08:32:54 - [app] app - [INFO] INFO - Admin-Benutzer admin (admin@mercedes-benz.com) existiert bereits. Passwort wurde zurückgesetzt.
2025-06-12 08:32:54 - [app] app - [INFO] INFO - [STARTUP] ✅ Admin-Benutzer geprüft
2025-06-12 08:32:54 - [app] app - [INFO] INFO - [STARTUP] Starte Queue Manager...
2025-06-12 08:32:54 - [app] app - [INFO] INFO - [STARTUP] ✅ Queue Manager gestartet
2025-06-12 08:32:54 - [app] app - [INFO] INFO - [STARTUP] Starte Job Scheduler...
2025-06-12 08:32:54 - [app] app - [INFO] INFO - [STARTUP] ✅ Job Scheduler gestartet
2025-06-12 08:32:54 - [app] app - [INFO] INFO - [STARTUP] 🌐 Server startet auf http://0.0.0.0:5000
2025-06-12 08:32:54 - [app] app - [INFO] INFO - Locating template 'jobs.html':
1: trying loader of application '__main__'
class: jinja2.loaders.FileSystemLoader
encoding: 'utf-8'
followlinks: False
searchpath:
- C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\templates
-> found ('C:\\Users\\TTOMCZA.EMEA\\Dev\\Projektarbeit-MYP\\backend\\templates\\jobs.html')
2025-06-12 08:32:54 - [app] app - [INFO] INFO - Locating template 'base.html':
1: trying loader of application '__main__'
class: jinja2.loaders.FileSystemLoader
encoding: 'utf-8'
followlinks: False
searchpath:
- C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\templates
-> found ('C:\\Users\\TTOMCZA.EMEA\\Dev\\Projektarbeit-MYP\\backend\\templates\\base.html')
2025-06-12 08:32:54 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:32:54 - [app] app - [DEBUG] DEBUG - Request: GET /.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:32:54 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:32:54 - [app] app - [INFO] INFO - Locating template 'errors/404.html':
1: trying loader of application '__main__'
class: jinja2.loaders.FileSystemLoader
encoding: 'utf-8'
followlinks: False
searchpath:
- C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\templates
-> found ('C:\\Users\\TTOMCZA.EMEA\\Dev\\Projektarbeit-MYP\\backend\\templates\\errors\\404.html')
2025-06-12 08:32:54 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:32:55 - [app] app - [DEBUG] DEBUG - Request: GET /api/notifications
2025-06-12 08:32:55 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:32:55 - [app] app - [DEBUG] DEBUG - Request: GET /api/jobs
2025-06-12 08:32:55 - [app] app - [DEBUG] DEBUG - Request: GET /api/session/status
2025-06-12 08:32:55 - [app] app - [DEBUG] DEBUG - Request: GET /api/user/settings
2025-06-12 08:32:55 - [app] app - [DEBUG] DEBUG - Response: 500
2025-06-12 08:32:55 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:32:55 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:32:56 - [app] app - [DEBUG] DEBUG - Request: GET /stats
2025-06-12 08:32:56 - [app] app - [INFO] INFO - Locating template 'stats.html':
1: trying loader of application '__main__'
class: jinja2.loaders.FileSystemLoader
encoding: 'utf-8'
followlinks: False
searchpath:
- C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\templates
-> found ('C:\\Users\\TTOMCZA.EMEA\\Dev\\Projektarbeit-MYP\\backend\\templates\\stats.html')
2025-06-12 08:32:56 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:32:56 - [app] app - [DEBUG] DEBUG - Request: GET /.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:32:56 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:32:56 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:32:57 - [app] app - [DEBUG] DEBUG - Request: GET /api/notifications
2025-06-12 08:32:57 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:32:57 - [app] app - [DEBUG] DEBUG - Request: GET /api/session/status
2025-06-12 08:32:57 - [app] app - [DEBUG] DEBUG - Request: GET /api/user/settings
2025-06-12 08:32:57 - [app] app - [DEBUG] DEBUG - Response: 500
2025-06-12 08:32:57 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:04 - [app] app - [DEBUG] DEBUG - Request: GET /calendar
2025-06-12 08:33:04 - [app] app - [INFO] INFO - Locating template 'calendar.html':
1: trying loader of application '__main__'
class: jinja2.loaders.FileSystemLoader
encoding: 'utf-8'
followlinks: False
searchpath:
- C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\templates
-> found ('C:\\Users\\TTOMCZA.EMEA\\Dev\\Projektarbeit-MYP\\backend\\templates\\calendar.html')
2025-06-12 08:33:04 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:04 - [app] app - [DEBUG] DEBUG - Request: GET /.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:04 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:04 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:33:05 - [app] app - [DEBUG] DEBUG - Request: GET /api/notifications
2025-06-12 08:33:05 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:05 - [app] app - [DEBUG] DEBUG - Request: GET /api/calendar/events
2025-06-12 08:33:05 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:05 - [app] app - [DEBUG] DEBUG - Request: GET /api/session/status
2025-06-12 08:33:05 - [app] app - [DEBUG] DEBUG - Request: GET /api/user/settings
2025-06-12 08:33:05 - [app] app - [DEBUG] DEBUG - Response: 500
2025-06-12 08:33:05 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:13 - [app] app - [DEBUG] DEBUG - Request: GET /stats
2025-06-12 08:33:13 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:13 - [app] app - [DEBUG] DEBUG - Request: GET /.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:13 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:13 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:33:14 - [app] app - [DEBUG] DEBUG - Request: GET /api/notifications
2025-06-12 08:33:14 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:14 - [app] app - [DEBUG] DEBUG - Request: GET /api/session/status
2025-06-12 08:33:14 - [app] app - [DEBUG] DEBUG - Request: GET /api/user/settings
2025-06-12 08:33:14 - [app] app - [DEBUG] DEBUG - Response: 500
2025-06-12 08:33:14 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:16 - [app] app - [DEBUG] DEBUG - Request: GET /request
2025-06-12 08:33:16 - [app] app - [INFO] INFO - Locating template 'guest_request.html':
1: trying loader of application '__main__'
class: jinja2.loaders.FileSystemLoader
encoding: 'utf-8'
followlinks: False
searchpath:
- C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\templates
-> found ('C:\\Users\\TTOMCZA.EMEA\\Dev\\Projektarbeit-MYP\\backend\\templates\\guest_request.html')
2025-06-12 08:33:16 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:16 - [app] app - [ERROR] ERROR - Fehler beim Laden des Benutzers 1: (sqlite3.InterfaceError) bad parameter or other API misuse
[SQL: SELECT users.id AS users_id, users.email AS users_email, users.username AS users_username, users.password_hash AS users_password_hash, users.name AS users_name, users.role AS users_role, users.active AS users_active, users.created_at AS users_created_at, users.last_login AS users_last_login, users.updated_at AS users_updated_at, users.settings AS users_settings, users.last_activity AS users_last_activity, users.department AS users_department, users.position AS users_position, users.phone AS users_phone, users.bio AS users_bio, users.theme_preference AS users_theme_preference, users.language_preference AS users_language_preference, users.email_notifications AS users_email_notifications, users.browser_notifications AS users_browser_notifications, users.dashboard_layout AS users_dashboard_layout, users.compact_mode AS users_compact_mode, users.show_completed_jobs AS users_show_completed_jobs, users.auto_refresh_interval AS users_auto_refresh_interval, users.auto_logout_timeout AS users_auto_logout_timeout
FROM users
WHERE users.id = ?
LIMIT ? OFFSET ?]
[parameters: (1, 1, 0)]
(Background on this error at: https://sqlalche.me/e/20/rvf5)
2025-06-12 08:33:16 - [app] app - [DEBUG] DEBUG - Request: GET /.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:16 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:16 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:33:17 - [app] app - [DEBUG] DEBUG - Request: GET /api/notifications
2025-06-12 08:33:17 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:17 - [app] app - [DEBUG] DEBUG - Request: GET /api/session/status
2025-06-12 08:33:17 - [app] app - [DEBUG] DEBUG - Request: GET /api/user/settings
2025-06-12 08:33:17 - [app] app - [DEBUG] DEBUG - Response: 500
2025-06-12 08:33:17 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:21 - [app] app - [DEBUG] DEBUG - Request: POST /request
2025-06-12 08:33:21 - [app] app - [INFO] INFO - OTP generiert für Guest Request 1
2025-06-12 08:33:21 - [app] app - [DEBUG] DEBUG - Response: 302
2025-06-12 08:33:21 - [app] app - [DEBUG] DEBUG - Request: GET /request/1
2025-06-12 08:33:21 - [app] app - [INFO] INFO - Locating template 'guest_status.html':
1: trying loader of application '__main__'
class: jinja2.loaders.FileSystemLoader
encoding: 'utf-8'
followlinks: False
searchpath:
- C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\templates
-> found ('C:\\Users\\TTOMCZA.EMEA\\Dev\\Projektarbeit-MYP\\backend\\templates\\guest_status.html')
2025-06-12 08:33:21 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:28 - [app] app - [DEBUG] DEBUG - Request: GET /request/1
2025-06-12 08:33:28 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:28 - [app] app - [DEBUG] DEBUG - Request: GET /.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:28 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:28 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:33:28 - [app] app - [DEBUG] DEBUG - Request: GET /api/notifications
2025-06-12 08:33:28 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:28 - [app] app - [DEBUG] DEBUG - Request: GET /api/session/status
2025-06-12 08:33:28 - [app] app - [DEBUG] DEBUG - Request: GET /api/user/settings
2025-06-12 08:33:28 - [app] app - [DEBUG] DEBUG - Response: 500
2025-06-12 08:33:28 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:35 - [app] app - [DEBUG] DEBUG - Request: GET /admin/
2025-06-12 08:33:35 - [app] app - [INFO] INFO - Locating template 'admin.html':
1: trying loader of application '__main__'
class: jinja2.loaders.FileSystemLoader
encoding: 'utf-8'
followlinks: False
searchpath:
- C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\templates
-> found ('C:\\Users\\TTOMCZA.EMEA\\Dev\\Projektarbeit-MYP\\backend\\templates\\admin.html')
2025-06-12 08:33:35 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:35 - [app] app - [DEBUG] DEBUG - Request: GET /.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:35 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:35 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:33:35 - [app] app - [DEBUG] DEBUG - Request: GET /api/notifications
2025-06-12 08:33:35 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:35 - [app] app - [DEBUG] DEBUG - Request: GET /api/stats
2025-06-12 08:33:35 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/stats
2025-06-12 08:33:35 - [app] app - [DEBUG] DEBUG - Request: GET /api/session/status
2025-06-12 08:33:35 - [app] app - [DEBUG] DEBUG - Request: GET /api/printers/monitor/live-status
2025-06-12 08:33:35 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:33:35 - [app] app - [DEBUG] DEBUG - Request: GET /api/user/settings
2025-06-12 08:33:35 - [app] app - [DEBUG] DEBUG - Response: 500
2025-06-12 08:33:36 - [app] app - [DEBUG] DEBUG - Response: 500
2025-06-12 08:33:36 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:36 - [app] app - [DEBUG] DEBUG - Request: GET /api/admin/system-health
2025-06-12 08:33:36 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/admin/system-health
2025-06-12 08:33:36 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:33:38 - [app] app - [DEBUG] DEBUG - Request: GET /admin/logs
2025-06-12 08:33:38 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:38 - [app] app - [DEBUG] DEBUG - Request: GET /.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:38 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:38 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Request: GET /api/notifications
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Request: GET /api/stats
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Request: GET /api/printers/monitor/live-status
2025-06-12 08:33:39 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/stats
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Request: GET /api/session/status
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Request: GET /api/user/settings
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Response: 302
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Response: 500
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Response: 500
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Request: GET /api/admin/system-health
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Request: GET /auth/login
2025-06-12 08:33:39 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/admin/system-health
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Response: 302
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Request: GET /
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Response: 302
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Request: GET /admin/api/logs
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Request: GET /dashboard
2025-06-12 08:33:39 - [app] app - [INFO] INFO - Locating template 'dashboard.html':
1: trying loader of application '__main__'
class: jinja2.loaders.FileSystemLoader
encoding: 'utf-8'
followlinks: False
searchpath:
- C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\templates
-> found ('C:\\Users\\TTOMCZA.EMEA\\Dev\\Projektarbeit-MYP\\backend\\templates\\dashboard.html')
2025-06-12 08:33:39 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:40 - [app] app - [DEBUG] DEBUG - Request: GET /admin/api/logs
2025-06-12 08:33:40 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Request: GET /admin/guest-requests
2025-06-12 08:33:42 - [app] app - [INFO] INFO - Locating template 'admin_guest_requests.html':
1: trying loader of application '__main__'
class: jinja2.loaders.FileSystemLoader
encoding: 'utf-8'
followlinks: False
searchpath:
- C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\templates
-> found ('C:\\Users\\TTOMCZA.EMEA\\Dev\\Projektarbeit-MYP\\backend\\templates\\admin_guest_requests.html')
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Request: GET /.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:42 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/.well-known/appspecific/com.chrome.devtools.json
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Request: GET /api/notifications
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Request: GET /api/printers/monitor/live-status
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Request: GET /api/session/status
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Request: GET /api/user/settings
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Request: GET /api/admin/guest-requests
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Response: 500
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Response: 500
2025-06-12 08:33:42 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/admin/guest-requests
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Response: 404
2025-06-12 08:33:42 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-12 08:34:04 - [app] app - [INFO] INFO - [SHUTDOWN] 🧹 Cleanup wird ausgeführt...
2025-06-12 08:34:04 - [app] app - [INFO] INFO - [SHUTDOWN] ✅ Queue Manager gestoppt
2025-06-12 08:34:04 - [app] app - [ERROR] ERROR - [SHUTDOWN] ❌ Cleanup-Fehler: 'BackgroundTaskScheduler' object has no attribute 'shutdown'
2025-06-12 08:34:05 - [app] app - [WARNING] WARNING - DatabaseCleanupManager nicht verfügbar - Fallback auf Legacy-Cleanup
2025-06-12 08:34:05 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: backend/database/myp.db
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [CONFIG] Erkannte Umgebung: development
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [CONFIG] Production-Modus: False
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [CONFIG] Verwende Development-Konfiguration
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [DEVELOPMENT] Aktiviere Development-Konfiguration
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ MYP Development Environment Konfiguration aktiviert
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ Environment: Development/Testing
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ Debug Mode: True
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ SQL Echo: True
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [STARTUP] 🚀 Starte MYP DEVELOPMENT-Umgebung
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [STARTUP] 🏢 Mercedes-Benz TBA Marienfelde
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [STARTUP] 🔒 Air-Gapped: True
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [STARTUP] Initialisiere Datenbank...
2025-06-12 08:34:06 - [app] app - [INFO] INFO - SQLite für Raspberry Pi optimiert (reduzierte Cache-Größe, SD-Karten I/O)
2025-06-12 08:34:06 - [app] app - [INFO] INFO - Datenbank mit Optimierungen initialisiert
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [STARTUP] ✅ Datenbank initialisiert
2025-06-12 08:34:06 - [app] app - [INFO] INFO - [STARTUP] Prüfe Initial-Admin...
2025-06-12 08:34:07 - [app] app - [INFO] INFO - Admin-Benutzer admin (admin@mercedes-benz.com) existiert bereits. Passwort wurde zurückgesetzt.
2025-06-12 08:34:07 - [app] app - [INFO] INFO - [STARTUP] ✅ Admin-Benutzer geprüft
2025-06-12 08:34:07 - [app] app - [INFO] INFO - [STARTUP] Starte Queue Manager...
2025-06-12 08:34:07 - [app] app - [INFO] INFO - [STARTUP] ✅ Queue Manager gestartet
2025-06-12 08:34:07 - [app] app - [INFO] INFO - [STARTUP] Starte Job Scheduler...
2025-06-12 08:34:07 - [app] app - [INFO] INFO - [STARTUP] ✅ Job Scheduler gestartet
2025-06-12 08:34:07 - [app] app - [INFO] INFO - [STARTUP] 🌐 Server startet auf http://0.0.0.0:5000
2025-06-12 08:34:12 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/admin/guest-requests

View File

@ -0,0 +1 @@
2025-06-12 08:33:05 - [calendar] calendar - [INFO] INFO - 📅 Kalender-Events abgerufen: 0 Einträge für Zeitraum 2025-06-07 22:00:00+00:00 bis 2025-06-14 22:00:00+00:00

View File

@ -46,3 +46,7 @@
2025-06-12 08:32:05 - [core_system] core_system - [INFO] INFO - 📊 Massive Konsolidierung: 6 Dateien → 1 Datei (88% Reduktion)
2025-06-12 08:32:48 - [core_system] core_system - [INFO] INFO - ✅ Core System Management Module erfolgreich initialisiert
2025-06-12 08:32:48 - [core_system] core_system - [INFO] INFO - 📊 Massive Konsolidierung: 6 Dateien → 1 Datei (88% Reduktion)
2025-06-12 08:32:51 - [core_system] core_system - [INFO] INFO - ✅ Core System Management Module erfolgreich initialisiert
2025-06-12 08:32:51 - [core_system] core_system - [INFO] INFO - 📊 Massive Konsolidierung: 6 Dateien → 1 Datei (88% Reduktion)
2025-06-12 08:34:05 - [core_system] core_system - [INFO] INFO - ✅ Core System Management Module erfolgreich initialisiert
2025-06-12 08:34:05 - [core_system] core_system - [INFO] INFO - 📊 Massive Konsolidierung: 6 Dateien → 1 Datei (88% Reduktion)

View File

@ -46,3 +46,7 @@
2025-06-12 08:32:06 - [data_management] data_management - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
2025-06-12 08:32:49 - [data_management] data_management - [INFO] INFO - ✅ Data Management Module initialisiert
2025-06-12 08:32:49 - [data_management] data_management - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
2025-06-12 08:32:51 - [data_management] data_management - [INFO] INFO - ✅ Data Management Module initialisiert
2025-06-12 08:32:51 - [data_management] data_management - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
2025-06-12 08:34:05 - [data_management] data_management - [INFO] INFO - ✅ Data Management Module initialisiert
2025-06-12 08:34:05 - [data_management] data_management - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)

View File

@ -3,3 +3,4 @@
FROM users JOIN user_permissions ON users.id = user_permissions.user_id
WHERE user_permissions.can_approve_jobs = 1]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
2025-06-12 08:33:21 - [guest] guest - [INFO] INFO - Neue Gastanfrage erstellt: ID 1, Name: Till Tomczak, OTP generiert

View File

@ -98,3 +98,11 @@
2025-06-12 08:32:49 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Printer Monitor initialisiert
2025-06-12 08:32:49 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Hardware Integration Module initialisiert
2025-06-12 08:32:49 - [hardware_integration] hardware_integration - [INFO] INFO - 📊 Massive Konsolidierung: 2 Dateien → 1 Datei (50% Reduktion)
2025-06-12 08:32:51 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ PyP100 (TP-Link Tapo) verfügbar
2025-06-12 08:32:51 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Printer Monitor initialisiert
2025-06-12 08:32:51 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Hardware Integration Module initialisiert
2025-06-12 08:32:51 - [hardware_integration] hardware_integration - [INFO] INFO - 📊 Massive Konsolidierung: 2 Dateien → 1 Datei (50% Reduktion)
2025-06-12 08:34:05 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ PyP100 (TP-Link Tapo) verfügbar
2025-06-12 08:34:05 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Printer Monitor initialisiert
2025-06-12 08:34:05 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Hardware Integration Module initialisiert
2025-06-12 08:34:05 - [hardware_integration] hardware_integration - [INFO] INFO - 📊 Massive Konsolidierung: 2 Dateien → 1 Datei (50% Reduktion)

View File

@ -92,3 +92,10 @@
2025-06-12 08:32:49 - [job_queue_system] job_queue_system - [INFO] INFO - ✅ Job & Queue System Module initialisiert
2025-06-12 08:32:49 - [job_queue_system] job_queue_system - [INFO] INFO - 📊 MASSIVE Konsolidierung: 4 Dateien → 1 Datei (75% Reduktion)
2025-06-12 08:32:50 - [job_queue_system] job_queue_system - [INFO] INFO - Queue Manager gestartet (Legacy-Kompatibilität)
2025-06-12 08:32:51 - [job_queue_system] job_queue_system - [INFO] INFO - ✅ Job & Queue System Module initialisiert
2025-06-12 08:32:51 - [job_queue_system] job_queue_system - [INFO] INFO - 📊 MASSIVE Konsolidierung: 4 Dateien → 1 Datei (75% Reduktion)
2025-06-12 08:32:54 - [job_queue_system] job_queue_system - [INFO] INFO - Queue Manager gestartet (Legacy-Kompatibilität)
2025-06-12 08:34:04 - [job_queue_system] job_queue_system - [INFO] INFO - Queue Manager gestoppt (Legacy-Kompatibilität)
2025-06-12 08:34:05 - [job_queue_system] job_queue_system - [INFO] INFO - ✅ Job & Queue System Module initialisiert
2025-06-12 08:34:05 - [job_queue_system] job_queue_system - [INFO] INFO - 📊 MASSIVE Konsolidierung: 4 Dateien → 1 Datei (75% Reduktion)
2025-06-12 08:34:07 - [job_queue_system] job_queue_system - [INFO] INFO - Queue Manager gestartet (Legacy-Kompatibilität)

View File

@ -55,3 +55,5 @@ TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
2025-06-12 08:20:59 - [jobs] jobs - [INFO] INFO - ✅ Jobs erfolgreich abgerufen: 0 von 0 (Seite 1)
2025-06-12 08:21:14 - [jobs] jobs - [INFO] INFO - 📋 Jobs-Abfrage gestartet von Benutzer 1 (Admin: True)
2025-06-12 08:21:14 - [jobs] jobs - [INFO] INFO - ✅ Jobs erfolgreich abgerufen: 0 von 0 (Seite 1)
2025-06-12 08:32:55 - [jobs] jobs - [INFO] INFO - 📋 Jobs-Abfrage gestartet von Benutzer 1 (Admin: True)
2025-06-12 08:32:55 - [jobs] jobs - [INFO] INFO - ✅ Jobs erfolgreich abgerufen: 0 von 0 (Seite 1)

View File

@ -46,3 +46,7 @@
2025-06-12 08:32:07 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - 📊 MASSIVE Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
2025-06-12 08:32:50 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - ✅ Monitoring & Analytics Module initialisiert
2025-06-12 08:32:50 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - 📊 MASSIVE Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
2025-06-12 08:32:53 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - ✅ Monitoring & Analytics Module initialisiert
2025-06-12 08:32:53 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - 📊 MASSIVE Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
2025-06-12 08:34:06 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - ✅ Monitoring & Analytics Module initialisiert
2025-06-12 08:34:06 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - 📊 MASSIVE Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)

View File

@ -19,3 +19,15 @@
2025-06-12 08:32:29 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-12 08:32:29 - [printers] printers - [ERROR] ERROR - ❌ Fehler bei Live-Status-Abfrage: PrinterMonitor.get_live_printer_status() got an unexpected keyword argument 'use_session_cache'
2025-06-12 08:32:29 - [printers] printers - [INFO] INFO - [OK] API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.58ms
2025-06-12 08:33:36 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-12 08:33:36 - [printers] printers - [ERROR] ERROR - ❌ Fehler bei Live-Status-Abfrage: PrinterMonitor.get_live_printer_status() got an unexpected keyword argument 'use_session_cache'
2025-06-12 08:33:36 - [printers] printers - [INFO] INFO - [OK] API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 2.06ms
2025-06-12 08:33:39 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-12 08:33:39 - [printers] printers - [ERROR] ERROR - ❌ Fehler bei Live-Status-Abfrage: PrinterMonitor.get_live_printer_status() got an unexpected keyword argument 'use_session_cache'
2025-06-12 08:33:39 - [printers] printers - [INFO] INFO - [OK] API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 1.04ms
2025-06-12 08:33:42 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-12 08:33:42 - [printers] printers - [ERROR] ERROR - ❌ Fehler bei Live-Status-Abfrage: PrinterMonitor.get_live_printer_status() got an unexpected keyword argument 'use_session_cache'
2025-06-12 08:33:42 - [printers] printers - [INFO] INFO - [OK] API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.85ms
2025-06-12 08:34:12 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-12 08:34:12 - [printers] printers - [ERROR] ERROR - ❌ Fehler bei Live-Status-Abfrage: PrinterMonitor.get_live_printer_status() got an unexpected keyword argument 'use_session_cache'
2025-06-12 08:34:12 - [printers] printers - [INFO] INFO - [OK] API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.85ms

View File

@ -68,3 +68,9 @@
2025-06-12 08:32:49 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-12 08:32:50 - [scheduler] scheduler - [INFO] INFO - Scheduler-Thread gestartet
2025-06-12 08:32:50 - [scheduler] scheduler - [INFO] INFO - Scheduler gestartet
2025-06-12 08:32:51 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-12 08:32:54 - [scheduler] scheduler - [INFO] INFO - Scheduler-Thread gestartet
2025-06-12 08:32:54 - [scheduler] scheduler - [INFO] INFO - Scheduler gestartet
2025-06-12 08:34:05 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-12 08:34:07 - [scheduler] scheduler - [INFO] INFO - Scheduler-Thread gestartet
2025-06-12 08:34:07 - [scheduler] scheduler - [INFO] INFO - Scheduler gestartet

View File

@ -70,3 +70,9 @@
2025-06-12 08:32:49 - [security_suite] security_suite - [INFO] INFO - ✅ Security Suite Module initialisiert
2025-06-12 08:32:49 - [security_suite] security_suite - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
2025-06-12 08:32:50 - [security_suite] security_suite - [INFO] INFO - 🔒 Security Suite initialisiert
2025-06-12 08:32:51 - [security_suite] security_suite - [INFO] INFO - ✅ Security Suite Module initialisiert
2025-06-12 08:32:51 - [security_suite] security_suite - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
2025-06-12 08:32:53 - [security_suite] security_suite - [INFO] INFO - 🔒 Security Suite initialisiert
2025-06-12 08:34:05 - [security_suite] security_suite - [INFO] INFO - ✅ Security Suite Module initialisiert
2025-06-12 08:34:05 - [security_suite] security_suite - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
2025-06-12 08:34:06 - [security_suite] security_suite - [INFO] INFO - 🔒 Security Suite initialisiert

View File

@ -8,3 +8,12 @@
2025-06-12 08:31:50 - [sessions] sessions - [ERROR] ERROR - Fehler beim Abrufen des Session-Status: 'int' object has no attribute 'total_seconds'
2025-06-12 08:31:56 - [sessions] sessions - [ERROR] ERROR - Fehler beim Abrufen des Session-Status: 'int' object has no attribute 'total_seconds'
2025-06-12 08:31:59 - [sessions] sessions - [ERROR] ERROR - Fehler beim Abrufen des Session-Status: 'int' object has no attribute 'total_seconds'
2025-06-12 08:32:55 - [sessions] sessions - [ERROR] ERROR - Fehler beim Abrufen des Session-Status: 'int' object has no attribute 'total_seconds'
2025-06-12 08:32:57 - [sessions] sessions - [ERROR] ERROR - Fehler beim Abrufen des Session-Status: 'int' object has no attribute 'total_seconds'
2025-06-12 08:33:05 - [sessions] sessions - [ERROR] ERROR - Fehler beim Abrufen des Session-Status: 'int' object has no attribute 'total_seconds'
2025-06-12 08:33:14 - [sessions] sessions - [ERROR] ERROR - Fehler beim Abrufen des Session-Status: 'int' object has no attribute 'total_seconds'
2025-06-12 08:33:17 - [sessions] sessions - [ERROR] ERROR - Fehler beim Abrufen des Session-Status: 'int' object has no attribute 'total_seconds'
2025-06-12 08:33:28 - [sessions] sessions - [ERROR] ERROR - Fehler beim Abrufen des Session-Status: 'int' object has no attribute 'total_seconds'
2025-06-12 08:33:35 - [sessions] sessions - [ERROR] ERROR - Fehler beim Abrufen des Session-Status: 'int' object has no attribute 'total_seconds'
2025-06-12 08:33:39 - [sessions] sessions - [ERROR] ERROR - Fehler beim Abrufen des Session-Status: 'int' object has no attribute 'total_seconds'
2025-06-12 08:33:42 - [sessions] sessions - [ERROR] ERROR - Fehler beim Abrufen des Session-Status: 'int' object has no attribute 'total_seconds'

View File

@ -214,3 +214,21 @@
2025-06-12 08:32:50 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert
2025-06-12 08:32:50 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert
2025-06-12 08:32:50 - [startup] startup - [INFO] INFO - ==================================================
2025-06-12 08:32:53 - [startup] startup - [INFO] INFO - ==================================================
2025-06-12 08:32:53 - [startup] startup - [INFO] INFO - [START] MYP Platform Backend wird gestartet...
2025-06-12 08:32:53 - [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-12 08:32:53 - [startup] startup - [INFO] INFO - 💻 Betriebssystem: nt (win32)
2025-06-12 08:32:53 - [startup] startup - [INFO] INFO - 📁 Arbeitsverzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend
2025-06-12 08:32:53 - [startup] startup - [INFO] INFO - ⏰ Startzeit: 2025-06-12T08:32:53.808641
2025-06-12 08:32:53 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert
2025-06-12 08:32:53 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert
2025-06-12 08:32:53 - [startup] startup - [INFO] INFO - ==================================================
2025-06-12 08:34:06 - [startup] startup - [INFO] INFO - ==================================================
2025-06-12 08:34:06 - [startup] startup - [INFO] INFO - [START] MYP Platform Backend wird gestartet...
2025-06-12 08:34:06 - [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-12 08:34:06 - [startup] startup - [INFO] INFO - 💻 Betriebssystem: nt (win32)
2025-06-12 08:34:06 - [startup] startup - [INFO] INFO - 📁 Arbeitsverzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend
2025-06-12 08:34:06 - [startup] startup - [INFO] INFO - ⏰ Startzeit: 2025-06-12T08:34:06.865423
2025-06-12 08:34:06 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert
2025-06-12 08:34:06 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert
2025-06-12 08:34:06 - [startup] startup - [INFO] INFO - ==================================================

View File

@ -23,3 +23,5 @@
2025-06-12 08:31:54 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ tapo controller initialisiert
2025-06-12 08:32:06 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ tapo controller initialisiert
2025-06-12 08:32:49 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ tapo controller initialisiert
2025-06-12 08:32:51 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ tapo controller initialisiert
2025-06-12 08:34:05 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ tapo controller initialisiert

View File

@ -10,3 +10,11 @@
2025-06-12 08:31:50 - [user] user - [INFO] INFO - User admin retrieved settings via API
2025-06-12 08:31:56 - [user] user - [INFO] INFO - User admin retrieved settings via API
2025-06-12 08:31:59 - [user] user - [INFO] INFO - User admin retrieved settings via API
2025-06-12 08:32:55 - [user] user - [INFO] INFO - User admin retrieved settings via API
2025-06-12 08:32:57 - [user] user - [INFO] INFO - User admin retrieved settings via API
2025-06-12 08:33:05 - [user] user - [INFO] INFO - User admin retrieved settings via API
2025-06-12 08:33:14 - [user] user - [INFO] INFO - User admin retrieved settings via API
2025-06-12 08:33:17 - [user] user - [INFO] INFO - User admin retrieved settings via API
2025-06-12 08:33:28 - [user] user - [INFO] INFO - User admin retrieved settings via API
2025-06-12 08:33:36 - [user] user - [INFO] INFO - User admin retrieved settings via API
2025-06-12 08:33:42 - [user] user - [INFO] INFO - User admin retrieved settings via API

View File

@ -50,3 +50,7 @@
2025-06-12 08:32:05 - [utilities_collection] utilities_collection - [INFO] INFO - 🚨 ALLERLETZTE MEGA-Konsolidierung: 12+ Dateien → 1 Datei (90%+ Reduktion)
2025-06-12 08:32:48 - [utilities_collection] utilities_collection - [INFO] INFO - ✅ Utilities Collection initialisiert
2025-06-12 08:32:48 - [utilities_collection] utilities_collection - [INFO] INFO - 🚨 ALLERLETZTE MEGA-Konsolidierung: 12+ Dateien → 1 Datei (90%+ Reduktion)
2025-06-12 08:32:51 - [utilities_collection] utilities_collection - [INFO] INFO - ✅ Utilities Collection initialisiert
2025-06-12 08:32:51 - [utilities_collection] utilities_collection - [INFO] INFO - 🚨 ALLERLETZTE MEGA-Konsolidierung: 12+ Dateien → 1 Datei (90%+ Reduktion)
2025-06-12 08:34:05 - [utilities_collection] utilities_collection - [INFO] INFO - ✅ Utilities Collection initialisiert
2025-06-12 08:34:05 - [utilities_collection] utilities_collection - [INFO] INFO - 🚨 ALLERLETZTE MEGA-Konsolidierung: 12+ Dateien → 1 Datei (90%+ Reduktion)

View File

@ -49,3 +49,7 @@
2025-06-12 08:32:05 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-12 08:32:48 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-12 08:32:48 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-12 08:32:51 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-12 08:32:51 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-12 08:34:05 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-12 08:34:05 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet

View File

@ -93,7 +93,7 @@ async function loadGuestRequests() {
try {
showLoading(true);
const url = `${API_BASE_URL}/api/admin/guest-requests`;
const url = `${API_BASE_URL}/api/admin/requests`;
const response = await fetch(url, {
method: 'GET',
headers: {

View File

@ -1,58 +1,710 @@
let currentRequests=[];let filteredRequests=[];let currentPage=0;let totalPages=0;let totalRequests=0;let refreshInterval=null;let csrfToken='';function detectApiBaseUrl(){return'';}
const API_BASE_URL=detectApiBaseUrl();document.addEventListener('DOMContentLoaded',function(){csrfToken=document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')||'';initEventListeners();loadGuestRequests();startAutoRefresh();console.log('🎯 Admin Guest Requests Management geladen');});function initEventListeners(){const searchInput=document.getElementById('search-requests');if(searchInput){searchInput.addEventListener('input',debounce(handleSearch,300));}
const statusFilter=document.getElementById('status-filter');if(statusFilter){statusFilter.addEventListener('change',handleFilterChange);}
const sortOrder=document.getElementById('sort-order');if(sortOrder){sortOrder.addEventListener('change',handleSortChange);}
const refreshBtn=document.getElementById('refresh-btn');if(refreshBtn){refreshBtn.addEventListener('click',()=>{loadGuestRequests();showNotification('🔄 Gastaufträge aktualisiert','info');});}
const exportBtn=document.getElementById('export-btn');if(exportBtn){exportBtn.addEventListener('click',handleExport);}
const bulkActionsBtn=document.getElementById('bulk-actions-btn');if(bulkActionsBtn){bulkActionsBtn.addEventListener('click',showBulkActionsModal);}
const selectAllCheckbox=document.getElementById('select-all');if(selectAllCheckbox){selectAllCheckbox.addEventListener('change',handleSelectAll);}}
async function loadGuestRequests(){try{showLoading(true);const url=`${API_BASE_URL}/api/admin/guest-requests`;const response=await fetch(url,{method:'GET',headers:{'Content-Type':'application/json','X-CSRFToken':csrfToken}});if(!response.ok){throw new Error(`HTTP ${response.status}:${response.statusText}`);}
const data=await response.json();if(data.success){currentRequests=data.requests||[];totalRequests=data.total||0;updateStats(data.stats||{});applyFiltersAndSort();console.log(`${currentRequests.length}Gastaufträge geladen`);}else{throw new Error(data.message||'Fehler beim Laden der Gastaufträge');}}catch(error){console.error('Fehler beim Laden der Gastaufträge:',error);showNotification('❌ Fehler beim Laden der Gastaufträge: '+error.message,'error');showEmptyState();}finally{showLoading(false);}}
function updateStats(stats){const elements={'pending-count':stats.pending||0,'approved-count':stats.approved||0,'rejected-count':stats.rejected||0,'total-count':stats.total||0};Object.entries(elements).forEach(([id,value])=>{const element=document.getElementById(id);if(element){animateCounter(element,value);}});}
function animateCounter(element,targetValue){const currentValue=parseInt(element.textContent)||0;const difference=targetValue-currentValue;const steps=20;const stepValue=difference/steps;let step=0;const interval=setInterval(()=>{step++;const value=Math.round(currentValue+(stepValue*step));element.textContent=value;if(step>=steps){clearInterval(interval);element.textContent=targetValue;}},50);}
function applyFiltersAndSort(){let requests=[...currentRequests];const statusFilter=document.getElementById('status-filter')?.value;if(statusFilter&&statusFilter!=='all'){requests=requests.filter(req=>req.status===statusFilter);}
const searchTerm=document.getElementById('search-requests')?.value.toLowerCase();if(searchTerm){requests=requests.filter(req=>req.name?.toLowerCase().includes(searchTerm)||req.email?.toLowerCase().includes(searchTerm)||req.file_name?.toLowerCase().includes(searchTerm)||req.reason?.toLowerCase().includes(searchTerm));}
const sortOrder=document.getElementById('sort-order')?.value;requests.sort((a,b)=>{switch(sortOrder){case'oldest':return new Date(a.created_at)-new Date(b.created_at);case'priority':return getPriorityValue(b)-getPriorityValue(a);case'newest':default:return new Date(b.created_at)-new Date(a.created_at);}});filteredRequests=requests;renderRequestsTable();}
function getPriorityValue(request){const now=new Date();const created=new Date(request.created_at);const hoursOld=(now-created)/(1000*60*60);let priority=0;if(request.status==='pending')priority+=10;else if(request.status==='approved')priority+=5;if(hoursOld>24)priority+=5;else if(hoursOld>8)priority+=3;else if(hoursOld>2)priority+=1;return priority;}
function renderRequestsTable(){const tableBody=document.getElementById('requests-table-body');const emptyState=document.getElementById('empty-state');if(!tableBody)return;if(filteredRequests.length===0){tableBody.innerHTML='';showEmptyState();return;}
hideEmptyState();const requestsHtml=filteredRequests.map(request=>createRequestRow(request)).join('');tableBody.innerHTML=requestsHtml;addRowEventListeners();}
function createRequestRow(request){const statusColor=getStatusColor(request.status);const priorityLevel=getPriorityLevel(request);const timeAgo=getTimeAgo(request.created_at);return`<tr class="hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors duration-200"data-request-id="${request.id}"><td class="px-6 py-4"><input type="checkbox"class="request-checkbox rounded border-slate-300 text-blue-600 focus:ring-blue-500"
value="${request.id}"></td><td class="px-6 py-4"><div class="flex items-center"><div class="flex-shrink-0 h-10 w-10"><div class="h-10 w-10 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-semibold">${request.name?request.name[0].toUpperCase():'G'}</div></div><div class="ml-4"><div class="text-sm font-medium text-slate-900 dark:text-white">${escapeHtml(request.name||'Unbekannt')}</div><div class="text-sm text-slate-500 dark:text-slate-400">${escapeHtml(request.email||'Keine E-Mail')}</div></div></div></td><td class="px-6 py-4"><div class="text-sm text-slate-900 dark:text-white font-medium">${escapeHtml(request.file_name||'Keine Datei')}</div><div class="text-sm text-slate-500 dark:text-slate-400">${request.duration_minutes?`${request.duration_minutes}Min.`:'Unbekannte Dauer'}
${request.copies?`${request.copies}Kopien`:''}</div>${request.reason?`<div class="text-xs text-slate-400 dark:text-slate-500 mt-1 truncate max-w-xs">${escapeHtml(request.reason)}</div>`:''}</td><td class="px-6 py-4"><span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full ${statusColor}"><span class="w-2 h-2 mr-1 rounded-full ${getStatusDot(request.status)}"></span>${getStatusText(request.status)}</span></td><td class="px-6 py-4"><div class="text-sm text-slate-900 dark:text-white">${timeAgo}</div><div class="text-xs text-slate-500 dark:text-slate-400">${formatDateTime(request.created_at)}</div></td><td class="px-6 py-4"><div class="flex items-center">${getPriorityBadge(priorityLevel)}
${request.is_urgent?'<span class="ml-2 text-red-500 text-xs">🔥 Dringend</span>':''}</div></td><td class="px-6 py-4"><div class="flex items-center space-x-2"><button onclick="showRequestDetail(${request.id})"
class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300 transition-colors"
title="Details anzeigen"><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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button>${request.status==='pending'?`<button onclick="approveRequest(${request.id})"
class="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300 transition-colors"
title="Genehmigen"><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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg></button><button onclick="rejectRequest(${request.id})"
class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors"
title="Ablehnen"><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="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg></button>`:''}<button onclick="deleteRequest(${request.id})"
class="text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 transition-colors"
title="Löschen"><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="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg></button></div></td></tr>`;}
function getStatusColor(status){const colors={'pending':'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300','approved':'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300','rejected':'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300','expired':'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300'};return colors[status]||'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300';}
function getStatusDot(status){const dots={'pending':'bg-yellow-400 dark:bg-yellow-300','approved':'bg-green-400 dark:bg-green-300','rejected':'bg-red-400 dark:bg-red-300','expired':'bg-gray-400 dark:bg-gray-300'};return dots[status]||'bg-gray-400 dark:bg-gray-300';}
function getStatusText(status){const texts={'pending':'Wartend','approved':'Genehmigt','rejected':'Abgelehnt','expired':'Abgelaufen'};return texts[status]||status;}
function getPriorityLevel(request){const priority=getPriorityValue(request);if(priority>=15)return'high';if(priority>=8)return'medium';return'low';}
function getPriorityBadge(level){const badges={'high':'<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300">🔴 Hoch</span>','medium':'<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300">🟡 Mittel</span>','low':'<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300">🟢 Niedrig</span>'};return badges[level]||badges['low'];}
async function approveRequest(requestId){if(!confirm('Möchten Sie diesen Gastauftrag wirklich genehmigen?'))return;try{showLoading(true);const url=`${API_BASE_URL}/api/guest-requests/${requestId}/approve`;const response=await fetch(url,{method:'POST',headers:{'Content-Type':'application/json','X-CSRFToken':csrfToken},body:JSON.stringify({})});const data=await response.json();if(data.success){showNotification('✅ Gastauftrag erfolgreich genehmigt','success');loadGuestRequests();}else{throw new Error(data.message||'Fehler beim Genehmigen');}}catch(error){console.error('Fehler beim Genehmigen:',error);showNotification('❌ Fehler beim Genehmigen: '+error.message,'error');}finally{showLoading(false);}}
async function rejectRequest(requestId){const reason=prompt('Grund für die Ablehnung:');if(!reason)return;try{showLoading(true);const url=`${API_BASE_URL}/api/guest-requests/${requestId}/reject`;const response=await fetch(url,{method:'POST',headers:{'Content-Type':'application/json','X-CSRFToken':csrfToken},body:JSON.stringify({reason})});const data=await response.json();if(data.success){showNotification('✅ Gastauftrag erfolgreich abgelehnt','success');loadGuestRequests();}else{throw new Error(data.message||'Fehler beim Ablehnen');}}catch(error){console.error('Fehler beim Ablehnen:',error);showNotification('❌ Fehler beim Ablehnen: '+error.message,'error');}finally{showLoading(false);}}
async function deleteRequest(requestId){if(!confirm('Möchten Sie diesen Gastauftrag wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.'))return;try{showLoading(true);const url=`${API_BASE_URL}/api/guest-requests/${requestId}`;const response=await fetch(url,{method:'DELETE',headers:{'Content-Type':'application/json','X-CSRFToken':csrfToken}});const data=await response.json();if(data.success){showNotification('✅ Gastauftrag erfolgreich gelöscht','success');loadGuestRequests();}else{throw new Error(data.message||'Fehler beim Löschen');}}catch(error){console.error('Fehler beim Löschen:',error);showNotification('❌ Fehler beim Löschen: '+error.message,'error');}finally{showLoading(false);}}
function showRequestDetail(requestId){const request=currentRequests.find(req=>req.id===requestId);if(!request)return;const modal=document.getElementById('detail-modal');const content=document.getElementById('modal-content');content.innerHTML=`<div class="p-6 border-b border-gray-200 dark:border-gray-700"><div class="flex justify-between items-center"><h3 class="text-xl font-bold text-gray-900 dark:text-white">Gastauftrag Details</h3><button onclick="closeDetailModal()"class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"><svg class="w-6 h-6"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></div><div class="p-6"><div class="grid grid-cols-1 md:grid-cols-2 gap-6"><div class="space-y-4"><h4 class="text-lg font-semibold text-gray-900 dark:text-white">Antragsteller</h4><div class="bg-slate-50 dark:bg-slate-700 rounded-lg p-4"><p><strong>Name:</strong>${escapeHtml(request.name||'Unbekannt')}</p><p><strong>E-Mail:</strong>${escapeHtml(request.email||'Keine E-Mail')}</p><p><strong>Erstellt am:</strong>${formatDateTime(request.created_at)}</p></div></div><div class="space-y-4"><h4 class="text-lg font-semibold text-gray-900 dark:text-white">Auftrag Details</h4><div class="bg-slate-50 dark:bg-slate-700 rounded-lg p-4"><p><strong>Datei:</strong>${escapeHtml(request.file_name||'Keine Datei')}</p><p><strong>Dauer:</strong>${request.duration_minutes||'Unbekannt'}Minuten</p><p><strong>Kopien:</strong>${request.copies||1}</p><p><strong>Status:</strong>${getStatusText(request.status)}</p></div></div></div>${request.reason?`<div class="mt-6"><h4 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Begründung</h4><div class="bg-slate-50 dark:bg-slate-700 rounded-lg p-4"><p class="text-gray-700 dark:text-gray-300">${escapeHtml(request.reason)}</p></div></div>`:''}<div class="mt-8 flex justify-end space-x-3">${request.status==='pending'?`<button onclick="approveRequest(${request.id}); closeDetailModal();"
class="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors">Genehmigen</button><button onclick="rejectRequest(${request.id}); closeDetailModal();"
class="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors">Ablehnen</button>`:''}<button onclick="closeDetailModal()"
class="px-4 py-2 bg-gray-300 dark:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-400 dark:hover:bg-gray-500 transition-colors">Schließen</button></div></div>`;modal.classList.remove('hidden');}
function closeDetailModal(){const modal=document.getElementById('detail-modal');modal.classList.add('hidden');}
function showBulkActionsModal(){const selectedIds=getSelectedRequestIds();if(selectedIds.length===0){showNotification('⚠️ Bitte wählen Sie mindestens einen Gastauftrag aus','warning');return;}
const modal=document.getElementById('bulk-modal');modal.classList.remove('hidden');}
function closeBulkModal(){const modal=document.getElementById('bulk-modal');modal.classList.add('hidden');}
async function performBulkAction(action){const selectedIds=getSelectedRequestIds();if(selectedIds.length===0)return;const confirmMessages={'approve':`Möchten Sie ${selectedIds.length}Gastaufträge genehmigen?`,'reject':`Möchten Sie ${selectedIds.length}Gastaufträge ablehnen?`,'delete':`Möchten Sie ${selectedIds.length}Gastaufträge löschen?`};if(!confirm(confirmMessages[action]))return;try{showLoading(true);closeBulkModal();const promises=selectedIds.map(async(id)=>{const url=`${API_BASE_URL}/api/guest-requests/${id}/${action}`;const method=action==='delete'?'DELETE':'POST';return fetch(url,{method,headers:{'Content-Type':'application/json','X-CSRFToken':csrfToken}});});const results=await Promise.allSettled(promises);const successCount=results.filter(r=>r.status==='fulfilled'&&r.value.ok).length;showNotification(`${successCount}von ${selectedIds.length}Aktionen erfolgreich`,'success');loadGuestRequests();document.getElementById('select-all').checked=false;document.querySelectorAll('.request-checkbox').forEach(cb=>cb.checked=false);}catch(error){console.error('Fehler bei Bulk-Aktion:',error);showNotification('❌ Fehler bei der Bulk-Aktion: '+error.message,'error');}finally{showLoading(false);}}
function getSelectedRequestIds(){const checkboxes=document.querySelectorAll('.request-checkbox:checked');return Array.from(checkboxes).map(cb=>parseInt(cb.value));}
function handleSearch(){applyFiltersAndSort();}
function handleFilterChange(){applyFiltersAndSort();}
function handleSortChange(){applyFiltersAndSort();}
function handleSelectAll(event){const checkboxes=document.querySelectorAll('.request-checkbox');checkboxes.forEach(checkbox=>{checkbox.checked=event.target.checked;});}
function handleExport(){const selectedIds=getSelectedRequestIds();const exportData=selectedIds.length>0?filteredRequests.filter(req=>selectedIds.includes(req.id)):filteredRequests;if(exportData.length===0){showNotification('⚠️ Keine Daten zum Exportieren verfügbar','warning');return;}
exportToCSV(exportData);}
function exportToCSV(data){const headers=['ID','Name','E-Mail','Datei','Status','Erstellt','Dauer (Min)','Kopien','Begründung'];const rows=data.map(req=>[req.id,req.name||'',req.email||'',req.file_name||'',getStatusText(req.status),formatDateTime(req.created_at),req.duration_minutes||'',req.copies||'',req.reason||'']);const csvContent=[headers,...rows].map(row=>row.map(field=>`"${String(field).replace(/"/g,'""')}"`).join(','))
/**
* Mercedes-Benz MYP Admin Guest Requests Management
* Moderne Verwaltung von Gastaufträgen mit Live-Updates
*/
// Globale Variablen
let currentRequests = [];
let filteredRequests = [];
let currentPage = 0;
let totalPages = 0;
let totalRequests = 0;
let refreshInterval = null;
let csrfToken = '';
// API Base URL Detection - Korrigierte Version für CSP-Kompatibilität
function detectApiBaseUrl() {
// Für lokale Entwicklung und CSP-Kompatibilität immer relative URLs verwenden
// Das verhindert CSP-Probleme mit connect-src
return ''; // Leerer String für relative URLs
}
const API_BASE_URL = detectApiBaseUrl();
// Initialisierung beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() {
// CSRF Token abrufen
csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
// Event Listeners initialisieren
initEventListeners();
// Daten initial laden
loadGuestRequests();
// Auto-Refresh starten
startAutoRefresh();
console.log('🎯 Admin Guest Requests Management geladen');
});
/**
* Event Listeners initialisieren
*/
function initEventListeners() {
// Search Input
const searchInput = document.getElementById('search-requests');
if (searchInput) {
searchInput.addEventListener('input', debounce(handleSearch, 300));
}
// Status Filter
const statusFilter = document.getElementById('status-filter');
if (statusFilter) {
statusFilter.addEventListener('change', handleFilterChange);
}
// Sort Order
const sortOrder = document.getElementById('sort-order');
if (sortOrder) {
sortOrder.addEventListener('change', handleSortChange);
}
// Action Buttons
const refreshBtn = document.getElementById('refresh-btn');
if (refreshBtn) {
refreshBtn.addEventListener('click', () => {
loadGuestRequests();
showNotification('🔄 Gastaufträge aktualisiert', 'info');
});
}
const exportBtn = document.getElementById('export-btn');
if (exportBtn) {
exportBtn.addEventListener('click', handleExport);
}
const bulkActionsBtn = document.getElementById('bulk-actions-btn');
if (bulkActionsBtn) {
bulkActionsBtn.addEventListener('click', showBulkActionsModal);
}
// Select All Checkbox
const selectAllCheckbox = document.getElementById('select-all');
if (selectAllCheckbox) {
selectAllCheckbox.addEventListener('change', handleSelectAll);
}
}
/**
* Gastaufträge von der API laden
*/
async function loadGuestRequests() {
try {
showLoading(true);
const url = `${API_BASE_URL}/api/admin/requests`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.success) {
currentRequests = data.requests || [];
totalRequests = data.total || 0;
// Statistiken aktualisieren
updateStats(data.stats || {});
// Tabelle aktualisieren
applyFiltersAndSort();
console.log(`${currentRequests.length} Gastaufträge geladen`);
} else {
throw new Error(data.message || 'Fehler beim Laden der Gastaufträge');
}
} catch (error) {
console.error('Fehler beim Laden der Gastaufträge:', error);
showNotification('❌ Fehler beim Laden der Gastaufträge: ' + error.message, 'error');
showEmptyState();
} finally {
showLoading(false);
}
}
/**
* Statistiken aktualisieren
*/
function updateStats(stats) {
const elements = {
'pending-count': stats.pending || 0,
'approved-count': stats.approved || 0,
'rejected-count': stats.rejected || 0,
'total-count': stats.total || 0
};
Object.entries(elements).forEach(([id, value]) => {
const element = document.getElementById(id);
if (element) {
animateCounter(element, value);
}
});
}
/**
* Counter mit Animation
*/
function animateCounter(element, targetValue) {
const currentValue = parseInt(element.textContent) || 0;
const difference = targetValue - currentValue;
const steps = 20;
const stepValue = difference / steps;
let step = 0;
const interval = setInterval(() => {
step++;
const value = Math.round(currentValue + (stepValue * step));
element.textContent = value;
if (step >= steps) {
clearInterval(interval);
element.textContent = targetValue;
}
}, 50);
}
/**
* Filter und Sortierung anwenden
*/
function applyFiltersAndSort() {
let requests = [...currentRequests];
// Status Filter
const statusFilter = document.getElementById('status-filter')?.value;
if (statusFilter && statusFilter !== 'all') {
requests = requests.filter(req => req.status === statusFilter);
}
// Such-Filter
const searchTerm = document.getElementById('search-requests')?.value.toLowerCase();
if (searchTerm) {
requests = requests.filter(req =>
req.name?.toLowerCase().includes(searchTerm) ||
req.email?.toLowerCase().includes(searchTerm) ||
req.file_name?.toLowerCase().includes(searchTerm) ||
req.reason?.toLowerCase().includes(searchTerm)
);
}
// Sortierung
const sortOrder = document.getElementById('sort-order')?.value;
requests.sort((a, b) => {
switch (sortOrder) {
case 'oldest':
return new Date(a.created_at) - new Date(b.created_at);
case 'priority':
return getPriorityValue(b) - getPriorityValue(a);
case 'newest':
default:
return new Date(b.created_at) - new Date(a.created_at);
}
});
filteredRequests = requests;
renderRequestsTable();
}
/**
* Prioritätswert für Sortierung berechnen
*/
function getPriorityValue(request) {
const now = new Date();
const created = new Date(request.created_at);
const hoursOld = (now - created) / (1000 * 60 * 60);
let priority = 0;
// Status-basierte Priorität
if (request.status === 'pending') priority += 10;
else if (request.status === 'approved') priority += 5;
// Alter-basierte Priorität
if (hoursOld > 24) priority += 5;
else if (hoursOld > 8) priority += 3;
else if (hoursOld > 2) priority += 1;
return priority;
}
/**
* Requests-Tabelle rendern
*/
function renderRequestsTable() {
const tableBody = document.getElementById('requests-table-body');
const emptyState = document.getElementById('empty-state');
if (!tableBody) return;
if (filteredRequests.length === 0) {
tableBody.innerHTML = '';
showEmptyState();
return;
}
hideEmptyState();
const requestsHtml = filteredRequests.map(request => createRequestRow(request)).join('');
tableBody.innerHTML = requestsHtml;
// Event Listeners für neue Rows hinzufügen
addRowEventListeners();
}
/**
* Request Row HTML erstellen
*/
function createRequestRow(request) {
const statusColor = getStatusColor(request.status);
const priorityLevel = getPriorityLevel(request);
const timeAgo = getTimeAgo(request.created_at);
return `
<tr class="hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors duration-200" data-request-id="${request.id}">
<td class="px-6 py-4">
<input type="checkbox" class="request-checkbox rounded border-slate-300 text-blue-600 focus:ring-blue-500"
value="${request.id}">
</td>
<td class="px-6 py-4">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10">
<div class="h-10 w-10 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-semibold">
${request.name ? request.name[0].toUpperCase() : 'G'}
</div>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-slate-900 dark:text-white">${escapeHtml(request.name || 'Unbekannt')}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">${escapeHtml(request.email || 'Keine E-Mail')}</div>
</div>
</div>
</td>
<td class="px-6 py-4">
<div class="text-sm text-slate-900 dark:text-white font-medium">${escapeHtml(request.file_name || 'Keine Datei')}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">
${request.duration_minutes ? `${request.duration_minutes} Min.` : 'Unbekannte Dauer'}
${request.copies ? `${request.copies} Kopien` : ''}
</div>
${request.reason ? `<div class="text-xs text-slate-400 dark:text-slate-500 mt-1 truncate max-w-xs">${escapeHtml(request.reason)}</div>` : ''}
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full ${statusColor}">
<span class="w-2 h-2 mr-1 rounded-full ${getStatusDot(request.status)}"></span>
${getStatusText(request.status)}
</span>
</td>
<td class="px-6 py-4">
<div class="text-sm text-slate-900 dark:text-white">${timeAgo}</div>
<div class="text-xs text-slate-500 dark:text-slate-400">${formatDateTime(request.created_at)}</div>
</td>
<td class="px-6 py-4">
<div class="flex items-center">
${getPriorityBadge(priorityLevel)}
${request.is_urgent ? '<span class="ml-2 text-red-500 text-xs">🔥 Dringend</span>' : ''}
</div>
</td>
<td class="px-6 py-4">
<div class="flex items-center space-x-2">
<button onclick="showRequestDetail(${request.id})"
class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300 transition-colors"
title="Details anzeigen">
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
</svg>
</button>
${request.status === 'pending' ? `
<button onclick="approveRequest(${request.id})"
class="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300 transition-colors"
title="Genehmigen">
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</button>
<button onclick="rejectRequest(${request.id})"
class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors"
title="Ablehnen">
<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="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</button>
` : ''}
<button onclick="deleteRequest(${request.id})"
class="text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 transition-colors"
title="Löschen">
<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="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</button>
</div>
</td>
</tr>
`;
}
/**
* Status-Helper-Funktionen
*/
function getStatusColor(status) {
const colors = {
'pending': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300',
'approved': 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300',
'rejected': 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300',
'expired': 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300'
};
return colors[status] || 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300';
}
function getStatusDot(status) {
const dots = {
'pending': 'bg-yellow-400 dark:bg-yellow-300',
'approved': 'bg-green-400 dark:bg-green-300',
'rejected': 'bg-red-400 dark:bg-red-300',
'expired': 'bg-gray-400 dark:bg-gray-300'
};
return dots[status] || 'bg-gray-400 dark:bg-gray-300';
}
function getStatusText(status) {
const texts = {
'pending': 'Wartend',
'approved': 'Genehmigt',
'rejected': 'Abgelehnt',
'expired': 'Abgelaufen'
};
return texts[status] || status;
}
function getPriorityLevel(request) {
const priority = getPriorityValue(request);
if (priority >= 15) return 'high';
if (priority >= 8) return 'medium';
return 'low';
}
function getPriorityBadge(level) {
const badges = {
'high': '<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300">🔴 Hoch</span>',
'medium': '<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300">🟡 Mittel</span>',
'low': '<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300">🟢 Niedrig</span>'
};
return badges[level] || badges['low'];
}
/**
* CRUD-Operationen
*/
async function approveRequest(requestId) {
if (!confirm('Möchten Sie diesen Gastauftrag wirklich genehmigen?')) return;
try {
showLoading(true);
const url = `${API_BASE_URL}/api/guest-requests/${requestId}/approve`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({}) // Leeres JSON-Objekt senden
});
const data = await response.json();
if (data.success) {
showNotification('✅ Gastauftrag erfolgreich genehmigt', 'success');
loadGuestRequests();
} else {
throw new Error(data.message || 'Fehler beim Genehmigen');
}
} catch (error) {
console.error('Fehler beim Genehmigen:', error);
showNotification('❌ Fehler beim Genehmigen: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
async function rejectRequest(requestId) {
const reason = prompt('Grund für die Ablehnung:');
if (!reason) return;
try {
showLoading(true);
const url = `${API_BASE_URL}/api/guest-requests/${requestId}/reject`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({ reason })
});
const data = await response.json();
if (data.success) {
showNotification('✅ Gastauftrag erfolgreich abgelehnt', 'success');
loadGuestRequests();
} else {
throw new Error(data.message || 'Fehler beim Ablehnen');
}
} catch (error) {
console.error('Fehler beim Ablehnen:', error);
showNotification('❌ Fehler beim Ablehnen: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
async function deleteRequest(requestId) {
if (!confirm('Möchten Sie diesen Gastauftrag wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.')) return;
try {
showLoading(true);
const url = `${API_BASE_URL}/api/guest-requests/${requestId}`;
const response = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
const data = await response.json();
if (data.success) {
showNotification('✅ Gastauftrag erfolgreich gelöscht', 'success');
loadGuestRequests();
} else {
throw new Error(data.message || 'Fehler beim Löschen');
}
} catch (error) {
console.error('Fehler beim Löschen:', error);
showNotification('❌ Fehler beim Löschen: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
/**
* Detail-Modal Funktionen
*/
function showRequestDetail(requestId) {
const request = currentRequests.find(req => req.id === requestId);
if (!request) return;
const modal = document.getElementById('detail-modal');
const content = document.getElementById('modal-content');
content.innerHTML = `
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
<div class="flex justify-between items-center">
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Gastauftrag Details</h3>
<button onclick="closeDetailModal()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<svg class="w-6 h-6" 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>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-4">
<h4 class="text-lg font-semibold text-gray-900 dark:text-white">Antragsteller</h4>
<div class="bg-slate-50 dark:bg-slate-700 rounded-lg p-4">
<p><strong>Name:</strong> ${escapeHtml(request.name || 'Unbekannt')}</p>
<p><strong>E-Mail:</strong> ${escapeHtml(request.email || 'Keine E-Mail')}</p>
<p><strong>Erstellt am:</strong> ${formatDateTime(request.created_at)}</p>
</div>
</div>
<div class="space-y-4">
<h4 class="text-lg font-semibold text-gray-900 dark:text-white">Auftrag Details</h4>
<div class="bg-slate-50 dark:bg-slate-700 rounded-lg p-4">
<p><strong>Datei:</strong> ${escapeHtml(request.file_name || 'Keine Datei')}</p>
<p><strong>Dauer:</strong> ${request.duration_minutes || 'Unbekannt'} Minuten</p>
<p><strong>Kopien:</strong> ${request.copies || 1}</p>
<p><strong>Status:</strong> ${getStatusText(request.status)}</p>
</div>
</div>
</div>
${request.reason ? `
<div class="mt-6">
<h4 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Begründung</h4>
<div class="bg-slate-50 dark:bg-slate-700 rounded-lg p-4">
<p class="text-gray-700 dark:text-gray-300">${escapeHtml(request.reason)}</p>
</div>
</div>
` : ''}
<div class="mt-8 flex justify-end space-x-3">
${request.status === 'pending' ? `
<button onclick="approveRequest(${request.id}); closeDetailModal();"
class="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors">
Genehmigen
</button>
<button onclick="rejectRequest(${request.id}); closeDetailModal();"
class="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors">
Ablehnen
</button>
` : ''}
<button onclick="closeDetailModal()"
class="px-4 py-2 bg-gray-300 dark:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-400 dark:hover:bg-gray-500 transition-colors">
Schließen
</button>
</div>
</div>
`;
modal.classList.remove('hidden');
}
function closeDetailModal() {
const modal = document.getElementById('detail-modal');
modal.classList.add('hidden');
}
/**
* Bulk Actions
*/
function showBulkActionsModal() {
const selectedIds = getSelectedRequestIds();
if (selectedIds.length === 0) {
showNotification('⚠️ Bitte wählen Sie mindestens einen Gastauftrag aus', 'warning');
return;
}
const modal = document.getElementById('bulk-modal');
modal.classList.remove('hidden');
}
function closeBulkModal() {
const modal = document.getElementById('bulk-modal');
modal.classList.add('hidden');
}
async function performBulkAction(action) {
const selectedIds = getSelectedRequestIds();
if (selectedIds.length === 0) return;
const confirmMessages = {
'approve': `Möchten Sie ${selectedIds.length} Gastaufträge genehmigen?`,
'reject': `Möchten Sie ${selectedIds.length} Gastaufträge ablehnen?`,
'delete': `Möchten Sie ${selectedIds.length} Gastaufträge löschen?`
};
if (!confirm(confirmMessages[action])) return;
try {
showLoading(true);
closeBulkModal();
const promises = selectedIds.map(async (id) => {
const url = `${API_BASE_URL}/api/guest-requests/${id}/${action}`;
const method = action === 'delete' ? 'DELETE' : 'POST';
return fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
});
const results = await Promise.allSettled(promises);
const successCount = results.filter(r => r.status === 'fulfilled' && r.value.ok).length;
showNotification(`${successCount} von ${selectedIds.length} Aktionen erfolgreich`, 'success');
loadGuestRequests();
// Alle Checkboxen zurücksetzen
document.getElementById('select-all').checked = false;
document.querySelectorAll('.request-checkbox').forEach(cb => cb.checked = false);
} catch (error) {
console.error('Fehler bei Bulk-Aktion:', error);
showNotification('❌ Fehler bei der Bulk-Aktion: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
function getSelectedRequestIds() {
const checkboxes = document.querySelectorAll('.request-checkbox:checked');
return Array.from(checkboxes).map(cb => parseInt(cb.value));
}
/**
* Event Handlers
*/
function handleSearch() {
applyFiltersAndSort();
}
function handleFilterChange() {
applyFiltersAndSort();
}
function handleSortChange() {
applyFiltersAndSort();
}
function handleSelectAll(event) {
const checkboxes = document.querySelectorAll('.request-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = event.target.checked;
});
}
function handleExport() {
const selectedIds = getSelectedRequestIds();
const exportData = selectedIds.length > 0 ?
filteredRequests.filter(req => selectedIds.includes(req.id)) :
filteredRequests;
if (exportData.length === 0) {
showNotification('⚠️ Keine Daten zum Exportieren verfügbar', 'warning');
return;
}
exportToCSV(exportData);
}
/**
* Export-Funktionen
*/
function exportToCSV(data) {
const headers = ['ID', 'Name', 'E-Mail', 'Datei', 'Status', 'Erstellt', 'Dauer (Min)', 'Kopien', 'Begründung'];
const rows = data.map(req => [
req.id,
req.name || '',
req.email || '',
req.file_name || '',
getStatusText(req.status),
formatDateTime(req.created_at),
req.duration_minutes || '',
req.copies || '',
req.reason || ''
]);
const csvContent = [headers, ...rows]
.map(row => row.map(field => `"${String(field).replace(/"/g, '""')}"`).join(','))
.join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
@ -220,4 +872,4 @@ window.closeDetailModal = closeDetailModal;
window.closeBulkModal = closeBulkModal;
window.performBulkAction = performBulkAction;
console.log('📋 Admin Guest Requests JavaScript vollständig geladen'
console.log('📋 Admin Guest Requests JavaScript vollständig geladen');

View File

@ -0,0 +1,497 @@
/**
* Live-Drucker-Monitor für MYP Platform
* Verwaltet Live-Status-Updates mit Session-Caching und automatischer Aktualisierung
*/
class PrinterMonitor {
constructor() {
this.refreshInterval = 30000; // 30 Sekunden Standard-Intervall
this.fastRefreshInterval = 5000; // 5 Sekunden für schnelle Updates
this.currentInterval = null;
this.printers = new Map();
this.callbacks = new Set();
this.isActive = false;
this.useCache = true;
this.lastUpdate = null;
this.errorCount = 0;
this.maxErrors = 3;
// Status-Kategorien für bessere Übersicht
this.statusCategories = {
'online': { label: 'Online', color: 'success', icon: '🟢' },
'offline': { label: 'Offline', color: 'danger', icon: '🔴' },
'standby': { label: 'Standby', color: 'warning', icon: '🟡' },
'unreachable': { label: 'Nicht erreichbar', color: 'secondary', icon: '⚫' },
'unconfigured': { label: 'Nicht konfiguriert', color: 'info', icon: '🔵' }
};
console.log('🖨️ PrinterMonitor initialisiert');
}
/**
* Startet das Live-Monitoring
*/
start() {
if (this.isActive) {
console.log('⚠️ PrinterMonitor läuft bereits');
return;
}
this.isActive = true;
this.errorCount = 0;
console.log('🚀 Starte PrinterMonitor');
// Sofortige erste Aktualisierung
this.updatePrinterStatus();
// Reguläres Intervall starten
this.startInterval();
// Event-Listener für Sichtbarkeitsänderungen
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
}
/**
* Stoppt das Live-Monitoring
*/
stop() {
if (!this.isActive) {
return;
}
this.isActive = false;
if (this.currentInterval) {
clearInterval(this.currentInterval);
this.currentInterval = null;
}
document.removeEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
console.log('⏹️ PrinterMonitor gestoppt');
}
/**
* Startet das Update-Intervall
*/
startInterval() {
if (this.currentInterval) {
clearInterval(this.currentInterval);
}
this.currentInterval = setInterval(() => {
this.updatePrinterStatus();
}, this.refreshInterval);
}
/**
* Behandelt Sichtbarkeitsänderungen der Seite
*/
handleVisibilityChange() {
if (document.hidden) {
// Seite nicht sichtbar - langsamere Updates
this.refreshInterval = 60000; // 1 Minute
} else {
// Seite sichtbar - normale Updates
this.refreshInterval = 30000; // 30 Sekunden
// Sofortige Aktualisierung wenn Seite wieder sichtbar
this.updatePrinterStatus();
}
if (this.isActive) {
this.startInterval();
}
}
/**
* Holt aktuelle Drucker-Status-Daten
*/
async updatePrinterStatus() {
if (!this.isActive) {
return;
}
try {
const response = await fetch(`/api/printers/status`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.success) {
this.processPrinterData(data);
this.errorCount = 0; // Reset error count on success
} else {
throw new Error(data.error || 'Unbekannter Fehler');
}
} catch (error) {
this.errorCount++;
console.error('❌ Fehler beim Abrufen des Drucker-Status:', error);
// Bei wiederholten Fehlern weniger frequent versuchen
if (this.errorCount >= this.maxErrors) {
this.refreshInterval = Math.min(this.refreshInterval * 2, 300000); // Max 5 Minuten
this.startInterval();
this.notifyCallbacks({
type: 'error',
message: `Drucker-Status nicht verfügbar (${this.errorCount} Fehler)`,
timestamp: new Date().toISOString()
});
}
}
}
/**
* Verarbeitet empfangene Drucker-Daten
*/
processPrinterData(data) {
const previousPrinters = new Map(this.printers);
// Drucker-Daten aktualisieren
this.printers.clear();
// Flexible Datenextraktion für verschiedene API-Response-Strukturen
let printersData = null;
if (data && data.printers && Array.isArray(data.printers)) {
// Neue Struktur: data.printers als Array
printersData = data.printers;
} else if (data && data.printers && typeof data.printers === 'object') {
// Alte Struktur: data.printers als Objekt
printersData = Object.values(data.printers);
} else if (data && data.status && typeof data.status === 'object') {
// Alternative Struktur: data.status
printersData = Object.values(data.status);
} else if (data && typeof data === 'object' && !data.success && !data.error) {
// Direkte Drucker-Daten ohne Wrapper
printersData = Object.values(data);
}
if (printersData && Array.isArray(printersData)) {
// Drucker-Daten verarbeiten (jetzt als Array)
printersData.forEach(printer => {
// Sichere Validierung der Drucker-Objekte
if (printer && typeof printer === 'object' && printer.id) {
this.printers.set(printer.id, {
...printer,
statusInfo: this.statusCategories[printer.status] || this.statusCategories['offline']
});
} else {
console.warn('⚠️ Ungültiges Drucker-Objekt übersprungen:', printer);
}
});
console.log(`✅ ${this.printers.size} Drucker erfolgreich verarbeitet`);
} else {
console.warn('⚠️ Keine gültigen Drucker-Daten in Response-Struktur gefunden');
console.debug('Response-Struktur:', data);
// Benachrichtige Callbacks über fehlende Daten (aber nicht als Fehler)
this.notifyCallbacks({
type: 'warning',
message: 'Keine Drucker-Daten verfügbar',
data: data
});
return;
}
this.lastUpdate = new Date(data.timestamp || Date.now());
// Änderungen erkennen und benachrichtigen
const changes = this.detectChanges(previousPrinters, this.printers);
// Callbacks benachrichtigen
this.notifyCallbacks({
type: 'update',
printers: this.printers,
summary: data.summary,
changes: changes,
timestamp: this.lastUpdate,
cacheUsed: data.cache_used
});
console.log(`🔄 Drucker-Status aktualisiert: ${this.printers.size} Drucker`);
}
/**
* Erkennt Änderungen zwischen zwei Drucker-Status-Maps
*/
detectChanges(oldPrinters, newPrinters) {
const changes = [];
newPrinters.forEach((newPrinter, id) => {
const oldPrinter = oldPrinters.get(id);
if (!oldPrinter) {
changes.push({
type: 'added',
printer: newPrinter
});
} else if (oldPrinter.status !== newPrinter.status) {
changes.push({
type: 'status_change',
printer: newPrinter,
oldStatus: oldPrinter.status,
newStatus: newPrinter.status
});
}
});
oldPrinters.forEach((oldPrinter, id) => {
if (!newPrinters.has(id)) {
changes.push({
type: 'removed',
printer: oldPrinter
});
}
});
return changes;
}
/**
* Benachrichtigt alle registrierten Callbacks
*/
notifyCallbacks(data) {
this.callbacks.forEach(callback => {
try {
callback(data);
} catch (error) {
console.error('❌ Fehler in PrinterMonitor Callback:', error);
}
});
}
/**
* Registriert einen Callback für Status-Updates
*/
onUpdate(callback) {
if (typeof callback === 'function') {
this.callbacks.add(callback);
}
}
/**
* Entfernt einen Callback
*/
offUpdate(callback) {
this.callbacks.delete(callback);
}
/**
* Erzwingt eine sofortige Aktualisierung ohne Cache
*/
async forceUpdate() {
const oldUseCache = this.useCache;
this.useCache = false;
try {
await this.updatePrinterStatus();
} finally {
this.useCache = oldUseCache;
}
}
/**
* Löscht den Cache und erzwingt eine Aktualisierung
*/
async clearCache() {
try {
const response = await fetch('/api/printers/monitor/clear-cache', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
if (response.ok) {
console.log('🧹 Drucker-Cache geleert');
await this.forceUpdate();
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
console.error('❌ Fehler beim Leeren des Caches:', error);
}
}
/**
* Holt eine schnelle Status-Zusammenfassung
*/
async getSummary() {
try {
const response = await fetch('/api/printers/monitor/summary', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
if (response.ok) {
const data = await response.json();
return data.success ? data.summary : null;
}
} catch (error) {
console.error('❌ Fehler beim Abrufen der Zusammenfassung:', error);
}
return null;
}
/**
* Gibt den aktuellen Status eines Druckers zurück
*/
getPrinter(id) {
return this.printers.get(id);
}
/**
* Gibt alle Drucker zurück
*/
getAllPrinters() {
return Array.from(this.printers.values());
}
/**
* Gibt Drucker nach Status gefiltert zurück
*/
getPrintersByStatus(status) {
return this.getAllPrinters().filter(printer => printer.status === status);
}
/**
* Gibt eine Status-Zusammenfassung zurück
*/
getStatusSummary() {
const summary = {
total: this.printers.size,
online: 0,
offline: 0,
printing: 0, // Neuer Status: Drucker druckt gerade
standby: 0,
unreachable: 0,
unconfigured: 0,
error: 0 // Status für unbekannte Fehler
};
this.printers.forEach(printer => {
const status = printer.status;
if (summary.hasOwnProperty(status)) {
summary[status]++;
} else {
// Fallback für unbekannte Status
summary.offline++;
}
});
return summary;
}
/**
* Aktiviert schnelle Updates (für kritische Operationen)
*/
enableFastUpdates() {
this.refreshInterval = this.fastRefreshInterval;
if (this.isActive) {
this.startInterval();
}
console.log('⚡ Schnelle Updates aktiviert');
}
/**
* Deaktiviert schnelle Updates
*/
disableFastUpdates() {
this.refreshInterval = 30000; // Zurück zu normal
if (this.isActive) {
this.startInterval();
}
console.log('🐌 Normale Updates aktiviert');
}
/**
* Initialisiert alle Steckdosen (nur für Admins)
*/
async initializeAllOutlets() {
try {
const response = await fetch('/api/printers/monitor/initialize-outlets', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
if (response.ok) {
const data = await response.json();
if (data.success) {
console.log('🔌 Steckdosen-Initialisierung erfolgreich:', data.statistics);
// Benachrichtige über Initialisierung
this.notifyCallbacks({
type: 'initialization',
results: data.results,
statistics: data.statistics,
message: data.message
});
// Erzwinge Update nach Initialisierung
await this.forceUpdate();
return data;
} else {
throw new Error(data.error || 'Initialisierung fehlgeschlagen');
}
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
console.error('❌ Fehler bei Steckdosen-Initialisierung:', error);
throw error;
}
}
}
// Globale Instanz
window.printerMonitor = new PrinterMonitor();
// Auto-Start wenn DOM bereit ist
document.addEventListener('DOMContentLoaded', () => {
// Nur starten wenn wir auf einer relevanten Seite sind
const relevantPages = ['/printers', '/dashboard', '/admin', '/admin-dashboard'];
const currentPath = window.location.pathname;
if (relevantPages.some(page => currentPath.includes(page))) {
console.log('🖨️ Auto-Start PrinterMonitor für Seite:', currentPath);
window.printerMonitor.start();
}
});
// Automatisches Cleanup bei Seitenverlassen
window.addEventListener('beforeunload', () => {
if (window.printerMonitor) {
window.printerMonitor.stop();
}
});
// Export für Module
if (typeof module !== 'undefined' && module.exports) {
module.exports = PrinterMonitor;
}