47 KiB
47 KiB
Этап 11А: пользователи и администрирование устройств
Статус: подготовка 2026-10-01. ReUI PRO/base-nova, существующие Frame, KpiStatGrid, Data Grid v9 и Sheet. Изменения последовательно, локальные коммиты без push.
Референсы и схемы
| Область | Референс | Схема |
|---|---|---|
| Обзор раздела | Settings 16 / Stats 12 | Источник → подтверждённая сводка → проблемы → действие |
| Таблицы | Data Grid Filtering 2 | Фильтры → объект → детали → результат API |
| Формы | Sheet 8 | Контекст → форма → проверка → подтверждённый результат / сохранённый черновик |
[Источник и права]
↓
[Каталоги + свежесть] → [Ошибка: последние данные, явный повтор]
↓
[Объект / детали] → [Форма с черновиком]
↓
[Подтверждение API / частичное выполнение]
↓
[Повторное чтение / восстановление контекста]
Карта реализации
| Порядок | Раздел / API | Исходная проблема | Точка завершения |
|---|---|---|---|
| 1 | Пользователи: /users, /servers | Live начинает с INIT_USERS; Promise.all связывает независимые отказы; нет свежести и защиты смены источника | Пустой/unknown/ошибка различаются, последний снимок и read-only права |
| 2 | Профиль и привязки: /users/:id, /bindings | Многошаговое сохранение, нет lock удаления, refresh ошибка проглатывается; Sheet эффект может сбросить форму | Частичное выполнение явно показано, повтор без дублей, успешный API отдельно от refresh |
| 3 | Interface catalog | Ошибка превращается в []; каталог может сохранять прежнее устройство; peer ошибки требуют проверки | Контекст и доступность, точный выбор, сохранение формы |
| 4 | Пользователи UI | «Пригласить» фактически создаёт профиль; icon actions без имён, raw colors, доступность формы | ПК/390/320, клавиатура, понятные подписи/токены |
| 5 | Контейнеры | Отказ очищает прошлые объекты, Promise.all и операции требуют защиты/валидации | Состояния снимка, реальные подтверждения действий, контекст/права |
| 6 | Сертификаты | Сводка, доступность экспорта/операций и конкретный сервер требуют аудита | Неизвестные статусы, действительный экспорт и ошибки API |
| 7 | Резервирование | Общий Promise.all четырёх источников, polling задач/интеграций и сохранение требуют аудита | Частичные сбои и состояние job без ложного завершения, форма сохранена |
| 8 | Приёмка 11А | Сквозные действия и общие компоненты | Профильная регрессия, production build, снимки, внешние ограничения |
Границы
- Права пользователя в профиле и Portal JWT авторизация приложения — разные существующие контракты; не менять правила выдачи доступа в UX исправлениях.
- Сохранение профиля и привязок сейчас несколько HTTP запросов. Не обещать атомарность и не повторять подтверждённое создание после частичного отказа.
- Серверный каталог — зависимость формы прав/привязок; отсутствие доступа или отказ не должен обнулять сохранённые разрешения.
- Backend/моки не подтверждают реальный Portal, PostgreSQL, RouterOS, выдачу сертификатов, восстановление backup или доступность S3. Реальную приёмку перечислять отдельно.
- Оповещения, сбор данных, интеграции, релизы, настройки и расписание диагностики — 11Б.
Блок 11А.2 — достоверная загрузка пользователей (2026-10-01)
- Live не содержит INIT_USERS/демо серверов до API. Пользователи и серверы читаются независимо: быстрый список появляется до медленного каталога; отказ одной зависимости сохраняет другую и последние подтверждённые данные. Unknown KPI — —, подтверждённый пустой список — отдельное состояние.
- Получение пользователей имеет время браузерного чтения и ручной повтор. Смена backend/mode размонтирует прежний контекст, читатели отменяются и поздние ответы не меняют новый экран. Схемы обязательны; дубли ID отклоняются.
- Права users read/write и server read проверены перед запросами/действиями. Без подтверждённых актуальных каталогов редактирование приостановлено; неизвестный общий серверный каталог не показывается числом 0. Icon действия получили имена; «Пригласить» заменено фактическим «Добавить пользователя».
- 12 data E2E + 4 context E2E, профильный lint и TypeScript прошли. Production frontend прошёл в приёмочной сборке после подготовки нового контекста; дальнейшие операции ещё не приняты. Mock ветка подписана как локальное демо.
- Далее 11А.3: lock удаления/сохранения, сохранение диалога/черновика при ошибке, поздние мутации и отдельное подтверждение API от refresh. Затем частичное сохранение привязок без дублей и interface catalog. Реальные Portal/БД ещё не проверены.
Блок 11А.3 — подтверждённое удаление пользователя (2026-10-01)
- DELETE имеет single-flight lock, AbortSignal/30s срок и защиту позднего ответа после размонтирования. Ожидание блокирует повтор/отмену диалога; ошибка остаётся в диалоге с возможностью повторить, target не теряется.
- Удаление требует существующий backend контракт HTTP 204; успешный 200 с произвольным телом не принимается. Подтверждённая строка удаляется сразу, а отказ последующего refresh не называется отказом удаления: API результат и необходимость повторного чтения показаны отдельно.
- 17 data/operations E2E + 6 context E2E, TypeScript и профильный lint прошли. Проверены 403/503/неверный ответ, двойной click, Escape, refresh error, поздний success/error прежнего backend. Созданы общие HTTP-фикстуры пользователей.
- Далее 11А.4: безопасное сохранение профиля/черновика и частичных привязок, без повторного создания при подтверждённом первом шаге. Сохранение ещё не принято; interface catalog и визуальная приёмка далее. Реальный Portal/БД не проверен.
Блок 11А.4 — многошаговое сохранение профиля и привязок (2026-10-01)
- Общий mutation lock, отмена/30s срок каждого запроса, защита прежнего источника; Sheet не сбрасывает черновик при изменении каталога/подтверждённом промежуточном шаге. Во время отправки поля/кнопки и закрытие блокируются.
- UserSaveCheckpoint сохраняет подтверждённый id профиля и каждую привязку. После частичного отказа повтор читает профиль/привязки, обновляет профиль по этому id и продолжает diff без второго POST пользователя/повторной подтверждённой привязки. Потерянный ответ DELETE/POST привязки сверяется чтением. Это несколько HTTP запросов, не атомарная транзакция.
- При неизвестном исходе POST профиля повтор заблокирован; доступно чтение списка, черновик сохранён. 4xx создания допускает исправление/повтор. UI не присваивает найденный по login чужой профиль автоматически. Подтверждённый профиль/привязки отделены от отказа refresh.
- Ответы проверяют userId/login, отправленные поля/права и контекст привязки. Wrong role/sections/servers не подтверждают сохранение. IDs в URL кодируются. Техническая многострочная ошибка схемы заменена понятной краткой ошибкой — иначе footer выходил за экран; обнаружено E2E, исправлено и повторено без ослабления теста.
- 5 fault-injection unit, 27 различных data/operations E2E и 8 context E2E прошли; TypeScript, профильный lint и production frontend прошли. В UserSheet остаётся одна исходная catalog set-state-in-effect ошибка; она входит в следующий блок, suppressions нет.
- Далее 11А.5: строгий interface catalog, loading/error/empty, точный сервер и поздние ответы/peer сбои; затем форма/таблица, клавиатура и ПК/телефон. Реальная БД/Portal не проверены.
Блок 11А.5 — каталог интерфейсов формы (2026-10-01)
- Каталог привязан к serverId/host; ожидание, error и подтверждённое empty различаются. Поздний ответ другого устройства отменяется. При повторе сохраняются прошлые строки/время получения, выбор блокируется до успешного чтения; черновик формы не сбрасывается.
- API схемы отклоняют дубли интерфейсов/пиров, отсутствующий peer snapshot и пустые ключи. Ошибка peers показана явно, не выдаётся за пустой список; выбор неподтверждённого пира блокируется. Перед привязкой выбранные объекты сверяются с текущим каталогом.
- IPsec теперь выбирает конкретного клиента/CN как требует backend; раньше весь ipsec-vpn отправлялся с пустым ключом. WireGuard остаётся выбором public-key. Время handshake/активности не называется текущим подключением.
- Frame scroll имеет высоту родителя и min-h-0 flex цепочку. Демо каталог вычисляется при рендере; последняя исходная UserSheet set-state-in-effect ошибка устранена без suppressions.
- 25 catalog/operations E2E, TypeScript и профильный lint прошли. Проверены API/malformed/дубли/неполные peer данные, empty/loading, поздний success/error другого сервера, WG failure/retry, IPsec CN payload и прежние операции.
- Далее 11А.6: backend достоверность interface catalog (сейчас snapshot/история без метаданных, неизвестные флаги и скрытый IPsec failure); затем UI/доступность и локальная приёмка пользователей. Реальные RouterOS/DB/Portal не проверены.
Блок 11А.6 — backend метаданные каталога интерфейсов (2026-10-01)
- Добавлен /users/interface-catalog-state с serverId/source/sampledAt/inventoryComplete/failures и nullable running/disabled. Старый /interface-catalog и форма interfaces/boolean сохранены для совместимости; новый frontend будет подключён в следующем блоке.
- Явный пустой raw snapshot не заменяется трафиком; malformed snapshot возвращает 502. Missing snapshot допускает явно подписанный fallback только к одной последней дате traffic_samples: SQL ограничен sampledAt, старые интерфейсы/peer/loopback исключены. Unknown флаги не превращаются в false.
- WireGuard/IPsec failure не скрывается за пустыми пирами; каталог IPsec клиентов отдельно от физического состояния интерфейса, running/disabled неизвестны. Неподтверждённые клиенты raw IPsec интерфейса показаны peersError; нет дубля ipsec-vpn.
- Строгий новый контракт и factory DI проверяют serverId, источник/полноту, время, peer keys/дубли и точный контекст ответа. GET остаётся в существующем mm:users:read; правила сетевых действий/авторизации не изменены.
- Backend build, профильный lint, inventory parser и Fastify HTTP тесты прошли. test:users включён в общий test; прежние binding unique/cascade тесты явно skipped из-за отсутствия PostgreSQL. SQL/реальный RouterOS ещё требуют внешней приёмки.
- Далее 11А.7: подключить frontend к state контракту, показать источник/дату/частичные отказы и неизвестное состояние; обновить HTTP-фикстуры и проверить регрессию. Затем визуальная приёмка пользователей.
Блок 11А.7 — frontend state каталог (2026-10-01)
- UserSheet использует /interface-catalog-state, строго проверяет новый контракт и точный serverId. Legacy reader экспортирован для совместимости, новый экран к неоднозначному envelope не возвращается.
- UI отдельно показывает браузерное получение и время/источник среза. Snapshot и частичная traffic history подписаны; unknown не становится пустым каталогом. Nullable running отображается неизвестным состоянием, а исторические флаги не называются текущими.
- Частичные failures интерфейсов/WG/IPsec видны сразу с повтором, включая пустой IPsec failure. IPsec-vpn подписан как каталог клиентов, а не физический интерфейс. Демо и live состояния сохраняют прежнюю композицию Frame/Stepper.
- 44 различных data/operations/catalog E2E + 8 context E2E, TypeScript, профильный lint и production frontend прошли. Фикстуры перенесены на новый state envelope; отдельные negative тесты не дополняют отсутствующие метаданные автоматически.
- Далее 11А.8: семантические токены таблицы/формы, доступные имена, права/клавиатура, ПК/390/320 и снимки. После приёмки пользователей — контейнеры. Реальная БД/RouterOS/Portal остаётся внешней приёмкой.
Блок 11А.8 — доступность и локальная приёмка пользователей (2026-10-01)
- Существующие ReUI Stepper/Data Grid/Frame сохранены. Семантические токены, явные имена действий/прав, aria-pressed/expanded, клавиатурное раскрытие строки, подпись текущего шага. Vendor компоненты и тема не изменены; checklist ReUI проверен.
- Sheet занимает ширину телефона; min-w-0 fieldset удерживает шаги в 320 px, выбор роли переносится вертикально без разрыва слов. Ошибка сохранения внутри области прокрутки, footer остаётся доступен; обязательные поля имеют aria-invalid/describedby и получают фокус.
- Закрытие формы возвращает фокус к актуальной кнопке строки после её замены Data Grid. Найден и исправлен исходный no-op прав: отсутствующие section/server разрешения добавляются при явном выборе; «выбрать всё» охватывает показанные разделы/серверы.
- 48 уникальных data/operations/catalog/layout E2E, 8 context E2E, TypeScript, профильный lint и production frontend прошли. HTTP payload проверен для отсутствующих разрешений. 12 снимков ПК/390/320 сохранены; просмотрены обзор/профиль/каталог/ошибка, горизонтальная прокрутка ограничена таблицей.
- Пользователи приняты в границах локальных HTTP-подмен. Реальные Portal/PostgreSQL/RouterOS остаются внешней приёмкой; прежние DB binding тесты skipped.
- Далее 11А.9: достоверный state API контейнеров. Аудит выявил превращение отказов устройств/envs/mounts в пустые списки, неизвестного статуса в stopped и недостоверное общее empty. Затем подключение frontend, операции и UX; сертификаты/backup ещё не приняты.
Блок 11А.9 — достоверный backend каталог контейнеров (2026-10-01)
- Добавлен GET /api/containers/state с общим collectedAt и состояниями каждого enabled сервера. available + [] означает подтверждённую пустую загрузку, unsupported — отсутствие /container, error — отказ чтения. Сбой одного устройства сохраняет остальные.
- Загрузка envs/mounts независима, partial failures явно включены в контракт. Неизвестные status/startOnBoot не становятся stopped/false; отсутствующий образ не подменяется именем контейнера; нулевые CPU/RAM сохранены.
- Runtime контракт отклоняет отсутствующие/дубли RouterOS ID, чужой serverId, неверные состояния/даты и дубли устройств/ошибок. Новый reader не выдаёт синтетический rosId для сетевого действия.
- Старые /containers и /servers/:id/containers, mapper и сетевые операции сохранены для совместимости. RBAC соответствует существующим правилам: каталог dashboard read, команды server write; новых разрешений не введено.
- Backend build, профильный lint, новые service/HTTP tests и старый mapper test прошли. test:containers включён в общий test. Тесты без PostgreSQL/RouterOS; реальные чтения ещё требуют внешней приёмки.
- Далее 11А.10: frontend state reader, независимые серверы/контейнеры, последний снимок, unknown KPI, свежесть и защита смены источника. Затем подтверждённые команды, доступность/ПК/телефон.
Блок 11А.10 — frontend состояния контейнеров (2026-10-01)
- Live читает строгий /containers/state и отдельно /servers; slow/error одной зависимости не скрывает другую. Duplicate server IDs отклоняются. До подтверждения нет демо/ложного empty, неизвестные KPI и rail counts — —.
- Последний успешный снимок сохраняется при общем отказе и отдельно по устройству при частичной ошибке. Неизвестный status виден явно; envs/mounts partial, unsupported и known empty различаются. Браузерное получение каждого каталога подписано.
- Ключ backend/mode и prefsHydrated изолируют контекст; AbortController/revision/30s защищают чтение. Исчезнувший выбранный сервер не заменяется «всеми». Права соответствуют существующим dashboard read/server read/write; без свежих необходимых каталогов команды блокируются.
- Неподключённая кнопка создания disabled. Существующая композиция Frame/KpiStatGrid/ServerRail сохранена; семантическая и мобильная приёмка впереди.
- 14 data E2E, 4 source-context E2E, TypeScript, профильный lint и production frontend прошли.
- Далее 11А.11: single-flight команд, строгий ok:true, подтверждение API отдельно от фактического состояния/refresh, диалог удаления и поздние мутации. Затем экспорт/доступность/ПК/390/320. Реальный RouterOS/БД остаётся внешней приёмкой.
Блок 11А.11 — подтверждённые команды контейнеров (2026-10-01)
- POST start/stop/restart/remove проходит через typed reader со строгим ok:true, проверкой serverId/rosId и 30s сроком. Single-flight ref блокирует все карточки; поздние ответы после ухода/смены backend не меняют UI.
- Текст результата подтверждает команду API, фактический статус определяется повторным чтением. Ошибка refresh не отменяет подтверждение; подтверждённо удалённая карточка убирается до чтения.
- Удаление использует устойчивый AlertDialog с target/ошибкой/повтором. Ожидание блокирует Escape/отмену. 4xx — отклонение, malformed/network/5xx — неподтверждённый или частичный результат; новое действие требует явного чтения каталога. Исчезнувший target повторно не отправляется.
- 26 data/operations E2E, 8 context E2E, TypeScript, профильный lint и production frontend прошли. Модель сетевых команд/авторизации backend не менялась; реальные операции RouterOS требуют внешней приёмки.
- Далее 11А.12: доступность, семантические токены, ПК/390/320 и достоверность экспорта/метрик. Аудит экспорта выявил шаблонные veth IP и команды, которые не являются экспортом реального устройства; это нельзя выдавать за резервную конфигурацию.
Блок 11А.12 — достоверный снимок и интерфейс контейнеров (2026-10-01)
- Недостоверный генератор RSC (шаблонные IP veth, неподтверждённые настройки и некорректные env команды) заменён JSON-снимком каталога в существующем CodeExportSheet. Файл содержит наблюдаемые поля, collectedAt, source и partial failures; явно подписан как снимок, не конфигурация/backup.
- memory-high — лимит памяти по MikroTik Container, расход RAM теперь только memory-current (МиБ); отсутствие метрики не становится 0. Новые RouterOS list/mountlists и несколько envlist обрабатываются; неполные env/mount ответы становятся partial error.
- Семантические токены, русские статусы, aria имена/pressed, поиск/переносы и 44px меню на телефоне. Sheet на всю ширину, footer переносится на 320 px через props; vendor/theme и композиция Frame/ServerRail/KpiStatGrid не изменены. Неизвестный статус не предлагает запуск/перезапуск; KPI по статусам — —.
- 30 уникальных data/operations/layout E2E суммарно прошли (последний полный прогон 29/30, оставшийся narrow исправлен и все 4 layout повторно прошли); 8 context E2E, backend tests/build, TypeScript, профильный lint и production frontend прошли. Скачанный JSON проверен; 6 снимков сохранены и просмотрены.
- ReUI checklist проверен в границах адаптации. Далее 11А.13: URL-контекст и clipboard отказ/фокус, затем локальная приёмка контейнеров и аудит сертификатов. Общие ошибки Clipboard в CodeExportSheet требуют обработки; реальный RouterOS/БД ещё не проверен.
Блок 11А.13 — контекст и локальная приёмка контейнеров (2026-10-01)
- URL восстанавливает server/status/q, поддерживает Back/reload, сохраняет чужие параметры/hash. Дубли, неверные ID/status и слишком длинный поиск показывают ошибку со сбросом. Использован существующий preserveSelection ServerRail; исчезнувшее устройство не заменяет контекст.
- Устранены Clipboard rejection/отсутствие API в общей CodeExportSheet: сообщение с повтором/скачиванием вместо ложного «Скопировано». Бейдж относится к конкретному скопированному тексту; прежний каскадный effect заменён контролируемым сбросом при открытии/изменении начального формата. Дизайн/vendor/theme сохранены.
- Закрытие снимка и отмена удаления возвращают фокус к актуальной кнопке карточки; после исчезновения карточки предусмотрен fallback на обновление.
- 40 контейнерных E2E, 8 source-context, 10 профильных проверок экспорта, URL unit, TypeScript, профильный lint (включая общую оболочку) и production frontend прошли. Backend state тесты/сборка приняты в 11А.12. Контейнеры приняты локально; создание/логи/редактирование явно disabled, реальный RouterOS/БД требует внешней проверки.
- Далее 11А.14: observed state API сертификатов. Исходный mapper делает отсутствующий срок daysLeft=0, status=valid, keySize=0/trusted=false; это нельзя выдавать за наблюдаемые значения. Legacy API/renew/issue должны сохранять совместимость до отдельной миграции; frontend, ACME jobs, настройки и экспорт сертификатов ещё не приняты.
Блок 11А.14 — observed state API сертификатов (2026-10-01)
- Добавлен совместимый GET /certificates/state: collectedAt, отдельные enabled устройства, подтверждённые строки или error. Новое чтение /certificate не использует tolerant getCertificates, скрывавший неверный ответ как [].
- Новый CertificateObserved не подменяет отсутствующие срок/keySize/trusted/rosId. daysLeft nullable, status unknown; даты без часового пояса/некорректного календаря остаются raw полями и не используются для вычисления срока. Известные UTC/offset даты нормализованы; valid требует известных сроков и сведений об отзыве. Неизвестный будущий/неполный сертификат не становится valid.
- Контракт отклоняет дубли/чужой контекст, несогласованные срок/статус и error со строками. Частичный отказ одного устройства сохраняет остальные; общий DB отказ — HTTP 500, неверный envelope — 502.
- Legacy DTO/API, parser ролей, ACME issue/renew и сетевые операции сохранены для совместимости. Frontend ещё читает legacy; эти сценарии не приняты и требуют последующей миграции.
- Backend build, профильный lint, observed service/HTTP tests и прежний certificate-parse test прошли. test:certificates включён в общий test. PostgreSQL/RouterOS/ACME не использованы в тестах; локальные bare-time даты требуют отдельной проверки часового пояса устройства.
- Далее 11А.15: typed frontend reader и immutable merge последних снимков; затем 11А.16 — таблица/детали/unknown KPI, partial сбои, freshness, контекст/права. ACME settings/jobs/экспорт — последующие блоки. Резервирование, 11Б и итоговый 12 ещё не приняты.
Блок 11А.15 — typed reader и последние снимки сертификатов (2026-10-01)
- readCertificatesState использует строгий новый контракт, GET, caller AbortSignal + 30s срок. Unknown поля сохраняются; malformed/duplicate/чужой контекст, 204 и ошибки HTTP не становятся успешным пустым каталогом.
- mergeCertificateSnapshots хранит latest отдельно от confirmed: отказ устройства сохраняет последний успешный снимок с его временем, первоначальный отказ имеет confirmed=null. Подтверждённое empty очищает строки; исчезнувшие enabled устройства удаляются; восстановление заменяет свой снимок без мутации прежнего состояния.
- 5 fault/HTTP unit, TypeScript, профильный lint и production frontend прошли. Reader/merge готовы к подключению; текущая страница ещё использует legacy DTO. Приёмка сертификатов не завершена.
- Далее узкая проверка setup ошибки клиента устройства (11А.16), затем миграция страницы/таблицы/деталей на observed nullable поля и независимые состояния. Не заменять null числом 0/false, не терять partial rows и выбранный сервер; ACME settings/jobs/экспорт проверять отдельными блоками.
Блок 11А.16 — изоляция setup ошибок устройств (2026-10-01)
- В обоих новых state каталогах создание клиента/reader перенесено внутрь защищённого чтения устройства. Setup исключение одного клиента становится его error/partial, остальные строки остаются доступны; общий каталог не срывается.
- Container reader создаётся один раз и используется для /container/envs/mounts. Certificate reader остаётся отдельным для каждого устройства; сетевые команды, Legacy API и авторизация не изменены.
- Fault тесты setup для обоих каталогов, test:containers, test:certificates, backend build, профильный lint и diff check прошли. Frontend production/TypeScript проверены в 11А.15; новых frontend изменений в этом блоке нет.
- Последняя квота: 96% пятичасового и 77% недельного использовано. Новый большой блок не начинать при этом остатке; продолжить после проверки актуальной квоты.
- Точный следующий шаг — 11А.17: подключить readCertificatesState + mergeCertificateSnapshots в app/(main)/certificates/page.tsx. Мигрировать CertificatesDataGrid и CertificateExpandedDetail на CertificateObserved/совместимый view type без nullable→0/false; обновить фильтры/KPI, independent server read, error/partial/freshness, prefsHydrated/key backend-mode и отмену/revision чтения. Проверить live HTTP failures/unknown/empty/partial/recovery/права/смену источника, TypeScript/lint/build, обновить журнал и сделать локальный коммит без push.
- Далее отдельными блоками ACME settings и renew, issue job polling/частичное выполнение, реальный экспорт/импорт сертификатов, UX/ПК/390/320. Резервные копии, 11Б и общий 12 ещё не приняты; реальные RouterOS/Portal/PostgreSQL/ACME/S3 проверки остаются внешними.
Подключение observed сертификатов: следующий блок 11А.17
Референсы: существующий Data Grid Filtering 2 / Sheet 8. Сохранить композицию DataGrid v9/Frame/ServerRail и семантические токены.
| Данные | Сейчас в legacy UI | Требование observed UI |
|---|---|---|
| Срок отсутствует / дата без timezone | daysLeft=0, возможный valid | —, raw дата с пояснением, unknown; не включать в подтверждённо действительные/истекающие |
| keySize/trusted отсутствуют | 0 / false | Размер/доверие неизвестны, действия определяются API/правами |
| Отказ одного устройства | Общий error или краткий toast | latest.error постоянно виден, confirmed строки/время сохранены |
| Начальный отказ | Пустая таблица/нули | Список и KPI неизвестны, явный повтор |
| Подтверждённое empty | Пустая таблица | Известный пустой каталог и нули; очищает предыдущий снимок |
| Смена источника | Общий эффект старого контекста | Изолированный key + отмена/revision; поздний ответ не меняет новый экран |
[Источник и права] → [Независимые state + servers]
↓
[latest ошибка / confirmed снимок]
↓
[KPI известных данных → таблица → детали]
↓
[ACME/экспорт: последующие блоки]
Проверенные заготовки: shared/api/certificates.ts (readCertificatesState), lib/certificate-snapshots.ts, новый contracts CertificateObserved. Legacy API/renew/issue ещё используются, их миграцию не считать завершённой.
Блок 11А.17.2 — state каталог на странице сертификатов (2026-10-01)
- Страница читает readCertificatesState и независимо строгий каталог серверов. Снимки сохраняются при общем/частичном отказе; подтверждённое empty очищает прежние строки. Отображаются время каталога и время последнего успешного чтения каждого устройства.
- useCertificateCatalog отменяет чтения при unmount/смене источника, использует revision и общий срок 30s. Результат одного источника не ждёт другой; источники разделены key mode/backend после prefsHydrated.
- Неизвестная/частичная сводка показывает — с объяснением, фильтр неизвестных статусов доступен. Выбор сервера сохраняется при отказе каталога; неизвестный статус сервера не превращается в offline на rail.
- Refresh теперь повторяет строгий GET state, не подмешивает legacy DTO и не объявляет частичный результат полным успехом. Write-действия требуют права сертификатов, выпуск также требует доступного каталога серверов. Неподключённый импорт отключён вместо ложного сообщения об успехе.
- 12 профильных HTTP E2E, TypeScript, ESLint и production frontend прошли. API контракт/сетевые правила не менялись.
- Следующий блок 11А.17.3: URL server/status/q и проверки позднего ответа при смене mode/backend. Затем отдельные блоки ACME/renew, issue polling, UI/экспорт и приёмка. Сертификаты в целом, резервные копии, 11Б/12 не приняты; реальные RouterOS/ACME/Portal проверки остаются внешними.
Блок 11А.17.3 — URL и изоляция источника сертификатов (2026-10-01)
- server/status/q читаются из URL: reload/Back сохраняют контекст, чужие параметры и hash не теряются. Дубликаты, неизвестные статусы, некорректные ID и слишком длинный поиск явно отклоняются; сброс не удаляет чужой контекст. Mock srv ID поддержан отдельно.
- Недоступный сервер не заменяется всеми устройствами; ошибка ссылки скрывает строки. Нет совпадений отличается от подтверждённого пустого каталога. Моковые данные подписаны явно.
- 3 URL unit, 6 URL E2E и 4 E2E смены backend/mode с поздним успехом/ошибкой прошли. ESLint/TypeScript прошли; production сборка URL/Suspense прошла до финального добавления подписи моков, после него повторены TypeScript и E2E.
- Далее 11А.18: строгие ответы/отмена API ACME/renew/issue, затем достоверные формы настроек и issue checkpoint/polling. Настройки, выпуск и общая приёмка сертификатов пока не приняты.
Блок 11А.18 — строгие API настроек и выпуска сертификатов (2026-10-01)
- ACME/renew settings и issue request/ответы проверяются существующими contracts схемами. Malformed/204 не считаются успешным сохранением; пустой jobId и чужая задача в polling отклоняются. Legacy list/refresh тоже валидируют DTO.
- Все certificate API принимают caller AbortSignal вместе со сроком 30s. Ошибка Cloudflare с backend message сохраняет понятную причину вместо HTTP Bad Request. Payload проверяется до mutation запроса.
- 5 новых API fault тестов + 5 snapshot/reader тестов, TypeScript и профильный ESLint прошли. Следующий блок 11А.19 — достоверное чтение и сохранение renew настроек, затем ACME и issue polling. Реальные ACME/RouterOS операции пока не приняты.
Блок 11А.19 — достоверные настройки автообновления (2026-10-01)
- Renew форма начинает с unknown, а не enabled/default расписания. Ошибка чтения оставляет операции заблокированными до подтверждённого GET; есть явный повтор и отдельные read/write права. Read-only не выдаётся за недоступный backend.
- Числа проверяются без parseInt/default/clamping: целый интервал от 300 и срок 1–90. Ошибка/204/malformed PUT сохраняет подтверждённое состояние и dirty черновик. Переключатель меняется только после подтверждения, toggle/schedule имеют общий синхронный lock.
- Фоновый GET не стирает dirty черновик; запросы отменяются при unmount, поздний ответ не меняет другой источник и не показывает старый toast. Disabled MM не объявляется доказательством включённого RouterOS ACME.
- 10 renew E2E, повторные 12 data E2E, 2 context E2E позднего PUT, TypeScript/ESLint и production frontend прошли. HTTP fixtures приведены к реальному renewBeforeDays контракту.
- Следующий блок 11А.20: выделить достоверную форму ACME, неизвестное чтение, сохранение черновика/токена, общий lock save/test и отмена; затем issue checkpoint/polling и приёмка сертификатов.