📚 Improved error handling documentation and codebase organization in the frontend application. 🖥️🔍
This commit is contained in:
@@ -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
|
Reference in New Issue
Block a user