From 63c8b4f378b2fb7ceefcab316dbf61d6a5922b3e Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Sun, 1 Jun 2025 16:08:07 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20Backend:=20Aktualisierung=20der?= =?UTF-8?q?=20API-Routen=20und=20Verbesserung=20der=20Fehlerprotokollierun?= =?UTF-8?q?g=20f=C3=BCr=20Job-Erstellung.=20URL-Pr=C3=A4fix=20f=C3=BCr=20J?= =?UTF-8?q?obs-Blueprint=20ge=C3=A4ndert,=20um=20Konflikte=20zu=20vermeide?= =?UTF-8?q?n.=20Erweiterte=20Fehlerbehandlung=20und=20Protokollierung=20f?= =?UTF-8?q?=C3=BCr=20kritische=20Systemfehler=20hinzugef=C3=BCgt.=20?= =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/__pycache__/app.cpython-313.pyc | Bin 327071 -> 271477 bytes backend/app.py | 766 ++++++++++++++---- .../__pycache__/jobs.cpython-313.pyc | Bin 24737 -> 28644 bytes backend/blueprints/jobs.py | 152 ++-- backend/database/myp.db | Bin 118784 -> 118784 bytes backend/docs/FEHLER_BEHOBEN_API_JOBS_ROUTE.md | 199 +++++ backend/logs/analytics/analytics.log | 4 + backend/logs/app/app.log | 50 ++ backend/logs/auth/auth.log | 2 + backend/logs/backup/backup.log | 4 + backend/logs/dashboard/dashboard.log | 16 + backend/logs/database/database.log | 4 + .../email_notification/email_notification.log | 4 + backend/logs/jobs/jobs.log | 20 + backend/logs/maintenance/maintenance.log | 8 + .../logs/multi_location/multi_location.log | 8 + backend/logs/permissions/permissions.log | 4 + .../logs/printer_monitor/printer_monitor.log | 56 ++ backend/logs/printers/printers.log | 38 + backend/logs/scheduler/scheduler.log | 48 ++ backend/logs/security/security.log | 4 + .../shutdown_manager/shutdown_manager.log | 12 + backend/logs/startup/startup.log | 36 + backend/logs/user/user.log | 1 + backend/logs/windows_fixes/windows_fixes.log | 16 + 25 files changed, 1238 insertions(+), 214 deletions(-) create mode 100644 backend/docs/FEHLER_BEHOBEN_API_JOBS_ROUTE.md diff --git a/backend/__pycache__/app.cpython-313.pyc b/backend/__pycache__/app.cpython-313.pyc index 241e6c2f44fb64efa4ac5614b2ba55d35d50702d..443e71b59b969957da461979ccf734077022123e 100644 GIT binary patch delta 51183 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

