diff --git a/DOCS/sonnet-response-round2.md b/DOCS/sonnet-response-round2.md index fadd1e6..5e916b5 100644 --- a/DOCS/sonnet-response-round2.md +++ b/DOCS/sonnet-response-round2.md @@ -1,41 +1,39 @@ -# ⚠️ LEGACY — НЕАКТУАЛЬНО. Исторический документ. +# Sonnet 4.6 — Round 2 Response -# Sonnet — ответ 27.07.2026 (v1.0.49, раунд 2) +Дата: 2026-07-29 -## Найденные баги +## Ответы -### Баг #1: showStages() — ❌ для этапов в процессе -Файл: `site/templates/index.html` -Nubes возвращает `isSuccessful: false` для этапов «в процессе». Код: -```javascript -const icon=ok===true?'✅':ok===false?'❌':'⏳'; -``` -`false === false` → ❌. Должно быть ⏳. +### 1. Фильтр serviceId ✅ +Добавить `if i.get("serviceId") != svc_id: continue` — безопасно. +Tracker-fallback уже отфильтрован по svc_id, не сломается. +Даже улучшит дедупликацию cloud_names. -Исправление: проверять `s.dtFinish`. Если нет → ⏳, если есть и `true` → ✅, если есть и `false` → ❌. +### 2. Генерация 3 символов +Надёжнее: `(Math.random() * 46656 | 0).toString(36).padStart(3, '0')` +Ровно 3 символа, равномерно по 46 656 комбинациям. -### Баг #2: api_operations() — фильтр теряет новые инстансы -Файлы: `site/routes/api_test.py` + `site/routes/main.py` -После `tracker_add` (мгновенно in-memory) инстанс есть в трекере, но Nubes API ещё не обновил `explainedStatus` — инстанс имеет статус `"not created"`. Фильтр: -```python -and i.get("explainedStatus") not in ("deleted", "not created") -``` -отбрасывает его. Инстанс есть в трекере, есть в ответе API — но статус "not created" → фильтр удаляет. +### 3. descr +Не нужно тащить через JS! `app_version` уже есть в той же функции. +Одна строка: `"descr": f"created by autotest v{app_version}"` -Исправление: для tracked UID не применять фильтр `"not created"`. Добавлять отсутствующие из трекера напрямую. +### 4. Пропущенный баг: stand-ключ трекера +main.py → `stand_name(endpoint)` +api_test.py → `get_stand()` +Могут разойтись → tracker_add и tracker_list под разными ключами → creating инстансы не видны. -### Баг #3: Дублирующийся route -`main_bp` зарегистрирован ПЕРВЫМ, `api_test_bp` — ВТОРЫМ. Оба определяют `GET /api/operations/`. Flask использует первое совпадение → `main.api_operations` вызывается, `api_test.api_operations` — никогда. +### 5. Дубликат эндпоинта +Удалить `/api/operations/{svc_id}` из api_test.py (строки 108-148). +Недостижим, старый код, техдолг. -### Потенциальная проблема #4: Multi-worker gunicorn -In-memory dict НЕ работает при >1 gunicorn worker. Один worker добавляет в свой `_data`, другой читает из своего. Если pythonk8s запускает >1 worker — ни одно решение не сработает. +## Итоговый план исправлений -Нужно проверить логи пода на `Booting worker with pid`. - ---- - -## Исправления (v1.0.50) - -1. `showStages()`: использовать `dtFinish` этапа -2. `api_operations()`: не фильтровать "not created" для tracked UID, добавлять сирот из трекера -3. Убрать дублирующийся route +| # | Файл | Что | +|---|------|-----| +| 1 | main.py:194 | `if i.get("serviceId") != svc_id: continue` | +| 2 | main.py:227 | `"svcShort": detail.get("svcShort", "")` | +| 3 | app.js:53 | `currentSvcShort = d.svcShort\|\|''` | +| 4 | app.js:113-116 | Переписать `makeCreateDisplayName()` | +| 5 | api_test.py:212 | `f"created by autotest v{app_version}"` | +| 6 | api_test.py:108-148 | Удалить дубликат эндпоинта | +| 7 | main.py + api_test.py | Унифицировать stand | diff --git a/site/app.py b/site/app.py index a9fdca7..3188eb4 100644 --- a/site/app.py +++ b/site/app.py @@ -18,7 +18,7 @@ from routes.api import bp as api_bp from routes.api_test import bp as api_test_bp # Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI. -VERSION = "1.1.13" +VERSION = "1.1.14" app = Flask(__name__, template_folder="templates", static_folder="static") app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc") diff --git a/site/routes/api_test.py b/site/routes/api_test.py index 48c9d7b..c9fff2a 100644 --- a/site/routes/api_test.py +++ b/site/routes/api_test.py @@ -115,39 +115,6 @@ def api_instances_list(): return jsonify({"error": str(e)}), 500 -@bp.route("/api/operations/") -def api_operations(svc_id): - try: - detail = get_service_detail(get_client(), svc_id) - ops = detail.get("operations", []) - # только отслеживаемые инстансы этого сервиса - tracked = tracker_list(get_client_id(), get_stand()) - tracked_by_uid = {t["instanceUid"]: t for t in tracked if t["svcId"] == svc_id} - tracked_uids = set(tracked_by_uid.keys()) - instances = get_instances(get_client()) - nubes_uids = {i["instanceUid"] for i in instances} - svc_instances = [i for i in instances - if i.get("instanceUid") in tracked_uids - and i.get("explainedStatus") not in ("deleted",)] - # Добавить tracked инстансы, которых Nubes ещё не отдаёт (creating) - for uid, t in tracked_by_uid.items(): - if uid not in nubes_uids: - svc_instances.append({ - "instanceUid": uid, - "displayName": t["displayName"], - "serviceId": svc_id, - "svc": detail.get("svc", ""), - "explainedStatus": "creating", - }) - return jsonify({ - "svc": detail.get("svc", ""), - "operations": [{"svcOperationId": o["svcOperationId"], "operation": o["operation"]} for o in ops], - "instances": svc_instances, - }) - except Exception as e: - return jsonify({"error": str(e)}), 500 - - @bp.route("/api/params/") def api_params(op_id): """Параметры операции. Если передан instanceUid — с текущими значениями из API.""" @@ -209,7 +176,8 @@ def api_test(): if not display_name: display_name = f"autotest-{svc_id}" display_name = _unique_display_name(client, display_name) - payload = {"serviceId": svc_id, "displayName": display_name, "descr": ""} + descr = f"created by autotest v{current_app.config.get('VERSION', '')}" + payload = {"serviceId": svc_id, "displayName": display_name, "descr": descr} resp = client.post("/instances", payload) instance_uid = resp.get("instanceUid") or _find_uid(resp) or _uid_from_location(resp.get("_location", "")) if not instance_uid: diff --git a/site/routes/main.py b/site/routes/main.py index b16be44..fa012b4 100644 --- a/site/routes/main.py +++ b/site/routes/main.py @@ -194,6 +194,8 @@ def api_operations(svc_id): display_name = str(i.get("displayName", "") or "") if not display_name.startswith(AUTOTEST_PREFIX): continue # не наш инстанс + if i.get("serviceId") != svc_id: + continue # не нашего сервиса if i.get("explainedStatus") in ("deleted",): continue # удалённые не показываем item = dict(i) @@ -220,6 +222,7 @@ def api_operations(svc_id): return jsonify({ "svc": detail.get("svc", ""), + "svcShort": detail.get("svcShort", ""), "operations": ops, # [{svcOperationId, operation}, ...] "instances": svc_instances, # [{instanceUid, displayName, status, ...}, ...] }) diff --git a/site/static/app.js b/site/static/app.js index 6bc5e54..52596ac 100644 --- a/site/static/app.js +++ b/site/static/app.js @@ -47,6 +47,7 @@ async function selectService(svcId){ const d=await r.json(); svcInstances=d.instances||[]; currentSvcName=d.svc||''; + currentSvcShort=d.svcShort||''; // Обновить текст кнопки создания const btnSpan=document.querySelector('#create-btn-area span'); if(btnSpan) btnSpan.textContent='+ Создать новый инстанс '+(currentSvcName||'сервиса'); @@ -110,8 +111,8 @@ async function startCreate(){ } function makeCreateDisplayName(){ - const suffix = Date.now().toString(36); - return suffix; + const rand = (Math.random() * 46656 | 0).toString(36).padStart(3, '0'); + return currentSvcShort ? currentSvcShort+'-'+rand : rand; } function runOp(opName,opId){