Files
ipam_control/README.md
T
ayurishchevandClaude Sonnet 5 cd09ef0805 Задачи 006-010: пользователи, исправление удаления, журнал отказов, групповые операции, автовыделение префиксов
006 Пользователи: API /users (CRUD, смена своего пароля), раздел UI «Пользователи»,
    события журнала user.*, защита от отключения/удаления себя и последнего админа.
007 Исправление удаления организации: VRF удаляются явным DELETE до организации
    (без relationship() порядок DELETE не гарантирован → ложный 409).
008 Журнал фиксирует отказы в удалении (<entity>.delete_blocked) со списком
    мешающих объектов в «Данных»: организация, VRF, тип устройства, префикс, пользователь.
009 Выбор строк чекбоксами и групповые операции в UI (удаление, смена типа устройств,
    статус префиксов и адресов, доступ пользователей); цикл запросов из UI, итог и список отказов.
010 Автовыделение следующего вложенного префикса: POST/GET /prefixes/{id}/subnets/next,
    первый свободный выровненный блок; пункт «Добавить вложенный (авто)» в меню префикса.

Тесты: 14 (добавлены сценарии для 006, 007/008, 010); исправлена нестабильность
тестов журнала (IPv6-группы с ведущими нулями нормализуются PostgreSQL).
Документация: README.md, docs/changes/006-010 (планы и итоги).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-26 13:25:33 +03:00

89 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```