# IPAM Manager — backend (Python + PostgreSQL) и UI-админка ## Context Есть макеты «IPAM Manager» (страница 2 канваса): Обзор, Префиксы (дерево, VRF), Адреса в подсети, Организации, Операторы связи, Устройства (+ типы), Журнал, диалоги создания/редактирования. Проект пуст. Нужно спроектировать и реализовать API-first приложение: backend на Python с PostgreSQL, UI-админка — отдельный лёгкий фронт (без сборки), который только визуализирует ответы API. Решения (согласованы): FastAPI + SQLAlchemy 2 + Alembic + psycopg3; UI — статический SPA (Alpine.js + fetch), раздаётся тем же приложением; auth — локальные пользователи + JWT; окружение — Docker Compose (postgres + app), venv в корне для тестов/линтеров. ## Доменная модель (PostgreSQL) Нативные типы `CIDR` / `INET` — сравнения, вложенность (`<<=`, `>>=`) и семейство (`family()`) считает БД. | Таблица | Ключевые поля | |---|---| | `organizations` | name, short_name, inn (uniq), address, contact_person, phone, email, note | | `vrfs` | organization_id, name, route_target, note; uniq(org, name) | | `prefixes` | organization_id, vrf_id, prefix CIDR, description, status (active/reserved/deprecated), parent_id (авто по вложенности, можно задать), is_pool, note; uniq(vrf, prefix) | | `addresses` | prefix_id, address INET, status (assigned/reserved/deprecated), dns_name, description, device_id, note, updated_at; uniq(prefix, address); CHECK «адрес ∈ префикс» на уровне сервиса | | `isps` | name, organization_id, contract_number, hotline, note | | `isp_networks` | isp_id, cidr (несколько IP-блоков на оператора) | | `device_types` | name (uniq), is_default | | `devices` | name (hostname), device_type_id, mac, organization_id, note | | `users` | username (uniq), password_hash (argon2), role (admin/viewer), is_active | | `audit_log` | ts, user_id, entity_type, entity_id, entity_label, action (created/updated/deleted/assigned), diff JSONB | Вычисляемое (не хранится): использование префикса (назначено/ёмкость), «свободные» адреса (в макете статус «Свободен» — пропуски в диапазоне), число префиксов/адресов у организации, число устройств у типа, число IP у устройства. Правила: VRF/тип нельзя удалить, пока используются (в макете кнопка disabled) → 409. ## API (`/api/v1`, OpenAPI на `/docs`) - `auth`: `POST /auth/login`, `GET /auth/me` (logout — на клиенте, токен короткоживущий). - `overview`: `GET /overview` — карточки (префиксов, VRF, адресов, использование, резерв), топ загрузки, последние изменения. - `organizations`, `isps`, `devices`, `device-types`, `vrfs`: CRUD; списки с `q`, `limit`, `offset`, фильтры (org, тип, vrf). - `prefixes`: CRUD; `GET /prefixes?org=&vrf=&status=&family=&q=` (дерево через parent_id + utilization); счётчики вкладок VRF; `GET /prefixes/{id}`. - `prefixes/{id}/addresses`: список (`status`, `q`, `limit`/`offset` — «Показать ещё 100»), `POST` назначить, `POST .../next` — автоназначение из пула (`is_pool`), сводка «назначено/резерв/свободно». - `addresses/{id}`: PATCH/DELETE. - `audit`: `GET /audit` (фильтры, пагинация) — источник «Последних изменений»; полный экран «Журнал» — вне этого этапа. - Единый формат ошибок `{code, message, fields}`; валидация CIDR/IP/MAC/FQDN в pydantic; 409 на дубли и пересечения. - Запись в `audit_log` — в сервисном слое в одной транзакции с изменением. ## Структура репозитория ``` app/ main.py config.py db.py security.py models/ schemas/ services/ api/v1/ alembic/ (миграция 0001 — вся схема + seed: admin, типы устройств) web/ index.html, app.js, api.js, styles.css (IBM Plex, токены цвета из макетов) tests/ (минимум) docs/changes/001-ipam-backend/ PLAN.md, SUMMARY.md docker-compose.yml Dockerfile .env.example requirements.txt README.md venv/ (в .gitignore) ``` ## UI (web/) Экраны 1:1 с макетами: логин, Обзор, Префиксы (вкладки VRF, фильтры, «Управление VRF»), Адреса подсети, Организации, Операторы, Устройства («Управление типами»), модальные формы. Только `fetch` к `/api/v1`, JWT в `sessionStorage`; 401 → экран логина. Пункт «Журнал» — заглушка со ссылкой на `/audit` (по вопросу №1 из макета). ## Порядок работ (по артефактам из CLAUDE.md) 0. Создать `docs/changes/001-ipam-backend/PLAN.md` (этот план) — до кода. 1. Каркас: docker-compose (postgres:16 + app), Dockerfile, config, venv, Alembic. 2. Модели + миграция 0001 + seed. 3. Auth (login/JWT/роли; viewer — только чтение). 4. Справочники: организации, VRF, операторы, типы, устройства. 5. Префиксы и адреса: вложенность, использование, next-free, проверка «адрес ∈ префикс», пересечения в VRF. 6. Обзор + audit. 7. UI SPA. 8. Тесты, обновить `README.md`, написать `SUMMARY.md`. ## Тесты (минимум, pytest + реальный Postgres из compose) 1. Логин → защищённый эндпоинт (401 без токена, 200 с токеном). 2. Префикс: дубль в VRF → 409; адрес вне префикса → 422. 3. Использование префикса и next-free считаются верно. 4. Удаление VRF/типа «в использовании» → 409. ## Проверка end-to-end `docker compose up -d --build` → `alembic upgrade head` отрабатывает автоматически → `pytest` зелёный → открыть `http://localhost:8000/` (UI), войти `admin`, пройти сценарий: организация → VRF → префикс → назначить адрес → увидеть его в Обзоре и в audit. `http://localhost:8000/docs` — Swagger. ## Допущения (скажите, если не так) - VRF принадлежит организации (в макете «общий список для организации»). - Свободные адреса вычисляются, а не хранятся. - Экран «Журнал» с ротацией/очисткой (страница ROS Manager) в этот этап не входит — только запись и чтение audit. - Начальный пароль admin задаётся через `.env`, не хардкодится. ## Уточнения по итогам утверждения - План утверждён пользователем; сохранён в этом файле до начала кода. - Тесты выполняются против приложения, поднятого в контейнерах (docker compose). Учётные данные для тестов генерируются автоматически (случайные пароль admin и секрет JWT в `.env`, файл в .gitignore). Контейнеры локальные, без внешнего доступа. - Сборка и запуск вспомогательных инструментов (pytest, линтеры, alembic) — из `venv/` в корне проекта. - Обязательная сверка UI собранного приложения с макетами (все экраны и диалоги страницы «IPAM Manager»); расхождения устраняются до завершения работы, результат сверки фиксируется в SUMMARY.md.