🎉 Feature: Optimized CSS build process for improved performance 🎉

This commit is contained in:
Till Tomczak 2025-06-03 14:31:39 +02:00
parent 549de9934c
commit 7c30ca2188
3 changed files with 447 additions and 64 deletions

View File

@ -22,6 +22,124 @@ import shutil
from contextlib import contextmanager
import threading
# ===== OPTIMIERTE KONFIGURATION FÜR RASPBERRY PI =====
class OptimizedConfig:
"""Configuration for performance-optimized deployment on Raspberry Pi"""
# Performance optimization flags
OPTIMIZED_MODE = True
USE_MINIFIED_ASSETS = True
DISABLE_ANIMATIONS = True
LIMIT_GLASSMORPHISM = True
# Flask performance settings
DEBUG = False
TESTING = False
SEND_FILE_MAX_AGE_DEFAULT = 31536000 # 1 year cache for static files
# Template settings
TEMPLATES_AUTO_RELOAD = False
EXPLAIN_TEMPLATE_LOADING = False
# Session configuration
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
# Performance optimizations
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB max upload
JSON_SORT_KEYS = False
JSONIFY_PRETTYPRINT_REGULAR = False
# Database optimizations
SQLALCHEMY_ECHO = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ENGINE_OPTIONS = {
'pool_size': 5,
'pool_recycle': 3600,
'pool_pre_ping': True,
'connect_args': {
'check_same_thread': False
}
}
# Cache configuration
CACHE_TYPE = 'simple'
CACHE_DEFAULT_TIMEOUT = 300
CACHE_KEY_PREFIX = 'myp_'
# Static file caching headers
SEND_FILE_MAX_AGE_DEFAULT = 31536000 # 1 year
@staticmethod
def init_app(app):
"""Initialize application with optimized settings"""
# Set optimized template
app.jinja_env.globals['optimized_mode'] = True
app.jinja_env.globals['base_template'] = 'base-optimized.html'
# Add cache headers for static files
@app.after_request
def add_cache_headers(response):
if 'static' in response.headers.get('Location', ''):
response.headers['Cache-Control'] = 'public, max-age=31536000'
response.headers['Vary'] = 'Accept-Encoding'
return response
# Disable unnecessary features
app.config['EXPLAIN_TEMPLATE_LOADING'] = False
app.config['TEMPLATES_AUTO_RELOAD'] = False
print("🚀 Running in OPTIMIZED mode for Raspberry Pi")
def detect_raspberry_pi():
"""Erkennt ob das System auf einem Raspberry Pi läuft"""
try:
# Prüfe auf Raspberry Pi Hardware
with open('/proc/cpuinfo', 'r') as f:
cpuinfo = f.read()
if 'Raspberry Pi' in cpuinfo or 'BCM' in cpuinfo:
return True
except:
pass
try:
# Prüfe auf ARM-Architektur
import platform
machine = platform.machine().lower()
if 'arm' in machine or 'aarch64' in machine:
return True
except:
pass
# Umgebungsvariable für manuelle Aktivierung
return os.getenv('FORCE_OPTIMIZED_MODE', '').lower() in ['true', '1', 'yes']
def should_use_optimized_config():
"""Bestimmt ob die optimierte Konfiguration verwendet werden soll"""
# Kommandozeilen-Argument prüfen
if '--optimized' in sys.argv:
return True
# Raspberry Pi-Erkennung
if detect_raspberry_pi():
return True
# Umgebungsvariable
if os.getenv('USE_OPTIMIZED_CONFIG', '').lower() in ['true', '1', 'yes']:
return True
# Schwache Hardware-Erkennung (weniger als 2GB RAM)
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 früh importieren (sichere Version)
if os.name == 'nt':
try:
@ -311,9 +429,75 @@ register_aggressive_shutdown()
# Flask-App initialisieren
app = Flask(__name__)
app.secret_key = SECRET_KEY
app.config["PERMANENT_SESSION_LIFETIME"] = SESSION_LIFETIME
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["WTF_CSRF_ENABLED"] = True
# ===== OPTIMIERTE KONFIGURATION ANWENDEN =====
# Prüfe ob optimierte Konfiguration verwendet werden soll
USE_OPTIMIZED_CONFIG = should_use_optimized_config()
if USE_OPTIMIZED_CONFIG:
app_logger.info("🚀 Aktiviere optimierte Konfiguration für schwache Hardware/Raspberry Pi")
# Optimierte Flask-Konfiguration anwenden
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,
"SQLALCHEMY_ECHO": OptimizedConfig.SQLALCHEMY_ECHO,
"SQLALCHEMY_TRACK_MODIFICATIONS": OptimizedConfig.SQLALCHEMY_TRACK_MODIFICATIONS,
"SQLALCHEMY_ENGINE_OPTIONS": OptimizedConfig.SQLALCHEMY_ENGINE_OPTIONS
})
# Session-Konfiguration
app.config["PERMANENT_SESSION_LIFETIME"] = SESSION_LIFETIME
app.config["WTF_CSRF_ENABLED"] = True
# Jinja2-Globals für optimierte Templates
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'
})
# Optimierte After-Request-Handler
@app.after_request
def add_optimized_cache_headers(response):
"""Fügt optimierte Cache-Header für statische Dateien hinzu"""
if request.endpoint == 'static' or '/static/' in request.path:
response.headers['Cache-Control'] = 'public, max-age=31536000'
response.headers['Vary'] = 'Accept-Encoding'
# Preload-Header für kritische Assets
if request.path.endswith(('.css', '.js')):
response.headers['X-Optimized-Asset'] = 'true'
return response
app_logger.info("✅ Optimierte Konfiguration aktiviert")
else:
# Standard-Konfiguration
app.config["PERMANENT_SESSION_LIFETIME"] = SESSION_LIFETIME
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["WTF_CSRF_ENABLED"] = True
# Standard Jinja2-Globals
app.jinja_env.globals.update({
'optimized_mode': False,
'use_minified_assets': False,
'disable_animations': False,
'limit_glassmorphism': False,
'base_template': 'base.html'
})
app_logger.info("📋 Standard-Konfiguration verwendet")
# Globale db-Variable für Kompatibilität mit init_simple_db.py
db = db_engine
@ -549,6 +733,23 @@ def format_datetime_filter(value, format='%d.%m.%Y %H:%M'):
return value
return value.strftime(format)
# Template-Helper für Optimierungsstatus
@app.template_global()
def is_optimized_mode():
"""Prüft ob die Anwendung im optimierten Modus läuft"""
return USE_OPTIMIZED_CONFIG
@app.template_global()
def get_optimization_info():
"""Gibt Optimierungsinformationen für Templates zurück"""
return {
'active': USE_OPTIMIZED_CONFIG,
'raspberry_pi': detect_raspberry_pi(),
'minified_assets': app.jinja_env.globals.get('use_minified_assets', False),
'disabled_animations': app.jinja_env.globals.get('disable_animations', False),
'limited_glassmorphism': app.jinja_env.globals.get('limit_glassmorphism', False)
}
# HTTP-Request/Response-Middleware für automatisches Debug-Logging
@app.before_request
def log_request_info():
@ -5610,6 +5811,22 @@ def admin_advanced_settings():
user_settings = session.get('user_settings', {})
optimization_settings = user_settings.get('optimization', default_settings)
# Performance-Optimierungs-Status hinzufügen
performance_optimization = {
'active': USE_OPTIMIZED_CONFIG,
'raspberry_pi_detected': detect_raspberry_pi(),
'forced_mode': os.getenv('FORCE_OPTIMIZED_MODE', '').lower() in ['true', '1', 'yes'],
'cli_mode': '--optimized' in sys.argv,
'current_settings': {
'minified_assets': app.jinja_env.globals.get('use_minified_assets', False),
'disabled_animations': app.jinja_env.globals.get('disable_animations', False),
'limited_glassmorphism': app.jinja_env.globals.get('limit_glassmorphism', False),
'template_caching': not app.config.get('TEMPLATES_AUTO_RELOAD', True),
'json_optimization': not app.config.get('JSON_SORT_KEYS', True),
'static_cache_age': app.config.get('SEND_FILE_MAX_AGE_DEFAULT', 0)
}
}
# System-Statistiken sammeln
stats = {
'total_users': db_session.query(User).count(),
@ -5659,6 +5876,7 @@ def admin_advanced_settings():
'admin_advanced_settings.html',
title='Erweiterte Einstellungen',
optimization_settings=optimization_settings,
performance_optimization=performance_optimization,
stats=stats,
maintenance_info=maintenance_info
)
@ -5668,6 +5886,59 @@ def admin_advanced_settings():
flash('Fehler beim Laden der erweiterten Einstellungen', 'error')
return redirect(url_for('admin_page'))
@app.route("/admin/performance-optimization")
@login_required
@admin_required
def admin_performance_optimization():
"""Performance-Optimierungs-Verwaltungsseite für Admins"""
try:
app_logger.info(f"🚀 Performance-Optimierung-Seite aufgerufen von Admin {current_user.username}")
# Aktuelle Optimierungseinstellungen sammeln
optimization_status = {
'mode_active': USE_OPTIMIZED_CONFIG,
'detection': {
'raspberry_pi': detect_raspberry_pi(),
'forced_mode': os.getenv('FORCE_OPTIMIZED_MODE', '').lower() in ['true', '1', 'yes'],
'cli_mode': '--optimized' in sys.argv,
'low_memory': False
},
'settings': {
'minified_assets': app.jinja_env.globals.get('use_minified_assets', False),
'disabled_animations': app.jinja_env.globals.get('disable_animations', False),
'limited_glassmorphism': app.jinja_env.globals.get('limit_glassmorphism', False),
'template_caching': not app.config.get('TEMPLATES_AUTO_RELOAD', True),
'json_optimization': not app.config.get('JSON_SORT_KEYS', True),
'debug_disabled': not app.config.get('DEBUG', False),
'secure_sessions': app.config.get('SESSION_COOKIE_SECURE', False)
},
'performance': {
'static_cache_age_hours': app.config.get('SEND_FILE_MAX_AGE_DEFAULT', 0) / 3600,
'max_upload_mb': app.config.get('MAX_CONTENT_LENGTH', 0) / (1024 * 1024) if app.config.get('MAX_CONTENT_LENGTH') else 0,
'sqlalchemy_echo': app.config.get('SQLALCHEMY_ECHO', True)
}
}
# Memory-Erkennung hinzufügen
try:
import psutil
memory_gb = psutil.virtual_memory().total / (1024**3)
optimization_status['detection']['low_memory'] = memory_gb < 2.0
optimization_status['system_memory_gb'] = round(memory_gb, 2)
except ImportError:
optimization_status['system_memory_gb'] = None
return render_template(
'admin_performance_optimization.html',
title='Performance-Optimierung',
optimization_status=optimization_status
)
except Exception as e:
app_logger.error(f"❌ Fehler beim Laden der Performance-Optimierung-Seite: {str(e)}")
flash('Fehler beim Laden der Performance-Optimierung-Seite', 'error')
return redirect(url_for('admin_page'))
@app.route('/api/admin/maintenance/cleanup-logs', methods=['POST'])
@login_required
@admin_required
@ -8100,6 +8371,128 @@ def api_admin_system_status():
}), 500
# ===== OPTIMIERUNGSSTATUS API =====
@app.route("/api/system/optimization-status", methods=['GET'])
def api_optimization_status():
"""
API-Endpunkt für den aktuellen Optimierungsstatus.
Gibt Informationen über aktivierte Optimierungen zurück.
"""
try:
status = {
"optimized_mode_active": USE_OPTIMIZED_CONFIG,
"hardware_detected": {
"is_raspberry_pi": detect_raspberry_pi(),
"forced_optimization": os.getenv('FORCE_OPTIMIZED_MODE', '').lower() in ['true', '1', 'yes'],
"cli_optimization": '--optimized' in sys.argv
},
"active_optimizations": {
"minified_assets": app.jinja_env.globals.get('use_minified_assets', False),
"disabled_animations": app.jinja_env.globals.get('disable_animations', False),
"limited_glassmorphism": app.jinja_env.globals.get('limit_glassmorphism', False),
"cache_headers": USE_OPTIMIZED_CONFIG,
"template_caching": not app.config.get('TEMPLATES_AUTO_RELOAD', True),
"json_optimization": not app.config.get('JSON_SORT_KEYS', True)
},
"performance_settings": {
"max_upload_mb": app.config.get('MAX_CONTENT_LENGTH', 0) / (1024 * 1024) if app.config.get('MAX_CONTENT_LENGTH') else None,
"static_cache_age": app.config.get('SEND_FILE_MAX_AGE_DEFAULT', 0),
"sqlalchemy_echo": app.config.get('SQLALCHEMY_ECHO', True),
"session_secure": app.config.get('SESSION_COOKIE_SECURE', False)
}
}
# Zusätzliche System-Informationen wenn verfügbar
try:
import psutil
import platform
status["system_info"] = {
"cpu_count": psutil.cpu_count(),
"memory_gb": round(psutil.virtual_memory().total / (1024**3), 2),
"platform": platform.machine(),
"system": platform.system()
}
except ImportError:
status["system_info"] = {"error": "psutil nicht verfügbar"}
return jsonify({
"success": True,
"status": status,
"timestamp": datetime.now().isoformat()
})
except Exception as e:
app_logger.error(f"Fehler beim Abrufen des Optimierungsstatus: {str(e)}")
return jsonify({
"success": False,
"error": str(e)
}), 500
@app.route("/api/admin/optimization/toggle", methods=['POST'])
@login_required
@admin_required
def api_admin_toggle_optimization():
"""
API-Endpunkt zum Umschalten der Optimierungen zur Laufzeit (nur Admins).
Achtung: Einige Optimierungen erfordern einen Neustart.
"""
try:
data = request.get_json() or {}
# Welche Optimierung soll umgeschaltet werden?
optimization_type = data.get('type')
enabled = data.get('enabled', True)
changes_made = []
restart_required = False
if optimization_type == 'animations':
app.jinja_env.globals['disable_animations'] = enabled
changes_made.append(f"Animationen {'deaktiviert' if enabled else 'aktiviert'}")
elif optimization_type == 'glassmorphism':
app.jinja_env.globals['limit_glassmorphism'] = enabled
changes_made.append(f"Glassmorphism {'begrenzt' if enabled else 'vollständig'}")
elif optimization_type == 'minified_assets':
app.jinja_env.globals['use_minified_assets'] = enabled
changes_made.append(f"Minifizierte Assets {'aktiviert' if enabled else 'deaktiviert'}")
elif optimization_type == 'template_caching':
app.config['TEMPLATES_AUTO_RELOAD'] = not enabled
changes_made.append(f"Template-Caching {'aktiviert' if enabled else 'deaktiviert'}")
restart_required = True
elif optimization_type == 'debug_mode':
app.config['DEBUG'] = not enabled
changes_made.append(f"Debug-Modus {'deaktiviert' if enabled else 'aktiviert'}")
restart_required = True
else:
return jsonify({
"success": False,
"error": "Unbekannter Optimierungstyp"
}), 400
app_logger.info(f"Admin {current_user.username} hat Optimierung '{optimization_type}' auf {enabled} gesetzt")
return jsonify({
"success": True,
"changes": changes_made,
"restart_required": restart_required,
"message": f"Optimierung '{optimization_type}' erfolgreich {'aktiviert' if enabled else 'deaktiviert'}"
})
except Exception as e:
app_logger.error(f"Fehler beim Umschalten der Optimierung: {str(e)}")
return jsonify({
"success": False,
"error": str(e)
}), 500
# ===== ÖFFENTLICHE STATISTIK-API =====
@app.route("/api/statistics/public", methods=['GET'])
def api_public_statistics():
@ -9030,6 +9423,22 @@ if __name__ == "__main__":
# Template-Hilfsfunktionen registrieren
register_template_helpers(app)
# Optimierungsstatus beim Start anzeigen
if USE_OPTIMIZED_CONFIG:
app_logger.info("🚀 === OPTIMIERTE KONFIGURATION AKTIV ===")
app_logger.info(f"📊 Hardware erkannt: Raspberry Pi={detect_raspberry_pi()}")
app_logger.info(f"⚙️ Erzwungen: {os.getenv('FORCE_OPTIMIZED_MODE', '').lower() in ['true', '1', 'yes']}")
app_logger.info(f"🔧 CLI-Parameter: {'--optimized' in sys.argv}")
app_logger.info("🔧 Aktive Optimierungen:")
app_logger.info(f" - Minifizierte Assets: {app.jinja_env.globals.get('use_minified_assets', False)}")
app_logger.info(f" - Animationen deaktiviert: {app.jinja_env.globals.get('disable_animations', False)}")
app_logger.info(f" - Glassmorphism begrenzt: {app.jinja_env.globals.get('limit_glassmorphism', False)}")
app_logger.info(f" - Template-Caching: {not app.config.get('TEMPLATES_AUTO_RELOAD', True)}")
app_logger.info(f" - Static Cache: {app.config.get('SEND_FILE_MAX_AGE_DEFAULT', 0) / 3600:.1f}h")
app_logger.info("🚀 ========================================")
else:
app_logger.info("📋 Standard-Konfiguration aktiv (keine Optimierungen)")
# Drucker-Monitor Steckdosen-Initialisierung beim Start
try:
app_logger.info("🖨️ Starte automatische Steckdosen-Initialisierung...")

