Files
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

54 lines
8.0 KiB
Markdown

# Множественный выбор и групповые операции в UI (изменение 009)
## Context
Сейчас каждую запись можно удалить или изменить только по одной через меню «⋯». Нужен выбор нескольких строк чекбоксами и групповые операции
над выбранными на всех экранах управления, кроме «Журнала». Решения пользователя: операции — удаление везде, плюс доступ пользователей,
смена типа устройств, статусы префиксов и адресов; выполнение — цикл запросов из UI (бэкенд, API, схема БД и миграции **не меняются**).
## Охват
| Экран | Чекбокс у строк | Групповые операции |
|---|---|---|
| Организации | все | Удалить |
| Операторы | все | Удалить |
| Устройства | все | Удалить; «Сменить тип ▾» |
| Префиксы | только листовые (как и меню «⋯» сейчас) | Удалить; «Статус ▾» (Активен/Резерв/Устарел) |
| Адреса подсети | только занятые/резерв/устаревшие (у «Свободен» нет id) | Удалить; «Статус ▾» (Назначен/Резерв/Устаревший) |
| Пользователи | все, кроме своей учётной записи (чекбокс disabled) | Удалить; «Разрешить доступ»; «Отключить доступ» |
| Журнал | — не затрагивается | — |
Не входят: окна «Управление VRF» и «Управление типами» (это модальные мини-списки, а не вкладки). Для роли `viewer` чекбоксов нет (запись запрещена).
## Реализация (`web/app.js`, `web/styles.css`)
- **Состояние:** `S.sel` (Set id) и `S.selectable` (id выбираемых строк текущего экрана). `S` сбрасывается при смене экрана; при каждой перерисовке
выделение усекается до присутствующих строк (`pruneSel`), поэтому поиск/фильтры не оставляют «невидимых» выбранных.
- **Общие фрагменты** рядом с `badge/btn/iconBtn`: `selCell(id, disabled)` (чекбокс в `<label>`, `id="sel-<id>"` — фокус восстанавливается штатным кодом `draw()`),
`selAllCell()` (в шапке; `indeterminate` выставляется после рендера в `draw()`), `selCols(cols)` (добавляет колонку `40px`; для не-админа — без неё),
`bulkBar({...})` (панель «Выбрано: N · действия · Снять выделение», рендерится только при `S.sel.size`), `bulkMenu(id, label, items)` (на существующих `popMenu`/`data-action="menu"`).
- **Экраны:** в `screens.orgs/isps/devices/prefixes/address/users` — `selCols` в `--cols` строк и шапки (у орг. выносится в константу), `selCell` первой ячейкой, панель `bulkBar` под фильтрами,
класс `selected` у выбранных строк. Клики по чекбоксу не открывают строку: общий обработчик уже пропускает `input`/`label` (изменение 005).
- **Исполнитель** `bulk(title, ids, labelOf, call, after)`: последовательно вызывает существующие `DELETE`/`PATCH` (`/organizations`, `/isps`, `/devices`, `/prefixes`, `/addresses`, `/users`),
ловит `ApiError` по каждому элементу, останавливается на 401, защищён от повторного запуска (`S.busy`). По итогу: toast «выполнено N из M», перерисовка,
успешные снимаются с выделения (отказавшие остаются выбранными), при отказах — окно «Не выполнено для: …» со списком «объект — причина» (`openDialog`).
Для организаций после цикла вызывается `loadOrgs()`.
- **Подтверждение** удаления — `confirm()`, как в одиночных действиях («Удалить выбранное: 3 оператора?»). Префиксы удаляются без `force`: с адресами будут отклонены
и попадут в список отказов (принудительное удаление остаётся в одиночном меню).
- **Диспетчеризация:** новые `actions`: `sel`, `sel-all`, `sel-clear`, `bulk-del`, `bulk-access`; в `actions.pick` ветка `bulk-status` / `bulk-type` до общего `rowActions`-поиска.
Экран определяется через `route()` (`prefixes` с аргументом = «адреса»).
- **CSS:** `.sel-cell` (центрирование, чекбокс 16 px, `accent-color: var(--primary)`), `.bulkbar` (строка 48 px, фон `#f3f7ff`, `.pop` от `top:40px`), `.tr.selected{background:#eef3ff}`.
## Журнал и бэкенд
Каждое отклонённое удаление в цикле само попадает в журнал как `<entity>.delete_blocked` (изменение 008); каждое успешное действие — обычной записью по объекту.
Групповая операция отдельным событием не пишется. Серверные правила сохраняются (нельзя удалить/понизить себя и последнего админа → строка в списке отказов).
## Артефакты (правила проекта)
`docs/changes/009-bulk-actions/PLAN.md` (копия плана) и `SUMMARY.md`; `README.md` — в раздел «Поведение таблиц UI»: выбор чекбоксами, панель групповых действий, охват и ограничения.
Автотесты: бэкенд не менялся, новых тестов нет (правило «минимум тестов»); существующие 13 должны проходить.
## Проверка
1. `node --check web/app.js`; `docker compose -p ipam_control_006 up -d --build app`; `venv/bin/python -m pytest -q`.
2. Интерфейс (http://192.168.5.9:8088), по возможности через браузерную автоматизацию (skill `claude-in-chrome`), иначе вручную:
- Операторы: выбрать 2–3 → «Удалить» → подтверждение → строки исчезли, toast «выполнено 3 из 3»; «Выбрать все» в шапке и состояние «частично» работают.
- Организации: выбрать организацию с префиксами и пустую → удалено 1 из 2, окно отказов с причиной; в «Журнале» появилась `organization.delete_blocked`.
- Устройства: «Сменить тип»; Префиксы/Адреса: «Статус»; Пользователи: «Отключить/Разрешить доступ», у своей строки чекбокс недоступен.
- Для `viewer` чекбоксов и панели нет; на «Журнале» — нет.
3. Тестовые данные для проверки создавать отдельными демо-записями (не удалять существующие демо-данные стенда).