Files
MikrotikManager/docs/ux-ui-stage-11a.md
T

44 KiB
Raw Blame History

Этап 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. Настройки, выпуск и общая приёмка сертификатов пока не приняты.