""" API сценариев: запуск + поллинг. GET /api/scenarios — список из БД (для UI) POST /api/scenario/run — запуск по definition_id (409 если уже RUNNING) GET /api/scenario/run/ — статус конкретного запуска GET /api/scenario/status — последние 10 запусков """ from flask import Blueprint, current_app, jsonify, request import threading from api.auth import get_client, get_client_id, get_stand, get_token_info from operations.scenario import run_scenario from db.pool import get_conn, put_conn from db.scenario_defs import get_definition, lock_check, list_definitions bp_run = Blueprint("api_scenario_run", __name__) @bp_run.route("/api/scenarios") def api_scenarios(): """Список определений сценариев из БД (для UI запуска).""" try: defs = list_definitions(get_client_id(), get_stand()) result = [{"id": d["id"], "name": d["name"], "steps": len(d.get("steps", []))} for d in defs] return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 @bp_run.route("/api/scenario/run", methods=["POST"]) def api_scenario_run(): """Запустить сценарий по definition_id. 409 если уже RUNNING.""" try: data = request.get_json() def_id = data.get("definition_id") if not def_id: return jsonify({"error": "definition_id is required"}), 400 cid = get_client_id() stand = get_stand() can_run = lock_check(cid, stand) if can_run is None: return jsonify({"error": "DB unavailable"}), 503 if not can_run: return jsonify({"error": "Another scenario is already running"}), 409 defn = get_definition(def_id, cid, stand) if not defn: return jsonify({"error": "Definition not found"}), 404 steps = defn.get("steps", []) if not steps: return jsonify({"error": "Definition has no steps"}), 400 scenario_name = defn["name"] user_email = get_token_info().get("email", "") app_version = current_app.config.get("VERSION", "") conn = get_conn() if not conn: return jsonify({"error": "no db"}), 500 run_id = None try: cur = conn.cursor() cur.execute(""" INSERT INTO scenario_runs (client_id, stand, user_email, scenario_name, status, current_step, total_steps, app_version, definition_id, definition_version) VALUES (%s, %s, %s, %s, 'RUNNING', 0, %s, %s, %s, %s) RETURNING id """, (cid, stand, user_email, scenario_name, len(steps), app_version, def_id, defn["version"])) run_id = cur.fetchone()[0] conn.commit() cur.close() except Exception as e: # Unique violation (23505) → другая параллельная вставка уже создала RUNNING if hasattr(e, 'pgcode') and e.pgcode == '23505': conn.rollback() return jsonify({"error": "Another scenario is already running"}), 409 print(f"[API] create scenario_run error: {e}", flush=True) conn.rollback() return jsonify({"error": "Failed to create scenario_run"}), 500 finally: put_conn(conn) client = get_client() threading.Thread( target=run_scenario, args=(client, steps, cid, stand, user_email, app_version, run_id, scenario_name), daemon=True, ).start() return jsonify({"status": "RUNNING", "scenario": scenario_name, "run_id": run_id}), 202 except Exception as e: import traceback print(f"[SCENARIO API] run error: {traceback.format_exc()}", flush=True) return jsonify({"error": str(e)}), 500 @bp_run.route("/api/scenario/run/") def api_scenario_run_status(run_id): """Статус конкретного запуска.""" conn = get_conn() if not conn: return jsonify({"error": "no db"}), 500 try: cur = conn.cursor() cur.execute(""" SELECT id, created_at, scenario_name, status, current_step, total_steps, duration_sec, error_log, app_version FROM scenario_runs WHERE id = %s AND client_id = %s AND stand = %s """, (run_id, get_client_id(), get_stand())) row = cur.fetchone() if not row: return jsonify({"error": "not found"}), 404 cols = [d[0] for d in cur.description] d = dict(zip(cols, row)) if d["created_at"]: d["created_at"] = d["created_at"].isoformat() return jsonify(d) except Exception as e: return jsonify({"error": str(e)}), 500 finally: put_conn(conn) @bp_run.route("/api/scenario/status") def api_scenario_status(): """Последние 10 запусков.""" conn = get_conn() if not conn: return jsonify([]) try: cur = conn.cursor() cur.execute(""" SELECT id, created_at, scenario_name, status, current_step, total_steps, duration_sec, error_log, app_version FROM scenario_runs WHERE client_id = %s AND stand = %s ORDER BY created_at DESC LIMIT 10 """, (get_client_id(), get_stand())) rows = cur.fetchall() cols = [d[0] for d in cur.description] result = [] for r in rows: d = dict(zip(cols, r)) if d["created_at"]: d["created_at"] = d["created_at"].isoformat() result.append(d) return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 finally: put_conn(conn)