diff --git a/app.py b/app.py index f780a2aa..7ba752dc 100644 --- a/app.py +++ b/app.py @@ -1,9 +1,11 @@ -# app.py (v2.1 - Final für Docker) +# app.py (v2.1 - mit Job-Manager) from flask import Flask, jsonify, request import subprocess import sys import os import logging +import uuid +import json from datetime import datetime from pyngrok import ngrok, conf from config import Config @@ -11,32 +13,28 @@ from config import Config logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') app = Flask(__name__) -# Der Pfad, in dem sich der Code im Container befindet -# (muss mit WORKDIR im Dockerfile übereinstimmen) -APP_DIR = "/code" +STATUS_DIR = "job_status" +os.makedirs(STATUS_DIR, exist_ok=True) SCRIPT_MAP = { "run_duplicate_check": {"script": "duplicate_checker.py", "args": []}, "run_reclassify_branches": {"script": "brancheneinstufung2.py", "args": ["--mode", "reclassify_branches"]}, - # ... fügen Sie hier bei Bedarf brancheneinstufung2.py hinzu + "run_predict_technicians": {"script": "brancheneinstufung2.py", "args": ["--mode", "predict_technicians"]}, } def setup_ngrok(): - """Konfiguriert und startet den ngrok-Tunnel.""" + # ... (Ihre ngrok-Setup-Funktion bleibt unverändert) try: authtoken = os.environ.get("NGROK_AUTHTOKEN") if not authtoken: if os.path.exists("ngrok_authtoken.txt"): with open("ngrok_authtoken.txt", "r") as f: authtoken = f.read().strip() - if not authtoken: - logging.error("NGROK_AUTHTOKEN nicht gefunden. Tunnel kann nicht gestartet werden.") + logging.error("NGROK_AUTHTOKEN nicht gefunden.") return None - conf.get_default().auth_token = authtoken public_url = ngrok.connect(8080, "http") logging.info(f"!!! Ngrok-Tunnel gestartet: {public_url} !!!") - logging.info("!!! Bitte diese URL im Google Apps Script eintragen. !!!") return public_url except Exception as e: logging.error(f"Fehler beim Starten von ngrok: {e}") @@ -44,60 +42,51 @@ def setup_ngrok(): @app.route('/run-script', methods=['POST']) def run_script(): - """ - Startet ein Skript als Hintergrundprozess. - Finale, robuste Version mit absoluten Pfaden. - """ - action = "unknown" + """Startet ein Skript als Hintergrundprozess und verfolgt es über eine Job-ID.""" try: data = request.get_json() action = data.get('action') if not action or action not in SCRIPT_MAP: return jsonify({"status": "error", "message": "Ungültige Aktion."}), 400 - + script_config = SCRIPT_MAP[action] script_name = script_config["script"] - # ENTSCHEIDENDER FIX: Finde den Pfad relativ zu DIESER app.py Datei - # __file__ ist der Pfad zur aktuell laufenden Datei (app.py) - # os.path.dirname(__file__) ist der Ordner, in dem app.py liegt (/app im Container) + # Job-ID generieren und als Argument übergeben + job_id = str(uuid.uuid4()) + command = [sys.executable, script_name] + script_config["args"] + ["--job-id", job_id] + script_dir = os.path.dirname(os.path.abspath(__file__)) - script_path = os.path.join(script_dir, script_name) - - if not os.path.exists(script_path): - logging.error(f"Skript unter dem Pfad '{script_path}' NICHT GEFUNDEN.") - return jsonify({"status": "error", "message": f"Server-Fehler: Skript '{script_name}' nicht gefunden."}), 500 - - logging.info(f"Aktion '{action}' empfangen. Starte Skript: '{script_path}'...") - - python_executable = sys.executable - command = [python_executable, script_path] + script_config["args"] log_dir = os.path.join(script_dir, "Log") os.makedirs(log_dir, exist_ok=True) timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") - process_log_path = os.path.join(log_dir, f"{timestamp}_{action}.log") + process_log_path = os.path.join(log_dir, f"{timestamp}_{action}_{job_id[:8]}.log") with open(process_log_path, 'w') as log_file: - subprocess.Popen( - command, - # Wir müssen das Arbeitsverzeichnis nicht mehr setzen, da wir absolute Pfade verwenden - stdout=log_file, - stderr=log_file - ) + subprocess.Popen(command, cwd=script_dir, stdout=log_file, stderr=log_file) - return jsonify({"status": "success", "message": f"Aktion '{action}' gestartet. Log wird in '{process_log_path}' geschrieben."}), 200 - + # Erstelle eine initiale Statusdatei + status_file = os.path.join(STATUS_DIR, f"{job_id}.json") + with open(status_file, 'w') as f: + json.dump({ + "action": action, "status": "Gestartet", + "start_time": datetime.now().isoformat(), + "progress": "Prozess wird initialisiert..." + }, f) + + return jsonify({"status": "success", "message": f"Aktion '{action}' gestartet."}), 200 except Exception as e: - logging.error(f"Kritischer Fehler in der run_script-Route bei Aktion '{action}': {e}", exc_info=True) - return jsonify({"status": "error", "message": f"Server-Fehler: {str(e)}"}), 500 + logging.error(f"Fehler in run_script: {e}", exc_info=True) + return jsonify({"status": "error", "message": str(e)}), 500 +# --- NEUE FUNKTION --- @app.route('/get-status', methods=['GET']) def get_status(): """Liest alle Job-Status-Dateien und gibt den aktuellen Stand zurück.""" all_statuses = [] - for filename in os.listdir(STATUS_DIR): + for filename in sorted(os.listdir(STATUS_DIR), reverse=True): # Neueste zuerst if filename.endswith(".json"): try: with open(os.path.join(STATUS_DIR, filename), 'r') as f: @@ -107,8 +96,6 @@ def get_status(): except Exception as e: logging.warning(f"Konnte Statusdatei {filename} nicht lesen: {e}") - # Sortiere nach Startzeit, neuester Job zuerst - all_statuses.sort(key=lambda x: x.get('start_time', ''), reverse=True) return jsonify(all_statuses) if __name__ == '__main__':