# Автовыделение следующего вложенного префикса (изменение 010) ## Context Когда префикс-контейнер (например `172.20.0.0/24`) дробят на подсети одного размера (`/30`), свободный блок приходится считать вручную. Нужна кнопка: пользователь выбирает родителя и размер (`/30`), система сама находит первый свободный выровненный блок внутри родителя и создаёт дочерний префикс. ## Алгоритм выбора блока (`app/services.py`, чистая функция, без БД) `next_free_subnet(parent_cidr, length, occupied) -> str | None`: 1. `occupied` — диапазоны `[start, end]` (целые) всех **уже существующих префиксов внутри родителя в том же VRF** (все уровни вложенности) и адресов, записанных на самом родителе (чтобы не выдать блок, где уже есть назначенные IP). Диапазоны сортируются и склеиваются. 2. Кандидат начинается с адреса родителя и выравнивается по размеру блока (`ceil(start / size) * size`); если пересекает занятый диапазон — перескок за его конец, повторное выравнивание. Сложность O(n) от числа занятых диапазонов, без перебора всех подсетей (работает и для `/8 → /30`, и для IPv6). 3. Кандидат не выходит за границу родителя; иначе `None`. Первый подходящий (наименьший адрес) — предсказуемо: `.0/30`, `.4/30`, `.8/30`…; для смешанных размеров блок выравнивается сам (`/29` после `.0/30`, `.4/30` → `.8/29`). ## API (`app/api/v1/prefixes.py`, схемы в `app/schemas.py`) - `POST /prefixes/{id}/subnets/next` (admin) — тело `SubnetNextIn`: `length` (int), `description`, `status` (по умолчанию `active`), `is_pool`, `note`. Создаёт префикс в VRF и организации родителя, `parent_id = id`. Возвращает `PrefixOut` (201). Ошибки: `length` не больше длины родителя или больше 32/128 → 422 (с пределами в тексте); нет свободного блока → 409 «В префиксе нет свободного блока /30». Родитель блокируется `SELECT … FOR UPDATE` — параллельные запросы не выдают один блок; уникальность `(vrf_id, prefix)` остаётся страховкой (`flush` → 409). Переиспользуются `get_or_404`, `flush/commit`, `attach_to_tree(keep_parent=True)` (подхват существующих вложенных, если они шире), `audit(... "created", …, {"vrf": …, "allocated_from": "<родитель>"})`, `_prefix_outs`. - `GET /prefixes/{id}/subnets/next?length=30` — предпросмотр без создания: `{"prefix": "172.20.0.4/30"|null, "length_min": 25, "length_max": 32}` (для окна в UI; те же проверки диапазона). - БД, миграции, существующие эндпоинты не меняются. ## UI (`web/app.js`) - Пункт **«Добавить вложенный (авто)»** в меню «⋯» строки префикса: у листовых — рядом с «Открыть адреса/Редактировать/Удалить»; у родителей (сейчас без меню) — меню из этого одного пункта. Доступно только admin (как остальные меню записи). - Окно `subnetDialog(parent)` «Новый вложенный префикс в 172.20.0.0/24»: выпадающий «Размер» (`/25 … /32` для v4, для v6 — до `/128`; по умолчанию — длина последнего дочернего префикса, иначе `/30` (v4) / `/64` (v6) в пределах допустимого), строка «Будет создан: **172.20.0.4/30**» (обновляется по `change` списка через `GET …/subnets/next`; при отсутствии места — предупреждение и заблокированная кнопка), «Описание», «Статус», «Примечание», флажок «Пул». Использует `openDialog`, `formBody`, `fSelect`, `fInput`, `dlgFoot`, `S.dialog`. - После успеха: toast «Создан префикс 172.20.0.4/30», родитель раскрывается (`S.collapsed.delete(parent.id)`), `draw()`. ## Артефакты и тесты - `docs/changes/010-next-free-prefix/PLAN.md` (копия) и `SUMMARY.md`; `README.md` — раздел API/Модель данных: алгоритм и эндпоинты. - Один тест `test_allocate_next_subnet` в `tests/test_api.py`: родитель `/24` + ручной `/30` в начале → `/30` даёт `.4/30`; `/29` даёт `.8/29` (выравнивание); `parent_id` = родитель; предпросмотр совпадает с созданием; `length` ≤ длины родителя → 422; после заполнения всех блоков `/25` → 409. Плюс мини-проверка чистой функции на IPv6 (в том же тесте). ## Проверка 1. `node --check web/app.js`; `docker compose -p ipam_control_006 up -d --build app`; `venv/bin/python -m pytest -q`. 2. Браузером (Playwright headless, как в 009): создать `172.20.0.0/24` → вручную `172.20.0.0/30` → на родителе «⋯» → «Добавить вложенный (авто)» → в окне «Будет создан: 172.20.0.4/30» → создать 3 раза подряд (`.4`, `.8`, `.12`) → строки в дереве под родителем, родитель раскрыт; размер `/29` → следующий выровненный блок; в «Журнале» записи `prefix.created`. Тестовые данные удалить.