View File

@ -930,9 +930,9 @@ EOF
}
install_python_packages() {
log "=== ROBUSTE PYTHON-PAKETE INSTALLATION ==="
log "=== TIMEOUT-GESICHERTE PYTHON-PAKETE INSTALLATION ==="
progress "Installiere Python-Pakete mit verbesserter Strategie..."
progress "Installiere Python-Pakete mit Timeout-Sicherung..."
if [ ! -f "$CURRENT_DIR/requirements.txt" ]; then
error "requirements.txt nicht gefunden: $CURRENT_DIR/requirements.txt"
@ -941,74 +941,48 @@ install_python_packages() {
# Kopiere requirements.txt
cp "$CURRENT_DIR/requirements.txt" "$APP_DIR/" 2>/dev/null || true
# Strategie 1: Direkte Installation mit pip
progress "Versuche direkte requirements.txt Installation..."
# Timeout-gesicherte pip-Optionen
local pip_opts="--break-system-packages --timeout 60 --retries 2 --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --no-cache-dir"
local pip_opts="--break-system-packages --timeout 120 --retries 5 --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org"
# Strategie 1: Direkte Installation mit Timeout
progress "Versuche direkte requirements.txt Installation (max 10 Minuten)..."
if python3 -m pip install $pip_opts -r "$CURRENT_DIR/requirements.txt"; then
if timeout 600 python3 -m pip install $pip_opts -r "$CURRENT_DIR/requirements.txt" >/dev/null 2>&1; then
success "✅ requirements.txt erfolgreich installiert"
return 0
else
warning "⚠️ Direkte Installation fehlgeschlagen - verwende Schritt-für-Schritt Installation"
warning "⚠️ Direkte Installation fehlgeschlagen oder Timeout - verwende minimale Installation"
# Strategie 2: Pakete in optimierter Reihenfolge installieren
progress "Installiere Core-Abhängigkeiten zuerst..."
# Strategie 2: Nur essenzielle Pakete (timeout-gesichert)
progress "Installiere nur essenzielle Flask-Komponenten..."
# Grundlegende Abhängigkeiten zuerst
local core_deps=(
"MarkupSafe==3.0.2"
"itsdangerous==2.2.0"
"click==8.1.7"
"Werkzeug==3.1.3"
"Jinja2==3.1.4"
"Flask==3.1.1"
# Minimale Flask-Installation für Funktionalität
local essential_deps=(
"Flask"
"Werkzeug"
"Jinja2"
"requests"
)
for dep in "${core_deps[@]}"; do
progress "Installiere: $dep"
retry_command "python3 -m pip install $pip_opts '$dep'" "$dep Installation"
local installed_count=0
for dep in "${essential_deps[@]}"; do
progress "Installiere essenzielle Abhängigkeit: $dep"
if timeout 120 python3 -m pip install $pip_opts "$dep" >/dev/null 2>&1; then
success "$dep erfolgreich installiert"
((installed_count++))
else
warning "⚠️ $dep Installation fehlgeschlagen oder Timeout"
debug "$dep Timeout oder Fehler bei Installation"
fi
done
# Flask-Extensions
progress "Installiere Flask-Extensions..."
local flask_extensions=(
"WTForms==3.1.2"
"Flask-WTF==1.2.1"
"Flask-Login==0.6.3"
)
for ext in "${flask_extensions[@]}"; do
progress "Installiere: $ext"
retry_command "python3 -m pip install $pip_opts '$ext'" "$ext Installation"
done
# Datenbank und Sicherheit
progress "Installiere Datenbank und Sicherheits-Komponenten..."
local db_security=(
"SQLAlchemy==2.0.36"
"cryptography==44.0.0"
"bcrypt==4.2.1"
)
for comp in "${db_security[@]}"; do
progress "Installiere: $comp"
retry_command "python3 -m pip install $pip_opts '$comp'" "$comp Installation"
done
# System-Abhängigkeiten
progress "Installiere System-Abhängigkeiten..."
local system_deps=(
"requests==2.32.3"
"psutil==6.1.1"
"python-dateutil==2.9.0"
"colorlog==6.9.0"
"gunicorn==23.0.0"
)
for dep in "${system_deps[@]}"; do
progress "Installiere: $dep"
retry_command "python3 -m pip install $pip_opts '$dep'" "$dep Installation" || warning "⚠️ $dep Installation fehlgeschlagen (optional)"
done
if [ $installed_count -eq 0 ]; then
warning "⚠️ Keine Python-Pakete konnten installiert werden"
warning "⚠️ System läuft möglicherweise mit bereits installierten Paketen"
info " → Manuell: pip3 install --break-system-packages Flask requests"
else
success "$installed_count von ${#essential_deps[@]} essenziellen Paketen installiert"
fi
fi
# Umfassende Validierung

File diff suppressed because one or more lines are too long