From a271e863f3043541e5d95cfdab8d789eac3e55c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Mon, 27 Jul 2026 09:43:36 +0400 Subject: [PATCH] v1.0.50: fix showStages icon (dtFinish); fix api_operations (add tracked orphans); drop not_created filter --- DOCS/sonnet-missed-bugs.md | 66 ++++++++++++++++++++++++++++++++++ DOCS/sonnet-response-round2.md | 39 ++++++++++++++++++++ site/app.py | 2 +- site/routes/api_test.py | 16 +++++++-- site/templates/index.html | 4 +-- 5 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 DOCS/sonnet-missed-bugs.md create mode 100644 DOCS/sonnet-response-round2.md diff --git a/DOCS/sonnet-missed-bugs.md b/DOCS/sonnet-missed-bugs.md new file mode 100644 index 0000000..387b353 --- /dev/null +++ b/DOCS/sonnet-missed-bugs.md @@ -0,0 +1,66 @@ +# Sonnet — почему ты пропустил эти баги? + +Отправлено: 27.07.2026, после v1.0.49 (твой анализ выполнен, НЕ ПОМОГЛО) + +--- + +## Ты предложил 4 правки — мы сделали. Инстанс ВСЁ РАВНО не появляется в списке. Плюс новый баг: ❌ вместо ⏳ для этапов в процессе. + +## Ты НЕ заметил баг №1: showStages() — ❌ для выполняющихся этапов + +```javascript +function showStages(stages){ + stages.forEach(s=>{ + const ok=s.isSuccessful; + const icon=ok===true?'✅':ok===false?'❌':'⏳'; // ← БАГ +``` + +API Nubes для этапа в процессе возвращает `isSuccessful: false` (или `null`). +Код интерпретирует `false === false` → ❌ (авария). +Но этап просто **ещё не завершился** — должен быть ⏳. + +**Правильная логика:** проверять `dtFinish` этапа. Если `dtFinish` нет → ⏳. +Если есть и `isSuccessful === true` → ✅. Если есть и `false` → ❌. + +Почему ты это пропустил? Код `showStages()` был в твоём обзоре. + +--- + +## Ты НЕ заметил баг №2: api_operations() теряет инстансы + +```python +def api_operations(svc_id): + tracked = tracker_list() # ← 5 UID (включая новый) + tracked_uids = {t["instanceUid"] for t in tracked if t["svcId"] == svc_id} + instances = get_instances(_client()) # ← Nubes API: только 4 старых + svc_instances = [i for i in instances + if i.get("instanceUid") in tracked_uids # ← новый UID НЕ НАЙДЕН + and i.get("explainedStatus") not in ("deleted", "not created")] +``` + +`tracker_add` работает (in-memory, мгновенно). Но `get_instances()` из Nubes API **не сразу** возвращает только что созданный инстанс. +Фильтр требует чтобы инстанс был В ОБОИХ источниках. Новый UID есть в `tracked_uids` но отсутствует в `instances` → выпадает. + +Результат: список всегда показывает только старые инстансы, новый — никогда. + +**Правильная логика:** инстансы из трекера, отсутствующие в `get_instances()`, добавлять напрямую со статусом `"creating"`. + +Почему ты это пропустил? Код `api_operations()` был в твоём обзоре, полностью. + +--- + +## Что мы имеем после твоих рекомендаций + +| Версия | Что делали | Результат | +|--------|-----------|-----------| +| v1.0.48 | tracker_add синхронно, except:pass | ❌ инстанса нет | +| v1.0.49 | in-memory dict (твоя рекомендация) | ❌ инстанса нет | +| v1.0.49 | refreshInstances вместо selectService | ❌ stages ушли, но инстанса нет | + +Трекер РАБОТАЕТ — in-memory dict, добавление мгновенное. +НО фильтр `api_operations()` ВЫБРАСЫВАЕТ новый инстанс потому что Nubes API его ещё не проиндексировал. + +## Вопрос + +Ты проанализировал ВЕСЬ код, включая `api_operations()` и `showStages()`. +Как ты мог пропустить оба этих бага, которые видны при простой трассировке CREATE-потока? diff --git a/DOCS/sonnet-response-round2.md b/DOCS/sonnet-response-round2.md new file mode 100644 index 0000000..0dbbe9e --- /dev/null +++ b/DOCS/sonnet-response-round2.md @@ -0,0 +1,39 @@ +# Sonnet — ответ 27.07.2026 (v1.0.49, раунд 2) + +## Найденные баги + +### Баг #1: showStages() — ❌ для этапов в процессе +Файл: `site/templates/index.html` +Nubes возвращает `isSuccessful: false` для этапов «в процессе». Код: +```javascript +const icon=ok===true?'✅':ok===false?'❌':'⏳'; +``` +`false === false` → ❌. Должно быть ⏳. + +Исправление: проверять `s.dtFinish`. Если нет → ⏳, если есть и `true` → ✅, если есть и `false` → ❌. + +### Баг #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" → фильтр удаляет. + +Исправление: для tracked UID не применять фильтр `"not created"`. Добавлять отсутствующие из трекера напрямую. + +### Баг #3: Дублирующийся route +`main_bp` зарегистрирован ПЕРВЫМ, `api_test_bp` — ВТОРЫМ. Оба определяют `GET /api/operations/`. Flask использует первое совпадение → `main.api_operations` вызывается, `api_test.api_operations` — никогда. + +### Потенциальная проблема #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 diff --git a/site/app.py b/site/app.py index 5b4f9bd..9c76316 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.49" +VERSION = "1.0.50" 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 bc6a0b2..18b0bec 100644 --- a/site/routes/api_test.py +++ b/site/routes/api_test.py @@ -42,11 +42,23 @@ def api_operations(svc_id): ops = detail.get("operations", []) # только отслеживаемые инстансы этого сервиса tracked = tracker_list() - tracked_uids = {t["instanceUid"] for t in tracked if t["svcId"] == svc_id} + 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(_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", "not created")] + 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], diff --git a/site/templates/index.html b/site/templates/index.html index 51b0247..02e983e 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -263,8 +263,8 @@ function showStages(stages){ if(!stages||!stages.length) return; let html='
Этапы
'; stages.forEach(s=>{ - const ok=s.isSuccessful; - const icon=ok===true?'✅':ok===false?'❌':'⏳'; + const done=!!s.dtFinish; + const icon=done?(s.isSuccessful?'✅':'❌'):'⏳'; html+=`
${icon} ${s.stage} — ${(s.duration||0).toFixed(1)}s
`; }); document.getElementById('stages-box').innerHTML=html;