ich geh behindert
This commit is contained in:
485
backend/app_cleaned.py
Normal file
485
backend/app_cleaned.py
Normal file
@@ -0,0 +1,485 @@
|
||||
"""
|
||||
Hauptanwendung für das 3D-Druck-Management-System
|
||||
|
||||
Diese Datei initialisiert die Flask-Anwendung und registriert alle Blueprints.
|
||||
Die eigentlichen Routen sind in den jeweiligen Blueprint-Modulen definiert.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import atexit
|
||||
import signal
|
||||
from datetime import datetime
|
||||
from flask import Flask, render_template, request, jsonify, redirect, url_for, session, abort
|
||||
from flask_login import LoginManager, current_user, logout_user, login_required
|
||||
from flask_wtf import CSRFProtect
|
||||
from flask_wtf.csrf import CSRFError
|
||||
from sqlalchemy import event
|
||||
from contextlib import contextmanager
|
||||
import threading
|
||||
|
||||
# ===== OPTIMIERTE KONFIGURATION FÜR RASPBERRY PI =====
|
||||
class OptimizedConfig:
|
||||
"""Konfiguration für performance-optimierte Bereitstellung auf Raspberry Pi"""
|
||||
|
||||
# Performance-Optimierungs-Flags
|
||||
OPTIMIZED_MODE = True
|
||||
USE_MINIFIED_ASSETS = True
|
||||
DISABLE_ANIMATIONS = True
|
||||
LIMIT_GLASSMORPHISM = True
|
||||
|
||||
# Flask-Performance-Einstellungen
|
||||
DEBUG = False
|
||||
TESTING = False
|
||||
SEND_FILE_MAX_AGE_DEFAULT = 31536000 # 1 Jahr Cache für statische Dateien
|
||||
|
||||
# Template-Einstellungen
|
||||
TEMPLATES_AUTO_RELOAD = False
|
||||
EXPLAIN_TEMPLATE_LOADING = False
|
||||
|
||||
# Session-Konfiguration
|
||||
SESSION_COOKIE_SECURE = True
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||
|
||||
# Performance-Optimierungen
|
||||
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB max Upload
|
||||
JSON_SORT_KEYS = False
|
||||
JSONIFY_PRETTYPRINT_REGULAR = False
|
||||
|
||||
def detect_raspberry_pi():
|
||||
"""Erkennt ob das System auf einem Raspberry Pi läuft"""
|
||||
try:
|
||||
with open('/proc/cpuinfo', 'r') as f:
|
||||
cpuinfo = f.read()
|
||||
if 'Raspberry Pi' in cpuinfo or 'BCM' in cpuinfo:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
import platform
|
||||
machine = platform.machine().lower()
|
||||
if 'arm' in machine or 'aarch64' in machine:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
return os.getenv('FORCE_OPTIMIZED_MODE', '').lower() in ['true', '1', 'yes']
|
||||
|
||||
def should_use_optimized_config():
|
||||
"""Bestimmt ob die optimierte Konfiguration verwendet werden soll"""
|
||||
if '--optimized' in sys.argv:
|
||||
return True
|
||||
|
||||
if detect_raspberry_pi():
|
||||
return True
|
||||
|
||||
if os.getenv('USE_OPTIMIZED_CONFIG', '').lower() in ['true', '1', 'yes']:
|
||||
return True
|
||||
|
||||
try:
|
||||
import psutil
|
||||
memory_gb = psutil.virtual_memory().total / (1024**3)
|
||||
if memory_gb < 2.0:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# Windows-spezifische Fixes
|
||||
if os.name == 'nt':
|
||||
try:
|
||||
from utils.windows_fixes import get_windows_thread_manager
|
||||
print("[OK] Windows-Fixes (sichere Version) geladen")
|
||||
except ImportError as e:
|
||||
get_windows_thread_manager = None
|
||||
print(f"[WARN] Windows-Fixes nicht verfügbar: {str(e)}")
|
||||
else:
|
||||
get_windows_thread_manager = None
|
||||
|
||||
# Lokale Imports
|
||||
from models import init_database, create_initial_admin, User, get_db_session
|
||||
from utils.logging_config import setup_logging, get_logger, log_startup_info
|
||||
from utils.job_scheduler import JobScheduler, get_job_scheduler
|
||||
from utils.queue_manager import start_queue_manager, stop_queue_manager
|
||||
from utils.settings import SECRET_KEY, SESSION_LIFETIME
|
||||
|
||||
# ===== OFFLINE-MODUS KONFIGURATION =====
|
||||
OFFLINE_MODE = True # Produktionseinstellung für Offline-Betrieb
|
||||
|
||||
# Blueprints importieren
|
||||
from blueprints.auth import auth_blueprint
|
||||
from blueprints.user import user_blueprint
|
||||
from blueprints.admin import admin_blueprint
|
||||
from blueprints.admin_api import admin_api_blueprint
|
||||
from blueprints.guest import guest_blueprint
|
||||
from blueprints.calendar import calendar_blueprint
|
||||
from blueprints.users import users_blueprint
|
||||
from blueprints.printers import printers_blueprint
|
||||
from blueprints.jobs import jobs_blueprint
|
||||
from blueprints.kiosk import kiosk_blueprint
|
||||
from blueprints.uploads import uploads_blueprint
|
||||
from blueprints.sessions import sessions_blueprint
|
||||
|
||||
# Import der Sicherheits- und Hilfssysteme
|
||||
from utils.rate_limiter import cleanup_rate_limiter
|
||||
from utils.security import init_security
|
||||
from utils.permissions import init_permission_helpers
|
||||
|
||||
# Logging initialisieren
|
||||
setup_logging()
|
||||
log_startup_info()
|
||||
|
||||
# Logger für verschiedene Komponenten
|
||||
app_logger = get_logger("app")
|
||||
|
||||
# Thread-sichere Caches
|
||||
_user_cache = {}
|
||||
_user_cache_lock = threading.RLock()
|
||||
_printer_status_cache = {}
|
||||
_printer_status_cache_lock = threading.RLock()
|
||||
|
||||
# Cache-Konfiguration
|
||||
USER_CACHE_TTL = 300 # 5 Minuten
|
||||
PRINTER_STATUS_CACHE_TTL = 30 # 30 Sekunden
|
||||
|
||||
def clear_user_cache(user_id=None):
|
||||
"""Löscht User-Cache"""
|
||||
with _user_cache_lock:
|
||||
if user_id:
|
||||
_user_cache.pop(user_id, None)
|
||||
else:
|
||||
_user_cache.clear()
|
||||
|
||||
def clear_printer_status_cache():
|
||||
"""Löscht Drucker-Status-Cache"""
|
||||
with _printer_status_cache_lock:
|
||||
_printer_status_cache.clear()
|
||||
|
||||
# ===== AGGRESSIVE SHUTDOWN HANDLER =====
|
||||
def aggressive_shutdown_handler(sig, frame):
|
||||
"""Aggressiver Signal-Handler für sofortiges Herunterfahren bei Strg+C"""
|
||||
print("\n[ALERT] STRG+C ERKANNT - SOFORTIGES SHUTDOWN!")
|
||||
|
||||
try:
|
||||
# Caches leeren
|
||||
clear_user_cache()
|
||||
clear_printer_status_cache()
|
||||
|
||||
# Queue Manager stoppen
|
||||
try:
|
||||
stop_queue_manager()
|
||||
print("[OK] Queue Manager gestoppt")
|
||||
except Exception as e:
|
||||
print(f"[WARN] Queue Manager Stop fehlgeschlagen: {e}")
|
||||
|
||||
# Datenbank-Cleanup
|
||||
try:
|
||||
from models import _engine, _scoped_session
|
||||
if _scoped_session:
|
||||
_scoped_session.remove()
|
||||
if _engine:
|
||||
_engine.dispose()
|
||||
print("[OK] Datenbank geschlossen")
|
||||
except Exception as e:
|
||||
print(f"[WARN] Datenbank-Cleanup fehlgeschlagen: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Fehler beim Cleanup: {e}")
|
||||
|
||||
print("[STOP] SOFORTIGES PROGRAMM-ENDE")
|
||||
os._exit(0)
|
||||
|
||||
def register_aggressive_shutdown():
|
||||
"""Registriert den aggressiven Shutdown-Handler"""
|
||||
signal.signal(signal.SIGINT, aggressive_shutdown_handler)
|
||||
signal.signal(signal.SIGTERM, aggressive_shutdown_handler)
|
||||
|
||||
if os.name == 'nt':
|
||||
try:
|
||||
signal.signal(signal.SIGBREAK, aggressive_shutdown_handler)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
signal.signal(signal.SIGHUP, aggressive_shutdown_handler)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
atexit.register(lambda: print("[RESTART] Atexit-Handler ausgeführt"))
|
||||
print("[ALERT] AGGRESSIVER STRG+C SHUTDOWN-HANDLER AKTIVIERT")
|
||||
|
||||
# Shutdown-Handler registrieren
|
||||
register_aggressive_shutdown()
|
||||
|
||||
# Flask-App initialisieren
|
||||
app = Flask(__name__)
|
||||
app.secret_key = SECRET_KEY
|
||||
|
||||
# ===== KONFIGURATION ANWENDEN =====
|
||||
USE_OPTIMIZED_CONFIG = should_use_optimized_config()
|
||||
|
||||
if USE_OPTIMIZED_CONFIG:
|
||||
app_logger.info("[START] Aktiviere optimierte Konfiguration")
|
||||
|
||||
app.config.update({
|
||||
"DEBUG": OptimizedConfig.DEBUG,
|
||||
"TESTING": OptimizedConfig.TESTING,
|
||||
"SEND_FILE_MAX_AGE_DEFAULT": OptimizedConfig.SEND_FILE_MAX_AGE_DEFAULT,
|
||||
"TEMPLATES_AUTO_RELOAD": OptimizedConfig.TEMPLATES_AUTO_RELOAD,
|
||||
"EXPLAIN_TEMPLATE_LOADING": OptimizedConfig.EXPLAIN_TEMPLATE_LOADING,
|
||||
"SESSION_COOKIE_SECURE": OptimizedConfig.SESSION_COOKIE_SECURE,
|
||||
"SESSION_COOKIE_HTTPONLY": OptimizedConfig.SESSION_COOKIE_HTTPONLY,
|
||||
"SESSION_COOKIE_SAMESITE": OptimizedConfig.SESSION_COOKIE_SAMESITE,
|
||||
"MAX_CONTENT_LENGTH": OptimizedConfig.MAX_CONTENT_LENGTH,
|
||||
"JSON_SORT_KEYS": OptimizedConfig.JSON_SORT_KEYS,
|
||||
"JSONIFY_PRETTYPRINT_REGULAR": OptimizedConfig.JSONIFY_PRETTYPRINT_REGULAR
|
||||
})
|
||||
|
||||
app.jinja_env.globals.update({
|
||||
'optimized_mode': True,
|
||||
'use_minified_assets': OptimizedConfig.USE_MINIFIED_ASSETS,
|
||||
'disable_animations': OptimizedConfig.DISABLE_ANIMATIONS,
|
||||
'limit_glassmorphism': OptimizedConfig.LIMIT_GLASSMORPHISM,
|
||||
'base_template': 'base-optimized.html'
|
||||
})
|
||||
|
||||
@app.after_request
|
||||
def add_optimized_cache_headers(response):
|
||||
"""Fügt optimierte Cache-Header hinzu"""
|
||||
if request.endpoint == 'static' or '/static/' in request.path:
|
||||
response.headers['Cache-Control'] = 'public, max-age=31536000'
|
||||
response.headers['Vary'] = 'Accept-Encoding'
|
||||
return response
|
||||
|
||||
else:
|
||||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
||||
app.jinja_env.globals.update({
|
||||
'optimized_mode': False,
|
||||
'use_minified_assets': False,
|
||||
'disable_animations': False,
|
||||
'limit_glassmorphism': False,
|
||||
'base_template': 'base.html'
|
||||
})
|
||||
|
||||
# Session-Konfiguration
|
||||
app.config["PERMANENT_SESSION_LIFETIME"] = SESSION_LIFETIME
|
||||
app.config["WTF_CSRF_ENABLED"] = True
|
||||
|
||||
# CSRF-Schutz initialisieren
|
||||
csrf = CSRFProtect(app)
|
||||
|
||||
@app.errorhandler(CSRFError)
|
||||
def csrf_error(error):
|
||||
"""Behandelt CSRF-Fehler"""
|
||||
app_logger.warning(f"CSRF-Fehler: {error.description}")
|
||||
return jsonify({"error": "CSRF-Token ungültig oder fehlt"}), 400
|
||||
|
||||
# Login-Manager initialisieren
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = "auth.login"
|
||||
login_manager.login_message = "Bitte melden Sie sich an, um auf diese Seite zuzugreifen."
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
"""Lädt einen Benutzer für Flask-Login"""
|
||||
try:
|
||||
with get_db_session() as db_session:
|
||||
user = db_session.query(User).filter_by(id=int(user_id)).first()
|
||||
if user:
|
||||
db_session.expunge(user)
|
||||
return user
|
||||
except Exception as e:
|
||||
app_logger.error(f"Fehler beim Laden des Benutzers {user_id}: {str(e)}")
|
||||
return None
|
||||
|
||||
# ===== BLUEPRINTS REGISTRIEREN =====
|
||||
app.register_blueprint(auth_blueprint)
|
||||
app.register_blueprint(user_blueprint)
|
||||
app.register_blueprint(admin_blueprint)
|
||||
app.register_blueprint(admin_api_blueprint)
|
||||
app.register_blueprint(guest_blueprint)
|
||||
app.register_blueprint(calendar_blueprint)
|
||||
app.register_blueprint(users_blueprint)
|
||||
app.register_blueprint(printers_blueprint)
|
||||
app.register_blueprint(jobs_blueprint)
|
||||
app.register_blueprint(kiosk_blueprint)
|
||||
app.register_blueprint(uploads_blueprint)
|
||||
app.register_blueprint(sessions_blueprint)
|
||||
|
||||
# ===== HILFSSYSTEME INITIALISIEREN =====
|
||||
init_security(app)
|
||||
init_permission_helpers(app)
|
||||
|
||||
# ===== KONTEXT-PROZESSOREN =====
|
||||
@app.context_processor
|
||||
def inject_now():
|
||||
"""Injiziert die aktuelle Zeit in alle Templates"""
|
||||
return {'now': datetime.now}
|
||||
|
||||
@app.template_filter('format_datetime')
|
||||
def format_datetime_filter(value, format='%d.%m.%Y %H:%M'):
|
||||
"""Template-Filter für Datums-Formatierung"""
|
||||
if value is None:
|
||||
return ""
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
value = datetime.fromisoformat(value)
|
||||
except:
|
||||
return value
|
||||
return value.strftime(format)
|
||||
|
||||
@app.template_global()
|
||||
def is_optimized_mode():
|
||||
"""Prüft ob der optimierte Modus aktiv ist"""
|
||||
return USE_OPTIMIZED_CONFIG
|
||||
|
||||
# ===== REQUEST HOOKS =====
|
||||
@app.before_request
|
||||
def log_request_info():
|
||||
"""Loggt Request-Informationen"""
|
||||
if request.endpoint != 'static':
|
||||
app_logger.debug(f"Request: {request.method} {request.path}")
|
||||
|
||||
@app.after_request
|
||||
def log_response_info(response):
|
||||
"""Loggt Response-Informationen"""
|
||||
if request.endpoint != 'static':
|
||||
app_logger.debug(f"Response: {response.status_code}")
|
||||
return response
|
||||
|
||||
@app.before_request
|
||||
def check_session_activity():
|
||||
"""Prüft Session-Aktivität und meldet inaktive Benutzer ab"""
|
||||
if current_user.is_authenticated:
|
||||
last_activity = session.get('last_activity')
|
||||
if last_activity:
|
||||
try:
|
||||
last_activity_time = datetime.fromisoformat(last_activity)
|
||||
if (datetime.now() - last_activity_time).total_seconds() > SESSION_LIFETIME.total_seconds():
|
||||
app_logger.info(f"Session abgelaufen für Benutzer {current_user.id}")
|
||||
logout_user()
|
||||
return redirect(url_for('auth.login'))
|
||||
except:
|
||||
pass
|
||||
|
||||
# Aktivität aktualisieren
|
||||
session['last_activity'] = datetime.now().isoformat()
|
||||
session.permanent = True
|
||||
|
||||
# ===== HAUPTROUTEN =====
|
||||
@app.route("/")
|
||||
def index():
|
||||
"""Startseite - leitet zur Login-Seite oder zum Dashboard"""
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for("dashboard"))
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
@app.route("/dashboard")
|
||||
@login_required
|
||||
def dashboard():
|
||||
"""Haupt-Dashboard"""
|
||||
return render_template("dashboard.html")
|
||||
|
||||
@app.route("/admin")
|
||||
@login_required
|
||||
def admin():
|
||||
"""Admin-Dashboard"""
|
||||
if not current_user.is_admin:
|
||||
abort(403)
|
||||
return redirect(url_for("admin.dashboard"))
|
||||
|
||||
# Statische Seiten
|
||||
@app.route("/privacy")
|
||||
def privacy():
|
||||
"""Datenschutzerklärung"""
|
||||
return render_template("privacy.html")
|
||||
|
||||
@app.route("/terms")
|
||||
def terms():
|
||||
"""Nutzungsbedingungen"""
|
||||
return render_template("terms.html")
|
||||
|
||||
@app.route("/imprint")
|
||||
def imprint():
|
||||
"""Impressum"""
|
||||
return render_template("imprint.html")
|
||||
|
||||
@app.route("/legal")
|
||||
def legal():
|
||||
"""Rechtliche Hinweise - Weiterleitung zum Impressum"""
|
||||
return redirect(url_for("imprint"))
|
||||
|
||||
# ===== FEHLERBEHANDLUNG =====
|
||||
@app.errorhandler(404)
|
||||
def not_found_error(error):
|
||||
"""404-Fehlerseite"""
|
||||
return render_template('errors/404.html'), 404
|
||||
|
||||
@app.errorhandler(403)
|
||||
def forbidden_error(error):
|
||||
"""403-Fehlerseite"""
|
||||
return render_template('errors/403.html'), 403
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_error(error):
|
||||
"""500-Fehlerseite"""
|
||||
app_logger.error(f"Interner Serverfehler: {str(error)}")
|
||||
return render_template('errors/500.html'), 500
|
||||
|
||||
# ===== HAUPTFUNKTION =====
|
||||
def main():
|
||||
"""Hauptfunktion zum Starten der Anwendung"""
|
||||
try:
|
||||
# Datenbank initialisieren
|
||||
init_database()
|
||||
|
||||
# Initial-Admin erstellen falls nicht vorhanden
|
||||
create_initial_admin()
|
||||
|
||||
# Queue Manager starten
|
||||
start_queue_manager()
|
||||
|
||||
# Job Scheduler starten
|
||||
scheduler = get_job_scheduler()
|
||||
if scheduler:
|
||||
scheduler.start()
|
||||
|
||||
# SSL-Kontext
|
||||
ssl_context = None
|
||||
try:
|
||||
from utils.ssl_config import get_ssl_context
|
||||
ssl_context = get_ssl_context()
|
||||
except ImportError:
|
||||
app_logger.warning("SSL-Konfiguration nicht verfügbar")
|
||||
|
||||
# Server starten
|
||||
host = os.getenv('FLASK_HOST', '0.0.0.0')
|
||||
port = int(os.getenv('FLASK_PORT', 5000))
|
||||
|
||||
app_logger.info(f"[START] Server startet auf {host}:{port}")
|
||||
|
||||
if ssl_context:
|
||||
app.run(host=host, port=port, ssl_context=ssl_context, threaded=True)
|
||||
else:
|
||||
app.run(host=host, port=port, threaded=True)
|
||||
|
||||
except Exception as e:
|
||||
app_logger.error(f"Fehler beim Starten der Anwendung: {str(e)}")
|
||||
raise
|
||||
finally:
|
||||
# Cleanup
|
||||
try:
|
||||
stop_queue_manager()
|
||||
if scheduler:
|
||||
scheduler.shutdown()
|
||||
cleanup_rate_limiter()
|
||||
except:
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user