From 158210dc018142026f29850daa704452e2d2ba31 Mon Sep 17 00:00:00 2001 From: Hermes Team Date: Tue, 25 Aug 2026 00:32:42 +0700 Subject: [PATCH] =?UTF-8?q?fix(web):=20=D0=B1=D1=80=D0=B0=D1=83=D0=B7?= =?UTF-8?q?=D0=B5=D1=80=20=D0=BE=D1=82=D0=B4=D0=B0=D0=B2=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=80=D1=8B=D0=B9=20app.js,=20=D0=B8=D0=BD?= =?UTF-8?q?=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81=20=D0=B2=D1=8B=D0=B3?= =?UTF-8?q?=D0=BB=D1=8F=D0=B4=D0=B5=D0=BB=20=D0=BD=D0=B5=20=D0=BE=D0=B1?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=BB=D1=91=D0=BD=D0=BD=D1=8B=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Владелец сообщил, что в маршрутизации не переставляются блоки, нигде нет выбора аккаунта для роли и на «Обзоре» ничего нельзя изменить. На скриншоте при этом видна кнопка «Изменить цепочку», которой в коде уже нет: A24 её удалил вместе с renderTeam и renderProviders. Причина не в логике. FileResponse отдавал app.js и style.css без заголовка Cache-Control, поэтому браузер применял эвристическое кэширование и держал скрипт от прошлой сборки. index.html при навигации перепроверялся и был свежим — отсюда смесь нового текста подсказки со старыми кнопками, а перетаскивание и выбор модели просто отсутствовали в загруженном коде. Действие reorder_chain при этом исправно: проверено вызовом, порядок цепочки меняется и сохраняется в router_profiles.yaml. Заодно подключённые аккаунты выводятся первыми, а пустые слоты («не подключён») — в конце группы: рабочие карточки были разбросаны между пустыми и их приходилось выискивать. Co-Authored-By: Claude Opus 5 --- src/antigravity_provider/router/web/server.py | 20 ++++++++++++++++--- .../router/web/static/app.js | 17 ++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/antigravity_provider/router/web/server.py b/src/antigravity_provider/router/web/server.py index 5321228..3acea70 100644 --- a/src/antigravity_provider/router/web/server.py +++ b/src/antigravity_provider/router/web/server.py @@ -199,18 +199,32 @@ _STATIC_DIR = Path(__file__).resolve().parent / "static" if _STATIC_DIR.is_dir(): app.mount("/static", StaticFiles(directory=str(_STATIC_DIR)), name="static") + # Ассеты обязаны перепроверяться при каждой загрузке. + # Без Cache-Control браузер применяет эвристическое кэширование и может + # неделями отдавать старый app.js: интерфейс выглядит обновлённым (index.html + # перепроверяется при навигации), а поведение остаётся от прошлой сборки. + _NO_CACHE = {"Cache-Control": "no-cache, must-revalidate"} + @app.get("/") @app.get("/index.html") def index(): - return FileResponse(str(_STATIC_DIR / "index.html")) + return FileResponse(str(_STATIC_DIR / "index.html"), headers=_NO_CACHE) @app.get("/app.js") def _app_js(): - return FileResponse(str(_STATIC_DIR / "app.js"), media_type="application/javascript") + return FileResponse( + str(_STATIC_DIR / "app.js"), + media_type="application/javascript", + headers=_NO_CACHE, + ) @app.get("/style.css") def _style_css(): - return FileResponse(str(_STATIC_DIR / "style.css"), media_type="text/css") + return FileResponse( + str(_STATIC_DIR / "style.css"), + media_type="text/css", + headers=_NO_CACHE, + ) @app.get("/snapshot.example.json") def _fixture(): diff --git a/src/antigravity_provider/router/web/static/app.js b/src/antigravity_provider/router/web/static/app.js index 0644f00..dd3e83f 100644 --- a/src/antigravity_provider/router/web/static/app.js +++ b/src/antigravity_provider/router/web/static/app.js @@ -434,6 +434,23 @@ function renderAccountsView() { return matchesSearch && matchesHealth; }); + // Подключённые аккаунты идут первыми: владелец жаловался, что рабочие + // карточки разбросаны между пустыми слотами и их приходится выискивать. + // Пустые слоты ('не подключён') — всегда в конце группы. + filtered.sort((a, b) => { + const rank = (p) => { + if (p.health_state === 'not_configured') return 5; + if (p.is_cold_spare || !p.enabled || p.health_state === 'disabled') return 4; + if (p.health_state === 'not_tested') return 3; + if (p.health_state === 'auth_required' || p.health_state === 'auth_expired') return 2; + if (p.health_state === 'healthy') return 0; + return 1; + }; + const d = rank(a) - rank(b); + if (d !== 0) return d; + return (a.display_name || '').localeCompare(b.display_name || '', 'ru'); + }); + if (filtered.length === 0) continue; visibleProfiles += filtered.length;