158 lines
5.8 KiB
Python
158 lines
5.8 KiB
Python
"""
|
|
API сценариев: запуск + поллинг.
|
|
|
|
GET /api/scenarios — список из БД (для UI)
|
|
POST /api/scenario/run — запуск по definition_id (409 если уже RUNNING)
|
|
GET /api/scenario/run/<int:run_id> — статус конкретного запуска
|
|
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()
|
|
|
|
if not lock_check(cid, stand):
|
|
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/<int:run_id>")
|
|
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)
|