H 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 Q=_Us9(|LMOuS13Z288%aN&o-= diff --git a/backend/app.py b/backend/app.py index 5e8011fc..eaecbce5 100644 --- a/backend/app.py +++ b/backend/app.py @@ -3,7 +3,7 @@ import sys import logging import atexit from datetime import datetime, timedelta -from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, send_file, abort, session, make_response, Response +from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, send_file, abort, session, make_response, Response, current_app from flask_login import LoginManager, login_user, logout_user, login_required, current_user from flask_wtf import CSRFProtect from flask_wtf.csrf import CSRFError @@ -18,6 +18,7 @@ import time import subprocess import json import signal +import shutil from contextlib import contextmanager # Windows-spezifische Fixes früh importieren (sichere Version) @@ -1998,36 +1999,303 @@ def dragdrop_demo(): # ===== ERROR MONITORING SYSTEM ===== + @app.route("/api/admin/system-health", methods=['GET']) @login_required @admin_required def api_admin_system_health(): - """API-Endpunkt für System-Gesundheitscheck mit Dashboard-Integration.""" + """API-Endpunkt für System-Gesundheitscheck mit erweiterten Fehlermeldungen.""" try: - # Basis-System-Gesundheitscheck durchführen critical_errors = [] warnings = [] - # Dashboard-Event für System-Check senden + # 1. Datenbankverbindung prüfen + try: + db_session = get_db_session() + db_session.execute(text("SELECT 1")).fetchone() + db_session.close() + except Exception as e: + critical_errors.append({ + "type": "critical", + "title": "Datenbankverbindung fehlgeschlagen", + "description": f"Keine Verbindung zur Datenbank möglich: {str(e)[:100]}", + "solution": "Datenbankdienst neustarten oder Konfiguration prüfen", + "timestamp": datetime.now().isoformat() + }) + + # 2. Verfügbaren Speicherplatz prüfen + try: + import shutil + total, used, free = shutil.disk_usage("/") + free_percentage = (free / total) * 100 + + if free_percentage < 5: + critical_errors.append({ + "type": "critical", + "title": "Kritischer Speicherplatz", + "description": f"Nur noch {free_percentage:.1f}% Speicherplatz verfügbar", + "solution": "Temporäre Dateien löschen oder Speicher erweitern", + "timestamp": datetime.now().isoformat() + }) + elif free_percentage < 15: + warnings.append({ + "type": "warning", + "title": "Wenig Speicherplatz", + "description": f"Nur noch {free_percentage:.1f}% Speicherplatz verfügbar", + "solution": "Aufräumen empfohlen", + "timestamp": datetime.now().isoformat() + }) + except Exception as e: + warnings.append({ + "type": "warning", + "title": "Speicherplatz-Prüfung fehlgeschlagen", + "description": f"Konnte Speicherplatz nicht prüfen: {str(e)[:100]}", + "solution": "Manuell prüfen", + "timestamp": datetime.now().isoformat() + }) + + # 3. Upload-Ordner-Struktur prüfen + upload_paths = [ + "uploads/jobs", "uploads/avatars", "uploads/assets", + "uploads/backups", "uploads/logs", "uploads/temp" + ] + + for path in upload_paths: + full_path = os.path.join(current_app.root_path, path) + if not os.path.exists(full_path): + warnings.append({ + "type": "warning", + "title": f"Upload-Ordner fehlt: {path}", + "description": f"Der Upload-Ordner {path} existiert nicht", + "solution": "Ordner automatisch erstellen lassen", + "timestamp": datetime.now().isoformat() + }) + + # 4. Log-Dateien-Größe prüfen + try: + logs_dir = os.path.join(current_app.root_path, "logs") + if os.path.exists(logs_dir): + total_log_size = sum( + os.path.getsize(os.path.join(logs_dir, f)) + for f in os.listdir(logs_dir) + if os.path.isfile(os.path.join(logs_dir, f)) + ) + # Größe in MB + log_size_mb = total_log_size / (1024 * 1024) + + if log_size_mb > 500: # > 500 MB + warnings.append({ + "type": "warning", + "title": "Große Log-Dateien", + "description": f"Log-Dateien belegen {log_size_mb:.1f} MB Speicherplatz", + "solution": "Log-Rotation oder Archivierung empfohlen", + "timestamp": datetime.now().isoformat() + }) + except Exception as e: + app_logger.warning(f"Fehler beim Prüfen der Log-Dateien-Größe: {str(e)}") + + # 5. Aktive Drucker-Verbindungen prüfen + try: + db_session = get_db_session() + total_printers = db_session.query(Printer).count() + online_printers = db_session.query(Printer).filter(Printer.status == 'online').count() + db_session.close() + + if total_printers > 0: + offline_percentage = ((total_printers - online_printers) / total_printers) * 100 + + if offline_percentage > 50: + warnings.append({ + "type": "warning", + "title": "Viele Drucker offline", + "description": f"{offline_percentage:.0f}% der Drucker sind offline", + "solution": "Drucker-Verbindungen überprüfen", + "timestamp": datetime.now().isoformat() + }) + except Exception as e: + app_logger.warning(f"Fehler beim Prüfen der Drucker-Status: {str(e)}") + + # Dashboard-Event senden emit_system_alert( "System-Gesundheitscheck durchgeführt", - alert_type="info", - priority="normal" + alert_type="info" if not critical_errors else "warning", + priority="normal" if not critical_errors else "high" ) + health_status = "healthy" if not critical_errors else "unhealthy" + return jsonify({ "success": True, - "health_status": "healthy", + "health_status": health_status, "critical_errors": critical_errors, "warnings": warnings, - "timestamp": datetime.now().isoformat() + "timestamp": datetime.now().isoformat(), + "summary": { + "total_issues": len(critical_errors) + len(warnings), + "critical_count": len(critical_errors), + "warning_count": len(warnings) + } }) except Exception as e: app_logger.error(f"Fehler beim System-Gesundheitscheck: {str(e)}") return jsonify({ "success": False, - "error": str(e) + "error": str(e), + "health_status": "error" + }), 500 + +@app.route("/api/admin/fix-errors", methods=['POST']) +@login_required +@admin_required +def api_admin_fix_errors(): + """API-Endpunkt für automatische Fehlerbehebung.""" + try: + fixed_issues = [] + failed_fixes = [] + + # 1. Fehlende Upload-Ordner erstellen + upload_paths = [ + "uploads/jobs", "uploads/avatars", "uploads/assets", + "uploads/backups", "uploads/logs", "uploads/temp", + "uploads/guests" # Ergänzt um guests + ] + + for path in upload_paths: + full_path = os.path.join(current_app.root_path, path) + if not os.path.exists(full_path): + try: + os.makedirs(full_path, exist_ok=True) + fixed_issues.append(f"Upload-Ordner {path} erstellt") + app_logger.info(f"Upload-Ordner automatisch erstellt: {full_path}") + except Exception as e: + failed_fixes.append(f"Konnte Upload-Ordner {path} nicht erstellen: {str(e)}") + app_logger.error(f"Fehler beim Erstellen des Upload-Ordners {path}: {str(e)}") + + # 2. Temporäre Dateien aufräumen (älter als 24 Stunden) + try: + temp_path = os.path.join(current_app.root_path, "uploads/temp") + if os.path.exists(temp_path): + now = time.time() + cleaned_files = 0 + + for filename in os.listdir(temp_path): + file_path = os.path.join(temp_path, filename) + if os.path.isfile(file_path): + # Dateien älter als 24 Stunden löschen + if now - os.path.getmtime(file_path) > 24 * 3600: + try: + os.remove(file_path) + cleaned_files += 1 + except Exception as e: + app_logger.warning(f"Konnte temporäre Datei nicht löschen {filename}: {str(e)}") + + if cleaned_files > 0: + fixed_issues.append(f"{cleaned_files} alte temporäre Dateien gelöscht") + app_logger.info(f"Automatische Bereinigung: {cleaned_files} temporäre Dateien gelöscht") + + except Exception as e: + failed_fixes.append(f"Temporäre Dateien Bereinigung fehlgeschlagen: {str(e)}") + app_logger.error(f"Fehler bei der temporären Dateien Bereinigung: {str(e)}") + + # 3. Datenbankverbindung wiederherstellen + try: + db_session = get_db_session() + db_session.execute(text("SELECT 1")).fetchone() + db_session.close() + fixed_issues.append("Datenbankverbindung erfolgreich getestet") + except Exception as e: + failed_fixes.append(f"Datenbankverbindung konnte nicht wiederhergestellt werden: {str(e)}") + app_logger.error(f"Datenbankverbindung Wiederherstellung fehlgeschlagen: {str(e)}") + + # 4. Log-Rotation durchführen bei großen Log-Dateien + try: + logs_dir = os.path.join(current_app.root_path, "logs") + if os.path.exists(logs_dir): + rotated_logs = 0 + + for log_file in os.listdir(logs_dir): + log_path = os.path.join(logs_dir, log_file) + if os.path.isfile(log_path) and log_file.endswith('.log'): + # Log-Dateien größer als 10 MB rotieren + if os.path.getsize(log_path) > 10 * 1024 * 1024: + try: + # Backup erstellen + backup_name = f"{log_file}.{datetime.now().strftime('%Y%m%d_%H%M%S')}.bak" + backup_path = os.path.join(logs_dir, backup_name) + shutil.copy2(log_path, backup_path) + + # Log-Datei leeren (aber nicht löschen) + with open(log_path, 'w') as f: + f.write(f"# Log rotiert am {datetime.now().isoformat()}\n") + + rotated_logs += 1 + except Exception as e: + app_logger.warning(f"Konnte Log-Datei nicht rotieren {log_file}: {str(e)}") + + if rotated_logs > 0: + fixed_issues.append(f"{rotated_logs} große Log-Dateien rotiert") + app_logger.info(f"Automatische Log-Rotation: {rotated_logs} Dateien rotiert") + + except Exception as e: + failed_fixes.append(f"Log-Rotation fehlgeschlagen: {str(e)}") + app_logger.error(f"Fehler bei der Log-Rotation: {str(e)}") + + # 5. Offline-Drucker Reconnect versuchen + try: + db_session = get_db_session() + offline_printers = db_session.query(Printer).filter(Printer.status != 'online').all() + reconnected_printers = 0 + + for printer in offline_printers: + try: + # Status-Check durchführen + if printer.plug_ip: + status, is_reachable = check_printer_status(printer.plug_ip, timeout=3) + if is_reachable: + printer.status = 'online' + reconnected_printers += 1 + except Exception as e: + app_logger.debug(f"Drucker {printer.name} Reconnect fehlgeschlagen: {str(e)}") + + if reconnected_printers > 0: + db_session.commit() + fixed_issues.append(f"{reconnected_printers} Drucker wieder online") + app_logger.info(f"Automatischer Drucker-Reconnect: {reconnected_printers} Drucker") + + db_session.close() + + except Exception as e: + failed_fixes.append(f"Drucker-Reconnect fehlgeschlagen: {str(e)}") + app_logger.error(f"Fehler beim Drucker-Reconnect: {str(e)}") + + # Ergebnis zusammenfassen + total_fixed = len(fixed_issues) + total_failed = len(failed_fixes) + + success = total_fixed > 0 or total_failed == 0 + + app_logger.info(f"Automatische Fehlerbehebung abgeschlossen: {total_fixed} behoben, {total_failed} fehlgeschlagen") + + return jsonify({ + "success": success, + "message": f"Automatische Reparatur abgeschlossen: {total_fixed} Probleme behoben" + + (f", {total_failed} fehlgeschlagen" if total_failed > 0 else ""), + "fixed_issues": fixed_issues, + "failed_fixes": failed_fixes, + "summary": { + "total_fixed": total_fixed, + "total_failed": total_failed + }, + "timestamp": datetime.now().isoformat() + }) + + except Exception as e: + app_logger.error(f"Fehler bei der automatischen Fehlerbehebung: {str(e)}") + return jsonify({ + "success": False, + "error": str(e), + "message": "Automatische Fehlerbehebung fehlgeschlagen" }), 500 @app.route("/api/admin/system-health-dashboard", methods=['GET']) @@ -3507,6 +3775,7 @@ def cleanup_temp_files(): # ===== WEITERE API-ROUTEN ===== +# Legacy-Route für Kompatibilität - sollte durch Blueprint ersetzt werden @app.route("/api/jobs/current", methods=["GET"]) @login_required def get_current_job(): @@ -3523,12 +3792,40 @@ def get_current_job(): else: job_data = None - db_session.close() + db_session.close() return jsonify(job_data) except Exception as e: + jobs_logger.error(f"Fehler beim Abrufen des aktuellen Jobs: {str(e)}") db_session.close() return jsonify({"error": str(e)}), 500 +@app.route("/api/jobs/", methods=["GET"]) +@login_required +@job_owner_required +def get_job_detail(job_id): + """ + Gibt Details zu einem spezifischen Job zurück. + """ + db_session = get_db_session() + + try: + # Eagerly load the user and printer relationships + job = db_session.query(Job).options(joinedload(Job.user), joinedload(Job.printer)).filter(Job.id == job_id).first() + + if not job: + db_session.close() + return jsonify({"error": "Job nicht gefunden"}), 404 + + # Convert to dict before closing session + job_dict = job.to_dict() + db_session.close() + + return jsonify(job_dict) + except Exception as e: + jobs_logger.error(f"Fehler beim Abrufen des Jobs {job_id}: {str(e)}") + db_session.close() + return jsonify({"error": "Interner Serverfehler"}), 500 + @app.route("/api/jobs/", methods=["DELETE"]) @login_required @job_owner_required @@ -3615,32 +3912,6 @@ def get_jobs(): db_session.close() return jsonify({"error": "Interner Serverfehler"}), 500 -@app.route("/api/jobs/", methods=["GET"]) -@login_required -@job_owner_required -def get_job_detail(job_id): - """Gibt einen einzelnen Job zurück.""" - db_session = get_db_session() - - try: - from sqlalchemy.orm import joinedload - # Eagerly load the user and printer relationships - job = db_session.query(Job).options(joinedload(Job.user), joinedload(Job.printer)).filter(Job.id == job_id).first() - - if not job: - db_session.close() - return jsonify({"error": "Job nicht gefunden"}), 404 - - # Convert to dict before closing session - job_dict = job.to_dict() - db_session.close() - - return jsonify(job_dict) - except Exception as e: - jobs_logger.error(f"Fehler beim Abrufen des Jobs {job_id}: {str(e)}") - db_session.close() - return jsonify({"error": "Interner Serverfehler"}), 500 - @app.route('/api/jobs', methods=['POST']) @login_required @measure_execution_time(logger=jobs_logger, task_name="API-Job-Erstellung") @@ -4799,7 +5070,7 @@ def export_guest_requests(): 'success': False, 'message': f'Fehler beim Export: {str(e)}' }), 500 - + # ===== AUTO-OPTIMIERUNG-API-ENDPUNKTE ===== @@ -5033,7 +5304,7 @@ def admin_advanced_settings(): except Exception as e: app_logger.warning(f"Fehler beim Zählen der Log-Dateien: {str(e)}") - db_session.close() + db_session.close() return render_template( 'admin_advanced_settings.html', @@ -6936,150 +7207,322 @@ def export_admin_logs(): @admin_required def api_admin_database_status(): """ - API-Endpunkt für Datenbank-Status-Informationen + API-Endpunkt für erweiterten Datenbank-Gesundheitsstatus. - Liefert detaillierte Informationen über den Zustand der SQLite-Datenbank + Führt umfassende Datenbank-Diagnose durch und liefert detaillierte + Statusinformationen für den Admin-Bereich. + + Returns: + JSON: Detaillierter Datenbank-Gesundheitsstatus """ try: - from models import get_db_session, create_optimized_engine - from sqlalchemy import text - import os + app_logger.info(f"Datenbank-Gesundheitscheck gestartet von Admin-User {current_user.id}") + # Datenbankverbindung mit Timeout db_session = get_db_session() - engine = create_optimized_engine() + start_time = time.time() - # Basis-Datenbankpfad - db_path = os.path.join(os.path.dirname(__file__), 'database', 'printer_system.db') - - # Datenbank-Datei-Informationen - db_info = { - 'file_path': db_path, - 'file_exists': os.path.exists(db_path), - 'file_size_mb': 0, - 'last_modified': None - } - - if os.path.exists(db_path): - stat_info = os.stat(db_path) - db_info['file_size_mb'] = round(stat_info.st_size / (1024 * 1024), 2) - db_info['last_modified'] = datetime.fromtimestamp(stat_info.st_mtime).isoformat() - - # SQLite-spezifische Informationen - with engine.connect() as conn: - # Datenbankschema-Version und Pragma-Informationen - pragma_info = {} + # 1. Basis-Datenbankverbindung testen mit Timeout + connection_status = "OK" + connection_time_ms = 0 + try: + query_start = time.time() + result = db_session.execute(text("SELECT 1 as test_connection")).fetchone() + connection_time_ms = round((time.time() - query_start) * 1000, 2) - # Wichtige PRAGMA-Werte abrufen - pragma_queries = { - 'user_version': 'PRAGMA user_version', - 'schema_version': 'PRAGMA schema_version', - 'journal_mode': 'PRAGMA journal_mode', - 'synchronous': 'PRAGMA synchronous', - 'cache_size': 'PRAGMA cache_size', - 'page_size': 'PRAGMA page_size', - 'page_count': 'PRAGMA page_count', - 'freelist_count': 'PRAGMA freelist_count', - 'integrity_check': 'PRAGMA quick_check' + if connection_time_ms > 5000: # 5 Sekunden + connection_status = f"LANGSAM: {connection_time_ms}ms" + elif not result: + connection_status = "FEHLER: Keine Antwort" + + except Exception as e: + connection_status = f"FEHLER: {str(e)[:100]}" + app_logger.error(f"Datenbankverbindungsfehler: {str(e)}") + + # 2. Erweiterte Schema-Integrität prüfen + schema_status = {"status": "OK", "details": {}, "missing_tables": [], "table_counts": {}} + try: + required_tables = { + 'users': 'Benutzer-Verwaltung', + 'printers': 'Drucker-Verwaltung', + 'jobs': 'Druck-Aufträge', + 'guest_requests': 'Gast-Anfragen', + 'settings': 'System-Einstellungen' } - for key, query in pragma_queries.items(): + existing_tables = [] + table_counts = {} + + for table_name, description in required_tables.items(): try: - result = conn.execute(text(query)).fetchone() - pragma_info[key] = result[0] if result else None - except Exception as e: - pragma_info[key] = f"Error: {str(e)}" - - # Tabellen-Informationen - tables_result = conn.execute(text("SELECT name FROM sqlite_master WHERE type='table'")).fetchall() - tables = [row[0] for row in tables_result] - - # Tabellen-Statistiken - table_stats = {} - for table in tables: - try: - count_result = conn.execute(text(f"SELECT COUNT(*) FROM {table}")).fetchone() - table_stats[table] = count_result[0] if count_result else 0 - except Exception as e: - table_stats[table] = f"Error: {str(e)}" - - # Connection-Pool-Status - pool_status = {} - try: - # StaticPool hat andere Methoden als andere Pool-Typen - if hasattr(engine.pool, 'size'): - pool_status['pool_size'] = engine.pool.size() - else: - pool_status['pool_size'] = 'N/A (StaticPool)' - - if hasattr(engine.pool, 'checkedin'): - pool_status['checked_in'] = engine.pool.checkedin() - else: - pool_status['checked_in'] = 'N/A' - - if hasattr(engine.pool, 'checkedout'): - pool_status['checked_out'] = engine.pool.checkedout() - else: - pool_status['checked_out'] = 'N/A' - - if hasattr(engine.pool, 'overflow'): - pool_status['overflow'] = engine.pool.overflow() - else: - pool_status['overflow'] = 'N/A' - - if hasattr(engine.pool, 'invalid'): - pool_status['invalid'] = engine.pool.invalid() - else: - pool_status['invalid'] = 'N/A' + count_result = db_session.execute(text(f"SELECT COUNT(*) as count FROM {table_name}")).fetchone() + table_count = count_result[0] if count_result else 0 - # Zusätzliche StaticPool-spezifische Informationen - pool_status['pool_type'] = type(engine.pool).__name__ + existing_tables.append(table_name) + table_counts[table_name] = table_count + schema_status["details"][table_name] = { + "exists": True, + "count": table_count, + "description": description + } + + except Exception as table_error: + schema_status["missing_tables"].append(table_name) + schema_status["details"][table_name] = { + "exists": False, + "error": str(table_error)[:50], + "description": description + } + app_logger.warning(f"Tabelle {table_name} nicht verfügbar: {str(table_error)}") + + schema_status["table_counts"] = table_counts + + if len(schema_status["missing_tables"]) > 0: + schema_status["status"] = f"WARNUNG: {len(schema_status['missing_tables'])} fehlende Tabellen" + elif len(existing_tables) != len(required_tables): + schema_status["status"] = f"UNVOLLSTÄNDIG: {len(existing_tables)}/{len(required_tables)} Tabellen" + + except Exception as e: + schema_status["status"] = f"FEHLER: {str(e)[:100]}" + app_logger.error(f"Schema-Integritätsprüfung fehlgeschlagen: {str(e)}") + + # 3. Migrations-Status und Versionsinformationen + migration_info = {"status": "Unbekannt", "version": None, "details": {}} + try: + # Alembic-Version prüfen + try: + result = db_session.execute(text("SELECT version_num FROM alembic_version ORDER BY version_num DESC LIMIT 1")).fetchone() + if result: + migration_info["version"] = result[0] + migration_info["status"] = "Alembic-Migration aktiv" + migration_info["details"]["alembic"] = True + else: + migration_info["status"] = "Keine Alembic-Migration gefunden" + migration_info["details"]["alembic"] = False + except Exception: + # Fallback: Schema-Informationen sammeln + try: + # SQLite-spezifische Abfrage + tables_result = db_session.execute(text("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")).fetchall() + if tables_result: + table_list = [row[0] for row in tables_result] + migration_info["status"] = f"Schema mit {len(table_list)} Tabellen erkannt" + migration_info["details"]["detected_tables"] = table_list + migration_info["details"]["alembic"] = False + else: + migration_info["status"] = "Keine Tabellen erkannt" + except Exception: + # Weitere Datenbank-Engines + migration_info["status"] = "Schema-Erkennung nicht möglich" + migration_info["details"]["alembic"] = False + + except Exception as e: + migration_info["status"] = f"FEHLER: {str(e)[:100]}" + app_logger.error(f"Migrations-Statusprüfung fehlgeschlagen: {str(e)}") + + # 4. Performance-Benchmarks + performance_info = {"status": "OK", "benchmarks": {}, "overall_score": 100} + try: + benchmarks = {} + + # Einfache Select-Query + start = time.time() + db_session.execute(text("SELECT COUNT(*) FROM users")).fetchone() + benchmarks["simple_select"] = round((time.time() - start) * 1000, 2) + + # Join-Query (falls möglich) + try: + start = time.time() + db_session.execute(text("SELECT u.username, COUNT(j.id) FROM users u LEFT JOIN jobs j ON u.id = j.user_id GROUP BY u.id LIMIT 5")).fetchall() + benchmarks["join_query"] = round((time.time() - start) * 1000, 2) + except Exception: + benchmarks["join_query"] = None + + # Insert/Update-Performance simulieren + try: + start = time.time() + db_session.execute(text("SELECT 1 WHERE EXISTS (SELECT 1 FROM users LIMIT 1)")).fetchone() + benchmarks["exists_check"] = round((time.time() - start) * 1000, 2) + except Exception: + benchmarks["exists_check"] = None + + performance_info["benchmarks"] = benchmarks + + # Performance-Score berechnen + avg_time = sum(t for t in benchmarks.values() if t is not None) / len([t for t in benchmarks.values() if t is not None]) + + if avg_time < 10: + performance_info["status"] = "AUSGEZEICHNET" + performance_info["overall_score"] = 100 + elif avg_time < 50: + performance_info["status"] = "GUT" + performance_info["overall_score"] = 85 + elif avg_time < 200: + performance_info["status"] = "AKZEPTABEL" + performance_info["overall_score"] = 70 + elif avg_time < 1000: + performance_info["status"] = "LANGSAM" + performance_info["overall_score"] = 50 + else: + performance_info["status"] = "SEHR LANGSAM" + performance_info["overall_score"] = 25 - except Exception as pool_error: - app_logger.warning(f"Fehler beim Abrufen des Pool-Status: {str(pool_error)}") - pool_status = { - 'pool_size': 'Error', - 'checked_in': 'Error', - 'checked_out': 'Error', - 'overflow': 'Error', - 'invalid': 'Error', - 'pool_type': type(engine.pool).__name__, - 'error': str(pool_error) - } + except Exception as e: + performance_info["status"] = f"FEHLER: {str(e)[:100]}" + performance_info["overall_score"] = 0 + app_logger.error(f"Performance-Benchmark fehlgeschlagen: {str(e)}") + + # 5. Datenbankgröße und Speicher-Informationen + storage_info = {"size": "Unbekannt", "details": {}} + try: + # SQLite-Datei-Größe + db_uri = current_app.config.get('SQLALCHEMY_DATABASE_URI', '') + if 'sqlite:///' in db_uri: + db_file_path = db_uri.replace('sqlite:///', '') + if os.path.exists(db_file_path): + file_size = os.path.getsize(db_file_path) + storage_info["size"] = f"{file_size / (1024 * 1024):.2f} MB" + storage_info["details"]["file_path"] = db_file_path + storage_info["details"]["last_modified"] = datetime.fromtimestamp(os.path.getmtime(db_file_path)).isoformat() + + # Speicherplatz-Warnung + try: + import shutil + total, used, free = shutil.disk_usage(os.path.dirname(db_file_path)) + free_gb = free / (1024**3) + storage_info["details"]["disk_free_gb"] = round(free_gb, 2) + + if free_gb < 1: + storage_info["warning"] = "Kritisch wenig Speicherplatz" + elif free_gb < 5: + storage_info["warning"] = "Wenig Speicherplatz verfügbar" + except Exception: + pass + else: + # Für andere Datenbanken: Versuche Größe über Metadaten zu ermitteln + storage_info["size"] = "Externe Datenbank" + storage_info["details"]["database_type"] = "Nicht-SQLite" + + except Exception as e: + storage_info["size"] = f"FEHLER: {str(e)[:50]}" + app_logger.warning(f"Speicher-Informationen nicht verfügbar: {str(e)}") + + # 6. Aktuelle Verbindungs-Pool-Informationen + connection_pool_info = {"status": "Nicht verfügbar", "details": {}} + try: + # SQLAlchemy Pool-Status (falls verfügbar) + engine = db_session.get_bind() + if hasattr(engine, 'pool'): + pool = engine.pool + connection_pool_info["details"]["pool_size"] = getattr(pool, 'size', lambda: 'N/A')() + connection_pool_info["details"]["checked_in"] = getattr(pool, 'checkedin', lambda: 'N/A')() + connection_pool_info["details"]["checked_out"] = getattr(pool, 'checkedout', lambda: 'N/A')() + connection_pool_info["status"] = "Pool aktiv" + else: + connection_pool_info["status"] = "Kein Pool konfiguriert" + + except Exception as e: + connection_pool_info["status"] = f"Pool-Status nicht verfügbar: {str(e)[:50]}" db_session.close() - # Status bewerten - status = 'healthy' - issues = [] + # Gesamtstatus ermitteln + overall_status = "healthy" + health_score = 100 + critical_issues = [] + warnings = [] - if pragma_info.get('integrity_check') != 'ok': - status = 'warning' - issues.append('Datenbank-Integritätsprüfung fehlgeschlagen') + # Kritische Probleme + if "FEHLER" in connection_status: + overall_status = "critical" + health_score -= 50 + critical_issues.append("Datenbankverbindung fehlgeschlagen") + + if "FEHLER" in schema_status["status"]: + overall_status = "critical" + health_score -= 30 + critical_issues.append("Schema-Integrität kompromittiert") + + if performance_info["overall_score"] < 25: + overall_status = "critical" if overall_status != "critical" else overall_status + health_score -= 25 + critical_issues.append("Extreme Performance-Probleme") - if db_info['file_size_mb'] > 100: # Warnung bei >100MB - issues.append(f"Große Datenbankdatei: {db_info['file_size_mb']}MB") + # Warnungen + if "WARNUNG" in schema_status["status"] or len(schema_status["missing_tables"]) > 0: + if overall_status == "healthy": + overall_status = "warning" + health_score -= 15 + warnings.append(f"Schema-Probleme: {len(schema_status['missing_tables'])} fehlende Tabellen") + + if "LANGSAM" in connection_status: + if overall_status == "healthy": + overall_status = "warning" + health_score -= 10 + warnings.append("Langsame Datenbankverbindung") + + if "warning" in storage_info: + if overall_status == "healthy": + overall_status = "warning" + health_score -= 15 + warnings.append(storage_info["warning"]) - if pragma_info.get('freelist_count', 0) > 1000: - issues.append('Hohe Anzahl freier Seiten - VACUUM empfohlen') + health_score = max(0, health_score) # Nicht unter 0 + + total_time = round((time.time() - start_time) * 1000, 2) + + result = { + "success": True, + "status": overall_status, + "health_score": health_score, + "critical_issues": critical_issues, + "warnings": warnings, + "connection": { + "status": connection_status, + "response_time_ms": connection_time_ms + }, + "schema": schema_status, + "migration": migration_info, + "performance": performance_info, + "storage": storage_info, + "connection_pool": connection_pool_info, + "timestamp": datetime.now().isoformat(), + "check_duration_ms": total_time, + "summary": { + "database_responsive": "FEHLER" not in connection_status, + "schema_complete": len(schema_status["missing_tables"]) == 0, + "performance_acceptable": performance_info["overall_score"] >= 50, + "storage_adequate": "warning" not in storage_info, + "overall_healthy": overall_status == "healthy" + } + } + + app_logger.info(f"Datenbank-Gesundheitscheck abgeschlossen: Status={overall_status}, Score={health_score}, Dauer={total_time}ms") + + return jsonify(result) - return jsonify({ - 'success': True, - 'status': status, - 'issues': issues, - 'database_info': db_info, - 'pragma_info': pragma_info, - 'tables': tables, - 'table_stats': table_stats, - 'connection_pool': pool_status, - 'timestamp': datetime.now().isoformat() - }) - except Exception as e: - app_logger.error(f"Fehler beim Abrufen des Datenbank-Status: {str(e)}") + app_logger.error(f"Kritischer Fehler beim Datenbank-Gesundheitscheck: {str(e)}") return jsonify({ - 'success': False, - 'error': f'Fehler beim Abrufen des Datenbank-Status: {str(e)}', - 'status': 'error' + "success": False, + "error": f"Kritischer Systemfehler: {str(e)}", + "status": "critical", + "health_score": 0, + "critical_issues": ["System-Gesundheitscheck fehlgeschlagen"], + "warnings": [], + "connection": {"status": "FEHLER bei der Prüfung"}, + "schema": {"status": "FEHLER bei der Prüfung"}, + "migration": {"status": "FEHLER bei der Prüfung"}, + "performance": {"status": "FEHLER bei der Prüfung"}, + "storage": {"size": "FEHLER bei der Prüfung"}, + "timestamp": datetime.now().isoformat(), + "summary": { + "database_responsive": False, + "schema_complete": False, + "performance_acceptable": False, + "storage_adequate": False, + "overall_healthy": False + } }), 500 @app.route("/api/admin/system/status", methods=['GET']) @@ -7313,7 +7756,6 @@ def api_admin_system_status(): # ===== ÖFFENTLICHE STATISTIK-API ===== - @app.route("/api/statistics/public", methods=['GET']) def api_public_statistics(): """ diff --git a/backend/blueprints/__pycache__/jobs.cpython-313.pyc b/backend/blueprints/__pycache__/jobs.cpython-313.pyc index e9ee81491adcca9b93c2f59bc63048fddeda8e51..9ee5f4835d3fe3a4ddc8185ab429ac6d0b8e24ab 100644 GIT binary patch delta 8306 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 delta 4989 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', methods=['GET']) @login_required @@ -109,10 +118,13 @@ def get_job(job_id): db_session = get_db_session() try: + jobs_logger.info(f"🔍 Job-Detail-Abfrage für Job {job_id} von Benutzer {current_user.id}") + # Eagerly load the user and printer relationships job = db_session.query(Job).options(joinedload(Job.user), joinedload(Job.printer)).filter(Job.id == job_id).first() if not job: + jobs_logger.warning(f"⚠️ Job {job_id} nicht gefunden") db_session.close() return jsonify({"error": "Job nicht gefunden"}), 404 @@ -120,11 +132,15 @@ def get_job(job_id): job_dict = job.to_dict() db_session.close() + jobs_logger.info(f"✅ Job-Details erfolgreich abgerufen für Job {job_id}") return jsonify(job_dict) except Exception as e: - jobs_logger.error(f"Fehler beim Abrufen des Jobs {job_id}: {str(e)}") - db_session.close() - return jsonify({"error": "Interner Serverfehler"}), 500 + jobs_logger.error(f"❌ Fehler beim Abrufen des Jobs {job_id}: {str(e)}", exc_info=True) + try: + db_session.close() + except: + pass + return jsonify({"error": "Interner Serverfehler", "details": str(e)}), 500 @jobs_blueprint.route('', methods=['POST']) @login_required @@ -142,18 +158,31 @@ def create_job(): } """ try: + jobs_logger.info(f"🚀 Neue Job-Erstellung gestartet von Benutzer {current_user.id}") + data = request.json + if not data: + jobs_logger.error("❌ Keine JSON-Daten empfangen") + return jsonify({"error": "Keine JSON-Daten empfangen"}), 400 + + jobs_logger.debug(f"📋 Empfangene Daten: {data}") # Pflichtfelder prüfen required_fields = ["printer_id", "start_iso", "duration_minutes"] for field in required_fields: if field not in data: + jobs_logger.error(f"❌ Pflichtfeld '{field}' fehlt in den Daten") return jsonify({"error": f"Feld '{field}' fehlt"}), 400 # Daten extrahieren und validieren - printer_id = int(data["printer_id"]) - start_iso = data["start_iso"] - duration_minutes = int(data["duration_minutes"]) + try: + printer_id = int(data["printer_id"]) + start_iso = data["start_iso"] + duration_minutes = int(data["duration_minutes"]) + jobs_logger.debug(f"✅ Grunddaten validiert: printer_id={printer_id}, duration={duration_minutes}") + except (ValueError, TypeError) as e: + jobs_logger.error(f"❌ Fehler bei Datenvalidierung: {str(e)}") + return jsonify({"error": f"Ungültige Datenformate: {str(e)}"}), 400 # Optional: Jobtitel, Beschreibung und Dateipfad name = data.get("name", f"Druckjob vom {datetime.now().strftime('%d.%m.%Y %H:%M')}") @@ -163,11 +192,14 @@ def create_job(): # Start-Zeit parsen try: start_at = datetime.fromisoformat(start_iso.replace('Z', '+00:00')) - except ValueError: - return jsonify({"error": "Ungültiges Startdatum"}), 400 + jobs_logger.debug(f"✅ Startzeit geparst: {start_at}") + except ValueError as e: + jobs_logger.error(f"❌ Ungültiges Startdatum '{start_iso}': {str(e)}") + return jsonify({"error": f"Ungültiges Startdatum: {str(e)}"}), 400 # Dauer validieren if duration_minutes <= 0: + jobs_logger.error(f"❌ Ungültige Dauer: {duration_minutes} Minuten") return jsonify({"error": "Dauer muss größer als 0 sein"}), 400 # End-Zeit berechnen @@ -175,47 +207,63 @@ def create_job(): db_session = get_db_session() - # Prüfen, ob der Drucker existiert - printer = db_session.query(Printer).get(printer_id) - if not printer: + try: + # Prüfen, ob der Drucker existiert + printer = db_session.query(Printer).get(printer_id) + if not printer: + jobs_logger.error(f"❌ Drucker mit ID {printer_id} nicht gefunden") + db_session.close() + return jsonify({"error": "Drucker nicht gefunden"}), 404 + + jobs_logger.debug(f"✅ Drucker gefunden: {printer.name} (ID: {printer_id})") + + # Prüfen, ob der Drucker online ist + printer_status, printer_active = check_printer_status(printer.plug_ip if printer.plug_ip else "") + jobs_logger.debug(f"🖨️ Drucker-Status: {printer_status}, aktiv: {printer_active}") + + # Status basierend auf Drucker-Verfügbarkeit setzen + if printer_status == "online" and printer_active: + job_status = "scheduled" + else: + job_status = "waiting_for_printer" + + jobs_logger.info(f"📋 Job-Status festgelegt: {job_status}") + + # Neuen Job erstellen + new_job = Job( + name=name, + description=description, + printer_id=printer_id, + user_id=current_user.id, + owner_id=current_user.id, + start_at=start_at, + end_at=end_at, + status=job_status, + file_path=file_path, + duration_minutes=duration_minutes + ) + + db_session.add(new_job) + db_session.commit() + + # Job-Objekt für die Antwort serialisieren + job_dict = new_job.to_dict() db_session.close() - return jsonify({"error": "Drucker nicht gefunden"}), 404 - - # Prüfen, ob der Drucker online ist - printer_status, printer_active = check_printer_status(printer.plug_ip if printer.plug_ip else "") - - # Status basierend auf Drucker-Verfügbarkeit setzen - if printer_status == "online" and printer_active: - job_status = "scheduled" - else: - job_status = "waiting_for_printer" - - # Neuen Job erstellen - new_job = Job( - name=name, - description=description, - printer_id=printer_id, - user_id=current_user.id, - owner_id=current_user.id, - start_at=start_at, - end_at=end_at, - status=job_status, - file_path=file_path, - duration_minutes=duration_minutes - ) - - db_session.add(new_job) - db_session.commit() - - # Job-Objekt für die Antwort serialisieren - job_dict = new_job.to_dict() - db_session.close() - - jobs_logger.info(f"Neuer Job {new_job.id} erstellt für Drucker {printer_id}, Start: {start_at}, Dauer: {duration_minutes} Minuten") - return jsonify({"job": job_dict}), 201 + + jobs_logger.info(f"✅ Neuer Job {new_job.id} erfolgreich erstellt für Drucker {printer_id}, Start: {start_at}, Dauer: {duration_minutes} Minuten") + return jsonify({"job": job_dict}), 201 + + except Exception as db_error: + jobs_logger.error(f"❌ Datenbankfehler beim Job-Erstellen: {str(db_error)}") + try: + db_session.rollback() + db_session.close() + except: + pass + return jsonify({"error": "Datenbankfehler beim Erstellen des Jobs", "details": str(db_error)}), 500 except Exception as e: - jobs_logger.error(f"Fehler beim Erstellen eines Jobs: {str(e)}") + jobs_logger.error(f"❌ Kritischer Fehler beim Erstellen eines Jobs: {str(e)}", exc_info=True) return jsonify({"error": "Interner Serverfehler", "details": str(e)}), 500 @jobs_blueprint.route('/', methods=['PUT']) diff --git a/backend/database/myp.db b/backend/database/myp.db index 6043676d21da3fc31c7ce240bf47b419d1ce6059..768ba077e8f386766d8d248f76c3159d59cb8ccb 100644 GIT binary patch delta 2401 zcmcJQO=#O@7{~Qmk}buS9*@RkX~LW?D;b2y@2d|>_6(hu(6VJ4Jrs6my2NQ@;$XXJ zESL0BHMgEhjc>awl(9=`_u)esW3b-ZOCg6bGFTy$((N$z(Uu;zvb}O>Y-x3A)>VeT zd3dD%?|I(Ov+I$w>yZmX=u%^N5?%UYE}Al?jRHAz>iv3g>PTHFSo6*Yi_Vl{rEGlo z!irFU1S-!by5>yx|C*S=y|2PWrk%N z=<8>=IR^PUmK261I+jHSWtBSRdbp)kPF|pIpG}ALQFgG)_++@J0Ak4tPz{Y)!f!~*J7VmRj6P`xybDJj$?Bbq$E_yR_ zqXFyWi_xl+SH=WRc8`%!G_kZWhnQ!oYYa5c!pgwH%EVO9(Ad<(+^Erj1+3aWI$*L} zjO^qM(OlwSbw-9(CWcm~7J3$jmd3`GjRwphb({I)zA7$enc%RPr9eQ4o$oIL|1JJ? z{C)g|{GR;Ee1Cz?c+R)^?>%, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) +2025-06-01 16:04:40 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten +2025-06-01 16:04:40 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test +2025-06-01 16:04:43 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) +2025-06-01 16:04:43 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 8 nicht einschalten +2025-06-01 16:04:43 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 1: test +2025-06-01 16:04:45 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) +2025-06-01 16:04:45 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 1 nicht einschalten +2025-06-01 16:04:45 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 2: test +2025-06-01 16:04:47 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) +2025-06-01 16:04:47 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 2 nicht einschalten +2025-06-01 16:04:47 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 3: test +2025-06-01 16:04:49 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) +2025-06-01 16:04:49 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 3 nicht einschalten +2025-06-01 16:04:49 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 4: test +2025-06-01 16:04:51 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) +2025-06-01 16:04:51 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 4 nicht einschalten +2025-06-01 16:04:51 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 5: test +2025-06-01 16:04:53 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) +2025-06-01 16:04:53 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 5 nicht einschalten +2025-06-01 16:04:53 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 6: test +2025-06-01 16:04:55 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) +2025-06-01 16:04:55 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten +2025-06-01 16:05:08 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 7: test +2025-06-01 16:05:10 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) +2025-06-01 16:05:10 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten +2025-06-01 16:05:10 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test +2025-06-01 16:05:13 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) +2025-06-01 16:05:13 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 8 nicht einschalten +2025-06-01 16:05:13 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 1: test +2025-06-01 16:05:15 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) +2025-06-01 16:05:15 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 1 nicht einschalten +2025-06-01 16:05:15 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 2: test +2025-06-01 16:05:17 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) +2025-06-01 16:05:17 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 2 nicht einschalten +2025-06-01 16:05:17 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 3: test +2025-06-01 16:05:19 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) +2025-06-01 16:05:19 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 3 nicht einschalten +2025-06-01 16:05:19 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 4: test diff --git a/backend/logs/security/security.log b/backend/logs/security/security.log index 13e132b9..216ad991 100644 --- a/backend/logs/security/security.log +++ b/backend/logs/security/security.log @@ -59,3 +59,7 @@ 2025-06-01 15:18:31 - [security] security - [INFO] INFO - 🔒 Security System initialisiert 2025-06-01 15:19:19 - [security] security - [INFO] INFO - 🔒 Security System initialisiert 2025-06-01 15:20:15 - [security] security - [INFO] INFO - 🔒 Security System initialisiert +2025-06-01 15:32:54 - [security] security - [INFO] INFO - 🔒 Security System initialisiert +2025-06-01 15:46:52 - [security] security - [INFO] INFO - 🔒 Security System initialisiert +2025-06-01 15:49:00 - [security] security - [INFO] INFO - 🔒 Security System initialisiert +2025-06-01 16:04:34 - [security] security - [INFO] INFO - 🔒 Security System initialisiert diff --git a/backend/logs/shutdown_manager/shutdown_manager.log b/backend/logs/shutdown_manager/shutdown_manager.log index 27585573..d3e74140 100644 --- a/backend/logs/shutdown_manager/shutdown_manager.log +++ b/backend/logs/shutdown_manager/shutdown_manager.log @@ -124,3 +124,15 @@ 2025-06-01 15:18:31 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert 2025-06-01 15:19:19 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert 2025-06-01 15:20:15 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert +2025-06-01 15:32:54 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert +2025-06-01 15:46:52 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert +2025-06-01 15:46:52 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔄 Starte koordiniertes System-Shutdown... +2025-06-01 15:46:52 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🧹 Führe 1 Cleanup-Funktionen aus... +2025-06-01 15:46:52 - [shutdown_manager] shutdown_manager - [INFO] INFO - ✅ Koordiniertes Shutdown abgeschlossen in 0.0s +2025-06-01 15:46:52 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🏁 System wird beendet... +2025-06-01 15:49:00 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert +2025-06-01 15:49:01 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔄 Starte koordiniertes System-Shutdown... +2025-06-01 15:49:01 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🧹 Führe 1 Cleanup-Funktionen aus... +2025-06-01 15:49:01 - [shutdown_manager] shutdown_manager - [INFO] INFO - ✅ Koordiniertes Shutdown abgeschlossen in 0.0s +2025-06-01 15:49:01 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🏁 System wird beendet... +2025-06-01 16:04:34 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert diff --git a/backend/logs/startup/startup.log b/backend/logs/startup/startup.log index dc295d2b..f9d3232a 100644 --- a/backend/logs/startup/startup.log +++ b/backend/logs/startup/startup.log @@ -539,3 +539,39 @@ 2025-06-01 15:20:15 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert 2025-06-01 15:20:15 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert 2025-06-01 15:20:15 - [startup] startup - [INFO] INFO - ================================================== +2025-06-01 15:32:54 - [startup] startup - [INFO] INFO - ================================================== +2025-06-01 15:32:54 - [startup] startup - [INFO] INFO - 🚀 MYP Platform Backend wird gestartet... +2025-06-01 15:32:54 - [startup] startup - [INFO] INFO - 🐍 Python Version: 3.13.3 (tags/v3.13.3:6280bb5, Apr 8 2025, 14:47:33) [MSC v.1943 64 bit (AMD64)] +2025-06-01 15:32:54 - [startup] startup - [INFO] INFO - 💻 Betriebssystem: nt (win32) +2025-06-01 15:32:54 - [startup] startup - [INFO] INFO - 📁 Arbeitsverzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend +2025-06-01 15:32:54 - [startup] startup - [INFO] INFO - ⏰ Startzeit: 2025-06-01T15:32:54.248966 +2025-06-01 15:32:54 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert +2025-06-01 15:32:54 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert +2025-06-01 15:32:54 - [startup] startup - [INFO] INFO - ================================================== +2025-06-01 15:46:52 - [startup] startup - [INFO] INFO - ================================================== +2025-06-01 15:46:52 - [startup] startup - [INFO] INFO - 🚀 MYP Platform Backend wird gestartet... +2025-06-01 15:46:52 - [startup] startup - [INFO] INFO - 🐍 Python Version: 3.13.3 (tags/v3.13.3:6280bb5, Apr 8 2025, 14:47:33) [MSC v.1943 64 bit (AMD64)] +2025-06-01 15:46:52 - [startup] startup - [INFO] INFO - 💻 Betriebssystem: nt (win32) +2025-06-01 15:46:52 - [startup] startup - [INFO] INFO - 📁 Arbeitsverzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend +2025-06-01 15:46:52 - [startup] startup - [INFO] INFO - ⏰ Startzeit: 2025-06-01T15:46:52.302584 +2025-06-01 15:46:52 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert +2025-06-01 15:46:52 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert +2025-06-01 15:46:52 - [startup] startup - [INFO] INFO - ================================================== +2025-06-01 15:49:00 - [startup] startup - [INFO] INFO - ================================================== +2025-06-01 15:49:00 - [startup] startup - [INFO] INFO - 🚀 MYP Platform Backend wird gestartet... +2025-06-01 15:49:00 - [startup] startup - [INFO] INFO - 🐍 Python Version: 3.13.3 (tags/v3.13.3:6280bb5, Apr 8 2025, 14:47:33) [MSC v.1943 64 bit (AMD64)] +2025-06-01 15:49:00 - [startup] startup - [INFO] INFO - 💻 Betriebssystem: nt (win32) +2025-06-01 15:49:00 - [startup] startup - [INFO] INFO - 📁 Arbeitsverzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend +2025-06-01 15:49:00 - [startup] startup - [INFO] INFO - ⏰ Startzeit: 2025-06-01T15:49:00.712404 +2025-06-01 15:49:00 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert +2025-06-01 15:49:00 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert +2025-06-01 15:49:00 - [startup] startup - [INFO] INFO - ================================================== +2025-06-01 16:04:34 - [startup] startup - [INFO] INFO - ================================================== +2025-06-01 16:04:34 - [startup] startup - [INFO] INFO - 🚀 MYP Platform Backend wird gestartet... +2025-06-01 16:04:34 - [startup] startup - [INFO] INFO - 🐍 Python Version: 3.13.3 (tags/v3.13.3:6280bb5, Apr 8 2025, 14:47:33) [MSC v.1943 64 bit (AMD64)] +2025-06-01 16:04:34 - [startup] startup - [INFO] INFO - 💻 Betriebssystem: nt (win32) +2025-06-01 16:04:34 - [startup] startup - [INFO] INFO - 📁 Arbeitsverzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend +2025-06-01 16:04:34 - [startup] startup - [INFO] INFO - ⏰ Startzeit: 2025-06-01T16:04:34.165847 +2025-06-01 16:04:34 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert +2025-06-01 16:04:34 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert +2025-06-01 16:04:34 - [startup] startup - [INFO] INFO - ================================================== diff --git a/backend/logs/user/user.log b/backend/logs/user/user.log index 4dceb787..fc4080c6 100644 --- a/backend/logs/user/user.log +++ b/backend/logs/user/user.log @@ -9,3 +9,4 @@ 2025-06-01 14:30:10 - [user] user - [INFO] INFO - Benutzer 'corewe' (ID: 3) gelöscht von Admin 1 2025-06-01 14:37:04 - [user] user - [INFO] INFO - Benutzer admin hat seine Einstellungsseite aufgerufen 2025-06-01 14:37:21 - [user] user - [INFO] INFO - Benutzer admin hat seine Profilseite aufgerufen +2025-06-01 15:33:40 - [user] user - [INFO] INFO - Benutzer admin hat seine Einstellungsseite aufgerufen diff --git a/backend/logs/windows_fixes/windows_fixes.log b/backend/logs/windows_fixes/windows_fixes.log index 8558e6f9..3ad55257 100644 --- a/backend/logs/windows_fixes/windows_fixes.log +++ b/backend/logs/windows_fixes/windows_fixes.log @@ -242,3 +242,19 @@ 2025-06-01 15:20:14 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen) 2025-06-01 15:20:14 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet 2025-06-01 15:20:14 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet +2025-06-01 15:32:52 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an... +2025-06-01 15:32:52 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen) +2025-06-01 15:32:52 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet +2025-06-01 15:32:52 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet +2025-06-01 15:46:50 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an... +2025-06-01 15:46:50 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen) +2025-06-01 15:46:50 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet +2025-06-01 15:46:50 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet +2025-06-01 15:48:59 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an... +2025-06-01 15:48:59 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen) +2025-06-01 15:48:59 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet +2025-06-01 15:48:59 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet +2025-06-01 16:04:33 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an... +2025-06-01 16:04:33 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen) +2025-06-01 16:04:33 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet +2025-06-01 16:04:33 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet