From 766b50cd7b1b60fcf4150cb63c359fb4df6fc0f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Mon, 27 Jul 2026 13:29:38 +0400 Subject: [PATCH] v1.0.76: modify shows actual instance param values (preview operation) --- DOCS/ARCHITECTURE.md | 3 ++- DOCS/HISTORY.md | 24 +++++++++++++++++++++ site/app.py | 2 +- site/routes/api_test.py | 44 ++++++++++++++++++++++++++++++++++++--- site/templates/index.html | 11 +++++++--- 5 files changed, 76 insertions(+), 8 deletions(-) diff --git a/DOCS/ARCHITECTURE.md b/DOCS/ARCHITECTURE.md index 30b4696..9410c28 100644 --- a/DOCS/ARCHITECTURE.md +++ b/DOCS/ARCHITECTURE.md @@ -1,6 +1,6 @@ # Архитектура app-autotest — полный документ -v1.0.74, 27.07.2026 +v1.0.76, 27.07.2026 --- @@ -15,6 +15,7 @@ Flask-приложение для автотестов операций серв - CREATE использует фиксированный префикс `autotest-`, non-create работает по выбранному инстансу. - Финальный статус показывает статус, операцию, длительность и имя инстанса. - Список autotest-инстансов строится cloud-first; tracker нужен только как короткий fallback после CREATE. +- **Modify** (и другие операции с параметрами) показывает **текущие** значения параметров инстанса: создаётся pending-операция, читаются `cfsParams.paramValue`, форма заполняется реальными данными. При submit операция переиспользуется через `previewOpUid`. --- diff --git a/DOCS/HISTORY.md b/DOCS/HISTORY.md index 5c291e9..20537f3 100644 --- a/DOCS/HISTORY.md +++ b/DOCS/HISTORY.md @@ -1,5 +1,29 @@ # История разработки app-autotest +## v1.0.76 (27.07.2026) — modify показывает актуальные значения параметров инстанса + +### Что изменилось +- В `site/routes/api_test.py` `GET /api/params/` теперь принимает `?instanceUid=xxx&opName=xxx`: + - Создаёт pending-операцию через `POST /instanceOperations`, читает `GET /instanceOperations/{opUid}` и возвращает **текущие** `paramValue` из `cfsParams` вместо шаблонных `defaultValue`. + - В ответ добавлен `previewOpUid` — UID созданной preview-операции. +- В `site/routes/api_test.py` `POST /api/test` принимает опциональный `previewOpUid`: + - Если передан — переиспользует уже созданную операцию, пропускает `POST /instanceOperations`. +- В `site/templates/index.html` `showParams()` для не-create операций передаёт `?instanceUid=&opName=` в запрос параметров и сохраняет `previewOpUid`. +- В `site/templates/index.html` `executeOp()` передаёт `previewOpUid` в теле запроса. +- Версия поднята до `1.0.76`. + +### Что это чинит +- Форма modify теперь показывает **реальные текущие значения** параметров инстанса (а не шаблонные `0`, `false`, `""`). +- Раньше: `GET /instanceOperations/default/{opId}` → `defaultValue` из шаблона. +- Теперь: `POST /instanceOperations` → `GET /instanceOperations/{opUid}` → `paramValue` из инстанса. + +### Как это работает +1. Пользователь нажимает modify → `showParams(opId, 'modify')` +2. Фронт вызывает `GET /api/params/{opId}?instanceUid={iuid}&opName=modify` +3. Бэкенд создаёт pending-операцию и читает её `cfsParams` с актуальными `paramValue` +4. Форма заполняется реальными значениями, `previewOpUid` сохраняется +5. При submit → `POST /api/test` с `previewOpUid` → переиспользуется та же операция, выставляются новые params → run + # v1.0.74 (27.07.2026) — version bump for push ### Что изменилось diff --git a/site/app.py b/site/app.py index ef3ac33..2ff1229 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.74" +VERSION = "1.0.76" 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 d21603c..e78860f 100644 --- a/site/routes/api_test.py +++ b/site/routes/api_test.py @@ -100,10 +100,40 @@ def api_operations(svc_id): @bp.route("/api/params/") def api_params(op_id): try: + instance_uid = request.args.get("instanceUid") + op_name = request.args.get("opName") + + if instance_uid and op_name: + # Создать 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", "")) + 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", []) + + result = [] + for p in cfs_params: + dd = p.get("dataDescriptor") + result.append({ + "svcOperationCfsParamId": p["svcOperationCfsParamId"], + "name": p.get("svcOperationCfsParam", ""), + "dataType": p.get("dataType", ""), + "isRequired": p.get("isRequired", False), + "defaultValue": p.get("paramValue"), # текущее значение из инстанса + "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, + }) + return jsonify({"params": result, "previewOpUid": preview_op_uid}) + + # Без instanceUid — шаблонные значения по умолчанию data = _client().get(f"/instanceOperations/default/{op_id}") params = data["svcOperation"]["cfsParams"] result = [] for p in params: + dd = p.get("dataDescriptor") result.append({ "svcOperationCfsParamId": p["svcOperationCfsParamId"], "name": p.get("svcOperationCfsParam", ""), @@ -111,9 +141,9 @@ def api_params(op_id): "isRequired": p.get("isRequired", False), "defaultValue": p.get("defaultValue"), "valueList": p.get("valueList"), - "dataDescriptor": {k: {"dataType": v.get("dataType",""), "valueList": v.get("valueList",""), "isRequired": v.get("isRequired", False)} for k, v in p.get("dataDescriptor", {}).items()} if p.get("dataDescriptor") else None, + "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, }) - return jsonify(result) + return jsonify({"params": result}) except Exception as e: return jsonify({"error": str(e)}), 500 @@ -130,6 +160,7 @@ def api_test(): params = data.get("params", {}) instance_uid = data.get("instanceUid") display_name = data.get("displayName") or "" + preview_op_uid = data.get("previewOpUid") client = _client() @@ -172,7 +203,14 @@ def api_test(): if not instance_uid: return jsonify({"status": "FAIL", "error": "Нет instanceUid"}), 400 - if op_name == "redeploy": + if preview_op_uid: + # Переиспользовать preview-операцию (уже создана в api_params) + op_uid = preview_op_uid + for pid, pval in params.items(): + client.post("/instanceOperationCfsParams", + {"instanceOperationUid": op_uid, "svcOperationCfsParamId": int(pid), "paramValue": str(pval)}) + client.post(f"/instanceOperations/{op_uid}/run") + elif op_name == "redeploy": op_payload = {"instanceUid": instance_uid, "svcOperationId": svc_op_id, "operation": op_name} op_resp = client.post("/instanceOperations", op_payload) op_uid = _find_uid(op_resp) or _uid_from_location(op_resp.get("_location", "")) diff --git a/site/templates/index.html b/site/templates/index.html index 4952b4e..f5f251e 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -205,9 +205,13 @@ function showParams(opId,opName){ document.getElementById('stages-box').style.display='none'; document.getElementById('test-status').textContent=''; - fetch('/api/params/'+opId).then(r=>r.json()).then(params=>{ + const isCreate=opName==='create'; + const qs=isCreate?'' : `?instanceUid=${selectedInst}&opName=${opName}`; + fetch('/api/params/'+opId+qs).then(r=>r.json()).then(data=>{ + const params=data.params||data; // data.params в новом формате, fallback на старый массив + if(data.previewOpUid) selectedOp.previewOpUid=data.previewOpUid; const form=document.getElementById('params-form'); - const displayNameValue=opName==='create'?makeCreateDisplayName():''; + const displayNameValue=isCreate?makeCreateDisplayName():''; form.innerHTML=(opName==='create'?`
${AUTOTEST_PREFIX}
`:'') + params.map(p=>{ const req=p.isRequired; @@ -284,7 +288,8 @@ async function executeOp(params){ svcOperationId:selectedOp.opId, params, instanceUid:selectedInst||'', - displayName:isCreate ? displayName : '' + displayName:isCreate ? displayName : '', + previewOpUid:selectedOp.previewOpUid||'' })}); const d=await r.json(); if(d.status==='FAIL'){document.getElementById('test-status').innerHTML=` FAIL ${d.error||''}`;btn.disabled=false;return;}