🎉 Fix printer monitor complete issue & logs updates 📝
This commit is contained in:
@ -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.
@ -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):
|
||||
|
@ -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
|
||||
|
1
backend/docs/printer-monitor-complete-fix.md
Normal file
1
backend/docs/printer-monitor-complete-fix.md
Normal file
@ -0,0 +1 @@
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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 - ==================================================
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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: {
|
||||
|
764
backend/static/js/admin-guest-requests.min.js
vendored
764
backend/static/js/admin-guest-requests.min.js
vendored
@ -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');
|
497
backend/static/js/printer_monitor.js.backup
Normal file
497
backend/static/js/printer_monitor.js.backup
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user