Files
ayurishchevandClaude Opus 5.5 d1a2efd683 Задача 042: свёртка свободных адресов в раскрывающиеся диапазоны
В списке адресов префикса подряд идущие свободные адреса сворачиваются в строку
«начало – конец · Свободно: N» (параметр group_free, группировка на сервере — пагинация
по строкам после свёртки); раскрытие — существующим status=free&offset=free_offset
порциями по 500. Без group_free ответ API прежний.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 19:40:22 +03:00

5.7 KiB
Raw Permalink Blame History

Свёртка свободных адресов в раскрывающиеся диапазоны (изменение 042)

Context

В списке адресов префикса (screens.address) для подсетей до /20 занятые и свободные адреса идут вперемешку, каждый свободный — отдельной строкой. В почти пустой /24 это ~250 строк «Свободен», которые оттесняют занятые адреса. Нужно сворачивать подряд идущие свободные адреса в одну строку-диапазон: «10.0.0.5 – 10.0.0.20 · свободно 16». По клику строка раскрывается в отдельные адреса, повторный клик сворачивает.

Решение

Группировка на сервере. Если сворачивать на клиенте в пределах страницы, диапазоны рвутся на границе пагинации, а страница в 100 строк почти целиком занята свободными. С группировкой на сервере страница из 100 строк показывает 100 занятых адресов или диапазонов.

API — app/api/v1/prefixes.py::list_addresses

  • Новый параметр group_free: bool = False: по умолчанию поведение прежнее (совместимость, тесты). Действует только в смешанном режиме (mixed: без фильтра статуса и поиска, подсеть до FREE_LISTING_LIMIT).
  • Подряд идущие свободные адреса (серия ≥ 2) сворачиваются в одну строку AddressOut:
    • status="free_range", address — первый адрес, range_end — последний;
    • range_count — число адресов;
    • free_offset — сколько свободных адресов подсети идёт до начала диапазона. Одиночный свободный адрес остаётся обычной строкой free. total и пагинация считаются по строкам после свёртки.
  • app/schemas.py::AddressOut: range_end: str | None = None, range_count: int | None = None, free_offset: int | None = None.
  • Раскрытие диапазона — существующий GET …/addresses?status=free&offset=<free_offset>&limit=<n>: free_page уже считает страницу свободных арифметически, новых эндпоинтов не нужно.

UI — web/app.js::screens.address

  • Запрос списка — с group_free: true.
  • Строка free_range: шеврон ▸/▾, текст «<начало> – <конец>», в колонке статуса — бейдж «Свободно: N». Остальные колонки — «—». Действия «Назначить адрес» у свёрнутой строки нет: назначают из раскрытых строк. Строка не выбирается чекбоксом.
  • Клик по строке диапазона раскрывает или сворачивает её. Раскрытое состояние — S.freeOpen: множество начальных адресов, сбрасывается при смене префикса, фильтра или поиска. При раскрытии: api(…/addresses, {status: "free", offset: free_offset, limit: min(range_count, 500)}), строки вставляются под диапазоном с отступом, как обычные строки «Свободен», с действием «Назначить адрес». Если в диапазоне больше 500 адресов, после первых 500 показывается строка «ещё N — показать», она подгружает следующую порцию.
  • «Показано X из Y» учитывает строки после свёртки.
  • Стиль — существующие классы и токены (.tr.free, badge, I.chevR/I.chevD как в дереве префиксов); литеральных цветов нет, тёмная тема работает.

Тест (минимально)

В существующем тесте tests/test_api.py, работающем с адресами малой подсети (например, test_tree_utilization_and_next_free), проверить: GET /prefixes/{leaf}/addresses?group_free=true → строка free_range с корректными address, range_end, range_count, free_offset; status=free&offset=<free_offset>&limit=<range_count> возвращает ровно адреса этого диапазона.

Документация

  • README.md: «Адреса» и «Интерфейс» — свёртка свободных; строка 042 в истории.
  • SUMMARY.md — по завершении.

Исполнение

По принятой схеме: код и тест пишет агент на Sonnet, он же пересобирает стенд с --force-recreate. Моя часть — ревью и pytest. UI проверяет пользователь.

Проверка

  • pytest -q — все зелёные.
  • API: /24 с парой назначенных адресов → 3–5 строк вместо ~254; free_offset и раскрытие дают ровно адреса диапазона; IPv6 /120 — тоже.
  • Без group_free ответ идентичен прежнему.