📝 "Fix: Resolve issues with database shm andwal files in FEHLER_BEHOBEN.md, app.py, and templates/jobs.html (#123)"

This commit is contained in:
2025-05-30 19:21:15 +02:00
parent f8be70e1f7
commit 4d7d057805
5 changed files with 858 additions and 466 deletions

View File

@@ -2293,461 +2293,7 @@ def finish_job(job_id):
jobs_logger.error(f"Fehler beim manuellen Beenden von Job {job_id}: {str(e)}")
return jsonify({"error": "Interner Serverfehler", "details": str(e)}), 500
# ===== DRUCKER-ROUTEN =====
@app.route("/api/printers", methods=["GET"])
@login_required
def get_printers():
"""Gibt alle Drucker zurück - OHNE Status-Check für schnelleres Laden."""
db_session = get_db_session()
try:
# Windows-kompatible Timeout-Implementierung
import threading
import time
printers = None
timeout_occurred = False
def fetch_printers():
nonlocal printers, timeout_occurred
try:
printers = db_session.query(Printer).all()
except Exception as e:
printers_logger.error(f"Datenbankfehler beim Laden der Drucker: {str(e)}")
timeout_occurred = True
# Starte Datenbankabfrage in separatem Thread
thread = threading.Thread(target=fetch_printers)
thread.daemon = True
thread.start()
thread.join(timeout=5) # 5 Sekunden Timeout
if thread.is_alive() or timeout_occurred or printers is None:
printers_logger.warning("Database timeout when fetching printers for basic loading")
return jsonify({
'error': 'Database timeout beim Laden der Drucker',
'timeout': True,
'printers': []
}), 408
# Drucker-Daten OHNE Status-Check zusammenstellen für schnelles Laden
printer_data = []
current_time = datetime.now()
for printer in printers:
printer_data.append({
"id": printer.id,
"name": printer.name,
"model": printer.model or 'Unbekanntes Modell',
"location": printer.location or 'Unbekannter Standort',
"mac_address": printer.mac_address,
"plug_ip": printer.plug_ip,
"status": printer.status or "offline", # Letzter bekannter Status
"active": printer.active if hasattr(printer, 'active') else True,
"ip_address": printer.plug_ip if printer.plug_ip else getattr(printer, 'ip_address', None),
"created_at": printer.created_at.isoformat() if printer.created_at else current_time.isoformat(),
"last_checked": printer.last_checked.isoformat() if hasattr(printer, 'last_checked') and printer.last_checked else None
})
db_session.close()
printers_logger.info(f"Schnelles Laden abgeschlossen: {len(printer_data)} Drucker geladen (ohne Status-Check)")
return jsonify({
"printers": printer_data,
"count": len(printer_data),
"message": "Drucker erfolgreich geladen"
})
except Exception as e:
db_session.rollback()
db_session.close()
printers_logger.error(f"Fehler beim Abrufen der Drucker: {str(e)}")
return jsonify({
"error": f"Fehler beim Laden der Drucker: {str(e)}",
"printers": []
}), 500
@app.route("/api/printers/status", methods=["GET"])
@login_required
@measure_execution_time(logger=printers_logger, task_name="API-Drucker-Status-Abfrage")
def get_printers_with_status():
"""Gibt alle Drucker MIT aktuellem Status-Check zurück - für Aktualisierung."""
db_session = get_db_session()
try:
# Windows-kompatible Timeout-Implementierung
import threading
import time
printers = None
timeout_occurred = False
def fetch_printers():
nonlocal printers, timeout_occurred
try:
printers = db_session.query(Printer).all()
except Exception as e:
printers_logger.error(f"Datenbankfehler beim Status-Check: {str(e)}")
timeout_occurred = True
# Starte Datenbankabfrage in separatem Thread
thread = threading.Thread(target=fetch_printers)
thread.daemon = True
thread.start()
thread.join(timeout=8) # 8 Sekunden Timeout für Status-Check
if thread.is_alive() or timeout_occurred or printers is None:
printers_logger.warning("Database timeout when fetching printers for status check")
return jsonify({
'error': 'Database timeout beim Status-Check der Drucker',
'timeout': True
}), 408
# Drucker-Daten für Status-Check vorbereiten
printer_data = []
for printer in printers:
# Verwende plug_ip als primäre IP-Adresse, fallback auf ip_address
ip_to_check = printer.plug_ip if printer.plug_ip else getattr(printer, 'ip_address', None)
printer_data.append({
'id': printer.id,
'name': printer.name,
'ip_address': ip_to_check,
'location': printer.location,
'model': printer.model
})
# Status aller Drucker parallel überprüfen mit 7-Sekunden-Timeout
printers_logger.info(f"Starte Status-Check für {len(printer_data)} Drucker mit 7-Sekunden-Timeout")
# Fallback: Wenn keine IP-Adressen vorhanden sind, alle als offline markieren
if not any(p['ip_address'] for p in printer_data):
printers_logger.warning("Keine IP-Adressen für Drucker gefunden - alle als offline markiert")
status_results = {p['id']: ("offline", False) for p in printer_data}
else:
try:
status_results = check_multiple_printers_status(printer_data, timeout=7)
except Exception as e:
printers_logger.error(f"Fehler beim Status-Check: {str(e)}")
# Fallback: alle als offline markieren
status_results = {p['id']: ("offline", False) for p in printer_data}
# Ergebnisse zusammenstellen und Datenbank aktualisieren
status_data = []
current_time = datetime.now()
for printer in printers:
if printer.id in status_results:
status, active = status_results[printer.id]
# Mapping für Frontend-Kompatibilität
if status == "online":
frontend_status = "available"
else:
frontend_status = "offline"
else:
# Fallback falls kein Ergebnis vorliegt
frontend_status = "offline"
active = False
# Status in der Datenbank aktualisieren
printer.status = frontend_status
printer.active = active
# Setze last_checked falls das Feld existiert
if hasattr(printer, 'last_checked'):
printer.last_checked = current_time
status_data.append({
"id": printer.id,
"name": printer.name,
"model": printer.model or 'Unbekanntes Modell',
"location": printer.location or 'Unbekannter Standort',
"mac_address": printer.mac_address,
"plug_ip": printer.plug_ip,
"status": frontend_status,
"active": active,
"ip_address": printer.plug_ip if printer.plug_ip else getattr(printer, 'ip_address', None),
"created_at": printer.created_at.isoformat() if printer.created_at else current_time.isoformat(),
"last_checked": current_time.isoformat()
})
# Speichere die aktualisierten Status
try:
db_session.commit()
printers_logger.info("Drucker-Status erfolgreich in Datenbank aktualisiert")
except Exception as e:
printers_logger.warning(f"Fehler beim Speichern der Status-Updates: {str(e)}")
# Nicht kritisch, Status-Check kann trotzdem zurückgegeben werden
db_session.close()
online_count = len([s for s in status_data if s['status'] == 'available'])
printers_logger.info(f"Status-Check abgeschlossen: {online_count} von {len(status_data)} Drucker online")
return jsonify(status_data)
except Exception as e:
db_session.rollback()
db_session.close()
printers_logger.error(f"Fehler beim Status-Check der Drucker: {str(e)}")
return jsonify({
"error": f"Fehler beim Status-Check: {str(e)}",
"printers": []
}), 500
@app.route("/api/jobs/current", methods=["GET"])
@login_required
def get_current_job():
"""Gibt den aktuellen Job des Benutzers zurück."""
db_session = get_db_session()
try:
current_job = db_session.query(Job).filter(
Job.user_id == int(current_user.id),
Job.status.in_(["scheduled", "running"])
).order_by(Job.start_at).first()
if current_job:
job_data = current_job.to_dict()
else:
job_data = None
db_session.close()
return jsonify(job_data)
except Exception as e:
db_session.close()
return jsonify({"error": str(e)}), 500
# ===== WEITERE API-ROUTEN =====
@app.route("/api/printers/<int:printer_id>", methods=["GET"])
@login_required
def get_printer(printer_id):
"""Gibt einen spezifischen Drucker zurück."""
db_session = get_db_session()
try:
printer = db_session.query(Printer).get(printer_id)
if not printer:
db_session.close()
return jsonify({"error": "Drucker nicht gefunden"}), 404
# Status-Check für diesen Drucker
ip_to_check = printer.plug_ip if printer.plug_ip else getattr(printer, 'ip_address', None)
if ip_to_check:
status, active = check_printer_status(ip_to_check)
printer.status = "available" if status == "online" else "offline"
printer.active = active
db_session.commit()
printer_data = {
"id": printer.id,
"name": printer.name,
"model": printer.model or 'Unbekanntes Modell',
"location": printer.location or 'Unbekannter Standort',
"mac_address": printer.mac_address,
"plug_ip": printer.plug_ip,
"status": printer.status or "offline",
"active": printer.active if hasattr(printer, 'active') else True,
"ip_address": ip_to_check,
"created_at": printer.created_at.isoformat() if printer.created_at else datetime.now().isoformat()
}
db_session.close()
return jsonify(printer_data)
except Exception as e:
db_session.close()
printers_logger.error(f"Fehler beim Abrufen des Druckers {printer_id}: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
@app.route("/api/printers", methods=["POST"])
@login_required
def create_printer():
"""Erstellt einen neuen Drucker (nur für Admins)."""
if not current_user.is_admin:
return jsonify({"error": "Nur Administratoren können Drucker erstellen"}), 403
try:
data = request.json
# Pflichtfelder prüfen
required_fields = ["name", "plug_ip"]
for field in required_fields:
if field not in data:
return jsonify({"error": f"Feld '{field}' fehlt"}), 400
db_session = get_db_session()
# Prüfen, ob bereits ein Drucker mit diesem Namen existiert
existing_printer = db_session.query(Printer).filter(Printer.name == data["name"]).first()
if existing_printer:
db_session.close()
return jsonify({"error": "Ein Drucker mit diesem Namen existiert bereits"}), 400
# Neuen Drucker erstellen
new_printer = Printer(
name=data["name"],
model=data.get("model", ""),
location=data.get("location", ""),
mac_address=data.get("mac_address", ""),
plug_ip=data["plug_ip"],
status="offline",
active=True, # Neue Drucker sind standardmäßig aktiv
created_at=datetime.now()
)
db_session.add(new_printer)
db_session.commit()
# Sofortiger Status-Check für den neuen Drucker
ip_to_check = new_printer.plug_ip
if ip_to_check:
status, active = check_printer_status(ip_to_check)
new_printer.status = "available" if status == "online" else "offline"
new_printer.active = active
db_session.commit()
printer_data = {
"id": new_printer.id,
"name": new_printer.name,
"model": new_printer.model,
"location": new_printer.location,
"mac_address": new_printer.mac_address,
"plug_ip": new_printer.plug_ip,
"status": new_printer.status,
"active": new_printer.active,
"created_at": new_printer.created_at.isoformat()
}
db_session.close()
printers_logger.info(f"Neuer Drucker '{new_printer.name}' erstellt von Admin {current_user.id}")
return jsonify({"printer": printer_data, "message": "Drucker erfolgreich erstellt"}), 201
except Exception as e:
printers_logger.error(f"Fehler beim Erstellen eines Druckers: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
@app.route("/api/printers/add", methods=["POST"])
@login_required
def add_printer():
"""Alternativer Endpunkt zum Hinzufügen von Druckern (für Frontend-Kompatibilität)."""
return create_printer()
@app.route("/api/printers/<int:printer_id>", methods=["PUT"])
@login_required
def update_printer(printer_id):
"""Aktualisiert einen Drucker (nur für Admins)."""
if not current_user.is_admin:
return jsonify({"error": "Nur Administratoren können Drucker bearbeiten"}), 403
try:
data = request.json
db_session = get_db_session()
printer = db_session.query(Printer).get(printer_id)
if not printer:
db_session.close()
return jsonify({"error": "Drucker nicht gefunden"}), 404
# Aktualisierbare Felder
updatable_fields = ["name", "model", "location", "mac_address", "plug_ip"]
for field in updatable_fields:
if field in data:
setattr(printer, field, data[field])
db_session.commit()
printer_data = {
"id": printer.id,
"name": printer.name,
"model": printer.model,
"location": printer.location,
"mac_address": printer.mac_address,
"plug_ip": printer.plug_ip,
"status": printer.status,
"created_at": printer.created_at.isoformat() if printer.created_at else datetime.now().isoformat()
}
db_session.close()
printers_logger.info(f"Drucker {printer_id} aktualisiert von Admin {current_user.id}")
return jsonify({"printer": printer_data})
except Exception as e:
printers_logger.error(f"Fehler beim Aktualisieren des Druckers {printer_id}: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
@app.route("/api/printers/<int:printer_id>", methods=["DELETE"])
@login_required
def delete_printer(printer_id):
"""Löscht einen Drucker (nur für Admins)."""
if not current_user.is_admin:
return jsonify({"error": "Nur Administratoren können Drucker löschen"}), 403
try:
db_session = get_db_session()
printer = db_session.query(Printer).get(printer_id)
if not printer:
db_session.close()
return jsonify({"error": "Drucker nicht gefunden"}), 404
# Prüfen, ob noch aktive Jobs für diesen Drucker existieren
active_jobs = db_session.query(Job).filter(
Job.printer_id == printer_id,
Job.status.in_(["scheduled", "running"])
).count()
if active_jobs > 0:
db_session.close()
return jsonify({"error": f"Drucker kann nicht gelöscht werden: {active_jobs} aktive Jobs vorhanden"}), 400
printer_name = printer.name
db_session.delete(printer)
db_session.commit()
db_session.close()
printers_logger.info(f"Drucker '{printer_name}' (ID: {printer_id}) gelöscht von Admin {current_user.id}")
return jsonify({"message": "Drucker erfolgreich gelöscht"})
except Exception as e:
printers_logger.error(f"Fehler beim Löschen des Druckers {printer_id}: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
@app.route("/api/jobs/<int:job_id>", methods=["DELETE"])
@login_required
@job_owner_required
def delete_job(job_id):
"""Löscht einen Job."""
try:
db_session = get_db_session()
job = db_session.query(Job).get(job_id)
if not job:
db_session.close()
return jsonify({"error": "Job nicht gefunden"}), 404
# Prüfen, ob der Job gelöscht werden kann
if job.status == "running":
db_session.close()
return jsonify({"error": "Laufende Jobs können nicht gelöscht werden"}), 400
job_name = job.name
db_session.delete(job)
db_session.commit()
db_session.close()
jobs_logger.info(f"Job '{job_name}' (ID: {job_id}) gelöscht von Benutzer {current_user.id}")
return jsonify({"message": "Job erfolgreich gelöscht"})
except Exception as e:
jobs_logger.error(f"Fehler beim Löschen des Jobs {job_id}: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
@app.route("/api/jobs/<int:job_id>/cancel", methods=["POST"])
@app.route('/api/jobs/<int:job_id>/cancel', methods=['POST'])
@login_required
@job_owner_required
def cancel_job(job_id):
@@ -2786,6 +2332,160 @@ def cancel_job(job_id):
jobs_logger.error(f"Fehler beim Abbrechen des Jobs {job_id}: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
@app.route("/api/jobs/<int:job_id>/start", methods=["POST"])
@login_required
@job_owner_required
def start_job(job_id):
"""Startet einen Job manuell."""
try:
db_session = get_db_session()
job = db_session.query(Job).options(joinedload(Job.printer)).get(job_id)
if not job:
db_session.close()
return jsonify({"error": "Job nicht gefunden"}), 404
# Prüfen, ob der Job gestartet werden kann
if job.status not in ["scheduled", "queued", "waiting_for_printer"]:
db_session.close()
return jsonify({"error": f"Job kann im Status '{job.status}' nicht gestartet werden"}), 400
# Drucker einschalten falls verfügbar
try:
from utils.job_scheduler import toggle_plug
if job.printer and job.printer.plug_ip:
if toggle_plug(job.printer_id, True):
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:
jobs_logger.warning(f"Fehler beim Einschalten des Druckers für Job {job_id}: {str(e)}")
# Job als laufend markieren
job.status = "running"
job.start_at = datetime.now()
if job.duration_minutes:
job.end_at = job.start_at + timedelta(minutes=job.duration_minutes)
db_session.commit()
job_dict = job.to_dict()
db_session.close()
jobs_logger.info(f"Job {job_id} manuell gestartet von Benutzer {current_user.id}")
return jsonify({
"success": True,
"message": "Job erfolgreich gestartet",
"job": job_dict
})
except Exception as e:
jobs_logger.error(f"Fehler beim Starten des Jobs {job_id}: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
@app.route("/api/jobs/<int:job_id>/pause", methods=["POST"])
@login_required
@job_owner_required
def pause_job(job_id):
"""Pausiert einen laufenden Job."""
try:
db_session = get_db_session()
job = db_session.query(Job).options(joinedload(Job.printer)).get(job_id)
if not job:
db_session.close()
return jsonify({"error": "Job nicht gefunden"}), 404
# Prüfen, ob der Job pausiert werden kann
if job.status != "running":
db_session.close()
return jsonify({"error": f"Job kann im Status '{job.status}' nicht pausiert werden"}), 400
# Drucker ausschalten
try:
from utils.job_scheduler import toggle_plug
if job.printer and job.printer.plug_ip:
if toggle_plug(job.printer_id, False):
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:
jobs_logger.warning(f"Fehler beim Ausschalten des Druckers für Job {job_id}: {str(e)}")
# Job als pausiert markieren
job.status = "paused"
job.paused_at = datetime.now()
db_session.commit()
job_dict = job.to_dict()
db_session.close()
jobs_logger.info(f"Job {job_id} pausiert von Benutzer {current_user.id}")
return jsonify({
"success": True,
"message": "Job erfolgreich pausiert",
"job": job_dict
})
except Exception as e:
jobs_logger.error(f"Fehler beim Pausieren des Jobs {job_id}: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
@app.route("/api/jobs/<int:job_id>/resume", methods=["POST"])
@login_required
@job_owner_required
def resume_job(job_id):
"""Setzt einen pausierten Job fort."""
try:
db_session = get_db_session()
job = db_session.query(Job).options(joinedload(Job.printer)).get(job_id)
if not job:
db_session.close()
return jsonify({"error": "Job nicht gefunden"}), 404
# Prüfen, ob der Job fortgesetzt werden kann
if job.status != "paused":
db_session.close()
return jsonify({"error": f"Job kann im Status '{job.status}' nicht fortgesetzt werden"}), 400
# Drucker einschalten
try:
from utils.job_scheduler import toggle_plug
if job.printer and job.printer.plug_ip:
if toggle_plug(job.printer_id, True):
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:
jobs_logger.warning(f"Fehler beim Einschalten des Druckers für Job {job_id}: {str(e)}")
# Job als laufend markieren
job.status = "running"
job.resumed_at = datetime.now()
# Endzeit anpassen falls notwendig
if job.paused_at and job.end_at:
pause_duration = job.resumed_at - job.paused_at
job.end_at += pause_duration
db_session.commit()
job_dict = job.to_dict()
db_session.close()
jobs_logger.info(f"Job {job_id} fortgesetzt von Benutzer {current_user.id}")
return jsonify({
"success": True,
"message": "Job erfolgreich fortgesetzt",
"job": job_dict
})
except Exception as e:
jobs_logger.error(f"Fehler beim Fortsetzen des Jobs {job_id}: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
@app.route("/api/stats", methods=["GET"])
@login_required
def get_stats():
@@ -4961,4 +4661,135 @@ if __name__ == "__main__":
stop_queue_manager()
except:
pass
sys.exit(1)
sys.exit(1)
# ===== WEITERE API-ROUTEN =====
@app.route("/api/jobs/current", methods=["GET"])
@login_required
def get_current_job():
"""Gibt den aktuellen Job des Benutzers zurück."""
db_session = get_db_session()
try:
current_job = db_session.query(Job).filter(
Job.user_id == int(current_user.id),
Job.status.in_(["scheduled", "running"])
).order_by(Job.start_at).first()
if current_job:
job_data = current_job.to_dict()
else:
job_data = None
db_session.close()
return jsonify(job_data)
except Exception as e:
db_session.close()
return jsonify({"error": str(e)}), 500
@app.route("/api/jobs/<int:job_id>", methods=["DELETE"])
@login_required
@job_owner_required
def delete_job(job_id):
"""Löscht einen Job."""
try:
db_session = get_db_session()
job = db_session.query(Job).get(job_id)
if not job:
db_session.close()
return jsonify({"error": "Job nicht gefunden"}), 404
# Prüfen, ob der Job gelöscht werden kann
if job.status == "running":
db_session.close()
return jsonify({"error": "Laufende Jobs können nicht gelöscht werden"}), 400
job_name = job.name
db_session.delete(job)
db_session.commit()
db_session.close()
jobs_logger.info(f"Job '{job_name}' (ID: {job_id}) gelöscht von Benutzer {current_user.id}")
return jsonify({"success": True, "message": "Job erfolgreich gelöscht"})
except Exception as e:
jobs_logger.error(f"Fehler beim Löschen des Jobs {job_id}: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
# ===== DRUCKER-ROUTEN =====
@app.route("/api/printers", methods=["GET"])
@login_required
def get_printers():
"""Gibt alle Drucker zurück - OHNE Status-Check für schnelleres Laden."""
db_session = get_db_session()
try:
# Windows-kompatible Timeout-Implementierung
import threading
import time
printers = None
timeout_occurred = False
def fetch_printers():
nonlocal printers, timeout_occurred
try:
printers = db_session.query(Printer).all()
except Exception as e:
printers_logger.error(f"Datenbankfehler beim Laden der Drucker: {str(e)}")
timeout_occurred = True
# Starte Datenbankabfrage in separatem Thread
thread = threading.Thread(target=fetch_printers)
thread.daemon = True
thread.start()
thread.join(timeout=5) # 5 Sekunden Timeout
if thread.is_alive() or timeout_occurred or printers is None:
printers_logger.warning("Database timeout when fetching printers for basic loading")
return jsonify({
'error': 'Database timeout beim Laden der Drucker',
'timeout': True,
'printers': []
}), 408
# Drucker-Daten OHNE Status-Check zusammenstellen für schnelles Laden
printer_data = []
current_time = datetime.now()
for printer in printers:
printer_data.append({
"id": printer.id,
"name": printer.name,
"model": printer.model or 'Unbekanntes Modell',
"location": printer.location or 'Unbekannter Standort',
"mac_address": printer.mac_address,
"plug_ip": printer.plug_ip,
"status": printer.status or "offline", # Letzter bekannter Status
"active": printer.active if hasattr(printer, 'active') else True,
"ip_address": printer.plug_ip if printer.plug_ip else getattr(printer, 'ip_address', None),
"created_at": printer.created_at.isoformat() if printer.created_at else current_time.isoformat(),
"last_checked": printer.last_checked.isoformat() if hasattr(printer, 'last_checked') and printer.last_checked else None
})
db_session.close()
printers_logger.info(f"Schnelles Laden abgeschlossen: {len(printer_data)} Drucker geladen (ohne Status-Check)")
return jsonify({
"success": True,
"printers": printer_data,
"count": len(printer_data),
"message": "Drucker erfolgreich geladen"
})
except Exception as e:
db_session.rollback()
db_session.close()
printers_logger.error(f"Fehler beim Abrufen der Drucker: {str(e)}")
return jsonify({
"error": f"Fehler beim Laden der Drucker: {str(e)}",
"printers": []
}), 500