📚 Improved error handling documentation and codebase organization in the frontend application. 🖥️🔍

This commit is contained in:
2025-05-30 20:47:28 +02:00
parent d1a6281577
commit 0d966712a7
5 changed files with 871 additions and 19 deletions

View File

@@ -343,11 +343,11 @@ def login():
password = request.form.get("password")
remember_me = request.form.get("remember_me") == "on"
# Zusätzlicher Fallback für verschiedene Feldnamen
if not username:
username = request.form.get("username") or request.values.get("email") or request.values.get("username")
if not password:
password = request.form.get("password") or request.values.get("password")
# Zusätzlicher Fallback für verschiedene Feldnamen
if not username:
username = request.form.get("username") or request.values.get("email") or request.values.get("username")
if not password:
password = request.form.get("password") or request.values.get("password")
except Exception as extract_error:
auth_logger.error(f"Fehler beim Extrahieren der Login-Daten: {str(extract_error)}")
@@ -2299,16 +2299,16 @@ def finish_job(job_id):
def cancel_job(job_id):
"""Bricht einen Job ab."""
try:
db_session = get_db_session()
db_session = get_db_session()
job = db_session.query(Job).get(job_id)
if not job:
db_session.close()
db_session.close()
return jsonify({"error": "Job nicht gefunden"}), 404
# Prüfen, ob der Job abgebrochen werden kann
if job.status not in ["scheduled", "running"]:
db_session.close()
db_session.close()
return jsonify({"error": f"Job kann im Status '{job.status}' nicht abgebrochen werden"}), 400
# Job als abgebrochen markieren
@@ -2320,7 +2320,7 @@ def cancel_job(job_id):
from utils.job_scheduler import toggle_plug
toggle_plug(job.printer_id, False)
db_session.commit()
db_session.commit()
job_dict = job.to_dict()
db_session.close()
@@ -2338,7 +2338,7 @@ def cancel_job(job_id):
def start_job(job_id):
"""Startet einen Job manuell."""
try:
db_session = get_db_session()
db_session = get_db_session()
job = db_session.query(Job).options(joinedload(Job.printer)).get(job_id)
if not job:
@@ -2347,7 +2347,7 @@ def start_job(job_id):
# Prüfen, ob der Job gestartet werden kann
if job.status not in ["scheduled", "queued", "waiting_for_printer"]:
db_session.close()
db_session.close()
return jsonify({"error": f"Job kann im Status '{job.status}' nicht gestartet werden"}), 400
# Drucker einschalten falls verfügbar
@@ -2358,7 +2358,7 @@ def start_job(job_id):
jobs_logger.info(f"Drucker {job.printer.name} für Job {job_id} eingeschaltet")
else:
jobs_logger.warning(f"Konnte Drucker {job.printer.name} für Job {job_id} nicht einschalten")
except Exception as e:
except Exception as e:
jobs_logger.warning(f"Fehler beim Einschalten des Druckers für Job {job_id}: {str(e)}")
# Job als laufend markieren
@@ -2398,7 +2398,7 @@ def pause_job(job_id):
# Prüfen, ob der Job pausiert werden kann
if job.status != "running":
db_session.close()
db_session.close()
return jsonify({"error": f"Job kann im Status '{job.status}' nicht pausiert werden"}), 400
# Drucker ausschalten
@@ -2409,7 +2409,7 @@ def pause_job(job_id):
jobs_logger.info(f"Drucker {job.printer.name} für Job {job_id} ausgeschaltet (Pause)")
else:
jobs_logger.warning(f"Konnte Drucker {job.printer.name} für Job {job_id} nicht ausschalten")
except Exception as e:
except Exception as e:
jobs_logger.warning(f"Fehler beim Ausschalten des Druckers für Job {job_id}: {str(e)}")
# Job als pausiert markieren
@@ -2419,7 +2419,7 @@ def pause_job(job_id):
db_session.commit()
job_dict = job.to_dict()
db_session.close()
db_session.close()
jobs_logger.info(f"Job {job_id} pausiert von Benutzer {current_user.id}")
return jsonify({
@@ -2458,7 +2458,7 @@ def resume_job(job_id):
jobs_logger.info(f"Drucker {job.printer.name} für Job {job_id} eingeschaltet (Resume)")
else:
jobs_logger.warning(f"Konnte Drucker {job.printer.name} für Job {job_id} nicht einschalten")
except Exception as e:
except Exception as e:
jobs_logger.warning(f"Fehler beim Einschalten des Druckers für Job {job_id}: {str(e)}")
# Job als laufend markieren
@@ -4859,4 +4859,179 @@ def get_printers():
return jsonify({
"error": f"Fehler beim Laden der Drucker: {str(e)}",
"printers": []
}), 500
}), 500
# ===== ERWEITERTE SESSION-MANAGEMENT UND AUTO-LOGOUT =====
@app.before_request
def check_session_activity():
"""
Überprüft Session-Aktivität und meldet Benutzer bei Inaktivität automatisch ab.
"""
# Skip für nicht-authentifizierte Benutzer und Login-Route
if not current_user.is_authenticated or request.endpoint in ['login', 'static', 'auth_logout']:
return
# Skip für AJAX/API calls die nicht als Session-Aktivität zählen sollen
if request.path.startswith('/api/') and request.path.endswith('/heartbeat'):
return
now = datetime.now()
# Session-Aktivität tracken
if 'last_activity' in session:
last_activity = datetime.fromisoformat(session['last_activity'])
inactive_duration = now - last_activity
# Definiere Inaktivitäts-Limits basierend auf Benutzerrolle
max_inactive_minutes = 30 # Standard: 30 Minuten
if hasattr(current_user, 'is_admin') and current_user.is_admin:
max_inactive_minutes = 60 # Admins: 60 Minuten
max_inactive_duration = timedelta(minutes=max_inactive_minutes)
# Benutzer abmelden wenn zu lange inaktiv
if inactive_duration > max_inactive_duration:
auth_logger.info(f"🕒 Automatische Abmeldung: Benutzer {current_user.email} war {inactive_duration.total_seconds()/60:.1f} Minuten inaktiv (Limit: {max_inactive_minutes}min)")
# Session-Daten vor Logout speichern für Benachrichtigung
session['auto_logout_reason'] = f"Automatische Abmeldung nach {max_inactive_minutes} Minuten Inaktivität"
session['auto_logout_time'] = now.isoformat()
logout_user()
session.clear()
# JSON-Response für AJAX-Requests
if request.headers.get('X-Requested-With') == 'XMLHttpRequest' or request.is_json:
return jsonify({
"error": "Session abgelaufen",
"reason": "auto_logout_inactivity",
"message": f"Sie wurden nach {max_inactive_minutes} Minuten Inaktivität automatisch abgemeldet",
"redirect_url": url_for("login")
}), 401
# HTML-Redirect für normale Requests
flash(f"Sie wurden nach {max_inactive_minutes} Minuten Inaktivität automatisch abgemeldet.", "warning")
return redirect(url_for("login"))
# Session-Aktivität aktualisieren (aber nicht bei jedem API-Call)
if not request.path.startswith('/api/stats/') and not request.path.startswith('/api/heartbeat'):
session['last_activity'] = now.isoformat()
session['user_agent'] = request.headers.get('User-Agent', '')[:200] # Begrenzt auf 200 Zeichen
session['ip_address'] = request.remote_addr
# Session-Sicherheit: Überprüfe IP-Adresse und User-Agent (Optional)
if 'session_ip' in session and session['session_ip'] != request.remote_addr:
auth_logger.warning(f"⚠️ IP-Adresse geändert für Benutzer {current_user.email}: {session['session_ip']}{request.remote_addr}")
# Optional: Benutzer abmelden bei IP-Wechsel (kann bei VPN/Proxy problematisch sein)
# session['security_warning'] = "IP-Adresse hat sich geändert"
@app.before_request
def setup_session_security():
"""
Initialisiert Session-Sicherheit für neue Sessions.
"""
if current_user.is_authenticated and 'session_created' not in session:
session['session_created'] = datetime.now().isoformat()
session['session_ip'] = request.remote_addr
session['last_activity'] = datetime.now().isoformat()
session.permanent = True # Session als permanent markieren
auth_logger.info(f"🔐 Neue Session erstellt für Benutzer {current_user.email} von IP {request.remote_addr}")
# ===== SESSION-MANAGEMENT API-ENDPUNKTE =====
@app.route('/api/session/heartbeat', methods=['POST'])
@login_required
def session_heartbeat():
"""
Heartbeat-Endpunkt um Session am Leben zu halten.
Wird vom Frontend alle 5 Minuten aufgerufen.
"""
try:
now = datetime.now()
session['last_activity'] = now.isoformat()
# Berechne verbleibende Session-Zeit
last_activity = datetime.fromisoformat(session.get('last_activity', now.isoformat()))
max_inactive_minutes = 60 if hasattr(current_user, 'is_admin') and current_user.is_admin else 30
time_left = max_inactive_minutes * 60 - (now - last_activity).total_seconds()
return jsonify({
"success": True,
"session_active": True,
"time_left_seconds": max(0, int(time_left)),
"max_inactive_minutes": max_inactive_minutes,
"current_time": now.isoformat()
})
except Exception as e:
auth_logger.error(f"Fehler beim Session-Heartbeat: {str(e)}")
return jsonify({"error": "Heartbeat fehlgeschlagen"}), 500
@app.route('/api/session/status', methods=['GET'])
@login_required
def session_status():
"""
Gibt detaillierten Session-Status zurück.
"""
try:
now = datetime.now()
last_activity = datetime.fromisoformat(session.get('last_activity', now.isoformat()))
session_created = datetime.fromisoformat(session.get('session_created', now.isoformat()))
max_inactive_minutes = 60 if hasattr(current_user, 'is_admin') and current_user.is_admin else 30
inactive_duration = (now - last_activity).total_seconds()
time_left = max_inactive_minutes * 60 - inactive_duration
return jsonify({
"success": True,
"user": {
"id": current_user.id,
"email": current_user.email,
"name": current_user.name,
"is_admin": getattr(current_user, 'is_admin', False)
},
"session": {
"created": session_created.isoformat(),
"last_activity": last_activity.isoformat(),
"inactive_seconds": int(inactive_duration),
"time_left_seconds": max(0, int(time_left)),
"max_inactive_minutes": max_inactive_minutes,
"ip_address": session.get('session_ip', 'unbekannt'),
"user_agent": session.get('user_agent', 'unbekannt')[:50] + "..." if len(session.get('user_agent', '')) > 50 else session.get('user_agent', 'unbekannt')
},
"warnings": []
})
except Exception as e:
auth_logger.error(f"Fehler beim Abrufen des Session-Status: {str(e)}")
return jsonify({"error": "Session-Status nicht verfügbar"}), 500
@app.route('/api/session/extend', methods=['POST'])
@login_required
def extend_session():
"""
Verlängert die aktuelle Session um weitere Zeit.
"""
try:
data = request.get_json() or {}
extend_minutes = data.get('extend_minutes', 30)
# Begrenzen der Verlängerung (max 2 Stunden)
extend_minutes = min(extend_minutes, 120)
now = datetime.now()
session['last_activity'] = now.isoformat()
session['session_extended'] = now.isoformat()
session['extended_by_minutes'] = extend_minutes
auth_logger.info(f"🕒 Session verlängert für Benutzer {current_user.email} um {extend_minutes} Minuten")
return jsonify({
"success": True,
"message": f"Session um {extend_minutes} Minuten verlängert",
"extended_until": (now + timedelta(minutes=extend_minutes)).isoformat(),
"extended_minutes": extend_minutes
})
except Exception as e:
auth_logger.error(f"Fehler beim Verlängern der Session: {str(e)}")
return jsonify({"error": "Session-Verlängerung fehlgeschlagen"}), 500