diff --git a/installer/HermesHubSetup.cs b/installer/HermesHubSetup.cs index be08342..af141f2 100644 --- a/installer/HermesHubSetup.cs +++ b/installer/HermesHubSetup.cs @@ -18,7 +18,7 @@ namespace HermesHubSetup // Подставляется сборщиком из фактического git-коммита. Раньше здесь // жил зашитый "8cddc9f", то есть манифест сообщал неправду о том, из // какого кода собран установщик. - public const string BuildCommit = "fa7bbef"; + public const string BuildCommit = "90aeceb"; public const string MIN_HERMES_VERSION = "0.20.0"; public const string MAX_TESTED_HERMES = "0.20.4"; diff --git a/launcher/HermesHub.exe b/launcher/HermesHub.exe index 7cef02a..e966728 100644 Binary files a/launcher/HermesHub.exe and b/launcher/HermesHub.exe differ diff --git a/launcher/HermesHubWeb.exe b/launcher/HermesHubWeb.exe index 38e0dfd..57a0728 100644 Binary files a/launcher/HermesHubWeb.exe and b/launcher/HermesHubWeb.exe differ diff --git a/src/antigravity_provider/router/web/static/index.html b/src/antigravity_provider/router/web/static/index.html index 45cefe7..96ab776 100644 --- a/src/antigravity_provider/router/web/static/index.html +++ b/src/antigravity_provider/router/web/static/index.html @@ -457,7 +457,7 @@
- +
Google отказывает по местоположению: «not currently available in your location». Выход через разрешённую страну снимает отказ. Пример: socks5://127.0.0.1:1080. Пусто — без прокси. Отдельному аккаунту можно задать свой адрес.
diff --git a/src/antigravity_provider/router/web/static/workspace.js b/src/antigravity_provider/router/web/static/workspace.js index 02a99d6..299e64e 100644 --- a/src/antigravity_provider/router/web/static/workspace.js +++ b/src/antigravity_provider/router/web/static/workspace.js @@ -229,14 +229,32 @@ function arrangeSettingsPanels() { ['Общие настройки',['setting-default-role','setting-theme']], ['Управление квотами',['setting-account-check-interval','setting-quota-interval','setting-quota-threshold-percent','setting-quota-threshold-action']], ['Безопасность и API',['setting-server-host','setting-server-token-input','setting-email-masking-mode']], + ['Сеть и доступ',['setting-provider-proxy-url']], ]; for (const [title,ids] of groups) { const card = document.createElement('section'); card.className='settings-card'; const heading=document.createElement('h2'); heading.className='settings-group-title'; heading.textContent=title; card.append(heading); - for (const id of ids) card.append(document.getElementById(id).closest('.setting-row')); + for (const id of ids) { + const row = document.getElementById(id)?.closest('.setting-row'); + if (row) card.append(row); + } if (title === 'Управление квотами') card.append(document.getElementById('btn-save-hub-settings').closest('.settings-actions')); view.insertBefore(card, first); } + + // Строки, которых нет ни в одной группе, раньше удалялись вместе с исходной + // карточкой: новая настройка просто пропадала с экрана, и найти её было + // нельзя. Так исчезло поле прокси. Теперь остаток не выбрасывается, а + // собирается отдельной карточкой — забыть настройку в списке всё ещё можно, + // но потерять её уже нет. + const leftovers = Array.from(first.querySelectorAll('.setting-row')); + if (leftovers.length) { + const card = document.createElement('section'); card.className='settings-card'; + const heading=document.createElement('h2'); heading.className='settings-group-title'; + heading.textContent='Прочие настройки'; card.append(heading); + for (const row of leftovers) card.append(row); + view.insertBefore(card, first); + } first.remove(); } document.addEventListener('DOMContentLoaded', arrangeSettingsPanels); diff --git a/tests/test_settings_screen_keeps_every_row.py b/tests/test_settings_screen_keeps_every_row.py new file mode 100644 index 0000000..3161611 --- /dev/null +++ b/tests/test_settings_screen_keeps_every_row.py @@ -0,0 +1,61 @@ +"""Экран настроек не должен молча терять поля. + +Владелец не нашёл настройку прокси. Она была в разметке, но исчезала при +открытии экрана: arrangeSettingsPanels пересобирает настройки по жёсткому +списку идентификаторов, а исходную карточку удаляет целиком — вместе со всем, +чего в списке нет. + +Поле существовало в файле, но его не было на экране, и никакая проверка этого +не ловила. +""" +from __future__ import annotations + +import re +from pathlib import Path + +import pytest + +STATIC = Path(__file__).resolve().parent.parent / "src" / "antigravity_provider" / "router" / "web" / "static" + + +@pytest.fixture(scope="module") +def markup() -> str: + return (STATIC / "index.html").read_text(encoding="utf-8") + + +@pytest.fixture(scope="module") +def workspace() -> str: + return (STATIC / "workspace.js").read_text(encoding="utf-8") + + +def _grouped_ids(workspace: str) -> list[str]: + block = workspace.split("const groups = [")[1].split("];")[0] + return re.findall(r"'(setting-[a-z0-9-]+)'", block) + + +def test_every_grouped_id_exists_in_the_markup(markup, workspace): + for element_id in _grouped_ids(workspace): + assert f'id="{element_id}"' in markup, f"{element_id} перечислен в группах, но его нет в разметке" + + +def test_proxy_setting_is_reachable(markup, workspace): + assert 'id="setting-provider-proxy-url"' in markup + assert "setting-provider-proxy-url" in _grouped_ids(workspace), ( + "негруппированная строка удаляется вместе с исходной карточкой" + ) + + +def test_unlisted_rows_are_kept_not_deleted(workspace): + """Забыть настройку в списке можно, потерять её с экрана — нет.""" + body = workspace.split("function arrangeSettingsPanels")[1].split("function ")[0] + + assert "leftovers" in body, "остаток строк должен собираться, а не выбрасываться" + assert body.index("leftovers") < body.index("first.remove()"), ( + "остаток надо забрать до удаления исходной карточки" + ) + + +def test_missing_element_does_not_break_the_screen(workspace): + """Опечатка в списке не должна ронять сборку экрана целиком.""" + body = workspace.split("function arrangeSettingsPanels")[1].split("function ")[0] + assert "?.closest" in body, "обращение к возможному null должно быть защищено"