# IPAM Manager Реестр IP-адресов и адресных префиксов в разрезе организаций. API-first: backend на Python (FastAPI) с PostgreSQL, UI-админка — отдельный лёгкий SPA (`web/`, без сборки), который только визуализирует ответы API. Макеты: страница «IPAM Manager» дизайн-канваса ros_control. ## Быстрый старт ```bash python3 scripts/gen_env.py # .env со случайными паролями/секретами (в .gitignore) docker compose up -d --build # postgres + app; миграции применяются автоматически python3 -m venv venv && venv/bin/pip install -r requirements-dev.txt venv/bin/python scripts/seed_demo.py # (по желанию) демо-данные из макетов ``` - UI: http://127.0.0.1:8088/ · Swagger: http://127.0.0.1:8088/docs (порт приложения — `APP_PORT` в `.env`, слушает 0.0.0.0; порт БД — только 127.0.0.1) - Логин/пароль администратора — `ADMIN_USERNAME` / `ADMIN_PASSWORD` из `.env` (создаётся при первом старте). ## Архитектура | Слой | Технологии | |---|---| | API | FastAPI, pydantic v2, JWT (argon2), роли `admin` (запись) / `viewer` (чтение) | | БД | PostgreSQL 16, SQLAlchemy 2, Alembic (`alembic/versions`), типы `CIDR`/`INET` | | UI | статический SPA (ES-модуль, vanilla JS), раздаётся приложением; шрифты IBM Plex — локально в `web/fonts/` (woff2 latin+cyrillic, лицензия OFL), внешних зависимостей нет | ``` app/ main.py config.py db.py security.py models.py schemas.py services.py api/v1/{auth,refs,prefixes,overview}.py web/ index.html styles.css app.js alembic/ scripts/{gen_env,seed_demo}.py tests/ docs/changes/ ``` ## Модель данных `organizations` → `vrfs` (по организации, «default» создаётся автоматически) → `prefixes` (дерево через `parent_id`, вложенность определяется автоматически) → `addresses`; `devices` + `device_types`; `isps` + `isp_networks`; `users`; `audit_log`. - Ёмкость листового префикса — размер подсети (IPv4 без сетевого/broadcast); родителя — сумма вложенных листьев. - «Свободные» адреса не хранятся, а вычисляются; в списке адресов они показываются для подсетей до /20. - Обзор считает использование только по IPv4. - VRF — часть адресного плана организации: имя уникально **в пределах организации** (без учёта регистра), в разных организациях имена могут совпадать; в одном VRF может быть много префиксов. Принадлежность VRF организации префикса гарантирует составной FK в БД. - Смена VRF у префикса (`PATCH /prefixes/{id}` с `vrf_id`) — только среди VRF той же организации; переносится префикс вместе с вложенными, дубль CIDR в целевом VRF → 409 (без частичных изменений), VRF другой организации → 422. - Автовыделение вложенного префикса (изменение 010): `POST /prefixes/{id}/subnets/next` с `length` (например 30) создаёт дочерний префикс в первом свободном выровненном блоке родителя (учитываются вложенные префиксы и адреса родителя); `GET` с тем же путём и `?length=` — предпросмотр. Нет места → 409, недопустимый размер → 422. В UI — пункт «Добавить вложенный (авто)» в меню «⋯» префикса. - VRF, тип устройства, организация с зависимыми объектами не удаляются (409). У организации без префиксов, устройств и операторов служебный VRF `default` удаляется вместе с ней (исправлено в изменении 007: раньше такое удаление давало 409). ## API (`/api/v1`) `POST /auth/login` · `GET /auth/me` · `GET /overview` CRUD: `/organizations`, `/vrfs`, `/isps`, `/device-types`, `/devices`, `/prefixes`, `/addresses/{id}`, `/users` Адреса префикса: `GET|POST /prefixes/{id}/addresses` (`status`, `q`, `limit`, `offset`), `POST …/addresses/next` — автоназначение из пула. Ошибки: `{code, message, fields}` (для блокировок/лимитов — дополнительные поля `attempts_left`, `retry_after_seconds`). Каждое изменение пишется в `audit_log`. ### Пользователи и роли - `GET|POST /users`, `PATCH|DELETE /users/{id}` — управление учётными записями, только `admin`: логин (3–100 символов, латиница, цифры, `. _ -`), роль, доступ и пароль (при сбросе администратором). Логин после создания не меняется — он же `sub` в токене. - `POST /users/me/password {current_password, new_password}` — смена своего пароля, доступна любой роли; неверный текущий пароль → 403. - Занятый логин (в том числе в другом регистре) → 409; служебные логины `system` и `anonymous`, короткий логин или пароль → 422. - Свою учётную запись нельзя понизить, отключить или удалить, как и последнего активного администратора → 409. - Отключение действует немедленно (токен проверяется по `users.is_active` на каждом запросе), а смена пароля уже выданные токены не отзывает — они живут до истечения `JWT_TTL_MINUTES`. Роль `viewer` видит реестр и журнал, но любые изменения получает с 403. - Изменения пишутся в журнал: `user.created`, `user.updated`, `user.password_reset`, `user.deleted` (значения паролей не сохраняются). ### Журнал - Отказ в удалении по бизнес-правилу (409: организация, VRF, тип устройства, префикс с адресами, пользователь) фиксируется событием `<сущность>.delete_blocked` (изменение 008); в «Данных» записи — `reason` и `blocked_by` со списками мешающих объектов (`total` и до 20 названий: префиксы, устройства, операторы, адреса). - `GET /audit` — поиск и фильтры: `q` (сообщение, метка объекта, начало ID записи), `event_type` (`prefix.created`), `entity_type`, `actor` (`ui:admin`, `system`, `anonymous`), `date_from`/`date_to` (UTC), `limit`/`offset`; `GET /audit/summary`, `/audit/facets`, `/audit/{uid}`. - `GET|PUT /journal/settings` — ротация: `retention_days` (по умолчанию 90) и `max_entries` (100 000), `0` — без ограничения. Ротация идёт раз в час и сразу при сохранении настроек (advisory-lock защищает от параллельного запуска); каждая ротация с удалениями фиксируется записью `journal.rotated`. Запись — только admin. - `POST /journal/clear {password}` — очистка с подтверждением пароля текущего пользователя (admin); 5 неверных попыток за 10 минут → блокировка на 10 минут (429). В журнале остаётся запись `journal.cleared`. - **IP и метаданные запроса:** каждая запись, созданная в рамках HTTP-запроса, хранит `client_ip` и `meta` (`user_agent`, `method`, `path`, `request_id`; ответ содержит `X-Request-ID`); системные события (ротация) — без IP. Фильтр `GET /audit?client_ip=` принимает IP или подсеть (`192.168.5.0/24`), текстовый поиск `q` ищет и по началу IP. IP берётся из адреса сокета. `X-Forwarded-For` учитывается только от прокси из `TRUSTED_PROXIES` (CIDR через запятую в `.env`, по умолчанию пусто) — иначе IP можно подделать. Запросы с самой машины через `127.0.0.1` Docker показывает адресом шлюза сети (`172.x.0.1`); с LAN-адреса и удалённых хостов виден реальный источник. - В журнал пишутся также входы (`session.login`, `session.failed` — актор `anonymous`) и служебные события (`journal.*`, актор `system`). ## Поведение таблиц UI Администратор может выбирать строки чекбоксами (в шапке — «выбрать все») на экранах «Организации», «Операторы», «Устройства», «Префиксы» (листовые), «Адреса» (кроме «Свободен») и «Пользователи» (кроме себя); в «Журнале» выбора нет. Панель над таблицей: «Удалить» везде, «Сменить тип» (устройства), «Статус» (префиксы, адреса), «Разрешить/Отключить доступ» (пользователи). Операции идут по одному запросу на объект (изменение 009): итог «выполнено N из M», отказы (зависимые объекты, свой аккаунт, последний администратор) показаны списком с причиной, попадают в журнал как `*.delete_blocked`, и остаются выбранными. Префиксы удаляются без `force`: префикс с адресами удаляется из одиночного меню строки. Строка реестра кликабельна целиком (как в журнале): «Префиксы» — лист открывает адреса подсети, родитель сворачивает/разворачивает ветку; «Организации» — префиксы организации; «Операторы», «Устройства» и «Адреса» — окно редактирования (свободный адрес — «Назначить адрес» с этим IP). Ссылки, шеврон, меню «⋯» работают как раньше и не запускают действие строки; Ctrl/Shift+клик и выделение текста тоже игнорируются. Экран «Пользователи» доступен только администратору: создание, редактирование роли и доступа, удаление на месте, а свой пароль меняется кнопкой «Сменить пароль» в шапке. ## Тесты Идут против приложения в контейнерах, учётные данные берутся из `.env`: ```bash docker compose up -d --build && venv/bin/python -m pytest -q ```