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>
8.0 KiB
Множественный выбор и групповые операции в 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 должны проходить.
Проверка
node --check web/app.js;docker compose -p ipam_control_006 up -d --build app;venv/bin/python -m pytest -q.- Интерфейс (http://192.168.5.9:8088), по возможности через браузерную автоматизацию (skill
claude-in-chrome), иначе вручную:- Операторы: выбрать 2–3 → «Удалить» → подтверждение → строки исчезли, toast «выполнено 3 из 3»; «Выбрать все» в шапке и состояние «частично» работают.
- Организации: выбрать организацию с префиксами и пустую → удалено 1 из 2, окно отказов с причиной; в «Журнале» появилась
organization.delete_blocked. - Устройства: «Сменить тип»; Префиксы/Адреса: «Статус»; Пользователи: «Отключить/Разрешить доступ», у своей строки чекбокс недоступен.
- Для
viewerчекбоксов и панели нет; на «Журнале» — нет.
- Тестовые данные для проверки создавать отдельными демо-записями (не удалять существующие демо-данные стенда).