diff --git a/site/app.py b/site/app.py index afbcf3c..a3e2807 100644 --- a/site/app.py +++ b/site/app.py @@ -6,7 +6,7 @@ from routes.main import bp as main_bp from routes.api import bp as api_bp from routes.api_test import bp as api_test_bp -VERSION = "1.0.77" +VERSION = "1.0.78" app = Flask(__name__, template_folder="templates", static_folder="static") app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc") diff --git a/site/routes/api_test.py b/site/routes/api_test.py index e8229c9..85b2206 100644 --- a/site/routes/api_test.py +++ b/site/routes/api_test.py @@ -1,5 +1,6 @@ from flask import Blueprint, current_app, jsonify, request import uuid +import collections from api.http_client import HttpClient, detect_endpoint from operations.get_services import get_services, get_service_detail @@ -11,6 +12,15 @@ bp = Blueprint("api_test", __name__) AUTOTEST_PREFIX = "autotest-" +# Кольцевой буфер логов (показываются в UI) +_log_lines = collections.deque(maxlen=200) + + +def _log(msg): + """Пишет и в stdout (для gunicorn), и в буфер (для UI).""" + print(msg, flush=True) + _log_lines.append(msg) + def _with_prefix(name): name = (name or "").strip() or "instance" @@ -102,28 +112,28 @@ def api_params(op_id): try: instance_uid = request.args.get("instanceUid") op_name = request.args.get("opName") - print(f"[api_params] op_id={op_id} instanceUid={instance_uid!r} opName={op_name!r}", flush=True) + _log(f"[api_params] op_id={op_id} instanceUid={instance_uid!r} opName={op_name!r}") if instance_uid and op_name: - print(f"[api_params] PREVIEW branch: creating operation for instance={instance_uid}", flush=True) + _log(f"[api_params] PREVIEW branch: creating operation for instance={instance_uid}") # Создать pending-операцию чтобы прочитать ТЕКУЩИЕ значения параметров инстанса op_payload = {"instanceUid": instance_uid, "svcOperationId": op_id, "operation": op_name} op_resp = _client().post("/instanceOperations", op_payload) preview_op_uid = _find_uid(op_resp) or _uid_from_location(op_resp.get("_location", "")) - print(f"[api_params] preview_op_uid={preview_op_uid!r}", flush=True) + _log(f"[api_params] preview_op_uid={preview_op_uid!r}") if not preview_op_uid: return jsonify({"error": "Не удалось создать preview-операцию"}), 500 op_data = _client().get(f"/instanceOperations/{preview_op_uid}") cfs_params = op_data.get("instanceOperation", {}).get("cfsParams", []) - print(f"[api_params] cfsParams count={len(cfs_params)}", flush=True) + _log(f"[api_params] cfsParams count={len(cfs_params)}") result = [] for p in cfs_params: dd = p.get("dataDescriptor") pv = p.get("paramValue") dv = p.get("defaultValue") - print(f"[api_params] {p.get('svcOperationCfsParam')}: paramValue={pv!r} defaultValue={dv!r}", flush=True) + _log(f"[api_params] {p.get('svcOperationCfsParam')}: paramValue={pv!r} defaultValue={dv!r}") result.append({ "svcOperationCfsParamId": p["svcOperationCfsParamId"], "name": p.get("svcOperationCfsParam", ""), @@ -133,11 +143,11 @@ def api_params(op_id): "valueList": p.get("valueList"), "dataDescriptor": {k: {"dataType": v.get("dataType",""), "valueList": v.get("valueList",""), "isRequired": v.get("isRequired", False)} for k, v in dd.items()} if isinstance(dd, dict) else None, }) - print(f"[api_params] PREVIEW returning {len(result)} params", flush=True) + _log(f"[api_params] PREVIEW returning {len(result)} params") return jsonify({"params": result, "previewOpUid": preview_op_uid}) # Без instanceUid — шаблонные значения по умолчанию - print(f"[api_params] DEFAULT branch: template defaults", flush=True) + _log(f"[api_params] DEFAULT branch: template defaults") data = _client().get(f"/instanceOperations/default/{op_id}") params = data["svcOperation"]["cfsParams"] result = [] @@ -152,11 +162,11 @@ def api_params(op_id): "valueList": p.get("valueList"), "dataDescriptor": {k: {"dataType": v.get("dataType",""), "valueList": v.get("valueList",""), "isRequired": v.get("isRequired", False)} for k, v in dd.items()} if isinstance(dd, dict) else None, }) - print(f"[api_params] DEFAULT returning {len(result)} params", flush=True) + _log(f"[api_params] DEFAULT returning {len(result)} params") return jsonify({"params": result}) except Exception as e: import traceback - print(f"[api_params] EXCEPTION: {e}", flush=True) + _log(f"[api_params] EXCEPTION: {e}") traceback.print_exc() return jsonify({"error": str(e)}), 500 @@ -326,6 +336,12 @@ def api_test_status(op_uid): return jsonify({"error": str(e)}), 500 +@bp.route("/api/log") +def api_log(): + """Отдать последние строки лога для UI-панели.""" + return jsonify(list(_log_lines)) + + def _find_uid(resp): """Извлечь instanceUid или instanceOperationUid из ответа API.""" if isinstance(resp, dict): diff --git a/site/templates/index.html b/site/templates/index.html index f5f251e..27f6703 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -349,6 +349,26 @@ async function refreshInstances(){ `; document.getElementById('inst-list').innerHTML=html; } + +// === Панель логов === +let logPollTimer=null; +function startLogPoll(){ + if(logPollTimer) return; + logPollTimer=setInterval(async()=>{ + try{ + const r=await fetch('/api/log'); + const lines=await r.json(); + const el=document.getElementById('log-panel'); + if(!el) return; + el.innerHTML=lines.map(l=>`