v1.2.0: unified executor + flexible instance refs + params-render.js

Phase 1 — New modules:
- api/utils.py: find_uid(), uid_from_location() (replaces 2 duplicates)
- operations/poll.py: poll_until_done() (shared sync/async polling)
- operations/executor.py: execute_operation() (single CREATE/non-CREATE flow)

Phase 2 — Format + validation:
- routes/api_scenario_defs.py: _validate_steps with output/instance_ref/instance_uid
- operations/scenario.py: resolve instance_uid > instance_ref > service_id,
  use executor + poll_until_done, persist instance_bindings

Phase 3 — Migration:
- routes/api_test.py: create/non-create through executor, _finish_op through poll_until_done
- db/init_db.py: startup cleanup of stuck scenario_runs (>1h)

Phase 4 — UI shared module:
- static/js/params-render.js: renderParamRow, renderMapFixedRow, collectParams
- static/js/operations.js: use params-render.js (remove duplicates)
- templates/index.html: include params-render.js
- app.py: bump 1.1.57 → 1.2.0
This commit is contained in:
2026-07-31 08:55:06 +04:00
parent 38f58ddbd7
commit 91e97f6f75
11 changed files with 429 additions and 272 deletions
+25
View File
@@ -22,6 +22,7 @@ def _validate_steps(steps):
"""Проверить структуру шагов сценария. Возвращает None или str с ошибкой."""
if not isinstance(steps, list) or len(steps) == 0:
return "Шаги: непустой массив"
outputs = {} # output_name → step_index (для проверки ссылок и уникальности)
for i, s in enumerate(steps):
if not isinstance(s, dict):
return f"Шаг {i+1}: должен быть объектом"
@@ -33,6 +34,30 @@ def _validate_steps(steps):
return f"Шаг {i+1}: operation обязателен"
if not isinstance(s.get("params", {}), dict):
return f"Шаг {i+1}: params — объект"
# Новые опциональные ключи: output, instance_ref, instance_uid
out = (s.get("output") or "").strip()
ref = (s.get("instance_ref") or "").strip()
uid = (s.get("instance_uid") or "").strip()
# output — только для create, уникален
if out:
if op != "create":
return f"Шаг {i+1}: output допустим только для create"
if out in outputs:
return f"Шаг {i+1}: output '{out}' уже используется в шаге {outputs[out]+1}"
outputs[out] = i
# instance_ref — должен ссылаться на существующий output из предыдущих шагов
if ref:
if ref not in outputs:
return f"Шаг {i+1}: instance_ref '{ref}' — output не найден (должен быть в create-шаге выше)"
# Для не-create: должен быть instance_ref, instance_uid, или старый формат (fallback)
if op != "create":
has_target = bool(ref or uid or (not out)) # out в не-create — ошибка выше
# ok: ref есть, uid есть, или старый формат без новых ключей (fallback)
return None