612 lines
23 KiB
Python
612 lines
23 KiB
Python
"""
|
|
Jobs Blueprint - API-Endpunkte für Job-Verwaltung
|
|
Alle Job-bezogenen API-Endpunkte sind hier zentralisiert.
|
|
"""
|
|
|
|
from flask import Blueprint, request, jsonify, current_app
|
|
from flask_login import login_required, current_user
|
|
from datetime import datetime, timedelta
|
|
from functools import wraps
|
|
from sqlalchemy.orm import joinedload
|
|
|
|
from models import get_db_session, Job, Printer
|
|
from utils.logging_config import get_logger
|
|
from utils.conflict_manager import conflict_manager
|
|
|
|
# Blueprint initialisieren - URL-Präfix geändert um Konflikte zu vermeiden
|
|
jobs_blueprint = Blueprint('jobs', __name__, url_prefix='/api/jobs-bp')
|
|
|
|
# Logger für Jobs
|
|
jobs_logger = get_logger("jobs")
|
|
|
|
def job_owner_required(f):
|
|
"""Decorator um zu prüfen, ob der aktuelle Benutzer Besitzer eines Jobs ist oder Admin"""
|
|
@wraps(f)
|
|
def decorated_function(job_id, *args, **kwargs):
|
|
db_session = get_db_session()
|
|
job = db_session.query(Job).filter(Job.id == job_id).first()
|
|
|
|
if not job:
|
|
db_session.close()
|
|
return jsonify({"error": "Job nicht gefunden"}), 404
|
|
|
|
is_owner = job.user_id == int(current_user.id) or job.owner_id == int(current_user.id)
|
|
is_admin = current_user.is_admin
|
|
|
|
if not (is_owner or is_admin):
|
|
db_session.close()
|
|
return jsonify({"error": "Keine Berechtigung"}), 403
|
|
|
|
db_session.close()
|
|
return f(job_id, *args, **kwargs)
|
|
return decorated_function
|
|
|
|
def check_printer_status(ip_address: str, timeout: int = 7):
|
|
"""Mock-Implementierung für Drucker-Status-Check"""
|
|
# TODO: Implementiere echten Status-Check
|
|
if ip_address:
|
|
return "online", True
|
|
return "offline", False
|
|
|
|
@jobs_blueprint.route('', methods=['GET'])
|
|
@login_required
|
|
def get_jobs():
|
|
"""Gibt alle Jobs zurück. Admins sehen alle Jobs, normale Benutzer nur ihre eigenen."""
|
|
db_session = get_db_session()
|
|
|
|
try:
|
|
jobs_logger.info(f"📋 Jobs-Abfrage gestartet von Benutzer {current_user.id} (Admin: {current_user.is_admin})")
|
|
|
|
# Paginierung unterstützen
|
|
page = request.args.get('page', 1, type=int)
|
|
per_page = request.args.get('per_page', 50, type=int)
|
|
status_filter = request.args.get('status')
|
|
|
|
jobs_logger.debug(f"📋 Parameter: page={page}, per_page={per_page}, status_filter={status_filter}")
|
|
|
|
# Query aufbauen
|
|
query = db_session.query(Job).options(joinedload(Job.user), joinedload(Job.printer))
|
|
|
|
# Admin sieht alle Jobs, User nur eigene
|
|
if not current_user.is_admin:
|
|
query = query.filter(Job.user_id == int(current_user.id))
|
|
jobs_logger.debug(f"🔒 Benutzerfilter angewendet für User {current_user.id}")
|
|
|
|
# Status-Filter anwenden
|
|
if status_filter:
|
|
query = query.filter(Job.status == status_filter)
|
|
jobs_logger.debug(f"🏷️ Status-Filter angewendet: {status_filter}")
|
|
|
|
# Sortierung: neueste zuerst
|
|
query = query.order_by(Job.created_at.desc())
|
|
|
|
# Paginierung anwenden
|
|
offset = (page - 1) * per_page
|
|
jobs = query.offset(offset).limit(per_page).all()
|
|
|
|
# Gesamtanzahl für Paginierung
|
|
total_count = query.count()
|
|
|
|
# Convert jobs to dictionaries before closing the session
|
|
job_dicts = [job.to_dict() for job in jobs]
|
|
|
|
db_session.close()
|
|
|
|
jobs_logger.info(f"✅ Jobs erfolgreich abgerufen: {len(job_dicts)} von {total_count} (Seite {page})")
|
|
|
|
return jsonify({
|
|
"jobs": job_dicts,
|
|
"pagination": {
|
|
"page": page,
|
|
"per_page": per_page,
|
|
"total": total_count,
|
|
"pages": (total_count + per_page - 1) // per_page
|
|
}
|
|
})
|
|
except Exception as e:
|
|
jobs_logger.error(f"❌ Fehler beim Abrufen von Jobs: {str(e)}", exc_info=True)
|
|
try:
|
|
db_session.close()
|
|
except:
|
|
pass
|
|
return jsonify({"error": "Interner Serverfehler", "details": str(e)}), 500
|
|
|
|
@jobs_blueprint.route('/<int:job_id>', methods=['GET'])
|
|
@login_required
|
|
@job_owner_required
|
|
def get_job(job_id):
|
|
"""Gibt einen einzelnen Job zurück."""
|
|
db_session = get_db_session()
|
|
|
|
try:
|
|
jobs_logger.info(f"🔍 Job-Detail-Abfrage für Job {job_id} von Benutzer {current_user.id}")
|
|
|
|
# Eagerly load the user and printer relationships
|
|
job = db_session.query(Job).options(joinedload(Job.user), joinedload(Job.printer)).filter(Job.id == job_id).first()
|
|
|
|
if not job:
|
|
jobs_logger.warning(f"⚠️ Job {job_id} nicht gefunden")
|
|
db_session.close()
|
|
return jsonify({"error": "Job nicht gefunden"}), 404
|
|
|
|
# Convert to dict before closing session
|
|
job_dict = job.to_dict()
|
|
db_session.close()
|
|
|
|
jobs_logger.info(f"✅ Job-Details erfolgreich abgerufen für Job {job_id}")
|
|
return jsonify(job_dict)
|
|
except Exception as e:
|
|
jobs_logger.error(f"❌ Fehler beim Abrufen des Jobs {job_id}: {str(e)}", exc_info=True)
|
|
try:
|
|
db_session.close()
|
|
except:
|
|
pass
|
|
return jsonify({"error": "Interner Serverfehler", "details": str(e)}), 500
|
|
|
|
@jobs_blueprint.route('', methods=['POST'])
|
|
@login_required
|
|
def create_job():
|
|
"""
|
|
Erstellt einen neuen Job.
|
|
|
|
Body: {
|
|
"name": str (optional),
|
|
"description": str (optional),
|
|
"printer_id": int,
|
|
"start_iso": str,
|
|
"duration_minutes": int,
|
|
"file_path": str (optional)
|
|
}
|
|
"""
|
|
try:
|
|
jobs_logger.info(f"🚀 Neue Job-Erstellung gestartet von Benutzer {current_user.id}")
|
|
|
|
data = request.json
|
|
if not data:
|
|
jobs_logger.error("❌ Keine JSON-Daten empfangen")
|
|
return jsonify({"error": "Keine JSON-Daten empfangen"}), 400
|
|
|
|
jobs_logger.debug(f"📋 Empfangene Daten: {data}")
|
|
|
|
# Pflichtfelder prüfen
|
|
required_fields = ["printer_id", "start_iso", "duration_minutes"]
|
|
for field in required_fields:
|
|
if field not in data:
|
|
jobs_logger.error(f"❌ Pflichtfeld '{field}' fehlt in den Daten")
|
|
return jsonify({"error": f"Feld '{field}' fehlt"}), 400
|
|
|
|
# Daten extrahieren und validieren
|
|
try:
|
|
printer_id = int(data["printer_id"])
|
|
start_iso = data["start_iso"]
|
|
duration_minutes = int(data["duration_minutes"])
|
|
jobs_logger.debug(f"✅ Grunddaten validiert: printer_id={printer_id}, duration={duration_minutes}")
|
|
except (ValueError, TypeError) as e:
|
|
jobs_logger.error(f"❌ Fehler bei Datenvalidierung: {str(e)}")
|
|
return jsonify({"error": f"Ungültige Datenformate: {str(e)}"}), 400
|
|
|
|
# Optional: Jobtitel, Beschreibung und Dateipfad
|
|
name = data.get("name", f"Druckjob vom {datetime.now().strftime('%d.%m.%Y %H:%M')}")
|
|
description = data.get("description", "")
|
|
file_path = data.get("file_path")
|
|
|
|
# Start-Zeit parsen
|
|
try:
|
|
start_at = datetime.fromisoformat(start_iso.replace('Z', '+00:00'))
|
|
jobs_logger.debug(f"✅ Startzeit geparst: {start_at}")
|
|
except ValueError as e:
|
|
jobs_logger.error(f"❌ Ungültiges Startdatum '{start_iso}': {str(e)}")
|
|
return jsonify({"error": f"Ungültiges Startdatum: {str(e)}"}), 400
|
|
|
|
# Dauer validieren
|
|
if duration_minutes <= 0:
|
|
jobs_logger.error(f"❌ Ungültige Dauer: {duration_minutes} Minuten")
|
|
return jsonify({"error": "Dauer muss größer als 0 sein"}), 400
|
|
|
|
# End-Zeit berechnen
|
|
end_at = start_at + timedelta(minutes=duration_minutes)
|
|
|
|
db_session = get_db_session()
|
|
|
|
try:
|
|
# Prüfen, ob der Drucker existiert
|
|
printer = db_session.query(Printer).get(printer_id)
|
|
if not printer:
|
|
jobs_logger.error(f"❌ Drucker mit ID {printer_id} nicht gefunden")
|
|
db_session.close()
|
|
return jsonify({"error": "Drucker nicht gefunden"}), 404
|
|
|
|
jobs_logger.debug(f"✅ Drucker gefunden: {printer.name} (ID: {printer_id})")
|
|
|
|
# ERWEITERTE KONFLIKTPRÜFUNG
|
|
job_data = {
|
|
'printer_id': printer_id,
|
|
'start_time': start_at,
|
|
'end_time': end_at,
|
|
'priority': data.get('priority', 'normal'),
|
|
'duration_minutes': duration_minutes
|
|
}
|
|
|
|
# Konflikte erkennen
|
|
conflicts = conflict_manager.detect_conflicts(job_data, db_session)
|
|
|
|
if conflicts:
|
|
critical_conflicts = [c for c in conflicts if c.severity.value in ['kritisch', 'hoch']]
|
|
if critical_conflicts:
|
|
# Kritische Konflikte verhindern Job-Erstellung
|
|
conflict_descriptions = [c.description for c in critical_conflicts]
|
|
jobs_logger.warning(f"⚠️ Kritische Konflikte gefunden: {conflict_descriptions}")
|
|
|
|
db_session.close()
|
|
return jsonify({
|
|
"error": "Kritische Konflikte gefunden",
|
|
"conflicts": conflict_descriptions,
|
|
"suggestions": [s for c in critical_conflicts for s in c.suggested_solutions]
|
|
}), 409
|
|
|
|
# Mittlere/niedrige Konflikte protokollieren aber zulassen
|
|
jobs_logger.info(f"📋 {len(conflicts)} Konflikte erkannt, aber übergehbar")
|
|
|
|
# Prüfen, ob der Drucker online ist
|
|
printer_status, printer_active = check_printer_status(printer.plug_ip if printer.plug_ip else "")
|
|
jobs_logger.debug(f"🖨️ Drucker-Status: {printer_status}, aktiv: {printer_active}")
|
|
|
|
# Status basierend auf Drucker-Verfügbarkeit setzen
|
|
if printer_status == "online" and printer_active:
|
|
job_status = "scheduled"
|
|
else:
|
|
job_status = "waiting_for_printer"
|
|
|
|
jobs_logger.info(f"📋 Job-Status festgelegt: {job_status}")
|
|
|
|
# Neuen Job erstellen
|
|
new_job = Job(
|
|
name=name,
|
|
description=description,
|
|
printer_id=printer_id,
|
|
user_id=current_user.id,
|
|
owner_id=current_user.id,
|
|
start_at=start_at,
|
|
end_at=end_at,
|
|
status=job_status,
|
|
file_path=file_path,
|
|
duration_minutes=duration_minutes
|
|
)
|
|
|
|
db_session.add(new_job)
|
|
db_session.commit()
|
|
|
|
# Job-Objekt für die Antwort serialisieren
|
|
job_dict = new_job.to_dict()
|
|
db_session.close()
|
|
|
|
jobs_logger.info(f"✅ Neuer Job {new_job.id} erfolgreich erstellt für Drucker {printer_id}, Start: {start_at}, Dauer: {duration_minutes} Minuten")
|
|
return jsonify({"job": job_dict}), 201
|
|
|
|
except Exception as db_error:
|
|
jobs_logger.error(f"❌ Datenbankfehler beim Job-Erstellen: {str(db_error)}")
|
|
try:
|
|
db_session.rollback()
|
|
db_session.close()
|
|
except:
|
|
pass
|
|
return jsonify({"error": "Datenbankfehler beim Erstellen des Jobs", "details": str(db_error)}), 500
|
|
|
|
except Exception as e:
|
|
jobs_logger.error(f"❌ Kritischer Fehler beim Erstellen eines Jobs: {str(e)}", exc_info=True)
|
|
return jsonify({"error": "Interner Serverfehler", "details": str(e)}), 500
|
|
|
|
@jobs_blueprint.route('/<int:job_id>', methods=['PUT'])
|
|
@login_required
|
|
@job_owner_required
|
|
def update_job(job_id):
|
|
"""Aktualisiert einen existierenden Job."""
|
|
try:
|
|
data = request.json
|
|
|
|
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 bearbeitet werden kann
|
|
if job.status in ["finished", "aborted"]:
|
|
db_session.close()
|
|
return jsonify({"error": f"Job kann im Status '{job.status}' nicht bearbeitet werden"}), 400
|
|
|
|
# Felder aktualisieren, falls vorhanden
|
|
if "name" in data:
|
|
job.name = data["name"]
|
|
|
|
if "description" in data:
|
|
job.description = data["description"]
|
|
|
|
if "notes" in data:
|
|
job.notes = data["notes"]
|
|
|
|
if "start_iso" in data:
|
|
try:
|
|
new_start = datetime.fromisoformat(data["start_iso"].replace('Z', '+00:00'))
|
|
job.start_at = new_start
|
|
|
|
# End-Zeit neu berechnen falls Duration verfügbar
|
|
if job.duration_minutes:
|
|
job.end_at = new_start + timedelta(minutes=job.duration_minutes)
|
|
except ValueError:
|
|
db_session.close()
|
|
return jsonify({"error": "Ungültiges Startdatum"}), 400
|
|
|
|
if "duration_minutes" in data:
|
|
duration = int(data["duration_minutes"])
|
|
if duration <= 0:
|
|
db_session.close()
|
|
return jsonify({"error": "Dauer muss größer als 0 sein"}), 400
|
|
|
|
job.duration_minutes = duration
|
|
# End-Zeit neu berechnen
|
|
if job.start_at:
|
|
job.end_at = job.start_at + timedelta(minutes=duration)
|
|
|
|
# Aktualisierungszeitpunkt setzen
|
|
job.updated_at = datetime.now()
|
|
|
|
db_session.commit()
|
|
|
|
# Job-Objekt für die Antwort serialisieren
|
|
job_dict = job.to_dict()
|
|
db_session.close()
|
|
|
|
jobs_logger.info(f"Job {job_id} aktualisiert")
|
|
return jsonify({"job": job_dict})
|
|
|
|
except Exception as e:
|
|
jobs_logger.error(f"Fehler beim Aktualisieren von Job {job_id}: {str(e)}")
|
|
return jsonify({"error": "Interner Serverfehler", "details": str(e)}), 500
|
|
|
|
@jobs_blueprint.route('/<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
|
|
|
|
@jobs_blueprint.route('/active', methods=['GET'])
|
|
@login_required
|
|
def get_active_jobs():
|
|
"""Gibt alle aktiven Jobs zurück."""
|
|
try:
|
|
db_session = get_db_session()
|
|
|
|
query = db_session.query(Job).options(
|
|
joinedload(Job.user),
|
|
joinedload(Job.printer)
|
|
).filter(
|
|
Job.status.in_(["scheduled", "running"])
|
|
)
|
|
|
|
# Normale Benutzer sehen nur ihre eigenen aktiven Jobs
|
|
if not current_user.is_admin:
|
|
query = query.filter(Job.user_id == current_user.id)
|
|
|
|
active_jobs = query.all()
|
|
|
|
result = []
|
|
for job in active_jobs:
|
|
job_dict = job.to_dict()
|
|
# Aktuelle Restzeit berechnen
|
|
if job.status == "running" and job.end_at:
|
|
remaining_time = job.end_at - datetime.now()
|
|
if remaining_time.total_seconds() > 0:
|
|
job_dict["remaining_minutes"] = int(remaining_time.total_seconds() / 60)
|
|
else:
|
|
job_dict["remaining_minutes"] = 0
|
|
|
|
result.append(job_dict)
|
|
|
|
db_session.close()
|
|
return jsonify({"jobs": result})
|
|
except Exception as e:
|
|
jobs_logger.error(f"Fehler beim Abrufen aktiver Jobs: {str(e)}")
|
|
return jsonify({"error": "Interner Serverfehler", "details": str(e)}), 500
|
|
|
|
@jobs_blueprint.route('/current', methods=['GET'])
|
|
@login_required
|
|
def get_current_job():
|
|
"""Gibt den aktuell laufenden Job für den eingeloggten Benutzer zurück."""
|
|
db_session = get_db_session()
|
|
|
|
try:
|
|
current_job = db_session.query(Job).filter(
|
|
Job.user_id == current_user.id,
|
|
Job.status == "running"
|
|
).first()
|
|
|
|
if current_job:
|
|
job_dict = current_job.to_dict()
|
|
db_session.close()
|
|
return jsonify(job_dict)
|
|
else:
|
|
db_session.close()
|
|
return jsonify({"message": "Kein aktueller Job"}), 404
|
|
except Exception as e:
|
|
jobs_logger.error(f"Fehler beim Abrufen des aktuellen Jobs: {str(e)}")
|
|
db_session.close()
|
|
return jsonify({"error": "Interner Serverfehler"}), 500
|
|
|
|
@jobs_blueprint.route('/<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", "waiting_for_printer"]:
|
|
db_session.close()
|
|
return jsonify({"error": f"Job kann im Status '{job.status}' nicht gestartet werden"}), 400
|
|
|
|
# Drucker-Status prüfen
|
|
if job.printer.plug_ip:
|
|
status, active = check_printer_status(job.printer.plug_ip)
|
|
if status != "online" or not active:
|
|
db_session.close()
|
|
return jsonify({"error": "Drucker ist nicht online"}), 400
|
|
|
|
# Job als laufend markieren
|
|
job.status = "running"
|
|
job.start_at = datetime.now()
|
|
job.end_at = job.start_at + timedelta(minutes=job.duration_minutes)
|
|
|
|
db_session.commit()
|
|
|
|
# Job-Objekt für die Antwort serialisieren
|
|
job_dict = job.to_dict()
|
|
db_session.close()
|
|
|
|
jobs_logger.info(f"Job {job_id} manuell gestartet")
|
|
return jsonify({"job": job_dict})
|
|
|
|
except Exception as e:
|
|
jobs_logger.error(f"Fehler beim Starten von Job {job_id}: {str(e)}")
|
|
return jsonify({"error": "Interner Serverfehler", "details": str(e)}), 500
|
|
|
|
@jobs_blueprint.route('/<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).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
|
|
|
|
# Job pausieren
|
|
job.status = "paused"
|
|
|
|
db_session.commit()
|
|
|
|
# Job-Objekt für die Antwort serialisieren
|
|
job_dict = job.to_dict()
|
|
db_session.close()
|
|
|
|
jobs_logger.info(f"Job {job_id} pausiert")
|
|
return jsonify({"job": job_dict})
|
|
|
|
except Exception as e:
|
|
jobs_logger.error(f"Fehler beim Pausieren von Job {job_id}: {str(e)}")
|
|
return jsonify({"error": "Interner Serverfehler", "details": str(e)}), 500
|
|
|
|
@jobs_blueprint.route('/<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).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
|
|
|
|
# Job fortsetzen
|
|
job.status = "running"
|
|
|
|
db_session.commit()
|
|
|
|
# Job-Objekt für die Antwort serialisieren
|
|
job_dict = job.to_dict()
|
|
db_session.close()
|
|
|
|
jobs_logger.info(f"Job {job_id} fortgesetzt")
|
|
return jsonify({"job": job_dict})
|
|
|
|
except Exception as e:
|
|
jobs_logger.error(f"Fehler beim Fortsetzen von Job {job_id}: {str(e)}")
|
|
return jsonify({"error": "Interner Serverfehler", "details": str(e)}), 500
|
|
|
|
@jobs_blueprint.route('/<int:job_id>/finish', methods=['POST'])
|
|
@login_required
|
|
def finish_job(job_id):
|
|
"""Beendet 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
|
|
|
|
# Berechtigung prüfen
|
|
is_owner = job.user_id == int(current_user.id) or job.owner_id == int(current_user.id)
|
|
is_admin = current_user.is_admin
|
|
|
|
if not (is_owner or is_admin):
|
|
db_session.close()
|
|
return jsonify({"error": "Keine Berechtigung"}), 403
|
|
|
|
# Prüfen, ob der Job beendet werden kann
|
|
if job.status not in ["scheduled", "running", "paused"]:
|
|
db_session.close()
|
|
return jsonify({"error": f"Job kann im Status '{job.status}' nicht beendet werden"}), 400
|
|
|
|
# Job als beendet markieren
|
|
job.status = "finished"
|
|
job.actual_end_time = datetime.now()
|
|
|
|
db_session.commit()
|
|
|
|
# Job-Objekt für die Antwort serialisieren
|
|
job_dict = job.to_dict()
|
|
db_session.close()
|
|
|
|
jobs_logger.info(f"Job {job_id} manuell beendet durch Benutzer {current_user.id}")
|
|
return jsonify({"job": job_dict})
|
|
|
|
except Exception as e:
|
|
jobs_logger.error(f"Fehler beim manuellen Beenden von Job {job_id}: {str(e)}")
|
|
return jsonify({"error": "Interner Serverfehler", "details": str(e)}), 500 |