From 3d51bba86dc69a4f36f1d5b329be75e9402a133a Mon Sep 17 00:00:00 2001 From: Denozordec Date: Thu, 15 Oct 2026 14:37:30 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D1=81=D0=B5=D1=82=D0=B5=D0=B2=D1=8B=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BA=D0=B0=D0=B7=D0=B0=D1=82=D0=B5=D0=BB=D0=B8=20=D0=B8?= =?UTF-8?q?=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=87=D0=B8=D1=82=D0=B0=D0=B5=D0=BC=D1=8B=D0=BC=D0=B8=20=D0=B2?= =?UTF-8?q?=20=D0=BE=D0=B1=D0=B5=D0=B8=D1=85=20=D1=82=D0=B5=D0=BC=D0=B0?= =?UTF-8?q?=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/bgp/page.tsx | 2 - app/(main)/gre/page.tsx | 1 - app/(main)/ospf/page.tsx | 2 - app/(main)/recursive-routes/page.tsx | 12 +- app/(main)/route-optimizer/page.tsx | 7 +- app/(main)/vxlan/page.tsx | 1 - .../scripts/acceptance/ui-readonly-audit.mjs | 5 +- docs/ux-ui-external-acceptance.md | 21 +- docs/ux-ui-plan.md | 23 +- docs/ux-ui-progress.md | 23 +- docs/ux-ui-remaining-acceptance.md | 29 +- docs/ux-ui-stage-12.md | 21 +- docs/ux-ui-test-server.md | 23 +- docs/ux-ui/acceptance/ui261-baseline.json | 1484 +++++++++++ .../ux-ui/acceptance/ui261-changed-final.json | 1088 ++++++++ docs/ux-ui/acceptance/ui261-deployment.json | 20 + docs/ux-ui/acceptance/ui261-final.json | 2286 +++++++++++++++++ docs/ux-ui/acceptance/ui261-live-ecmp.json | 17 + docs/ux-ui/acceptance/ui261-summary.json | 58 + docs/ux-ui/screens/ui261-bgp320.png | Bin 0 -> 49262 bytes tests/e2e/network-kpi-readability.spec.ts | 56 + 21 files changed, 5148 insertions(+), 31 deletions(-) create mode 100644 docs/ux-ui/acceptance/ui261-baseline.json create mode 100644 docs/ux-ui/acceptance/ui261-changed-final.json create mode 100644 docs/ux-ui/acceptance/ui261-deployment.json create mode 100644 docs/ux-ui/acceptance/ui261-final.json create mode 100644 docs/ux-ui/acceptance/ui261-live-ecmp.json create mode 100644 docs/ux-ui/acceptance/ui261-summary.json create mode 100644 docs/ux-ui/screens/ui261-bgp320.png create mode 100644 tests/e2e/network-kpi-readability.spec.ts diff --git a/app/(main)/bgp/page.tsx b/app/(main)/bgp/page.tsx index 2a377b1f..551b6574 100644 --- a/app/(main)/bgp/page.tsx +++ b/app/(main)/bgp/page.tsx @@ -456,7 +456,6 @@ function AnalyticsTab({ sessions }: { sessions: BgpSession[] }) { return (
+ {type === "jump-host" ? "JH" : type === "home-router" ? "HR" : "EN"} - + ) } diff --git a/app/(main)/route-optimizer/page.tsx b/app/(main)/route-optimizer/page.tsx index 41ee3aa1..e76ee731 100644 --- a/app/(main)/route-optimizer/page.tsx +++ b/app/(main)/route-optimizer/page.tsx @@ -949,7 +949,8 @@ function RouteOptimizerContent({requestedServer,onSelectServer}:{requestedServer contentClassName="px-5 py-4 flex flex-col gap-4" > -
+
+ Параметры черновика ECMP
Макс. путей
@@ -966,7 +967,7 @@ function RouteOptimizerContent({requestedServer,onSelectServer}:{requestedServer {([["per-dst","По dst"],["per-conn","По conn"],["per-packet","По пакет"]] as const).map(([v, l]) => ( ))} @@ -977,7 +978,7 @@ function RouteOptimizerContent({requestedServer,onSelectServer}:{requestedServer
-
+
{/* RPF Card */} diff --git a/app/(main)/vxlan/page.tsx b/app/(main)/vxlan/page.tsx index 5c14265a..901e9e1e 100644 --- a/app/(main)/vxlan/page.tsx +++ b/app/(main)/vxlan/page.tsx @@ -298,7 +298,6 @@ function VxlanPageContent({ selectedServerId, selectedTunnelId, allTunnelsHref, elements.filter(el => el.getClientRects().length).map(el => ({ label: el.textContent.trim(), clipped: el.scrollWidth > el.clientWidth + 1 }))); + } item.performance = await page.evaluate(() => { const n = performance.getEntriesByType('navigation')[0]; return { ...window.__acceptanceMetrics, ttfbMs: n.responseStart - n.requestStart, domInteractiveMs: n.domInteractive, durationMs: n.duration }; @@ -129,7 +132,7 @@ try { await context.close(); } report.completed = true; - report.passed = report.blockedWrites.length === 0 && report.cases.every(c => c.auditCompleted && !c.layout.overflow && !c.layout.mainOverflow && c.violations.length === 0 && c.pageErrors.length === 0 && c.apiErrors.length === 0); + report.passed = report.blockedWrites.length === 0 && report.cases.every(c => c.auditCompleted && !c.layout.overflow && !c.layout.mainOverflow && c.violations.length === 0 && c.pageErrors.length === 0 && c.apiErrors.length === 0 && !c.kpiLabels?.some(label => label.clipped)); if (!report.passed) process.exitCode = 1; } finally { fs.writeFileSync(output, JSON.stringify(report, null, 2) + '\n'); diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index c7321332..8bc63286 100644 --- a/docs/ux-ui-external-acceptance.md +++ b/docs/ux-ui-external-acceptance.md @@ -1,6 +1,6 @@ # UX/UI: протокол внешней приёмки 12.5 -Актуальное продолжение 2026-10-15: 260 — исправлены общие focus/target-size/contrast и BGP320 tabs; production frontend ux-accessibility260-final развёрнут, backend258 healthy.72 live WCAG/layout состояния прошли,59 уникальных E2E прошли в записанных итерациях; synthetic timing сохранён. Следующий261 — оставшиеся12 экранов и точные границы приёмки; этап12.5 открыт. +Актуальное продолжение 2026-10-15: 261 — оставшиеся12 default screens проверены; контраст recursive/ECMP исправлен, KPI BGP/GRE/VXLAN/OSPF используют штатную адаптивную сетку.64 unique live states261, вместе с260 —120 состояний30 экранов с явным происхождением;40 локальных E2E приняты. Runtime backend258/frontend261. Следующий262 — оформить десятиминутный REST-протокол, затем263 — итоговая UI-регрессия; этап12.5 открыт. IPsec78: реальный PSK responder established, mode-config198.18.78.10 и online=true подтверждены API; точные собственные объекты удалены. Сертификатный/DN online, late failure и history restore остаются открытыми. [Отчёт](ux-ui/acceptance/ipsec-mode-config-2026-10-09.json), [журнал](ux-ui-progress.md). @@ -18,6 +18,25 @@ OSPF84: real GRE Full adjacency/private routes/Ping3/3 и4 live PC/320px URL/bro BFD85: Up/Full/own route/Ping,4 live UI и loss/withdrawal/recovery приняты; exact own cleanup8 коллекций×2 passed. Withdrawal5,245ms не подтверждает sub-second BFD timing; этот вопрос открыт. Runtime routing83 без новой сборки. [Отчёт](ux-ui/acceptance/ospf-bfd-loss-2026-10-09.json). +## 12.5.261 — оставшиеся экраны и читаемые сетевые KPI (2026-10-15) + +Baseline12 разделов×2 темы×1440/320px:48 live states, API/JS errors0, overflow0, blocked configuration writes0. Четыре проверенных read-only POST OSPF preview разрешены явно после чтения backend handler; они рассчитывают план, не применяют его. DNS resolution/catalog read POST также имеют точный opt-in allowlist в helper, остальные non-GET блокируются. EvoBGP на стенде disabled, URL/key не настроены: каталоги здесь проверены в состоянии недоступной интеграции, не с реальными внешними данными. [Baseline](ux-ui/acceptance/ui261-baseline.json). + +Найдены4 contrast failures: TypeChip в recursive-routes/light и opacity40 всего ECMP disabled section в двух темах на ПК. TypeChip теперь использует установленный ReUI Badge light variants/text-foreground; ECMP — native disabled fieldset с legend вместо затемнения всех подписей. Профильная проверка включённого ECMP дополнительно выявила неконтрастный выбранный алгоритм; применены bg-muted/text-foreground. Это локальный черновик, серверная логика/применение маршрутов не менялись. + +В BGP summary/analytics, GRE, VXLAN, OSPF summary/BFD убраны принудительные grid-cols2 overrides. Используются существующие kpiCols и неизменённые Frame/IconTile/KpiStatGrid: одна колонка в узком контейнере, прежние container breakpoints для плотного ПК. Шрифты, vendor компоненты, theme tokens и motion не менялись. В8 локальных geometry cases320/390 и16 live theme/width cases подписи KPI не обрезаются; explicit краткая подпись Интерф. в OSPF сохранена. [Снимок BGP320 после загрузки](ux-ui/screens/ui261-bgp320.png). + +Локальные40 E2E: initial38 passed/2 ECMP contrast failed; после исправления повторены только2 affected cases, passed. Geometry8, ECMP2, recursive route contracts15 и OSPF preview15; разные прогоны не суммируются в42 сценария. ECMP false/true проверены также в настоящем Portal/UI, scoped axe0, configuration writes0, draft возвращён off. [Live ECMP](ux-ui/acceptance/ui261-live-ecmp.json). TypeScript/scoped ESLint и production build passed, source528 matched. Runtime frontend ux-accessibility261, backend ux-certificates258 healthy; DB/Portal сохранены. [Deployment](ux-ui/acceptance/ui261-deployment.json). + +Повторены только6 изменённых страниц:24 live states, WCAG-tagged axe violations0/API/JS errors0/overflow0/clipped KPI labels0. С48 baseline это64 уникальных состояния16 страниц: восемь состояний двух изменённых baseline страниц заменены повторными, четыре сетевых KPI страницы добавлены. Вместе с260 —120 уникальных default states30 экранов. Report сохраняет фазу/образ каждого результата: остальные страницы не выданы за повторный прогон на261. [Changed24](ux-ui/acceptance/ui261-changed-final.json), [итог64](ux-ui/acceptance/ui261-final.json), [сводка](ux-ui/acceptance/ui261-summary.json). Во время части замеров шла контролируемая REST-нагрузка262, timings не field CWV и не сравнение независимых бенчмарков. + +| Референс | Схема доработки | +| --- | --- | +| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Узкий контейнер → штатная одна колонка → полная подпись KPI; [BGP320](ux-ui/screens/ui261-bgp320.png) | +| [ReUI Badge](https://reui.io/components/badge?ref=mcp) | Тип устройства → light variant → semantic foreground | + +Настройки CHR, DNS, сертификаты и пароли не менялись. Native/background telemetry reads не объявляются отсутствием всех DB metadata updates. Manual screen-reader, новые клиентские ОС/WAN/public IPv6/multi-instance, CA revoke и внешняя публикация остаются отдельными границами. Следующий262 — оформить завершённый ten-minute REST soak, затем263 — актуальная полная локальная UI-регрессия после общего keyboard adapter260 и responsive delta261. Автоматическая120-state проверка не заменяет сквозные сценарии и не является полной WCAG сертификацией. + ## 12.5.260 — общая доступность, клавиатура и профильная скорость UI (2026-10-15) Проведён read-only аудит18 разделов×2 темы×1440/320px:72 состояния текущего стенда/настоящий Portal UI, без response mocks. Baseline выявил23 состояния с axe нарушениями: недостаточная независимая pointer-area у focusable listbox-контейнера, контраст счётчиков SegmentedControl, raw type badges серверов и заголовка VXLAN. Document overflow0/JS errors0/API errors0; BGP320 имел2 внутренних переполнения main. Dashboard в baseline ещё содержал skeleton: его ранние timings не считаются готовностью данных. [Baseline](ux-ui/acceptance/ui260-baseline.json). diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 5cf697f8..22537c22 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,9 +1,28 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-15: 260 — исправлены общие focus/target-size/contrast и BGP320 tabs; production frontend ux-accessibility260-final развёрнут, backend258 healthy.72 live WCAG/layout состояния прошли,59 уникальных E2E прошли в записанных итерациях; synthetic timing сохранён. Следующий261 — оставшиеся12 экранов и точные границы приёмки; этап12.5 открыт. +Актуальное продолжение 2026-10-15: 261 — оставшиеся12 default screens проверены; контраст recursive/ECMP исправлен, KPI BGP/GRE/VXLAN/OSPF используют штатную адаптивную сетку.64 unique live states261, вместе с260 —120 состояний30 экранов с явным происхождением;40 локальных E2E приняты. Runtime backend258/frontend261. Следующий262 — оформить десятиминутный REST-протокол, затем263 — итоговая UI-регрессия; этап12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-15. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. +## 12.5.261 — оставшиеся экраны и читаемые сетевые KPI (2026-10-15) + +Baseline12 разделов×2 темы×1440/320px:48 live states, API/JS errors0, overflow0, blocked configuration writes0. Четыре проверенных read-only POST OSPF preview разрешены явно после чтения backend handler; они рассчитывают план, не применяют его. DNS resolution/catalog read POST также имеют точный opt-in allowlist в helper, остальные non-GET блокируются. EvoBGP на стенде disabled, URL/key не настроены: каталоги здесь проверены в состоянии недоступной интеграции, не с реальными внешними данными. [Baseline](ux-ui/acceptance/ui261-baseline.json). + +Найдены4 contrast failures: TypeChip в recursive-routes/light и opacity40 всего ECMP disabled section в двух темах на ПК. TypeChip теперь использует установленный ReUI Badge light variants/text-foreground; ECMP — native disabled fieldset с legend вместо затемнения всех подписей. Профильная проверка включённого ECMP дополнительно выявила неконтрастный выбранный алгоритм; применены bg-muted/text-foreground. Это локальный черновик, серверная логика/применение маршрутов не менялись. + +В BGP summary/analytics, GRE, VXLAN, OSPF summary/BFD убраны принудительные grid-cols2 overrides. Используются существующие kpiCols и неизменённые Frame/IconTile/KpiStatGrid: одна колонка в узком контейнере, прежние container breakpoints для плотного ПК. Шрифты, vendor компоненты, theme tokens и motion не менялись. В8 локальных geometry cases320/390 и16 live theme/width cases подписи KPI не обрезаются; explicit краткая подпись Интерф. в OSPF сохранена. [Снимок BGP320 после загрузки](ux-ui/screens/ui261-bgp320.png). + +Локальные40 E2E: initial38 passed/2 ECMP contrast failed; после исправления повторены только2 affected cases, passed. Geometry8, ECMP2, recursive route contracts15 и OSPF preview15; разные прогоны не суммируются в42 сценария. ECMP false/true проверены также в настоящем Portal/UI, scoped axe0, configuration writes0, draft возвращён off. [Live ECMP](ux-ui/acceptance/ui261-live-ecmp.json). TypeScript/scoped ESLint и production build passed, source528 matched. Runtime frontend ux-accessibility261, backend ux-certificates258 healthy; DB/Portal сохранены. [Deployment](ux-ui/acceptance/ui261-deployment.json). + +Повторены только6 изменённых страниц:24 live states, WCAG-tagged axe violations0/API/JS errors0/overflow0/clipped KPI labels0. С48 baseline это64 уникальных состояния16 страниц: восемь состояний двух изменённых baseline страниц заменены повторными, четыре сетевых KPI страницы добавлены. Вместе с260 —120 уникальных default states30 экранов. Report сохраняет фазу/образ каждого результата: остальные страницы не выданы за повторный прогон на261. [Changed24](ux-ui/acceptance/ui261-changed-final.json), [итог64](ux-ui/acceptance/ui261-final.json), [сводка](ux-ui/acceptance/ui261-summary.json). Во время части замеров шла контролируемая REST-нагрузка262, timings не field CWV и не сравнение независимых бенчмарков. + +| Референс | Схема доработки | +| --- | --- | +| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Узкий контейнер → штатная одна колонка → полная подпись KPI; [BGP320](ux-ui/screens/ui261-bgp320.png) | +| [ReUI Badge](https://reui.io/components/badge?ref=mcp) | Тип устройства → light variant → semantic foreground | + +Настройки CHR, DNS, сертификаты и пароли не менялись. Native/background telemetry reads не объявляются отсутствием всех DB metadata updates. Manual screen-reader, новые клиентские ОС/WAN/public IPv6/multi-instance, CA revoke и внешняя публикация остаются отдельными границами. Следующий262 — оформить завершённый ten-minute REST soak, затем263 — актуальная полная локальная UI-регрессия после общего keyboard adapter260 и responsive delta261. Автоматическая120-state проверка не заменяет сквозные сценарии и не является полной WCAG сертификацией. + ## 12.5.260 — общая доступность, клавиатура и профильная скорость UI (2026-10-15) Проведён read-only аудит18 разделов×2 темы×1440/320px:72 состояния текущего стенда/настоящий Portal UI, без response mocks. Baseline выявил23 состояния с axe нарушениями: недостаточная независимая pointer-area у focusable listbox-контейнера, контраст счётчиков SegmentedControl, raw type badges серверов и заголовка VXLAN. Document overflow0/JS errors0/API errors0; BGP320 имел2 внутренних переполнения main. Dashboard в baseline ещё содержал skeleton: его ранние timings не считаются готовностью данных. [Baseline](ux-ui/acceptance/ui260-baseline.json). @@ -352,7 +371,7 @@ Production backend/frontend ux-backups237 собраны и развёрнуты | Масштаб и устойчивость | Контролируемые PG lock/dedupe/loss/recovery, отдельные multi-process сценарии194/200/201 | Несколько полноценных backend/worker со всеми writers, длительный rekey/traffic, физические потери WAN/DB и maximum throughput | | Хранилища и внешние jobs | Реальные Portal/Telegram195–201, Selectel S3/limited bridge restore/retention228–240, GeoIP mirror242; контейнеры248; production ACME250 и реальное renewal/rotation259 с trusted TLS/cleanup | Large native export/migration buckets/multi-instance S3; CA revocation отдельно не принят. Gitea publishing CI не запускался: действует no push. Direct licensed MaxMind отсутствует в текущей реализации | | Расширенные сетевые конфигурации | GRE/WG/VXLAN/IPsec/OSPF/BFD/BGP в записанных сценариях, включая private IPv6 | from-pool/EUI-64/multiple-address import/export, outer IPv6, произвольная PKI/OID/path-length, независимые внешние peers и длительные переходы. BFD-only gate не гарантирован | -| UX, доступность и производительность | UI226, ARIA мастеров251, новая проверка260 текущего runtime описана выше | Исправления260 и72 live состояния приняты; оставшиеся12 экранов и ручная screen-reader приёмка отдельно. Synthetic timing не field CWV, scoped axe не полная WCAG сертификация | +| UX, доступность и производительность | UI226, ARIA мастеров251, новая проверка260 текущего runtime описана выше | Default UI260/261:120 unique states30 экранов приняты в автоматическом scope; актуальная полная UI-регрессия263 и ручная screen-reader приёмка отдельно. Synthetic timing не field CWV, scoped axe не полная WCAG сертификация | | Границы выпуска | Датированный журнал, протоколы и локальные коммиты без push | Подготовить итоговый перечень принятых конфигураций/исключений для решения о выпуске; лабораторный успех не объявлять приёмкой всех конфигураций | Portal offline JWT действует доexp после logout/role change; это записанная граница198, не мгновенная revocation. Controlled Telegram unknown/retry201 не доказывает физическую потерю ACK в WAN. Архивные записи ниже отражают состояние на свою дату; актуальный статус задают эта таблица и верхняя точка продолжения. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 1a7d79d9..e9d81a06 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-15: 260 — исправлены общие focus/target-size/contrast и BGP320 tabs; production frontend ux-accessibility260-final развёрнут, backend258 healthy.72 live WCAG/layout состояния прошли,59 уникальных E2E прошли в записанных итерациях; synthetic timing сохранён. Следующий261 — оставшиеся12 экранов и точные границы приёмки; этап12.5 открыт. +Актуальное продолжение 2026-10-15: 261 — оставшиеся12 default screens проверены; контраст recursive/ECMP исправлен, KPI BGP/GRE/VXLAN/OSPF используют штатную адаптивную сетку.64 unique live states261, вместе с260 —120 состояний30 экранов с явным происхождением;40 локальных E2E приняты. Runtime backend258/frontend261. Следующий262 — оформить десятиминутный REST-протокол, затем263 — итоговая UI-регрессия; этап12.5 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -36,7 +36,26 @@ | 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 | | 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 | | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 | -| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная12.3.83; внешняя12.5 открыта, блоки1–260 в записанных границах. Ротация/renewal259 и откат приняты; общий UI260:72 live states passed. Runtime backend258/frontend260. Следующий261 — оставшиеся12 экранов и итоговые границы. | +| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная12.3.83; внешняя12.5 открыта, блоки1–261 в записанных границах. ACME259; default UI260/261:120 unique states30 экранов. Runtime backend258/frontend261. Следующий262 — REST, затем263 — полная актуальная UI-регрессия. | + +## 12.5.261 — оставшиеся экраны и читаемые сетевые KPI (2026-10-15) + +Baseline12 разделов×2 темы×1440/320px:48 live states, API/JS errors0, overflow0, blocked configuration writes0. Четыре проверенных read-only POST OSPF preview разрешены явно после чтения backend handler; они рассчитывают план, не применяют его. DNS resolution/catalog read POST также имеют точный opt-in allowlist в helper, остальные non-GET блокируются. EvoBGP на стенде disabled, URL/key не настроены: каталоги здесь проверены в состоянии недоступной интеграции, не с реальными внешними данными. [Baseline](ux-ui/acceptance/ui261-baseline.json). + +Найдены4 contrast failures: TypeChip в recursive-routes/light и opacity40 всего ECMP disabled section в двух темах на ПК. TypeChip теперь использует установленный ReUI Badge light variants/text-foreground; ECMP — native disabled fieldset с legend вместо затемнения всех подписей. Профильная проверка включённого ECMP дополнительно выявила неконтрастный выбранный алгоритм; применены bg-muted/text-foreground. Это локальный черновик, серверная логика/применение маршрутов не менялись. + +В BGP summary/analytics, GRE, VXLAN, OSPF summary/BFD убраны принудительные grid-cols2 overrides. Используются существующие kpiCols и неизменённые Frame/IconTile/KpiStatGrid: одна колонка в узком контейнере, прежние container breakpoints для плотного ПК. Шрифты, vendor компоненты, theme tokens и motion не менялись. В8 локальных geometry cases320/390 и16 live theme/width cases подписи KPI не обрезаются; explicit краткая подпись Интерф. в OSPF сохранена. [Снимок BGP320 после загрузки](ux-ui/screens/ui261-bgp320.png). + +Локальные40 E2E: initial38 passed/2 ECMP contrast failed; после исправления повторены только2 affected cases, passed. Geometry8, ECMP2, recursive route contracts15 и OSPF preview15; разные прогоны не суммируются в42 сценария. ECMP false/true проверены также в настоящем Portal/UI, scoped axe0, configuration writes0, draft возвращён off. [Live ECMP](ux-ui/acceptance/ui261-live-ecmp.json). TypeScript/scoped ESLint и production build passed, source528 matched. Runtime frontend ux-accessibility261, backend ux-certificates258 healthy; DB/Portal сохранены. [Deployment](ux-ui/acceptance/ui261-deployment.json). + +Повторены только6 изменённых страниц:24 live states, WCAG-tagged axe violations0/API/JS errors0/overflow0/clipped KPI labels0. С48 baseline это64 уникальных состояния16 страниц: восемь состояний двух изменённых baseline страниц заменены повторными, четыре сетевых KPI страницы добавлены. Вместе с260 —120 уникальных default states30 экранов. Report сохраняет фазу/образ каждого результата: остальные страницы не выданы за повторный прогон на261. [Changed24](ux-ui/acceptance/ui261-changed-final.json), [итог64](ux-ui/acceptance/ui261-final.json), [сводка](ux-ui/acceptance/ui261-summary.json). Во время части замеров шла контролируемая REST-нагрузка262, timings не field CWV и не сравнение независимых бенчмарков. + +| Референс | Схема доработки | +| --- | --- | +| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Узкий контейнер → штатная одна колонка → полная подпись KPI; [BGP320](ux-ui/screens/ui261-bgp320.png) | +| [ReUI Badge](https://reui.io/components/badge?ref=mcp) | Тип устройства → light variant → semantic foreground | + +Настройки CHR, DNS, сертификаты и пароли не менялись. Native/background telemetry reads не объявляются отсутствием всех DB metadata updates. Manual screen-reader, новые клиентские ОС/WAN/public IPv6/multi-instance, CA revoke и внешняя публикация остаются отдельными границами. Следующий262 — оформить завершённый ten-minute REST soak, затем263 — актуальная полная локальная UI-регрессия после общего keyboard adapter260 и responsive delta261. Автоматическая120-state проверка не заменяет сквозные сценарии и не является полной WCAG сертификацией. ## 12.5.260 — общая доступность, клавиатура и профильная скорость UI (2026-10-15) diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index ae3a89b6..ec4d43c8 100644 --- a/docs/ux-ui-remaining-acceptance.md +++ b/docs/ux-ui-remaining-acceptance.md @@ -1,10 +1,29 @@ # Оставшаяся внешняя приёмка — 2026-10-15 -Актуальное продолжение 2026-10-15: 260 — исправлены общие focus/target-size/contrast и BGP320 tabs; production frontend ux-accessibility260-final развёрнут, backend258 healthy.72 live WCAG/layout состояния прошли,59 уникальных E2E прошли в записанных итерациях; synthetic timing сохранён. Следующий261 — оставшиеся12 экранов и точные границы приёмки; этап12.5 открыт. +Актуальное продолжение 2026-10-15: 261 — оставшиеся12 default screens проверены; контраст recursive/ECMP исправлен, KPI BGP/GRE/VXLAN/OSPF используют штатную адаптивную сетку.64 unique live states261, вместе с260 —120 состояний30 экранов с явным происхождением;40 локальных E2E приняты. Runtime backend258/frontend261. Следующий262 — оформить десятиминутный REST-протокол, затем263 — итоговая UI-регрессия; этап12.5 открыт. -Это актуальная матрица после 12.5.260. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. +Это актуальная матрица после 12.5.261. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. -Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-certificates258`, frontend `ux-accessibility260-final`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. +Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-certificates258`, frontend `ux-accessibility261`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. + +## 12.5.261 — оставшиеся экраны и читаемые сетевые KPI (2026-10-15) + +Baseline12 разделов×2 темы×1440/320px:48 live states, API/JS errors0, overflow0, blocked configuration writes0. Четыре проверенных read-only POST OSPF preview разрешены явно после чтения backend handler; они рассчитывают план, не применяют его. DNS resolution/catalog read POST также имеют точный opt-in allowlist в helper, остальные non-GET блокируются. EvoBGP на стенде disabled, URL/key не настроены: каталоги здесь проверены в состоянии недоступной интеграции, не с реальными внешними данными. [Baseline](ux-ui/acceptance/ui261-baseline.json). + +Найдены4 contrast failures: TypeChip в recursive-routes/light и opacity40 всего ECMP disabled section в двух темах на ПК. TypeChip теперь использует установленный ReUI Badge light variants/text-foreground; ECMP — native disabled fieldset с legend вместо затемнения всех подписей. Профильная проверка включённого ECMP дополнительно выявила неконтрастный выбранный алгоритм; применены bg-muted/text-foreground. Это локальный черновик, серверная логика/применение маршрутов не менялись. + +В BGP summary/analytics, GRE, VXLAN, OSPF summary/BFD убраны принудительные grid-cols2 overrides. Используются существующие kpiCols и неизменённые Frame/IconTile/KpiStatGrid: одна колонка в узком контейнере, прежние container breakpoints для плотного ПК. Шрифты, vendor компоненты, theme tokens и motion не менялись. В8 локальных geometry cases320/390 и16 live theme/width cases подписи KPI не обрезаются; explicit краткая подпись Интерф. в OSPF сохранена. [Снимок BGP320 после загрузки](ux-ui/screens/ui261-bgp320.png). + +Локальные40 E2E: initial38 passed/2 ECMP contrast failed; после исправления повторены только2 affected cases, passed. Geometry8, ECMP2, recursive route contracts15 и OSPF preview15; разные прогоны не суммируются в42 сценария. ECMP false/true проверены также в настоящем Portal/UI, scoped axe0, configuration writes0, draft возвращён off. [Live ECMP](ux-ui/acceptance/ui261-live-ecmp.json). TypeScript/scoped ESLint и production build passed, source528 matched. Runtime frontend ux-accessibility261, backend ux-certificates258 healthy; DB/Portal сохранены. [Deployment](ux-ui/acceptance/ui261-deployment.json). + +Повторены только6 изменённых страниц:24 live states, WCAG-tagged axe violations0/API/JS errors0/overflow0/clipped KPI labels0. С48 baseline это64 уникальных состояния16 страниц: восемь состояний двух изменённых baseline страниц заменены повторными, четыре сетевых KPI страницы добавлены. Вместе с260 —120 уникальных default states30 экранов. Report сохраняет фазу/образ каждого результата: остальные страницы не выданы за повторный прогон на261. [Changed24](ux-ui/acceptance/ui261-changed-final.json), [итог64](ux-ui/acceptance/ui261-final.json), [сводка](ux-ui/acceptance/ui261-summary.json). Во время части замеров шла контролируемая REST-нагрузка262, timings не field CWV и не сравнение независимых бенчмарков. + +| Референс | Схема доработки | +| --- | --- | +| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Узкий контейнер → штатная одна колонка → полная подпись KPI; [BGP320](ux-ui/screens/ui261-bgp320.png) | +| [ReUI Badge](https://reui.io/components/badge?ref=mcp) | Тип устройства → light variant → semantic foreground | + +Настройки CHR, DNS, сертификаты и пароли не менялись. Native/background telemetry reads не объявляются отсутствием всех DB metadata updates. Manual screen-reader, новые клиентские ОС/WAN/public IPv6/multi-instance, CA revoke и внешняя публикация остаются отдельными границами. Следующий262 — оформить завершённый ten-minute REST soak, затем263 — актуальная полная локальная UI-регрессия после общего keyboard adapter260 и responsive delta261. Автоматическая120-state проверка не заменяет сквозные сценарии и не является полной WCAG сертификацией. ## 12.5.260 — общая доступность, клавиатура и профильная скорость UI (2026-10-15) @@ -348,7 +367,7 @@ Production backend/frontend ux-backups237 собраны и развёрнуты | Масштаб и устойчивость | Контролируемые PG lock/dedupe/loss/recovery, отдельные multi-process сценарии194/200/201 | Несколько полноценных backend/worker со всеми writers, длительный rekey/traffic, физические потери WAN/DB и maximum throughput | | Хранилища и внешние jobs | Реальные Portal/Telegram195–201, Selectel S3/limited bridge restore/retention228–240, GeoIP mirror242; контейнеры248; production ACME250 и реальное renewal/rotation259 с trusted TLS/cleanup | Large native export/migration buckets/multi-instance S3; CA revocation отдельно не принят. Gitea publishing CI не запускался: действует no push. Direct licensed MaxMind отсутствует в текущей реализации | | Расширенные сетевые конфигурации | GRE/WG/VXLAN/IPsec/OSPF/BFD/BGP в записанных сценариях, включая private IPv6 | from-pool/EUI-64/multiple-address import/export, outer IPv6, произвольная PKI/OID/path-length, независимые внешние peers и длительные переходы. BFD-only gate не гарантирован | -| UX, доступность и производительность | UI226, ARIA мастеров251, новая проверка260 текущего runtime описана выше | Исправления260 и72 live состояния приняты; оставшиеся12 экранов и ручная screen-reader приёмка отдельно. Synthetic timing не field CWV, scoped axe не полная WCAG сертификация | +| UX, доступность и производительность | UI226, ARIA мастеров251, новая проверка260 текущего runtime описана выше | Default UI260/261:120 unique states30 экранов приняты в автоматическом scope; актуальная полная UI-регрессия263 и ручная screen-reader приёмка отдельно. Synthetic timing не field CWV, scoped axe не полная WCAG сертификация | | Границы выпуска | Датированный журнал, протоколы и локальные коммиты без push | Подготовить итоговый перечень принятых конфигураций/исключений для решения о выпуске; лабораторный успех не объявлять приёмкой всех конфигураций | Portal offline JWT действует доexp после logout/role change; это записанная граница198, не мгновенная revocation. Controlled Telegram unknown/retry201 не доказывает физическую потерю ACK в WAN. Архивные записи ниже отражают состояние на свою дату; актуальный статус задают эта таблица и верхняя точка продолжения. @@ -373,7 +392,7 @@ Portal offline JWT действует доexp после logout/role change; э ## Следующая последовательность — 2026-10-15 -1. **Приёмка261:**12 оставшихся default screens с real Portal/API и точным read-only guard; профильный audit/layout/performance. Сертификаты259 и UI260 приняты в записанных границах. +1. **REST262 и регрессия263:** оформить десятиминутную стабильность backend258; один полный локальный UI-набор после shared keyboard260/responsive261 с сохранением исходных screenshots и недоступным proxy backend, без native writes. 2. **Профильный контроль:** для новых изменений выполнять затронутые SQL/API/UI проверки. Общая регрессия192 выполнена; не повторять её без изменений общей основы или нового основания. 3. **Внешние зависимости:** Portal issuer/login/revoke, Telegram bot/chat/разрешённое сообщение, клиентские ОС, независимые WAN/public IPv6 и scale/multi-instance — отдельные группы по таблице выше. Ограничения текущего стенда не превращать в обещание приёмки всех конфигураций. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 3868ea1a..790d4ccf 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-15: 260 — исправлены общие focus/target-size/contrast и BGP320 tabs; production frontend ux-accessibility260-final развёрнут, backend258 healthy.72 live WCAG/layout состояния прошли,59 уникальных E2E прошли в записанных итерациях; synthetic timing сохранён. Следующий261 — оставшиеся12 экранов и точные границы приёмки; этап12.5 открыт. +Актуальное продолжение 2026-10-15: 261 — оставшиеся12 default screens проверены; контраст recursive/ECMP исправлен, KPI BGP/GRE/VXLAN/OSPF используют штатную адаптивную сетку.64 unique live states261, вместе с260 —120 состояний30 экранов с явным происхождением;40 локальных E2E приняты. Runtime backend258/frontend261. Следующий262 — оформить десятиминутный REST-протокол, затем263 — итоговая UI-регрессия; этап12.5 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -4152,6 +4152,25 @@ Real Portal/browser API, provider local verified: создан один job ро Runtime backend ux-backup234/frontend ux-routing218 healthy; новое application image для volume-only fix не требовалось. Пароли CHR не менялись, S3 mutations0 в235, configs body/JWT не опубликованы, private local manifest удалён. Исходные10 missing local files не восстановлены и records не удалены. Force-recreate — не host reboot/power loss/fsync/DR приёмка. Далее236 — UX недоступных local files, затем version retention границы и оставшиеся7 группы матрицы; multi-instance guard не принимается. Этап12.5 открыт. +## 12.5.261 — оставшиеся экраны и читаемые сетевые KPI (2026-10-15) + +Baseline12 разделов×2 темы×1440/320px:48 live states, API/JS errors0, overflow0, blocked configuration writes0. Четыре проверенных read-only POST OSPF preview разрешены явно после чтения backend handler; они рассчитывают план, не применяют его. DNS resolution/catalog read POST также имеют точный opt-in allowlist в helper, остальные non-GET блокируются. EvoBGP на стенде disabled, URL/key не настроены: каталоги здесь проверены в состоянии недоступной интеграции, не с реальными внешними данными. [Baseline](ux-ui/acceptance/ui261-baseline.json). + +Найдены4 contrast failures: TypeChip в recursive-routes/light и opacity40 всего ECMP disabled section в двух темах на ПК. TypeChip теперь использует установленный ReUI Badge light variants/text-foreground; ECMP — native disabled fieldset с legend вместо затемнения всех подписей. Профильная проверка включённого ECMP дополнительно выявила неконтрастный выбранный алгоритм; применены bg-muted/text-foreground. Это локальный черновик, серверная логика/применение маршрутов не менялись. + +В BGP summary/analytics, GRE, VXLAN, OSPF summary/BFD убраны принудительные grid-cols2 overrides. Используются существующие kpiCols и неизменённые Frame/IconTile/KpiStatGrid: одна колонка в узком контейнере, прежние container breakpoints для плотного ПК. Шрифты, vendor компоненты, theme tokens и motion не менялись. В8 локальных geometry cases320/390 и16 live theme/width cases подписи KPI не обрезаются; explicit краткая подпись Интерф. в OSPF сохранена. [Снимок BGP320 после загрузки](ux-ui/screens/ui261-bgp320.png). + +Локальные40 E2E: initial38 passed/2 ECMP contrast failed; после исправления повторены только2 affected cases, passed. Geometry8, ECMP2, recursive route contracts15 и OSPF preview15; разные прогоны не суммируются в42 сценария. ECMP false/true проверены также в настоящем Portal/UI, scoped axe0, configuration writes0, draft возвращён off. [Live ECMP](ux-ui/acceptance/ui261-live-ecmp.json). TypeScript/scoped ESLint и production build passed, source528 matched. Runtime frontend ux-accessibility261, backend ux-certificates258 healthy; DB/Portal сохранены. [Deployment](ux-ui/acceptance/ui261-deployment.json). + +Повторены только6 изменённых страниц:24 live states, WCAG-tagged axe violations0/API/JS errors0/overflow0/clipped KPI labels0. С48 baseline это64 уникальных состояния16 страниц: восемь состояний двух изменённых baseline страниц заменены повторными, четыре сетевых KPI страницы добавлены. Вместе с260 —120 уникальных default states30 экранов. Report сохраняет фазу/образ каждого результата: остальные страницы не выданы за повторный прогон на261. [Changed24](ux-ui/acceptance/ui261-changed-final.json), [итог64](ux-ui/acceptance/ui261-final.json), [сводка](ux-ui/acceptance/ui261-summary.json). Во время части замеров шла контролируемая REST-нагрузка262, timings не field CWV и не сравнение независимых бенчмарков. + +| Референс | Схема доработки | +| --- | --- | +| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Узкий контейнер → штатная одна колонка → полная подпись KPI; [BGP320](ux-ui/screens/ui261-bgp320.png) | +| [ReUI Badge](https://reui.io/components/badge?ref=mcp) | Тип устройства → light variant → semantic foreground | + +Настройки CHR, DNS, сертификаты и пароли не менялись. Native/background telemetry reads не объявляются отсутствием всех DB metadata updates. Manual screen-reader, новые клиентские ОС/WAN/public IPv6/multi-instance, CA revoke и внешняя публикация остаются отдельными границами. Следующий262 — оформить завершённый ten-minute REST soak, затем263 — актуальная полная локальная UI-регрессия после общего keyboard adapter260 и responsive delta261. Автоматическая120-state проверка не заменяет сквозные сценарии и не является полной WCAG сертификацией. + ## 12.5.260 — общая доступность, клавиатура и профильная скорость UI (2026-10-15) Проведён read-only аудит18 разделов×2 темы×1440/320px:72 состояния текущего стенда/настоящий Portal UI, без response mocks. Baseline выявил23 состояния с axe нарушениями: недостаточная независимая pointer-area у focusable listbox-контейнера, контраст счётчиков SegmentedControl, raw type badges серверов и заголовка VXLAN. Document overflow0/JS errors0/API errors0; BGP320 имел2 внутренних переполнения main. Dashboard в baseline ещё содержал skeleton: его ранние timings не считаются готовностью данных. [Baseline](ux-ui/acceptance/ui260-baseline.json). diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index dd636915..2eda037e 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -1,6 +1,6 @@ # Тестовый сервер UX/UI — 2026-10-10 -Актуальное продолжение 2026-10-15: 260 — исправлены общие focus/target-size/contrast и BGP320 tabs; production frontend ux-accessibility260-final развёрнут, backend258 healthy.72 live WCAG/layout состояния прошли,59 уникальных E2E прошли в записанных итерациях; synthetic timing сохранён. Следующий261 — оставшиеся12 экранов и точные границы приёмки; этап12.5 открыт. +Актуальное продолжение 2026-10-15: 261 — оставшиеся12 default screens проверены; контраст recursive/ECMP исправлен, KPI BGP/GRE/VXLAN/OSPF используют штатную адаптивную сетку.64 unique live states261, вместе с260 —120 состояний30 экранов с явным происхождением;40 локальных E2E приняты. Runtime backend258/frontend261. Следующий262 — оформить десятиминутный REST-протокол, затем263 — итоговая UI-регрессия; этап12.5 открыт. | Параметр | Проверенное состояние | | --- | --- | @@ -14,7 +14,7 @@ | PostgreSQL | postgres:18-alpine,18.6;mmapp-test-postgres healthy;SELECT version() passed | | Данные | Отдельный Compose project mmapp-test,том mmapp-test_mmapp-pgdata и каталог ./data | | Доступ | PostgreSQL не публикуется;frontend3000/backend8000 привязаны к127.0.0.1 после запуска | -| Образы приложения | mmapp-test/backend:ux-certificates258 и mmapp-test/frontend:ux-accessibility260-final; backend healthy, проверено2026-10-15 в260 | +| Образы приложения | mmapp-test/backend:ux-certificates258 и mmapp-test/frontend:ux-accessibility261; backend healthy, проверено2026-10-15 в261 | Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example). @@ -34,6 +34,25 @@ OSPF84: real GRE Full adjacency/private routes/Ping3/3 и4 live PC/320px URL/bro BFD85: Up/Full/own route/Ping,4 live UI и loss/withdrawal/recovery приняты; exact own cleanup8 коллекций×2 passed. Withdrawal5,245ms не подтверждает sub-second BFD timing; этот вопрос открыт. Runtime routing83 без новой сборки. [Отчёт](ux-ui/acceptance/ospf-bfd-loss-2026-10-09.json). +## 12.5.261 — оставшиеся экраны и читаемые сетевые KPI (2026-10-15) + +Baseline12 разделов×2 темы×1440/320px:48 live states, API/JS errors0, overflow0, blocked configuration writes0. Четыре проверенных read-only POST OSPF preview разрешены явно после чтения backend handler; они рассчитывают план, не применяют его. DNS resolution/catalog read POST также имеют точный opt-in allowlist в helper, остальные non-GET блокируются. EvoBGP на стенде disabled, URL/key не настроены: каталоги здесь проверены в состоянии недоступной интеграции, не с реальными внешними данными. [Baseline](ux-ui/acceptance/ui261-baseline.json). + +Найдены4 contrast failures: TypeChip в recursive-routes/light и opacity40 всего ECMP disabled section в двух темах на ПК. TypeChip теперь использует установленный ReUI Badge light variants/text-foreground; ECMP — native disabled fieldset с legend вместо затемнения всех подписей. Профильная проверка включённого ECMP дополнительно выявила неконтрастный выбранный алгоритм; применены bg-muted/text-foreground. Это локальный черновик, серверная логика/применение маршрутов не менялись. + +В BGP summary/analytics, GRE, VXLAN, OSPF summary/BFD убраны принудительные grid-cols2 overrides. Используются существующие kpiCols и неизменённые Frame/IconTile/KpiStatGrid: одна колонка в узком контейнере, прежние container breakpoints для плотного ПК. Шрифты, vendor компоненты, theme tokens и motion не менялись. В8 локальных geometry cases320/390 и16 live theme/width cases подписи KPI не обрезаются; explicit краткая подпись Интерф. в OSPF сохранена. [Снимок BGP320 после загрузки](ux-ui/screens/ui261-bgp320.png). + +Локальные40 E2E: initial38 passed/2 ECMP contrast failed; после исправления повторены только2 affected cases, passed. Geometry8, ECMP2, recursive route contracts15 и OSPF preview15; разные прогоны не суммируются в42 сценария. ECMP false/true проверены также в настоящем Portal/UI, scoped axe0, configuration writes0, draft возвращён off. [Live ECMP](ux-ui/acceptance/ui261-live-ecmp.json). TypeScript/scoped ESLint и production build passed, source528 matched. Runtime frontend ux-accessibility261, backend ux-certificates258 healthy; DB/Portal сохранены. [Deployment](ux-ui/acceptance/ui261-deployment.json). + +Повторены только6 изменённых страниц:24 live states, WCAG-tagged axe violations0/API/JS errors0/overflow0/clipped KPI labels0. С48 baseline это64 уникальных состояния16 страниц: восемь состояний двух изменённых baseline страниц заменены повторными, четыре сетевых KPI страницы добавлены. Вместе с260 —120 уникальных default states30 экранов. Report сохраняет фазу/образ каждого результата: остальные страницы не выданы за повторный прогон на261. [Changed24](ux-ui/acceptance/ui261-changed-final.json), [итог64](ux-ui/acceptance/ui261-final.json), [сводка](ux-ui/acceptance/ui261-summary.json). Во время части замеров шла контролируемая REST-нагрузка262, timings не field CWV и не сравнение независимых бенчмарков. + +| Референс | Схема доработки | +| --- | --- | +| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Узкий контейнер → штатная одна колонка → полная подпись KPI; [BGP320](ux-ui/screens/ui261-bgp320.png) | +| [ReUI Badge](https://reui.io/components/badge?ref=mcp) | Тип устройства → light variant → semantic foreground | + +Настройки CHR, DNS, сертификаты и пароли не менялись. Native/background telemetry reads не объявляются отсутствием всех DB metadata updates. Manual screen-reader, новые клиентские ОС/WAN/public IPv6/multi-instance, CA revoke и внешняя публикация остаются отдельными границами. Следующий262 — оформить завершённый ten-minute REST soak, затем263 — актуальная полная локальная UI-регрессия после общего keyboard adapter260 и responsive delta261. Автоматическая120-state проверка не заменяет сквозные сценарии и не является полной WCAG сертификацией. + ## 12.5.260 — общая доступность, клавиатура и профильная скорость UI (2026-10-15) Проведён read-only аудит18 разделов×2 темы×1440/320px:72 состояния текущего стенда/настоящий Portal UI, без response mocks. Baseline выявил23 состояния с axe нарушениями: недостаточная независимая pointer-area у focusable listbox-контейнера, контраст счётчиков SegmentedControl, raw type badges серверов и заголовка VXLAN. Document overflow0/JS errors0/API errors0; BGP320 имел2 внутренних переполнения main. Dashboard в baseline ещё содержал skeleton: его ранние timings не считаются готовностью данных. [Baseline](ux-ui/acceptance/ui260-baseline.json). diff --git a/docs/ux-ui/acceptance/ui261-baseline.json b/docs/ux-ui/acceptance/ui261-baseline.json new file mode 100644 index 00000000..1b551e91 --- /dev/null +++ b/docs/ux-ui/acceptance/ui261-baseline.json @@ -0,0 +1,1484 @@ +{ + "date": "2026-10-15", + "block": "12.5.261", + "origin": "http://localhost:3000", + "realPortalUiLogin": true, + "responseMocks": false, + "deviceWritesPermitted": false, + "cases": [ + { + "theme": "dark", + "path": "/users", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1180, + "requiredReads": [ + "/api/users" + ], + "dataResponsesMs": 1180, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 1052, + "cls": 0.06308886127989968, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 110.90000000223517, + "domInteractiveMs": 349.8999999985099, + "durationMs": 666.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/users", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 533, + "requiredReads": [ + "/api/users" + ], + "dataResponsesMs": 635, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 576, + "cls": 0.096197790625, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 54.30000000074506, + "domInteractiveMs": 207.39999999850988, + "durationMs": 415.30000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/firewall", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 551, + "requiredReads": [ + "/api/firewall/all" + ], + "dataResponsesMs": 1414, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 540, + "cls": 0.09758711492091049, + "longTasks": 1, + "longTaskMs": 57, + "ttfbMs": 90.69999999925494, + "domInteractiveMs": 224, + "durationMs": 445.3999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/firewall", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 562, + "requiredReads": [ + "/api/firewall/all" + ], + "dataResponsesMs": 1424, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 512, + "cls": 0.238883196875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 95, + "domInteractiveMs": 246.89999999850988, + "durationMs": 462.79999999701977 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/probes", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 640, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 993, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 808, + "cls": 0.000031850405092592596, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 111.19999999925494, + "domInteractiveMs": 296, + "durationMs": 557.6999999992549 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/probes", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 540, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 908, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 712, + "cls": 0.00012464375, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 52.30000000074506, + "domInteractiveMs": 219.70000000298023, + "durationMs": 446.1000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/terminal", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 587, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 937, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 576, + "cls": 0.001050857536410108, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 106.5, + "domInteractiveMs": 275.80000000074506, + "durationMs": 475.30000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/terminal", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 456, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 561, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 400, + "cls": 0.004743726171875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53.600000001490116, + "domInteractiveMs": 138.30000000074506, + "durationMs": 318.19999999925494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/data-collection", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 621, + "requiredReads": [ + "/api/scheduler/status" + ], + "dataResponsesMs": 2484, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 600, + "cls": 0.030615641939139664, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 135.5, + "domInteractiveMs": 284.1000000014901, + "durationMs": 507.80000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/data-collection", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 973, + "requiredReads": [ + "/api/scheduler/status" + ], + "dataResponsesMs": 2860, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 608, + "cls": 0.086460921875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 54.400000002235174, + "domInteractiveMs": 142.5, + "durationMs": 514.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/recursive-routes", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 546, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 656, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 528, + "cls": 0.050287300588348766, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 59.600000001490116, + "domInteractiveMs": 230.10000000149012, + "durationMs": 450.1000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/recursive-routes", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 502, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 606, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 456, + "cls": 0.08047623984374999, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 50.19999999925494, + "domInteractiveMs": 187.29999999701977, + "durationMs": 397.5 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/route-optimizer", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 502, + "requiredReads": [ + "/api/servers", + "/api/filters/rules", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 874, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 504, + "cls": 0.633712198591821, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 51.100000001490116, + "domInteractiveMs": 188.09999999776483, + "durationMs": 397.5999999977648 + }, + "violations": [ + { + "id": "color-contrast", + "impact": "serious", + "targets": [ + [ + ".transition-opacity > .gap-1\\.5.flex-col.flex:nth-child(1) > .text-xs.text-muted-foreground" + ] + ] + } + ], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/route-optimizer", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 517, + "requiredReads": [ + "/api/servers", + "/api/filters/rules", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 875, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 752, + "cls": 0.06558586474609375, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 56.30000000074506, + "domInteractiveMs": 201.19999999925494, + "durationMs": 410.69999999925494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/network-map", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 548, + "requiredReads": [ + "/api/servers", + "/api/filters/gre-tunnels", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 913, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 760, + "cls": 0.4066049501470872, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 97.30000000074506, + "domInteractiveMs": 229.89999999850988, + "durationMs": 423.80000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/network-map", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 450, + "requiredReads": [ + "/api/servers", + "/api/filters/gre-tunnels", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 816, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 748, + "cls": 0.2168260953125, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 50.600000001490116, + "domInteractiveMs": 135.80000000074506, + "durationMs": 320 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/ip-ranges", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1085, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 1085, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 668, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 96.90000000223517, + "domInteractiveMs": 255.09999999776483, + "durationMs": 594.1999999992549 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/ip-ranges", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1010, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 1010, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 628, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 72.69999999925494, + "domInteractiveMs": 187, + "durationMs": 543.5 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/asns", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 504, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 870, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 488, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53.80000000074506, + "domInteractiveMs": 187.80000000074506, + "durationMs": 383.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/asns", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 453, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 814, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 380, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 54.19999999925494, + "domInteractiveMs": 139.10000000149012, + "durationMs": 325.19999999925494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/communities", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 497, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 851, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 464, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53.600000001490116, + "domInteractiveMs": 188.39999999850988, + "durationMs": 373.5999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/communities", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 484, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 848, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 420, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 52.20000000298023, + "domInteractiveMs": 166.39999999850988, + "durationMs": 343.3999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/domains", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1065, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 1065, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 568, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 54.80000000074506, + "domInteractiveMs": 218.80000000074506, + "durationMs": 473.6000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/domains", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 484, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 588, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 404, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 55.19999999925494, + "domInteractiveMs": 167.79999999701977, + "durationMs": 352.69999999925494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/users", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 625, + "requiredReads": [ + "/api/users" + ], + "dataResponsesMs": 978, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 748, + "cls": 0.06308886127989968, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 95.69999999925494, + "domInteractiveMs": 286.8999999985099, + "durationMs": 494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/users", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 521, + "requiredReads": [ + "/api/users" + ], + "dataResponsesMs": 624, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 544, + "cls": 0.096197790625, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 97.89999999850988, + "domInteractiveMs": 190.89999999850988, + "durationMs": 404.8999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/firewall", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 530, + "requiredReads": [ + "/api/firewall/all" + ], + "dataResponsesMs": 1387, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 512, + "cls": 0.09758711492091049, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 88.59999999776483, + "domInteractiveMs": 217.90000000223517, + "durationMs": 437.5 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/firewall", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 533, + "requiredReads": [ + "/api/firewall/all" + ], + "dataResponsesMs": 1406, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 476, + "cls": 0.238883196875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 55.69999999925494, + "domInteractiveMs": 203.40000000223517, + "durationMs": 418.1000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/probes", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 561, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 926, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 724, + "cls": 0.000031850405092592596, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 97.79999999701977, + "domInteractiveMs": 238.39999999850988, + "durationMs": 532.8999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/probes", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 503, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 616, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 576, + "cls": 0.00012464375, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 50.100000001490116, + "domInteractiveMs": 186.59999999776483, + "durationMs": 403.29999999701977 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/terminal", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 567, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 923, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 540, + "cls": 0.0009951179711612656, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 99, + "domInteractiveMs": 248.59999999776483, + "durationMs": 437 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/terminal", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 526, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 637, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 520, + "cls": 0.004671481640625, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 58.600000001490116, + "domInteractiveMs": 211.59999999776483, + "durationMs": 500.5 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/data-collection", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 570, + "requiredReads": [ + "/api/scheduler/status" + ], + "dataResponsesMs": 1440, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 544, + "cls": 0.030611679808063275, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 93, + "domInteractiveMs": 222.39999999850988, + "durationMs": 437.79999999701977 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/data-collection", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 514, + "requiredReads": [ + "/api/scheduler/status" + ], + "dataResponsesMs": 2392, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 500, + "cls": 0.086460921875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 49.5, + "domInteractiveMs": 180.19999999925494, + "durationMs": 391.3999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/recursive-routes", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 607, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 707, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 560, + "cls": 0.050287300588348766, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53.29999999701977, + "domInteractiveMs": 288.30000000074506, + "durationMs": 492.8999999985099 + }, + "violations": [ + { + "id": "color-contrast", + "impact": "serious", + "targets": [ + [ + ".rounded" + ] + ] + } + ], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/recursive-routes", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 556, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 665, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 500, + "cls": 0.08047623984374999, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 71.89999999850988, + "domInteractiveMs": 235.59999999776483, + "durationMs": 439.3999999985099 + }, + "violations": [ + { + "id": "color-contrast", + "impact": "serious", + "targets": [ + [ + ".rounded" + ] + ] + } + ], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/route-optimizer", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 553, + "requiredReads": [ + "/api/servers", + "/api/filters/rules", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 903, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 552, + "cls": 0.633712198591821, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 52.69999999925494, + "domInteractiveMs": 185.80000000074506, + "durationMs": 438.20000000298023 + }, + "violations": [ + { + "id": "color-contrast", + "impact": "serious", + "targets": [ + [ + ".transition-opacity > .gap-1\\.5.flex-col.flex:nth-child(1) > .text-xs.text-muted-foreground" + ] + ] + } + ], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/route-optimizer", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 512, + "requiredReads": [ + "/api/servers", + "/api/filters/rules", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 868, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 716, + "cls": 0.06558586474609375, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 58.599999997764826, + "domInteractiveMs": 199, + "durationMs": 409.30000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/network-map", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 589, + "requiredReads": [ + "/api/servers", + "/api/filters/gre-tunnels", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 948, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 808, + "cls": 0.41068980028694063, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 97.5, + "domInteractiveMs": 236.89999999850988, + "durationMs": 464.6000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/network-map", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1052, + "requiredReads": [ + "/api/servers", + "/api/filters/gre-tunnels", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 1052, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 796, + "cls": 0.2168260953125, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 51.80000000074506, + "domInteractiveMs": 210.09999999776483, + "durationMs": 486 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/ip-ranges", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1108, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 1109, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 640, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 109.69999999925494, + "domInteractiveMs": 290.30000000074506, + "durationMs": 540.6000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/ip-ranges", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 448, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 802, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 396, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 50.599999997764826, + "domInteractiveMs": 135.69999999925494, + "durationMs": 316.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/asns", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 625, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 975, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 572, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 55.399999998509884, + "domInteractiveMs": 301.6000000014901, + "durationMs": 489.1000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/asns", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 464, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 571, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 380, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 61.30000000074506, + "domInteractiveMs": 151.30000000074506, + "durationMs": 333.3999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/communities", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 516, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 887, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 504, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 52.900000002235174, + "domInteractiveMs": 185, + "durationMs": 463.5 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/communities", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 450, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 818, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 416, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53.5, + "domInteractiveMs": 137.89999999850988, + "durationMs": 330.1000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/domains", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 499, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 859, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 448, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 52.600000001490116, + "domInteractiveMs": 183, + "durationMs": 384.19999999925494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/domains", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 589, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 948, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 588, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 72.90000000223517, + "domInteractiveMs": 264.6000000014901, + "durationMs": 520 + }, + "violations": [], + "auditCompleted": true + } + ], + "blockedWrites": [], + "readOnlyPosts": [ + { + "theme": "dark", + "path": "/api/servers/1/ospf/optimize/preview" + }, + { + "theme": "dark", + "path": "/api/servers/1/ospf/optimize/preview" + }, + { + "theme": "light", + "path": "/api/servers/1/ospf/optimize/preview" + }, + { + "theme": "light", + "path": "/api/servers/1/ospf/optimize/preview" + } + ], + "bounds": [ + "Default rendered state only; data can still be refreshing", + "Synthetic lab timing over SSH, no CPU/network throttling; not field Core Web Vitals", + "Automated WCAG-tagged checks, not manual screen-reader certification" + ], + "completed": true, + "passed": false +} diff --git a/docs/ux-ui/acceptance/ui261-changed-final.json b/docs/ux-ui/acceptance/ui261-changed-final.json new file mode 100644 index 00000000..53b9b46a --- /dev/null +++ b/docs/ux-ui/acceptance/ui261-changed-final.json @@ -0,0 +1,1088 @@ +{ + "date": "2026-10-15", + "block": "12.5.261", + "origin": "http://localhost:3000", + "realPortalUiLogin": true, + "responseMocks": false, + "deviceWritesPermitted": false, + "cases": [ + { + "theme": "dark", + "path": "/recursive-routes", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1174, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 1175, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [], + "performance": { + "lcp": 816, + "cls": 0.050188635947145066, + "longTasks": 2, + "longTaskMs": 136, + "ttfbMs": 124.80000000074506, + "domInteractiveMs": 331.0999999977648, + "durationMs": 618.8999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/recursive-routes", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 564, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 666, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [], + "performance": { + "lcp": 516, + "cls": 0.08016383984375, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 55.20000000298023, + "domInteractiveMs": 213.30000000074506, + "durationMs": 423.80000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/route-optimizer", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 639, + "requiredReads": [ + "/api/servers", + "/api/filters/rules", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 991, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Home", + "clipped": false + }, + { + "label": "JumpHost", + "clipped": false + }, + { + "label": "Exit Node", + "clipped": false + }, + { + "label": "Рекомендации", + "clipped": false + } + ], + "performance": { + "lcp": 680, + "cls": 0.633712198591821, + "longTasks": 2, + "longTaskMs": 140, + "ttfbMs": 54.900000002235174, + "domInteractiveMs": 223.90000000223517, + "durationMs": 451.6000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/route-optimizer", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1110, + "requiredReads": [ + "/api/servers", + "/api/filters/rules", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 1111, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Home", + "clipped": false + }, + { + "label": "JumpHost", + "clipped": false + }, + { + "label": "Exit Node", + "clipped": false + }, + { + "label": "Рекомендации", + "clipped": false + } + ], + "performance": { + "lcp": 1080, + "cls": 0.06558586474609375, + "longTasks": 1, + "longTaskMs": 69, + "ttfbMs": 59.399999998509884, + "domInteractiveMs": 246.19999999925494, + "durationMs": 559.2999999970198 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/bgp", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 633, + "requiredReads": [ + "/api/bgp/catalog" + ], + "dataResponsesMs": 988, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Сессий", + "clipped": false + }, + { + "label": "Established", + "clipped": false + }, + { + "label": "Не поднято", + "clipped": false + }, + { + "label": "Префиксы", + "clipped": false + } + ], + "performance": { + "lcp": 648, + "cls": 0.008227206790123459, + "longTasks": 1, + "longTaskMs": 87, + "ttfbMs": 121.89999999850988, + "domInteractiveMs": 273.4000000022352, + "durationMs": 505.4000000022352 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/bgp", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 606, + "requiredReads": [ + "/api/bgp/catalog" + ], + "dataResponsesMs": 969, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Сессий", + "clipped": false + }, + { + "label": "Established", + "clipped": false + }, + { + "label": "Не поднято", + "clipped": false + }, + { + "label": "Префиксы", + "clipped": false + } + ], + "performance": { + "lcp": 808, + "cls": 0.045822321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 75.80000000074506, + "domInteractiveMs": 267.9000000022352, + "durationMs": 538 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/gre", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 652, + "requiredReads": [ + "/api/gre/tunnels" + ], + "dataResponsesMs": 1009, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активно", + "clipped": false + }, + { + "label": "С IPsec", + "clipped": false + }, + { + "label": "IP-пулов", + "clipped": false + } + ], + "performance": { + "lcp": 644, + "cls": 0.06258026005497686, + "longTasks": 1, + "longTaskMs": 67, + "ttfbMs": 96, + "domInteractiveMs": 303.19999999925494, + "durationMs": 533.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/gre", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 507, + "requiredReads": [ + "/api/gre/tunnels" + ], + "dataResponsesMs": 872, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активно", + "clipped": false + }, + { + "label": "С IPsec", + "clipped": false + }, + { + "label": "IP-пулов", + "clipped": false + } + ], + "performance": { + "lcp": 512, + "cls": 0.107477715625, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 56.19999999925494, + "domInteractiveMs": 171.30000000074506, + "durationMs": 402.80000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/vxlan", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 655, + "requiredReads": [ + "/api/vxlan" + ], + "dataResponsesMs": 2509, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активных", + "clipped": false + }, + { + "label": "VNI", + "clipped": false + }, + { + "label": "Серверов", + "clipped": false + } + ], + "performance": { + "lcp": 680, + "cls": 0.12087190923996914, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 98.10000000149012, + "domInteractiveMs": 248.19999999925494, + "durationMs": 738.3999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/vxlan", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1107, + "requiredReads": [ + "/api/vxlan" + ], + "dataResponsesMs": 1108, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активных", + "clipped": false + }, + { + "label": "VNI", + "clipped": false + }, + { + "label": "Серверов", + "clipped": false + } + ], + "performance": { + "lcp": 712, + "cls": 0.24378085312499997, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 56.5, + "domInteractiveMs": 240, + "durationMs": 613.8000000007451 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/ospf", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 732, + "requiredReads": [ + "/api/ospf/catalog" + ], + "dataResponsesMs": 1608, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Роутеры", + "clipped": false + }, + { + "label": "Интерф.", + "clipped": false + }, + { + "label": "Зоны", + "clipped": false + } + ], + "performance": { + "lcp": 728, + "cls": 0.0054106802782600315, + "longTasks": 1, + "longTaskMs": 118, + "ttfbMs": 108.29999999701977, + "domInteractiveMs": 274.5999999977648, + "durationMs": 561.5 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/ospf", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 499, + "requiredReads": [ + "/api/ospf/catalog" + ], + "dataResponsesMs": 1366, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Роутеры", + "clipped": false + }, + { + "label": "Интерф.", + "clipped": false + }, + { + "label": "Зоны", + "clipped": false + } + ], + "performance": { + "lcp": 492, + "cls": 0.027072464453125004, + "longTasks": 1, + "longTaskMs": 79, + "ttfbMs": 50.899999998509884, + "domInteractiveMs": 159.30000000074506, + "durationMs": 384.19999999925494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/recursive-routes", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1176, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 1176, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [], + "performance": { + "lcp": 736, + "cls": 0.05015528669945988, + "longTasks": 1, + "longTaskMs": 60, + "ttfbMs": 97.59999999776483, + "domInteractiveMs": 331.80000000074506, + "durationMs": 620.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/recursive-routes", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 553, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 663, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [], + "performance": { + "lcp": 492, + "cls": 0.08016383984375, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53, + "domInteractiveMs": 215.10000000149012, + "durationMs": 417 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/route-optimizer", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 633, + "requiredReads": [ + "/api/servers", + "/api/filters/rules", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 986, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Home", + "clipped": false + }, + { + "label": "JumpHost", + "clipped": false + }, + { + "label": "Exit Node", + "clipped": false + }, + { + "label": "Рекомендации", + "clipped": false + } + ], + "performance": { + "lcp": 628, + "cls": 0.633712198591821, + "longTasks": 2, + "longTaskMs": 131, + "ttfbMs": 57.399999998509884, + "domInteractiveMs": 243, + "durationMs": 458.80000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/route-optimizer", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 629, + "requiredReads": [ + "/api/servers", + "/api/filters/rules", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 984, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Home", + "clipped": false + }, + { + "label": "JumpHost", + "clipped": false + }, + { + "label": "Exit Node", + "clipped": false + }, + { + "label": "Рекомендации", + "clipped": false + } + ], + "performance": { + "lcp": 920, + "cls": 0.06558586474609375, + "longTasks": 1, + "longTaskMs": 64, + "ttfbMs": 51.899999998509884, + "domInteractiveMs": 204.20000000298023, + "durationMs": 464 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/bgp", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 763, + "requiredReads": [ + "/api/bgp/catalog" + ], + "dataResponsesMs": 2622, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Сессий", + "clipped": false + }, + { + "label": "Established", + "clipped": false + }, + { + "label": "Не поднято", + "clipped": false + }, + { + "label": "Префиксы", + "clipped": false + } + ], + "performance": { + "lcp": 752, + "cls": 0.008227206790123459, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 103.5, + "domInteractiveMs": 299.80000000074506, + "durationMs": 649.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/bgp", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 555, + "requiredReads": [ + "/api/bgp/catalog" + ], + "dataResponsesMs": 918, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Сессий", + "clipped": false + }, + { + "label": "Established", + "clipped": false + }, + { + "label": "Не поднято", + "clipped": false + }, + { + "label": "Префиксы", + "clipped": false + } + ], + "performance": { + "lcp": 764, + "cls": 0.045822321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53, + "domInteractiveMs": 223.89999999850988, + "durationMs": 501.69999999925494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/gre", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 631, + "requiredReads": [ + "/api/gre/tunnels" + ], + "dataResponsesMs": 997, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активно", + "clipped": false + }, + { + "label": "С IPsec", + "clipped": false + }, + { + "label": "IP-пулов", + "clipped": false + } + ], + "performance": { + "lcp": 608, + "cls": 0.06258026005497686, + "longTasks": 1, + "longTaskMs": 69, + "ttfbMs": 104.40000000223517, + "domInteractiveMs": 267.80000000074506, + "durationMs": 494.8999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/gre", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 521, + "requiredReads": [ + "/api/gre/tunnels" + ], + "dataResponsesMs": 887, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активно", + "clipped": false + }, + { + "label": "С IPsec", + "clipped": false + }, + { + "label": "IP-пулов", + "clipped": false + } + ], + "performance": { + "lcp": 476, + "cls": 0.107477715625, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53.30000000074506, + "domInteractiveMs": 175.60000000149012, + "durationMs": 396.4000000022352 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/vxlan", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 635, + "requiredReads": [ + "/api/vxlan" + ], + "dataResponsesMs": 2502, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активных", + "clipped": false + }, + { + "label": "VNI", + "clipped": false + }, + { + "label": "Серверов", + "clipped": false + } + ], + "performance": { + "lcp": 620, + "cls": 0.12087190923996914, + "longTasks": 1, + "longTaskMs": 71, + "ttfbMs": 117.60000000149012, + "domInteractiveMs": 285, + "durationMs": 509.6000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/vxlan", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 557, + "requiredReads": [ + "/api/vxlan" + ], + "dataResponsesMs": 922, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активных", + "clipped": false + }, + { + "label": "VNI", + "clipped": false + }, + { + "label": "Серверов", + "clipped": false + } + ], + "performance": { + "lcp": 548, + "cls": 0.24378085312499997, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 52.19999999925494, + "domInteractiveMs": 227, + "durationMs": 437.1000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/ospf", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1221, + "requiredReads": [ + "/api/ospf/catalog" + ], + "dataResponsesMs": 1221, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Роутеры", + "clipped": false + }, + { + "label": "Интерф.", + "clipped": false + }, + { + "label": "Зоны", + "clipped": false + } + ], + "performance": { + "lcp": 712, + "cls": 0.0054106802782600315, + "longTasks": 1, + "longTaskMs": 109, + "ttfbMs": 95.10000000149012, + "domInteractiveMs": 246.19999999925494, + "durationMs": 543.6000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/ospf", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 509, + "requiredReads": [ + "/api/ospf/catalog" + ], + "dataResponsesMs": 1395, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Роутеры", + "clipped": false + }, + { + "label": "Интерф.", + "clipped": false + }, + { + "label": "Зоны", + "clipped": false + } + ], + "performance": { + "lcp": 500, + "cls": 0.027072464453125004, + "longTasks": 1, + "longTaskMs": 66, + "ttfbMs": 54.399999998509884, + "domInteractiveMs": 160.19999999925494, + "durationMs": 409.5 + }, + "violations": [], + "auditCompleted": true + } + ], + "blockedWrites": [], + "readOnlyPosts": [ + { + "theme": "dark", + "path": "/api/servers/1/ospf/optimize/preview" + }, + { + "theme": "dark", + "path": "/api/servers/1/ospf/optimize/preview" + }, + { + "theme": "light", + "path": "/api/servers/1/ospf/optimize/preview" + }, + { + "theme": "light", + "path": "/api/servers/1/ospf/optimize/preview" + } + ], + "bounds": [ + "Default rendered state only; data can still be refreshing", + "Synthetic lab timing over SSH, no CPU/network throttling; not field Core Web Vitals", + "Automated WCAG-tagged checks, not manual screen-reader certification" + ], + "completed": true, + "passed": true +} diff --git a/docs/ux-ui/acceptance/ui261-deployment.json b/docs/ux-ui/acceptance/ui261-deployment.json new file mode 100644 index 00000000..98982956 --- /dev/null +++ b/docs/ux-ui/acceptance/ui261-deployment.json @@ -0,0 +1,20 @@ +{ + "date": "2026-10-15", + "block": "12.5.261", + "productionBuild": "passed", + "sourceMatched": 528, + "services": { + "backend": { + "image": "mmapp-test/backend:ux-certificates258", + "imageId": "sha256:984684709e619972bae8657208e3150d12f1c4f2b5642fecf5286a5d87e9fe71", + "status": "running", + "health": "healthy" + }, + "frontend": { + "image": "mmapp-test/frontend:ux-accessibility261", + "imageId": "sha256:eec4410f1109a46e864d695cfb4311fdb9a224247549c8a9e78e44c6c0e455cb", + "status": "running", + "health": null + } + } +} \ No newline at end of file diff --git a/docs/ux-ui/acceptance/ui261-final.json b/docs/ux-ui/acceptance/ui261-final.json new file mode 100644 index 00000000..bf8246c9 --- /dev/null +++ b/docs/ux-ui/acceptance/ui261-final.json @@ -0,0 +1,2286 @@ +{ + "date": "2026-10-15", + "block": "12.5.261", + "origin": "http://localhost:3000", + "realPortalUiLogin": true, + "responseMocks": false, + "deviceWritesPermitted": false, + "cases": [ + { + "theme": "dark", + "path": "/users", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1180, + "requiredReads": [ + "/api/users" + ], + "dataResponsesMs": 1180, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 1052, + "cls": 0.06308886127989968, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 110.90000000223517, + "domInteractiveMs": 349.8999999985099, + "durationMs": 666.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/users", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 533, + "requiredReads": [ + "/api/users" + ], + "dataResponsesMs": 635, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 576, + "cls": 0.096197790625, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 54.30000000074506, + "domInteractiveMs": 207.39999999850988, + "durationMs": 415.30000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/firewall", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 551, + "requiredReads": [ + "/api/firewall/all" + ], + "dataResponsesMs": 1414, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 540, + "cls": 0.09758711492091049, + "longTasks": 1, + "longTaskMs": 57, + "ttfbMs": 90.69999999925494, + "domInteractiveMs": 224, + "durationMs": 445.3999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/firewall", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 562, + "requiredReads": [ + "/api/firewall/all" + ], + "dataResponsesMs": 1424, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 512, + "cls": 0.238883196875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 95, + "domInteractiveMs": 246.89999999850988, + "durationMs": 462.79999999701977 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/probes", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 640, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 993, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 808, + "cls": 0.000031850405092592596, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 111.19999999925494, + "domInteractiveMs": 296, + "durationMs": 557.6999999992549 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/probes", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 540, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 908, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 712, + "cls": 0.00012464375, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 52.30000000074506, + "domInteractiveMs": 219.70000000298023, + "durationMs": 446.1000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/terminal", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 587, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 937, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 576, + "cls": 0.001050857536410108, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 106.5, + "domInteractiveMs": 275.80000000074506, + "durationMs": 475.30000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/terminal", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 456, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 561, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 400, + "cls": 0.004743726171875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53.600000001490116, + "domInteractiveMs": 138.30000000074506, + "durationMs": 318.19999999925494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/data-collection", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 621, + "requiredReads": [ + "/api/scheduler/status" + ], + "dataResponsesMs": 2484, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 600, + "cls": 0.030615641939139664, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 135.5, + "domInteractiveMs": 284.1000000014901, + "durationMs": 507.80000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/data-collection", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 973, + "requiredReads": [ + "/api/scheduler/status" + ], + "dataResponsesMs": 2860, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 608, + "cls": 0.086460921875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 54.400000002235174, + "domInteractiveMs": 142.5, + "durationMs": 514.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/recursive-routes", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1174, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 1175, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [], + "performance": { + "lcp": 816, + "cls": 0.050188635947145066, + "longTasks": 2, + "longTaskMs": 136, + "ttfbMs": 124.80000000074506, + "domInteractiveMs": 331.0999999977648, + "durationMs": 618.8999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/recursive-routes", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 564, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 666, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [], + "performance": { + "lcp": 516, + "cls": 0.08016383984375, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 55.20000000298023, + "domInteractiveMs": 213.30000000074506, + "durationMs": 423.80000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/route-optimizer", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 639, + "requiredReads": [ + "/api/servers", + "/api/filters/rules", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 991, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Home", + "clipped": false + }, + { + "label": "JumpHost", + "clipped": false + }, + { + "label": "Exit Node", + "clipped": false + }, + { + "label": "Рекомендации", + "clipped": false + } + ], + "performance": { + "lcp": 680, + "cls": 0.633712198591821, + "longTasks": 2, + "longTaskMs": 140, + "ttfbMs": 54.900000002235174, + "domInteractiveMs": 223.90000000223517, + "durationMs": 451.6000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/route-optimizer", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1110, + "requiredReads": [ + "/api/servers", + "/api/filters/rules", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 1111, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Home", + "clipped": false + }, + { + "label": "JumpHost", + "clipped": false + }, + { + "label": "Exit Node", + "clipped": false + }, + { + "label": "Рекомендации", + "clipped": false + } + ], + "performance": { + "lcp": 1080, + "cls": 0.06558586474609375, + "longTasks": 1, + "longTaskMs": 69, + "ttfbMs": 59.399999998509884, + "domInteractiveMs": 246.19999999925494, + "durationMs": 559.2999999970198 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/network-map", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 548, + "requiredReads": [ + "/api/servers", + "/api/filters/gre-tunnels", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 913, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 760, + "cls": 0.4066049501470872, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 97.30000000074506, + "domInteractiveMs": 229.89999999850988, + "durationMs": 423.80000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/network-map", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 450, + "requiredReads": [ + "/api/servers", + "/api/filters/gre-tunnels", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 816, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 748, + "cls": 0.2168260953125, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 50.600000001490116, + "domInteractiveMs": 135.80000000074506, + "durationMs": 320 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/ip-ranges", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1085, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 1085, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 668, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 96.90000000223517, + "domInteractiveMs": 255.09999999776483, + "durationMs": 594.1999999992549 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/ip-ranges", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1010, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 1010, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 628, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 72.69999999925494, + "domInteractiveMs": 187, + "durationMs": 543.5 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/asns", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 504, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 870, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 488, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53.80000000074506, + "domInteractiveMs": 187.80000000074506, + "durationMs": 383.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/asns", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 453, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 814, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 380, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 54.19999999925494, + "domInteractiveMs": 139.10000000149012, + "durationMs": 325.19999999925494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/communities", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 497, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 851, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 464, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53.600000001490116, + "domInteractiveMs": 188.39999999850988, + "durationMs": 373.5999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/communities", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 484, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 848, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 420, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 52.20000000298023, + "domInteractiveMs": 166.39999999850988, + "durationMs": 343.3999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/domains", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1065, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 1065, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 568, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 54.80000000074506, + "domInteractiveMs": 218.80000000074506, + "durationMs": 473.6000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/domains", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 484, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 588, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 404, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 55.19999999925494, + "domInteractiveMs": 167.79999999701977, + "durationMs": 352.69999999925494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/users", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 625, + "requiredReads": [ + "/api/users" + ], + "dataResponsesMs": 978, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 748, + "cls": 0.06308886127989968, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 95.69999999925494, + "domInteractiveMs": 286.8999999985099, + "durationMs": 494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/users", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 521, + "requiredReads": [ + "/api/users" + ], + "dataResponsesMs": 624, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 544, + "cls": 0.096197790625, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 97.89999999850988, + "domInteractiveMs": 190.89999999850988, + "durationMs": 404.8999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/firewall", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 530, + "requiredReads": [ + "/api/firewall/all" + ], + "dataResponsesMs": 1387, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 512, + "cls": 0.09758711492091049, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 88.59999999776483, + "domInteractiveMs": 217.90000000223517, + "durationMs": 437.5 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/firewall", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 533, + "requiredReads": [ + "/api/firewall/all" + ], + "dataResponsesMs": 1406, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 476, + "cls": 0.238883196875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 55.69999999925494, + "domInteractiveMs": 203.40000000223517, + "durationMs": 418.1000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/probes", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 561, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 926, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 724, + "cls": 0.000031850405092592596, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 97.79999999701977, + "domInteractiveMs": 238.39999999850988, + "durationMs": 532.8999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/probes", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 503, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 616, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 576, + "cls": 0.00012464375, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 50.100000001490116, + "domInteractiveMs": 186.59999999776483, + "durationMs": 403.29999999701977 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/terminal", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 567, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 923, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 540, + "cls": 0.0009951179711612656, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 99, + "domInteractiveMs": 248.59999999776483, + "durationMs": 437 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/terminal", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 526, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 637, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 520, + "cls": 0.004671481640625, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 58.600000001490116, + "domInteractiveMs": 211.59999999776483, + "durationMs": 500.5 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/data-collection", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 570, + "requiredReads": [ + "/api/scheduler/status" + ], + "dataResponsesMs": 1440, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 544, + "cls": 0.030611679808063275, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 93, + "domInteractiveMs": 222.39999999850988, + "durationMs": 437.79999999701977 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/data-collection", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 514, + "requiredReads": [ + "/api/scheduler/status" + ], + "dataResponsesMs": 2392, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 500, + "cls": 0.086460921875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 49.5, + "domInteractiveMs": 180.19999999925494, + "durationMs": 391.3999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/recursive-routes", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1176, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 1176, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [], + "performance": { + "lcp": 736, + "cls": 0.05015528669945988, + "longTasks": 1, + "longTaskMs": 60, + "ttfbMs": 97.59999999776483, + "domInteractiveMs": 331.80000000074506, + "durationMs": 620.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/recursive-routes", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 553, + "requiredReads": [ + "/api/servers" + ], + "dataResponsesMs": 663, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [], + "performance": { + "lcp": 492, + "cls": 0.08016383984375, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53, + "domInteractiveMs": 215.10000000149012, + "durationMs": 417 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/route-optimizer", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 633, + "requiredReads": [ + "/api/servers", + "/api/filters/rules", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 986, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Home", + "clipped": false + }, + { + "label": "JumpHost", + "clipped": false + }, + { + "label": "Exit Node", + "clipped": false + }, + { + "label": "Рекомендации", + "clipped": false + } + ], + "performance": { + "lcp": 628, + "cls": 0.633712198591821, + "longTasks": 2, + "longTaskMs": 131, + "ttfbMs": 57.399999998509884, + "domInteractiveMs": 243, + "durationMs": 458.80000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/route-optimizer", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 629, + "requiredReads": [ + "/api/servers", + "/api/filters/rules", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 984, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Home", + "clipped": false + }, + { + "label": "JumpHost", + "clipped": false + }, + { + "label": "Exit Node", + "clipped": false + }, + { + "label": "Рекомендации", + "clipped": false + } + ], + "performance": { + "lcp": 920, + "cls": 0.06558586474609375, + "longTasks": 1, + "longTaskMs": 64, + "ttfbMs": 51.899999998509884, + "domInteractiveMs": 204.20000000298023, + "durationMs": 464 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/network-map", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 589, + "requiredReads": [ + "/api/servers", + "/api/filters/gre-tunnels", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 948, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 808, + "cls": 0.41068980028694063, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 97.5, + "domInteractiveMs": 236.89999999850988, + "durationMs": 464.6000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/network-map", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1052, + "requiredReads": [ + "/api/servers", + "/api/filters/gre-tunnels", + "/api/uptime/speed-probes" + ], + "dataResponsesMs": 1052, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 796, + "cls": 0.2168260953125, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 51.80000000074506, + "domInteractiveMs": 210.09999999776483, + "durationMs": 486 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/ip-ranges", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1108, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 1109, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 640, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 109.69999999925494, + "domInteractiveMs": 290.30000000074506, + "durationMs": 540.6000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/ip-ranges", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 448, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 802, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 396, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 50.599999997764826, + "domInteractiveMs": 135.69999999925494, + "durationMs": 316.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/asns", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 625, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 975, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 572, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 55.399999998509884, + "domInteractiveMs": 301.6000000014901, + "durationMs": 489.1000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/asns", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 464, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 571, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 380, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 61.30000000074506, + "domInteractiveMs": 151.30000000074506, + "durationMs": 333.3999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/communities", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 516, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 887, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 504, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 52.900000002235174, + "domInteractiveMs": 185, + "durationMs": 463.5 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/communities", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 450, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 818, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 416, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53.5, + "domInteractiveMs": 137.89999999850988, + "durationMs": 330.1000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/domains", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 499, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 859, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 448, + "cls": 0.000013114872685185185, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 52.600000001490116, + "domInteractiveMs": 183, + "durationMs": 384.19999999925494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/domains", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 589, + "requiredReads": [ + "/api/evobgp/settings" + ], + "dataResponsesMs": 948, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "performance": { + "lcp": 588, + "cls": 0.000062321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 72.90000000223517, + "domInteractiveMs": 264.6000000014901, + "durationMs": 520 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/bgp", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 633, + "requiredReads": [ + "/api/bgp/catalog" + ], + "dataResponsesMs": 988, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Сессий", + "clipped": false + }, + { + "label": "Established", + "clipped": false + }, + { + "label": "Не поднято", + "clipped": false + }, + { + "label": "Префиксы", + "clipped": false + } + ], + "performance": { + "lcp": 648, + "cls": 0.008227206790123459, + "longTasks": 1, + "longTaskMs": 87, + "ttfbMs": 121.89999999850988, + "domInteractiveMs": 273.4000000022352, + "durationMs": 505.4000000022352 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/bgp", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 606, + "requiredReads": [ + "/api/bgp/catalog" + ], + "dataResponsesMs": 969, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Сессий", + "clipped": false + }, + { + "label": "Established", + "clipped": false + }, + { + "label": "Не поднято", + "clipped": false + }, + { + "label": "Префиксы", + "clipped": false + } + ], + "performance": { + "lcp": 808, + "cls": 0.045822321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 75.80000000074506, + "domInteractiveMs": 267.9000000022352, + "durationMs": 538 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/gre", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 652, + "requiredReads": [ + "/api/gre/tunnels" + ], + "dataResponsesMs": 1009, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активно", + "clipped": false + }, + { + "label": "С IPsec", + "clipped": false + }, + { + "label": "IP-пулов", + "clipped": false + } + ], + "performance": { + "lcp": 644, + "cls": 0.06258026005497686, + "longTasks": 1, + "longTaskMs": 67, + "ttfbMs": 96, + "domInteractiveMs": 303.19999999925494, + "durationMs": 533.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/gre", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 507, + "requiredReads": [ + "/api/gre/tunnels" + ], + "dataResponsesMs": 872, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активно", + "clipped": false + }, + { + "label": "С IPsec", + "clipped": false + }, + { + "label": "IP-пулов", + "clipped": false + } + ], + "performance": { + "lcp": 512, + "cls": 0.107477715625, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 56.19999999925494, + "domInteractiveMs": 171.30000000074506, + "durationMs": 402.80000000074506 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/vxlan", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 655, + "requiredReads": [ + "/api/vxlan" + ], + "dataResponsesMs": 2509, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активных", + "clipped": false + }, + { + "label": "VNI", + "clipped": false + }, + { + "label": "Серверов", + "clipped": false + } + ], + "performance": { + "lcp": 680, + "cls": 0.12087190923996914, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 98.10000000149012, + "domInteractiveMs": 248.19999999925494, + "durationMs": 738.3999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/vxlan", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1107, + "requiredReads": [ + "/api/vxlan" + ], + "dataResponsesMs": 1108, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активных", + "clipped": false + }, + { + "label": "VNI", + "clipped": false + }, + { + "label": "Серверов", + "clipped": false + } + ], + "performance": { + "lcp": 712, + "cls": 0.24378085312499997, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 56.5, + "domInteractiveMs": 240, + "durationMs": 613.8000000007451 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/ospf", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 732, + "requiredReads": [ + "/api/ospf/catalog" + ], + "dataResponsesMs": 1608, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Роутеры", + "clipped": false + }, + { + "label": "Интерф.", + "clipped": false + }, + { + "label": "Зоны", + "clipped": false + } + ], + "performance": { + "lcp": 728, + "cls": 0.0054106802782600315, + "longTasks": 1, + "longTaskMs": 118, + "ttfbMs": 108.29999999701977, + "domInteractiveMs": 274.5999999977648, + "durationMs": 561.5 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "dark", + "path": "/ospf", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 499, + "requiredReads": [ + "/api/ospf/catalog" + ], + "dataResponsesMs": 1366, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "dark", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Роутеры", + "clipped": false + }, + { + "label": "Интерф.", + "clipped": false + }, + { + "label": "Зоны", + "clipped": false + } + ], + "performance": { + "lcp": 492, + "cls": 0.027072464453125004, + "longTasks": 1, + "longTaskMs": 79, + "ttfbMs": 50.899999998509884, + "domInteractiveMs": 159.30000000074506, + "durationMs": 384.19999999925494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/bgp", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 763, + "requiredReads": [ + "/api/bgp/catalog" + ], + "dataResponsesMs": 2622, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Сессий", + "clipped": false + }, + { + "label": "Established", + "clipped": false + }, + { + "label": "Не поднято", + "clipped": false + }, + { + "label": "Префиксы", + "clipped": false + } + ], + "performance": { + "lcp": 752, + "cls": 0.008227206790123459, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 103.5, + "domInteractiveMs": 299.80000000074506, + "durationMs": 649.0999999977648 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/bgp", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 555, + "requiredReads": [ + "/api/bgp/catalog" + ], + "dataResponsesMs": 918, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Сессий", + "clipped": false + }, + { + "label": "Established", + "clipped": false + }, + { + "label": "Не поднято", + "clipped": false + }, + { + "label": "Префиксы", + "clipped": false + } + ], + "performance": { + "lcp": 764, + "cls": 0.045822321875, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53, + "domInteractiveMs": 223.89999999850988, + "durationMs": 501.69999999925494 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/gre", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 631, + "requiredReads": [ + "/api/gre/tunnels" + ], + "dataResponsesMs": 997, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активно", + "clipped": false + }, + { + "label": "С IPsec", + "clipped": false + }, + { + "label": "IP-пулов", + "clipped": false + } + ], + "performance": { + "lcp": 608, + "cls": 0.06258026005497686, + "longTasks": 1, + "longTaskMs": 69, + "ttfbMs": 104.40000000223517, + "domInteractiveMs": 267.80000000074506, + "durationMs": 494.8999999985099 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/gre", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 521, + "requiredReads": [ + "/api/gre/tunnels" + ], + "dataResponsesMs": 887, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активно", + "clipped": false + }, + { + "label": "С IPsec", + "clipped": false + }, + { + "label": "IP-пулов", + "clipped": false + } + ], + "performance": { + "lcp": 476, + "cls": 0.107477715625, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 53.30000000074506, + "domInteractiveMs": 175.60000000149012, + "durationMs": 396.4000000022352 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/vxlan", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 635, + "requiredReads": [ + "/api/vxlan" + ], + "dataResponsesMs": 2502, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активных", + "clipped": false + }, + { + "label": "VNI", + "clipped": false + }, + { + "label": "Серверов", + "clipped": false + } + ], + "performance": { + "lcp": 620, + "cls": 0.12087190923996914, + "longTasks": 1, + "longTaskMs": 71, + "ttfbMs": 117.60000000149012, + "domInteractiveMs": 285, + "durationMs": 509.6000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/vxlan", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 557, + "requiredReads": [ + "/api/vxlan" + ], + "dataResponsesMs": 922, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Туннелей", + "clipped": false + }, + { + "label": "Активных", + "clipped": false + }, + { + "label": "VNI", + "clipped": false + }, + { + "label": "Серверов", + "clipped": false + } + ], + "performance": { + "lcp": 548, + "cls": 0.24378085312499997, + "longTasks": 0, + "longTaskMs": 0, + "ttfbMs": 52.19999999925494, + "domInteractiveMs": 227, + "durationMs": 437.1000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/ospf", + "width": 1440, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 1221, + "requiredReads": [ + "/api/ospf/catalog" + ], + "dataResponsesMs": 1221, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Роутеры", + "clipped": false + }, + { + "label": "Интерф.", + "clipped": false + }, + { + "label": "Зоны", + "clipped": false + } + ], + "performance": { + "lcp": 712, + "cls": 0.0054106802782600315, + "longTasks": 1, + "longTaskMs": 109, + "ttfbMs": 95.10000000149012, + "domInteractiveMs": 246.19999999925494, + "durationMs": 543.6000000014901 + }, + "violations": [], + "auditCompleted": true + }, + { + "theme": "light", + "path": "/ospf", + "width": 320, + "pageErrors": [], + "apiErrors": [], + "mainVisibleMs": 509, + "requiredReads": [ + "/api/ospf/catalog" + ], + "dataResponsesMs": 1395, + "layout": { + "overflow": false, + "mainOverflow": false, + "theme": "light", + "pendingSkeletons": 0 + }, + "kpiLabels": [ + { + "label": "Роутеры", + "clipped": false + }, + { + "label": "Интерф.", + "clipped": false + }, + { + "label": "Зоны", + "clipped": false + } + ], + "performance": { + "lcp": 500, + "cls": 0.027072464453125004, + "longTasks": 1, + "longTaskMs": 66, + "ttfbMs": 54.399999998509884, + "domInteractiveMs": 160.19999999925494, + "durationMs": 409.5 + }, + "violations": [], + "auditCompleted": true + } + ], + "blockedWrites": [], + "readOnlyPosts": [ + { + "theme": "dark", + "path": "/api/servers/1/ospf/optimize/preview" + }, + { + "theme": "dark", + "path": "/api/servers/1/ospf/optimize/preview" + }, + { + "theme": "light", + "path": "/api/servers/1/ospf/optimize/preview" + }, + { + "theme": "light", + "path": "/api/servers/1/ospf/optimize/preview" + } + ], + "bounds": [ + "Default rendered state only; data can still be refreshing", + "Synthetic lab timing over SSH, no CPU/network throttling; not field Core Web Vitals", + "Automated WCAG-tagged checks, not manual screen-reader certification", + "Results retain phase provenance; unchanged pages are not claimed to have been rerun on261", + "Performance recorded while a controlled REST soak and normal collectors could run" + ], + "completed": true, + "passed": true, + "phases": [ + { + "file": "ui261-baseline.json", + "cases": 48, + "violating": 4, + "frontend": "ux-accessibility260-final" + }, + { + "file": "ui261-changed-final.json", + "cases": 24, + "violating": 0, + "frontend": "ux-accessibility261", + "changedPages": [ + "/recursive-routes", + "/route-optimizer", + "/bgp", + "/gre", + "/vxlan", + "/ospf" + ] + } + ], + "coverage": { + "uniqueStates": 64, + "uniqueRoutes": 16, + "unionWith260UniqueStates": 120, + "unionWith260Routes": 30 + } +} diff --git a/docs/ux-ui/acceptance/ui261-live-ecmp.json b/docs/ux-ui/acceptance/ui261-live-ecmp.json new file mode 100644 index 00000000..8454ec09 --- /dev/null +++ b/docs/ux-ui/acceptance/ui261-live-ecmp.json @@ -0,0 +1,17 @@ +{ + "date": "2026-10-15", + "block": "12.5.261", + "writes": [], + "states": [ + { + "enabled": false, + "disabled": true, + "violations": [] + }, + { + "enabled": true, + "disabled": false, + "violations": [] + } + ] +} diff --git a/docs/ux-ui/acceptance/ui261-summary.json b/docs/ux-ui/acceptance/ui261-summary.json new file mode 100644 index 00000000..3732daf4 --- /dev/null +++ b/docs/ux-ui/acceptance/ui261-summary.json @@ -0,0 +1,58 @@ +{ + "date": "2026-10-15", + "block": "12.5.261", + "runtime": { + "backend": "ux-certificates258", + "frontend": "ux-accessibility261" + }, + "baseline": { + "cases": 48, + "violatingCases": 4, + "blockedWrites": 0, + "readQueries": 4 + }, + "final": { + "uniqueStates": 64, + "newRoutes": 12, + "unionWith260UniqueStates": 120, + "unionWith260Routes": 30, + "axeViolations": 0, + "jsErrors": 0, + "apiErrors": 0, + "overflow": 0, + "clippedKpiLabels": 0 + }, + "local": { + "initial": { + "passed": 38, + "failed": 2 + }, + "ecmpRetest": { + "passed": 2, + "failed": 0 + }, + "uniqueTestsThatPassed": 40, + "apiMutationsFromEcmpDraft": 0, + "typecheck": "passed", + "scopedLint": "passed", + "productionBuild": "passed", + "sourceMatched": 528 + }, + "liveEcmp": { + "date": "2026-10-15", + "block": "12.5.261", + "writes": [], + "states": [ + { + "enabled": false, + "disabled": true, + "violations": [] + }, + { + "enabled": true, + "disabled": false, + "violations": [] + } + ] + } +} diff --git a/docs/ux-ui/screens/ui261-bgp320.png b/docs/ux-ui/screens/ui261-bgp320.png new file mode 100644 index 0000000000000000000000000000000000000000..c0ed5a3527ea20465ef6c448e443370d1640573d GIT binary patch literal 49262 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 { + await page.setViewportSize({ width, height: 1000 }) + await page.route("**/health", r => r.fulfill({ json: { ok: true } })) + await page.route("**/api/**", r => { + const endpoint = new URL(r.request().url()).pathname + return r.fulfill({ json: endpoint === "/api/auth/config" ? { required: false } + : endpoint === "/api/servers" ? [{ id: 1, name: "Роутер", host: "192.0.2.1", enabled: true, country: "RU", status: null }] + : endpoint === "/api/bgp/catalog" ? { sessions: [], failures: [] } + : endpoint === "/api/ospf/catalog" ? { interfaces: [], neighbors: [], instances: [], bfdSessions: [], routes: [], failures: [] } + : endpoint === "/api/gre/tunnels" ? { tunnels: [], failures: [] } + : endpoint === "/api/vxlan" ? { tunnels: [], failures: [] } : {} }) + }) + await page.goto(path) + const title = path === "/bgp" ? "Сводка BGP" : path === "/gre" ? "Сводка GRE" : path === "/vxlan" ? "Сводка VXLAN" : "Сводка OSPF" + const summary = page.getByLabel(title, { exact: true }) + await expect(summary).toBeVisible() + const labels = summary.locator('.text-muted-foreground.truncate') + await expect.poll(() => labels.count()).toBeGreaterThan(0) + // Compare actual rendered text bounds, rather than testing particular grid classes. + await expect.poll(() => labels.evaluateAll(elements => elements.every(el => el.scrollWidth <= el.clientWidth + 1))).toBe(true) + expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true) +}) + +for (const theme of ["light", "dark"]) test(`ECMP draft: disabled instructions remain readable and enable is local/${theme}`, async ({ page }) => { + await page.addInitScript(value => localStorage.setItem("rl-theme", value), theme) + let writes = 0 + await page.route("**/health", r => r.fulfill({ json: { ok: true } })) + await page.route("**/api/**", r => { + const path = new URL(r.request().url()).pathname + if (r.request().method() !== "GET" && !path.endsWith("/preview")) writes++ + return r.fulfill({ json: path === "/api/auth/config" ? { required: false } + : path === "/api/servers" ? [{ id: 1, name: "Роутер", host: "192.0.2.1", type: "home-router", country: "RU", enabled: true, status: null }] + : path === "/api/filters/rules" ? { rulesets: [] } : path === "/api/uptime/speed-probes" ? { probes: [] } + : path === "/api/ospf/catalog" ? { interfaces: [], instances: [], neighbors: [], bfdSessions: [], routes: [], failures: [] } + : path.endsWith("/preview") ? { serverId: 1, serverName: "Роутер", pingWeight: 60, interfacesTotal: 0, changedCount: 0, unchangedCount: 0, interfaces: [], changes: [] } : {} }) + }) + await page.goto("/route-optimizer") + const toggle = page.getByRole("switch", { name: "Черновик ECMP", exact: true }) + const group = page.getByRole("group", { name: "Параметры черновика ECMP", exact: true }) + await expect(toggle).toHaveAttribute("aria-checked", "false") + const range = group.getByRole("slider", { name: "Максимальное число путей ECMP", exact: true }) + await expect(range).toBeDisabled() + await group.scrollIntoViewIfNeeded() + await page.addScriptTag({ content: axe.source }) + for (const enabled of [false, true]) { + if (enabled) { await toggle.click(); await expect(range).toBeEnabled(); await range.focus(); await range.press("ArrowRight"); await expect(range).toHaveValue("5") } + const violations = await group.evaluate(async element => (await (window as unknown as { axe: typeof axe }).axe.run(element, { + runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"] }, + })).violations.map(v => ({ id: v.id, targets: v.nodes.map(n => n.target) }))) + expect(violations).toEqual([]) + } + expect(writes).toBe(0) +})