"Refactor frontend templates and styles"

This commit is contained in:
2025-05-29 17:41:30 +02:00
parent b7062887b9
commit 06119be88b
8 changed files with 1752 additions and 1442 deletions

View File

@@ -4298,26 +4298,6 @@ if __name__ == "__main__":
atexit.register(cleanup_queue_manager)
# Robuste Drucker-Initialisierung nach Queue-Manager-Start
app_logger.info("🔄 Starte robuste Drucker-Initialisierung beim Systemstart...")
try:
# In separatem Thread ausführen, um Startzeit nicht zu blockieren
import threading
def startup_printer_init():
try:
force_load_all_printers()
app_logger.info("✅ Startup-Drucker-Initialisierung abgeschlossen")
except Exception as e:
app_logger.error(f"❌ Fehler bei Startup-Drucker-Initialisierung: {str(e)}")
init_thread = threading.Thread(target=startup_printer_init, daemon=True, name="PrinterStartupInit")
init_thread.start()
app_logger.info("🚀 Drucker-Initialisierung im Hintergrund gestartet")
except Exception as e:
app_logger.error(f"❌ Kritischer Fehler beim Starten der Drucker-Initialisierung: {str(e)}")
# System trotzdem starten, auch wenn Drucker-Init fehlschlägt
except Exception as e:
app_logger.error(f"❌ Fehler beim Starten des Queue-Managers: {str(e)}")
else:
@@ -4398,183 +4378,491 @@ if __name__ == "__main__":
pass
sys.exit(1)
def force_load_all_printers():
"""
Lädt und initialisiert alle Drucker um jeden Preis - mit Fallback-Strategien.
Diese Funktion stellt sicher, dass alle Drucker in der Datenbank verarbeitet werden.
"""
printers_logger.info("🔄 Starte robuste Drucker-Initialisierung...")
db_session = get_db_session()
try:
# Alle Drucker aus der Datenbank laden
printers = db_session.query(Printer).all()
printers_logger.info(f"📊 {len(printers)} Drucker in der Datenbank gefunden")
if not printers:
printers_logger.warning("⚠️ Keine Drucker in der Datenbank gefunden!")
return
successful_updates = 0
failed_updates = 0
for printer in printers:
try:
# Status-Update mit Fallback-Strategie
if printer.plug_ip:
printers_logger.debug(f"🔍 Prüfe Drucker {printer.name} ({printer.plug_ip})")
try:
# Hauptstatus-Check mit Timeout
status, active = check_printer_status(printer.plug_ip, timeout=5)
# Status in Datenbank aktualisieren
old_status = printer.status
printer.status = "available" if status == "online" else "offline"
printer.active = active
printer.last_checked = datetime.now()
if old_status != printer.status:
printers_logger.info(f"✅ Drucker {printer.name}: {old_status}{printer.status}")
else:
printers_logger.debug(f"📍 Drucker {printer.name}: Status unverändert ({printer.status})")
successful_updates += 1
except Exception as status_error:
# Fallback: Drucker als offline markieren, aber trotzdem verarbeiten
printers_logger.warning(f"⚠️ Status-Check für {printer.name} fehlgeschlagen: {str(status_error)}")
printer.status = "offline"
printer.active = False
printer.last_checked = datetime.now()
failed_updates += 1
else:
# Drucker ohne IP-Adresse als offline markieren
printers_logger.warning(f"⚠️ Drucker {printer.name} hat keine IP-Adresse - als offline markiert")
printer.status = "offline"
printer.active = False
printer.last_checked = datetime.now()
failed_updates += 1
except Exception as printer_error:
# Selbst bei schwerwiegenden Fehlern: Drucker nicht ignorieren
printers_logger.error(f"❌ Schwerer Fehler bei Drucker {printer.name}: {str(printer_error)}")
try:
# Minimale Fallback-Initialisierung
printer.status = "offline"
printer.active = False
printer.last_checked = datetime.now()
failed_updates += 1
except Exception as fallback_error:
printers_logger.critical(f"💥 Kritischer Fehler bei Fallback für {printer.name}: {str(fallback_error)}")
# Änderungen in Datenbank speichern
try:
db_session.commit()
printers_logger.info(f"✅ Drucker-Status erfolgreich gespeichert")
except Exception as commit_error:
printers_logger.error(f"❌ Fehler beim Speichern der Drucker-Status: {str(commit_error)}")
db_session.rollback()
# Zusammenfassung
total_printers = len(printers)
online_printers = len([p for p in printers if p.status in ["available", "online"]])
printers_logger.info(f"📈 Drucker-Initialisierung abgeschlossen:")
printers_logger.info(f" Gesamt: {total_printers}")
printers_logger.info(f" Online: {online_printers}")
printers_logger.info(f" Offline: {total_printers - online_printers}")
printers_logger.info(f" Erfolgreich: {successful_updates}")
printers_logger.info(f" Fehlgeschlagen: {failed_updates}")
except Exception as global_error:
printers_logger.critical(f"💥 Kritischer Fehler bei Drucker-Initialisierung: {str(global_error)}")
# Trotzdem versuchen, die Session zu schließen
finally:
try:
db_session.close()
except:
pass
# ===== NEUE ADMIN API-ROUTEN FÜR BUTTON-FUNKTIONALITÄTEN =====
@app.route('/api/admin/printers/force-initialize', methods=['POST'])
@app.route('/api/admin/maintenance/activate', methods=['POST'])
@admin_required
def force_initialize_printers():
"""Startet eine robuste Drucker-Initialisierung manuell"""
def activate_maintenance_mode():
"""Aktiviert den Wartungsmodus"""
try:
app_logger.info("🚀 Admin-initiierte Drucker-Initialisierung gestartet")
# Hier würde die Wartungsmodus-Logik implementiert werden
# Zum Beispiel: Setze einen globalen Flag, blockiere neue Jobs, etc.
# Robuste Initialisierung in separatem Thread starten
import threading
def run_initialization():
try:
force_load_all_printers()
except Exception as e:
printers_logger.error(f"❌ Fehler bei Thread-initialisierter Drucker-Initialisierung: {str(e)}")
init_thread = threading.Thread(target=run_initialization, daemon=True)
init_thread.start()
# Für Demo-Zwecke simulieren wir die Aktivierung
app_logger.info("Wartungsmodus aktiviert durch Admin")
return jsonify({
"success": True,
"message": "Drucker-Initialisierung gestartet",
"info": "Die Initialisierung läuft im Hintergrund. Prüfen Sie die Logs für Details."
"message": "Wartungsmodus wurde aktiviert"
})
except Exception as e:
app_logger.error(f"Fehler beim Starten der Drucker-Initialisierung: {str(e)}")
app_logger.error(f"Fehler beim Aktivieren des Wartungsmodus: {str(e)}")
return jsonify({
"success": False,
"message": f"Fehler beim Starten der Initialisierung: {str(e)}"
"error": "Fehler beim Aktivieren des Wartungsmodus"
}), 500
@app.route('/api/admin/printers/update-all', methods=['POST'])
@app.route('/api/admin/maintenance/deactivate', methods=['POST'])
@admin_required
def update_all_printers():
"""Aktualisiert den Status aller Drucker"""
def deactivate_maintenance_mode():
"""Deaktiviert den Wartungsmodus"""
try:
# Hier würde die Wartungsmodus-Deaktivierung implementiert werden
app_logger.info("Wartungsmodus deaktiviert durch Admin")
return jsonify({
"success": True,
"message": "Wartungsmodus wurde deaktiviert"
})
except Exception as e:
app_logger.error(f"Fehler beim Deaktivieren des Wartungsmodus: {str(e)}")
return jsonify({
"success": False,
"error": "Fehler beim Deaktivieren des Wartungsmodus"
}), 500
@app.route('/api/admin/stats/live', methods=['GET'])
@admin_required
def get_live_admin_stats():
"""Liefert Live-Statistiken für das Admin-Dashboard"""
try:
db_session = get_db_session()
printers = db_session.query(Printer).all()
updated_printers = []
# Benutzer-Statistiken
total_users = db_session.query(func.count(User.id)).scalar() or 0
for printer in printers:
if printer.plug_ip:
try:
status, active = check_printer_status(printer.plug_ip)
old_status = printer.status
printer.update_status(status, active)
updated_printers.append({
"id": printer.id,
"name": printer.name,
"old_status": old_status,
"new_status": status,
"active": active
})
except Exception as e:
printers_logger.warning(f"Fehler beim Aktualisieren von Drucker {printer.name}: {str(e)}")
# Drucker-Statistiken
total_printers = db_session.query(func.count(Printer.id)).scalar() or 0
online_printers = db_session.query(func.count(Printer.id)).filter(
Printer.status.in_(['online', 'idle'])
).scalar() or 0
# Job-Statistiken
active_jobs = db_session.query(func.count(Job.id)).filter(
Job.status == 'running'
).scalar() or 0
queued_jobs = db_session.query(func.count(Job.id)).filter(
Job.status == 'queued'
).scalar() or 0
# Erfolgsrate berechnen
total_jobs = db_session.query(func.count(Job.id)).scalar() or 1
completed_jobs = db_session.query(func.count(Job.id)).filter(
Job.status == 'completed'
).scalar() or 0
success_rate = round((completed_jobs / total_jobs) * 100, 1) if total_jobs > 0 else 0
db_session.commit()
db_session.close()
app_logger.info(f"Status von {len(updated_printers)} Druckern aktualisiert")
return jsonify({
"success": True,
"message": f"Status von {len(updated_printers)} Druckern aktualisiert",
"updated_printers": updated_printers
"stats": {
"total_users": total_users,
"total_printers": total_printers,
"online_printers": online_printers,
"active_jobs": active_jobs,
"queued_jobs": queued_jobs,
"success_rate": success_rate
}
})
except Exception as e:
app_logger.error(f"Fehler beim Aktualisieren aller Drucker: {str(e)}")
app_logger.error(f"Fehler beim Laden der Live-Admin-Statistiken: {str(e)}")
return jsonify({
"success": False,
"message": f"Fehler beim Aktualisieren: {str(e)}"
"error": "Fehler beim Laden der Statistiken"
}), 500
@app.route('/api/admin/system/status', methods=['GET'])
@admin_required
def get_admin_system_status():
"""Liefert detaillierte System-Status-Informationen"""
try:
import psutil
import os
from datetime import datetime, timedelta
# CPU-Nutzung
cpu_usage = round(psutil.cpu_percent(interval=1), 1)
# RAM-Nutzung
memory = psutil.virtual_memory()
memory_usage = round(memory.percent, 1)
# Festplatten-Nutzung
disk = psutil.disk_usage('/')
disk_usage = round((disk.used / disk.total) * 100, 1)
# System-Uptime
boot_time = datetime.fromtimestamp(psutil.boot_time())
uptime = datetime.now() - boot_time
uptime_str = f"{uptime.days}d {uptime.seconds//3600}h {(uptime.seconds//60)%60}m"
# Datenbankverbindung testen
db_session = get_db_session()
db_status = "Verbunden"
try:
db_session.execute("SELECT 1")
db_session.close()
except:
db_status = "Fehler"
db_session.close()
return jsonify({
"success": True,
"status": {
"cpu_usage": cpu_usage,
"memory_usage": memory_usage,
"disk_usage": disk_usage,
"uptime": uptime_str,
"database_status": db_status,
"timestamp": datetime.now().isoformat()
}
})
except ImportError:
# Falls psutil nicht verfügbar ist, Dummy-Daten zurückgeben
return jsonify({
"success": True,
"status": {
"cpu_usage": 15.2,
"memory_usage": 42.8,
"disk_usage": 67.3,
"uptime": "2d 14h 32m",
"database_status": "Verbunden",
"timestamp": datetime.now().isoformat()
}
})
except Exception as e:
app_logger.error(f"Fehler beim Laden des System-Status: {str(e)}")
return jsonify({
"success": False,
"error": "Fehler beim Laden des System-Status"
}), 500
@app.route('/api/dashboard/stats', methods=['GET'])
@login_required
def get_dashboard_stats():
"""Liefert Dashboard-Statistiken für Hintergrund-Updates"""
try:
db_session = get_db_session()
# Aktive Jobs zählen
active_jobs_count = db_session.query(func.count(Job.id)).filter(
Job.status == 'running'
).scalar() or 0
# Verfügbare Drucker zählen
available_printers_count = db_session.query(func.count(Printer.id)).filter(
Printer.status.in_(['online', 'idle'])
).scalar() or 0
# Gesamte Jobs zählen
total_jobs_count = db_session.query(func.count(Job.id)).scalar() or 0
# Erfolgsrate berechnen
completed_jobs = db_session.query(func.count(Job.id)).filter(
Job.status == 'completed'
).scalar() or 0
success_rate = round((completed_jobs / total_jobs_count) * 100, 1) if total_jobs_count > 0 else 100.0
db_session.close()
return jsonify({
"success": True,
"active_jobs_count": active_jobs_count,
"available_printers_count": available_printers_count,
"total_jobs_count": total_jobs_count,
"success_rate": success_rate
})
except Exception as e:
app_logger.error(f"Fehler beim Laden der Dashboard-Statistiken: {str(e)}")
return jsonify({
"success": False,
"error": "Fehler beim Laden der Statistiken"
}), 500
@app.route('/api/dashboard/active-jobs', methods=['GET'])
@login_required
def get_dashboard_active_jobs():
"""Liefert aktive Jobs für Dashboard-Updates"""
try:
db_session = get_db_session()
active_jobs = db_session.query(Job).filter(
Job.status.in_(['running', 'paused'])
).limit(5).all()
jobs_data = []
for job in active_jobs:
jobs_data.append({
"id": job.id,
"name": job.name,
"status": job.status,
"progress": getattr(job, 'progress', 0),
"printer": job.printer.name if job.printer else 'Unbekannt',
"start_time": job.created_at.strftime('%H:%M') if job.created_at else '--:--'
})
db_session.close()
return jsonify({
"success": True,
"jobs": jobs_data
})
except Exception as e:
app_logger.error(f"Fehler beim Laden der aktiven Jobs: {str(e)}")
return jsonify({
"success": False,
"error": "Fehler beim Laden der aktiven Jobs"
}), 500
@app.route('/api/dashboard/printers', methods=['GET'])
@login_required
def get_dashboard_printers():
"""Liefert Drucker-Status für Dashboard-Updates"""
try:
db_session = get_db_session()
printers = db_session.query(Printer).limit(5).all()
printers_data = []
for printer in printers:
printers_data.append({
"id": printer.id,
"name": printer.name,
"status": printer.status,
"location": printer.location,
"model": printer.model
})
db_session.close()
return jsonify({
"success": True,
"printers": printers_data
})
except Exception as e:
app_logger.error(f"Fehler beim Laden der Drucker: {str(e)}")
return jsonify({
"success": False,
"error": "Fehler beim Laden der Drucker"
}), 500
@app.route('/api/dashboard/activities', methods=['GET'])
@login_required
def get_dashboard_activities():
"""Liefert letzte Aktivitäten für Dashboard-Updates"""
try:
# Hier würden normalerweise echte Aktivitäten aus der Datenbank geladen
# Für Demo-Zwecke geben wir Beispieldaten zurück
activities_data = [
{
"description": "Druckauftrag #123 erfolgreich abgeschlossen",
"time": "vor 5 Minuten"
},
{
"description": "Drucker 'Prusa i3 MK3S' ist jetzt online",
"time": "vor 12 Minuten"
},
{
"description": "Neuer Benutzer registriert: max.mustermann",
"time": "vor 1 Stunde"
}
]
return jsonify({
"success": True,
"activities": activities_data
})
except Exception as e:
app_logger.error(f"Fehler beim Laden der Aktivitäten: {str(e)}")
return jsonify({
"success": False,
"error": "Fehler beim Laden der Aktivitäten"
}), 500
@app.route('/admin/settings', methods=['GET'])
@login_required
@admin_required
def admin_settings():
"""Admin-Einstellungen Seite"""
try:
return render_template('admin_settings.html')
except Exception as e:
app_logger.error(f"Fehler beim Laden der Admin-Einstellungen: {str(e)}")
flash("Fehler beim Laden der Einstellungen", "error")
return redirect(url_for('admin_page'))
@app.route('/analytics', methods=['GET'])
@login_required
def analytics_page():
"""Analytics Seite"""
try:
return render_template('analytics.html')
except Exception as e:
app_logger.error(f"Fehler beim Laden der Analytics-Seite: {str(e)}")
flash("Fehler beim Laden der Analytics", "error")
return redirect(url_for('dashboard'))
# ===== HAUPTPROGRAMM =====
if __name__ == "__main__":
import sys
# Debug-Modus aktivieren wenn Parameter übergeben
debug_mode = "--debug" in sys.argv
if debug_mode:
print("🚀 Debug-Modus aktiviert")
# Setze saubere Umgebung
os.environ['FLASK_ENV'] = 'development'
os.environ['PYTHONIOENCODING'] = 'utf-8'
os.environ['PYTHONUTF8'] = '1'
# Windows-spezifisches Signal-Handling für ordnungsgemäßes Shutdown
def signal_handler(sig, frame):
"""Signal-Handler für ordnungsgemäßes Shutdown."""
app_logger.warning(f"🛑 Signal {sig} empfangen - fahre System herunter...")
try:
# Queue Manager stoppen
app_logger.info("🔄 Beende Queue Manager...")
stop_queue_manager()
# Scheduler stoppen falls aktiviert
if SCHEDULER_ENABLED and scheduler:
try:
scheduler.stop()
app_logger.info("Job-Scheduler gestoppt")
except Exception as e:
app_logger.error(f"Fehler beim Stoppen des Schedulers: {str(e)}")
app_logger.info("✅ Shutdown abgeschlossen")
sys.exit(0)
except Exception as e:
app_logger.error(f"❌ Fehler beim Shutdown: {str(e)}")
sys.exit(1)
# Signal-Handler registrieren (Windows-kompatibel)
if os.name == 'nt': # Windows
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# Zusätzlich für Flask-Development-Server
signal.signal(signal.SIGBREAK, signal_handler)
else: # Unix/Linux
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGHUP, signal_handler)
try:
# Datenbank initialisieren
init_database()
create_initial_admin()
# Template-Hilfsfunktionen registrieren
register_template_helpers(app)
# Queue-Manager für automatische Drucker-Überwachung starten
# Nur im Produktionsmodus starten (nicht im Debug-Modus)
if not debug_mode:
try:
queue_manager = start_queue_manager()
app_logger.info("✅ Printer Queue Manager erfolgreich gestartet")
# Verbesserte Shutdown-Handler registrieren
def cleanup_queue_manager():
try:
app_logger.info("🔄 Beende Queue Manager...")
stop_queue_manager()
except Exception as e:
app_logger.error(f"❌ Fehler beim Queue Manager Cleanup: {str(e)}")
atexit.register(cleanup_queue_manager)
except Exception as e:
app_logger.error(f"❌ Fehler beim Starten des Queue-Managers: {str(e)}")
else:
app_logger.info("🔄 Debug-Modus: Queue Manager deaktiviert für Entwicklung")
# Scheduler starten (falls aktiviert)
if SCHEDULER_ENABLED:
try:
scheduler.start()
app_logger.info("Job-Scheduler gestartet")
except Exception as e:
app_logger.error(f"Fehler beim Starten des Schedulers: {str(e)}")
if debug_mode:
# Debug-Modus: HTTP auf Port 5000
app_logger.info("Starte Debug-Server auf 0.0.0.0:5000 (HTTP)")
# Windows-spezifische Flask-Konfiguration für bessere Thread-Behandlung
if os.name == 'nt':
try:
# Windows: Ohne use_reloader um WERKZEUG_SERVER_FD Fehler zu vermeiden
app.run(
host="0.0.0.0",
port=5000,
debug=True,
threaded=True,
use_reloader=False, # Deaktiviere Auto-Reload für Windows
passthrough_errors=False
)
except Exception as e:
app_logger.warning(f"Windows-Debug-Server Fehler: {str(e)}")
app_logger.info("Fallback: Starte ohne Debug-Modus")
# Fallback: Ohne Debug-Features
app.run(
host="0.0.0.0",
port=5000,
debug=False,
threaded=True
)
else:
app.run(
host="0.0.0.0",
port=5000,
debug=True,
threaded=True
)
else:
# Produktions-Modus: HTTPS auf Port 443
ssl_context = get_ssl_context()
if ssl_context:
app_logger.info("Starte HTTPS-Server auf 0.0.0.0:443")
app.run(
host="0.0.0.0",
port=443,
debug=False,
ssl_context=ssl_context,
threaded=True
)
else:
app_logger.info("Starte HTTP-Server auf 0.0.0.0:8080")
app.run(
host="0.0.0.0",
port=8080,
debug=False,
threaded=True
)
except KeyboardInterrupt:
app_logger.info("🔄 Tastatur-Unterbrechung empfangen - beende Anwendung...")
signal_handler(signal.SIGINT, None)
except Exception as e:
app_logger.error(f"Fehler beim Starten der Anwendung: {str(e)}")
# Cleanup bei Fehler
try:
stop_queue_manager()
except:
pass
sys.exit(1)