diff --git a/app/(main)/firewall/page.tsx b/app/(main)/firewall/page.tsx index 27eae6b4..9c3798ae 100644 --- a/app/(main)/firewall/page.tsx +++ b/app/(main)/firewall/page.tsx @@ -988,7 +988,7 @@ function SimStepRow({ step, index }: { step: SimStep; index: number }) { type="button" onClick={() => isClickable && setExpanded(e => !e)} className={cn( - "w-full flex items-center gap-3 px-5 py-2.5 text-left transition-colors", + "w-full min-h-11 flex flex-wrap sm:flex-nowrap items-center gap-3 px-5 py-2.5 text-left transition-colors", isClickable ? "hover:bg-muted/30 cursor-pointer" : "cursor-default", step.status === "pending" && "opacity-40", step.status === "disabled" && "opacity-25", @@ -999,7 +999,7 @@ function SimStepRow({ step, index }: { step: SimStep; index: number }) {
- + {step.rule.src || "any"} → {step.rule.dst || "any"} {step.rule.proto && step.rule.proto !== "all" && ( {step.rule.proto} @@ -1015,7 +1015,7 @@ function SimStepRow({ step, index }: { step: SimStep; index: number }) { )} {/* Status badge */} -
+
{step.status === "pending" && ( ожидание )} @@ -1030,7 +1030,7 @@ function SimStepRow({ step, index }: { step: SimStep; index: number }) { )} {step.status === "passthrough" && ( - + passthrough )} @@ -1355,9 +1355,12 @@ function SimulatorTab({ rules: allRules }: { rules: FirewallRule[] }) { const [activePreset, setPreset] = useState("https") const [simState, setSimState] = useState<"idle" | "running" | "done">("idle") const [steps, setSteps] = useState([]) + const [tracePage, setTracePage] = useState(0) const [speed, setSpeed] = useState("normal") const [verdict, setVerdict] = useState<{ action: string; ruleIndex: number | null } | null>(null) const cancelRef = useRef(false) + const runEpoch = useRef(0) + useEffect(() => () => { runEpoch.current += 1; cancelRef.current = true }, []) // ── Scenarios state ───────────────────────────────────────────────────────── const [scenarios, setScenarios] = useState(() => { @@ -1392,9 +1395,11 @@ function SimulatorTab({ rules: allRules }: { rules: FirewallRule[] }) { // ── Handlers ──────────────────────────────────────────────────────────────── function resetSim() { + runEpoch.current += 1 cancelRef.current = true setSimState("idle") setSteps([]) + setTracePage(0) setVerdict(null) } @@ -1402,18 +1407,22 @@ function SimulatorTab({ rules: allRules }: { rules: FirewallRule[] }) { setActiveScenarioId(null) setPreset(p.id) setPacket({ ...DEFAULT_PKT, ...p.pkt }) + runEpoch.current += 1 cancelRef.current = true setSimState("idle") setSteps([]) + setTracePage(0) setVerdict(null) } function activateScenario(id: string | null) { setActiveScenarioId(id) setPreset(null) + runEpoch.current += 1 cancelRef.current = true setSimState("idle") setSteps([]) + setTracePage(0) setVerdict(null) if (id) { const sc = scenarios.find(s => s.id === id) @@ -1444,7 +1453,9 @@ function SimulatorTab({ rules: allRules }: { rules: FirewallRule[] }) { return } + const epoch = ++runEpoch.current cancelRef.current = false + setTracePage(0) setSimState("running") setVerdict(null) setSteps(rules.map((r, i) => ({ rule: r, index: i, status: "pending", checks: [] }))) @@ -1453,10 +1464,10 @@ function SimulatorTab({ rules: allRules }: { rules: FirewallRule[] }) { let finalVerdict: { action: string; ruleIndex: number | null } | null = null for (let i = 0; i < rules.length; i++) { - if (cancelRef.current) return + if (cancelRef.current || epoch !== runEpoch.current) return setSteps(prev => prev.map((s, idx) => idx === i ? { ...s, status: "evaluating" } : s)) if (delay > 0) await new Promise(res => setTimeout(res, delay)) - if (cancelRef.current) return + if (cancelRef.current || epoch !== runEpoch.current) return const result = evalRule(rules[i], packet) setSteps(prev => prev.map((s, idx) => @@ -1471,12 +1482,21 @@ function SimulatorTab({ rules: allRules }: { rules: FirewallRule[] }) { if (delay > 0) await new Promise(res => setTimeout(res, Math.max(delay * 0.25, 40))) } - if (!cancelRef.current) { + if (!cancelRef.current && epoch === runEpoch.current) { setVerdict(finalVerdict ?? { action: "accept (default)", ruleIndex: null }) setSimState("done") } } + const tracePageCount = Math.max(1, Math.ceil(steps.length / 50)) + const currentTracePage = Math.min(tracePage, tracePageCount - 1) + const traceOffset = currentTracePage * 50 + const traceChainCounts = useMemo(() => { + const counts = new Map() + for (const step of steps) counts.set(step.rule.chain, (counts.get(step.rule.chain) ?? 0) + 1) + return counts + }, [steps]) + const verdictColors = verdict ? getVerdictColors(verdict.action) : null return ( @@ -1690,7 +1710,7 @@ function SimulatorTab({ rules: allRules }: { rules: FirewallRule[] }) { {simState === "running" ? ( ) : ( @@ -1730,8 +1750,8 @@ function SimulatorTab({ rules: allRules }: { rules: FirewallRule[] }) { {/* ── Trace list ──────────────────────────────────────────────────────── */} {steps.length > 0 && ( - -
+ +

Трассировка · цепочка: {packet.chain} {activeScenario && ( @@ -1745,23 +1765,24 @@ function SimulatorTab({ rules: allRules }: { rules: FirewallRule[] }) {

- {steps.map((step, i) => { + {steps.slice(traceOffset, traceOffset + 50).map((step, pageIndex) => { + const i = traceOffset + pageIndex const prevChain = i > 0 ? steps[i - 1].rule.chain : null - const showHeader = step.rule.chain !== prevChain + const showHeader = pageIndex === 0 || step.rule.chain !== prevChain return ( -
+
{showHeader && (
- + {step.rule.chain} {CHAIN_TABLE_HINT[step.rule.chain] && ( - + · {CHAIN_TABLE_HINT[step.rule.chain]} )} - - {steps.filter(s => s.rule.chain === step.rule.chain).length} правил + + {traceChainCounts.get(step.rule.chain)} правил
)} @@ -1770,6 +1791,14 @@ function SimulatorTab({ rules: allRules }: { rules: FirewallRule[] }) { ) })}
+
+ Шаги {traceOffset + 1}–{Math.min(traceOffset + 50, steps.length)} из {steps.length} +
+ + + {verdict?.ruleIndex != null && } +
+
)} diff --git a/docs/ux-ui-performance.md b/docs/ux-ui-performance.md index 414e0ee5..78f2709a 100644 --- a/docs/ux-ui-performance.md +++ b/docs/ux-ui-performance.md @@ -87,3 +87,5 @@ Statistics pivot в backend/src/services/statistics-aggregate.ts огранич 12.3.44: stale операции Firewall заблокированы до успешного read/после retry, ограничения12.3.43 по полному GET failure исправлены.29+2 main/context4/type/build прошли;4 исходных ESLint errors страницы подтверждены сравнением с HEAD.11 manualPagination:true остаются. 12.3.45: локальный сценарий1000→50 rows/1847 modal nodes; pagination/full draft999/скрытый verdict#999/320px/axe проверены,6 browser/type/grid lint/build прошли. Трассировка999 остаётся15529 page nodes, требует12.3.46; ошибки записи localStorage не проверены.10 manualPagination:true остаются. + +12.3.46: trace999/15529→1293 page nodes,50 steps/whole verdict#999/страницы/jump/stop→restart epoch/reset/320px/axe проверены;6+1 browser/type/test lint/build прошли. Полный steps в памяти и O(N²) обновления instant не менялись; общий CPU performance не принят.10 manualPagination:true остаются. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index a80856be..b17da40c 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -4,7 +4,7 @@ ## Актуальная точка продолжения — 2026-10-04 -Этап 11Б завершён в границах локальной приёмки (11Б.7); внешний выпуск не проверен. 12.1 принят локально: доступность оболочки/клавиатура/health в описанных сценариях. 12.2.1 принят локально: strict BGP catalog/partial/nullable counters. 12.2.2 принят локально: strict traffic DTO и агрегация без выдуманных точек. 12.2.3 принят локально: observations для server traffic от backend до dashboard. 12.2.4 принят локально: strict servers/probes и unknown server status. 12.2.5 принят локально: реальные ping samples/unknown/ось RTT/Recharts. 12.2.6 принят локально: strict GRE/WireGuard/VXLAN и device failures. 12.2.7 принят локально: strict Internet path snapshot/fallback. 12.2.8 принят локально: подтверждение полного backend сбора. 12.2.9 принят локально: метрики участков и наблюдаемый WAN без эвристической подмены. 12.3.1 принят локально:1000 серверов/штатная пагинация. 12.3.2 принят локально: пагинация CompactDataGrid/1000 строк статистики. 12.3.3 принят локально: порционный DOM очереди проблем. 12.3.4 принят локально: большой BGP каталог/страница при обновлении. Production build и123 профильных browser приняты локально на3978976. 12.2.10 принят локально: обязательные RouterOS поля. 12.3.5 принят локально: страница CompactDataGrid сохраняется при опросе,89 browser и повторный production build прошли. 12.3.6 сохранён: baseline1000 speed probes (ограничение DOM пока не выполнено). 12.3.7 принят локально:50 строк speed-проб/сохранение страницы/глобальная сортировка/1000→1; каталог1001 устройства остаётся в DOM. 12.3.8 принят локально:50 строк ресурсов, URL/поиск/сохранение страницы/1000→1; исправлена pagination оболочки с children, production build и8 больших browser прошли. 12.3.9 принят локально:порционное раскрытие1000 предупреждений ресурсов/клавиатура/axe; исходная ошибка ESLint speedDraft подтверждена. 12.3.10 принят локально:недоступные интерфейсы очищаются в обработчиках,54 browser/полный ESLint/production build прошли. 12.3.11 принят локально:ServerTileRail50 устройств/URL/поиск/клавиатура/две площадки/mobile Sheet/axe обе темы;47 browser и production build прошли. 12.3.12 принят локально:BGP ручная страница при выбранной URL сессии/refresh/reload,16 browser/build прошли. 12.4.1 записан:main1091 сценарий/1088 passed/3 устаревших fixtures; оба исправленных файла6 passed. 12.4.2 принят локально:context205/24 файла и unit28 прошли. 12.3.13 принят локально:wide pivot51×16/ScrollArea/axe/24+2 browser/builds. 12.3.14 принят локально:history200/50 DOM/54+1 browser/build. 12.3.15 принят локально:live расписание1000/page20 error cache/15 browser. 12.3.16 принят локально:certificates1000/50 DOM/refresh деталей/19+3 browser/build. 12.3.17 принят локально:pinning идентификатора/видимость значений/29 browser/build. 12.3.18 принят локально:users1000/поиск всего списка/детали и ошибка refresh/32 browser/build. 12.3.19 принят локально:backups1000/все мобильные действия/возврат focus/39+4 browser/build. 12.3.20 принят локально:независимые speed GET/cache/freshness/58+4 browser/build. 12.3.21 принят локально:strict history DTO/null rates от DB до UI/60+5 browser/unit4/builds. 12.4.3 принят локально:users/backups delta context36 на a558412. 12.3.22 принят локально:shared speed-probes DTO/context8/60+6+47 browser/unit5/builds. 12.3.23 принят локально:strict write DTO/atomic replacement/confirmed scheduler warning/65 browser/HTTP DI JWT/builds. 12.3.24 принят локально:OSPF routes1000/50 DOM/page20/cache/320px/22+1 browser/build. 12.3.25 принят локально:BFD1000/50 DOM/cache/320px/12 browser/build. 12.4.4 принят локально:context213/25 файлов и unit36 прошли на5b2a291. 12.4.5 принят локально:main1110/171 прошли на21859be. 12.3.26 принят локально:strict atomic Ping write/подтверждение каталога и сохранения/история удаления/62 browser/context11/DI6/builds. 12.3.27 принят локально:WireGuard zero/missing counters/UI/backend mapper/29 browser/unit/builds. 12.3.28 принят локально:WireGuard interfaces1000/50 DOM/pinning/actions/cache/29+1 browser/build. 12.3.29 принят локально:WG peers1000/50 DOM/pinning/точный delete target/29+3 browser/build. 12.3.30 принят локально:WG detail1000/50 DOM/320px/клавиатура/31+1 browser/build. 12.3.31 принят локально:WG handshake без ложного online/unknown/7+1 browser/build. 12.3.32 принят локально:WG URL search/tab/status/reload/история/34+4 browser/unit/build. 12.3.33 принят локально:WG strict read/cache/freshness/unknown/40+7 browser/context8/build. 12.3.34 принят локально:VXLAN1000/50 DOM/pinning/export/22 browser/build. 12.3.35 принят локально:GRE1000/50 DOM/pinning/20+1 browser/build. 12.3.36 карта15 оставшихся источников обновлена. 12.3.37 top50 traffic/пустой локальный фильтр/перенос tabs принят в описанных границах. 12.3.38 Communities1000/50 DOM/19 browser/build принят локально. 12.3.39 clipboard ack/error/retry/unmount/5 browser/build принят локально. 12.3.40 recursive groups/endpoints1000/50 DOM/35 browser/build принят локально. 12.3.41 большая форма1000×1000/10 cards/полный PUT/22+1 browser/build принята локально. 12.3.42 Filters1000/50 DOM/полный PUT/10+1 browser/build принят локально. 12.3.43 Firewall1000/50 DOM/DnD/35+1 main/context4/build принят локально. 12.3.44 stale guard/29+2 main/context4/build принят локально;4 исходных lint errors страницы требуют cleanup. 12.3.45 сценарий1000/50 DOM/полный draft/verdict/6 browser/build принят локально. Следующий блок12.3.46: trace999/15529 nodes; затем lint cleanup/OSPF/optimizer и полная main/context регрессия.12.5 отдельно. См. docs/ux-ui-stage-12.md. Внешние ограничения 11Б сохраняются. +Этап 11Б завершён в границах локальной приёмки (11Б.7); внешний выпуск не проверен. 12.1 принят локально: доступность оболочки/клавиатура/health в описанных сценариях. 12.2.1 принят локально: strict BGP catalog/partial/nullable counters. 12.2.2 принят локально: strict traffic DTO и агрегация без выдуманных точек. 12.2.3 принят локально: observations для server traffic от backend до dashboard. 12.2.4 принят локально: strict servers/probes и unknown server status. 12.2.5 принят локально: реальные ping samples/unknown/ось RTT/Recharts. 12.2.6 принят локально: strict GRE/WireGuard/VXLAN и device failures. 12.2.7 принят локально: strict Internet path snapshot/fallback. 12.2.8 принят локально: подтверждение полного backend сбора. 12.2.9 принят локально: метрики участков и наблюдаемый WAN без эвристической подмены. 12.3.1 принят локально:1000 серверов/штатная пагинация. 12.3.2 принят локально: пагинация CompactDataGrid/1000 строк статистики. 12.3.3 принят локально: порционный DOM очереди проблем. 12.3.4 принят локально: большой BGP каталог/страница при обновлении. Production build и123 профильных browser приняты локально на3978976. 12.2.10 принят локально: обязательные RouterOS поля. 12.3.5 принят локально: страница CompactDataGrid сохраняется при опросе,89 browser и повторный production build прошли. 12.3.6 сохранён: baseline1000 speed probes (ограничение DOM пока не выполнено). 12.3.7 принят локально:50 строк speed-проб/сохранение страницы/глобальная сортировка/1000→1; каталог1001 устройства остаётся в DOM. 12.3.8 принят локально:50 строк ресурсов, URL/поиск/сохранение страницы/1000→1; исправлена pagination оболочки с children, production build и8 больших browser прошли. 12.3.9 принят локально:порционное раскрытие1000 предупреждений ресурсов/клавиатура/axe; исходная ошибка ESLint speedDraft подтверждена. 12.3.10 принят локально:недоступные интерфейсы очищаются в обработчиках,54 browser/полный ESLint/production build прошли. 12.3.11 принят локально:ServerTileRail50 устройств/URL/поиск/клавиатура/две площадки/mobile Sheet/axe обе темы;47 browser и production build прошли. 12.3.12 принят локально:BGP ручная страница при выбранной URL сессии/refresh/reload,16 browser/build прошли. 12.4.1 записан:main1091 сценарий/1088 passed/3 устаревших fixtures; оба исправленных файла6 passed. 12.4.2 принят локально:context205/24 файла и unit28 прошли. 12.3.13 принят локально:wide pivot51×16/ScrollArea/axe/24+2 browser/builds. 12.3.14 принят локально:history200/50 DOM/54+1 browser/build. 12.3.15 принят локально:live расписание1000/page20 error cache/15 browser. 12.3.16 принят локально:certificates1000/50 DOM/refresh деталей/19+3 browser/build. 12.3.17 принят локально:pinning идентификатора/видимость значений/29 browser/build. 12.3.18 принят локально:users1000/поиск всего списка/детали и ошибка refresh/32 browser/build. 12.3.19 принят локально:backups1000/все мобильные действия/возврат focus/39+4 browser/build. 12.3.20 принят локально:независимые speed GET/cache/freshness/58+4 browser/build. 12.3.21 принят локально:strict history DTO/null rates от DB до UI/60+5 browser/unit4/builds. 12.4.3 принят локально:users/backups delta context36 на a558412. 12.3.22 принят локально:shared speed-probes DTO/context8/60+6+47 browser/unit5/builds. 12.3.23 принят локально:strict write DTO/atomic replacement/confirmed scheduler warning/65 browser/HTTP DI JWT/builds. 12.3.24 принят локально:OSPF routes1000/50 DOM/page20/cache/320px/22+1 browser/build. 12.3.25 принят локально:BFD1000/50 DOM/cache/320px/12 browser/build. 12.4.4 принят локально:context213/25 файлов и unit36 прошли на5b2a291. 12.4.5 принят локально:main1110/171 прошли на21859be. 12.3.26 принят локально:strict atomic Ping write/подтверждение каталога и сохранения/история удаления/62 browser/context11/DI6/builds. 12.3.27 принят локально:WireGuard zero/missing counters/UI/backend mapper/29 browser/unit/builds. 12.3.28 принят локально:WireGuard interfaces1000/50 DOM/pinning/actions/cache/29+1 browser/build. 12.3.29 принят локально:WG peers1000/50 DOM/pinning/точный delete target/29+3 browser/build. 12.3.30 принят локально:WG detail1000/50 DOM/320px/клавиатура/31+1 browser/build. 12.3.31 принят локально:WG handshake без ложного online/unknown/7+1 browser/build. 12.3.32 принят локально:WG URL search/tab/status/reload/история/34+4 browser/unit/build. 12.3.33 принят локально:WG strict read/cache/freshness/unknown/40+7 browser/context8/build. 12.3.34 принят локально:VXLAN1000/50 DOM/pinning/export/22 browser/build. 12.3.35 принят локально:GRE1000/50 DOM/pinning/20+1 browser/build. 12.3.36 карта15 оставшихся источников обновлена. 12.3.37 top50 traffic/пустой локальный фильтр/перенос tabs принят в описанных границах. 12.3.38 Communities1000/50 DOM/19 browser/build принят локально. 12.3.39 clipboard ack/error/retry/unmount/5 browser/build принят локально. 12.3.40 recursive groups/endpoints1000/50 DOM/35 browser/build принят локально. 12.3.41 большая форма1000×1000/10 cards/полный PUT/22+1 browser/build принята локально. 12.3.42 Filters1000/50 DOM/полный PUT/10+1 browser/build принят локально. 12.3.43 Firewall1000/50 DOM/DnD/35+1 main/context4/build принят локально. 12.3.44 stale guard/29+2 main/context4/build принят локально;4 исходных lint errors страницы требуют cleanup. 12.3.45 сценарий1000/50 DOM/полный draft/verdict/6 browser/build принят локально. 12.3.46 trace999/50 steps/1293 nodes/epoch/6+1 browser/build принят локально. Следующий блок12.3.47: Firewall lint cleanup; затем OSPF/optimizer и полная main/context регрессия.12.5 отдельно. См. docs/ux-ui-stage-12.md. Внешние ограничения 11Б сохраняются. Реальные pg_dump/pg_restore, PG rollback и остановка UDP worker требуют внешней приёмки. Датированные продолжения ниже — история; точный следующий шаг также указан в начале [журнала](ux-ui-progress.md). diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 247dad0b..1ea4892c 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -31,6 +31,14 @@ | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | В работе;12.1/12.2.1–12.2.10 и12.3.1–12.3.5 и12.3.7–12.3.12 приняты в описанных локальных границах;12.4.1 main регрессия и профильный повтор записаны;12.4.2 context205 и unit28 прошли;12.3.13–12.3.25 приняты локально в описанных границах;12.4.4 context213 и12.4.5 main1110 прошли, 12.3.26–12.3.33 приняты локально; 12.3.34 VXLAN и12.3.35 GRE tunnels приняты; остаются network grids/общая регрессия после delta/внешняя приёмка | +## 12.3.46 — ограниченная трассировка Firewall (2026-10-04) + +Trace показывает50 шагов, общий verdict/counts/номера относятся ко всему расчёту. Предыдущая/следующая страница и переход к правилу результата44px; новая симуляция/reset/preset/scenario начинают с первой страницы. Строки переносятся на320px, исправлен контраст passthrough и пояснений. runEpoch защищает от старого таймера после stop→новый run; unmount инвалидирует запуск. + +6/6 browser на начальной pagination и финальный large1/1 после mobile/epoch/axe прошли: trace999 уменьшен15529→1293 page DOM nodes,50 steps на странице/49 на последней. Full verdict#999, page2, keyboard jump951–999,320px/axe, stop→instant run и продвижение clock1000ms без старого evaluating, reset проверены. TypeScript без incremental/ESLint теста/production build прошли; ESLint API baseline/current подтверждает те же4 исходных ошибки страницы и2 warnings. [Трассировка320px](ux-ui/screens/large-firewall-trace-320.png) просмотрена. Алгоритм вычисления не изменён: instant по-прежнему делает обновления полного steps на каждом правиле, CPU complexity не принята нагрузочным тестом. Данные остаются полными в памяти. + +Следующий блок12.3.47: Firewall lint cleanup четырёх старых эффектов без потери draft/URL/filter/focus. Затем OSPF таблица+topology/optimizer; ошибки localStorage и большой multi-server sort отдельно.10 manualPagination:true остаются. Общая main/context после delta и внешняя12.5 отдельно. Коммит локальный без push. + ## 12.3.45 — большой редактор сценария Firewall (2026-10-04) ReUI pagination50/clamp/ScrollArea, закреплённые номер и chain, действия44px без hover; номера и up/down края считаются по полному draft. ScenarioSheet320px занимает всю ширину viewport. Кнопки редактирования/удаления сценария получили44px и доступные имена. Локальный сценарий не отправляет операции RouterOS. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index dd162789..8f115bc0 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -560,3 +560,12 @@ ReUI pagination50/clamp/ScrollArea, закреплённые номер и chain Обнаружено: trace999 создаёт15529 page DOM nodes; ограничение редактора не означает принятие всего симулятора. Следующий блок12.3.46: страницы трассировки/полный verdict/переход к результату и сохранение отмены/повторного запуска. Затем Firewall lint cleanup, OSPF таблица+topology/optimizer.10 manualPagination:true specialized grids остаются. Общая main/context после delta и внешняя12.5 отдельно. Коммит локальный без push. + +## 12.3.46 — ограниченная трассировка Firewall (2026-10-04) + +Trace показывает50 шагов, общий verdict/counts/номера относятся ко всему расчёту. Предыдущая/следующая страница и переход к правилу результата44px; новая симуляция/reset/preset/scenario начинают с первой страницы. Строки переносятся на320px, исправлен контраст passthrough и пояснений. runEpoch защищает от старого таймера после stop→новый run; unmount инвалидирует запуск. + +6/6 browser на начальной pagination и финальный large1/1 после mobile/epoch/axe прошли: trace999 уменьшен15529→1293 page DOM nodes,50 steps на странице/49 на последней. Full verdict#999, page2, keyboard jump951–999,320px/axe, stop→instant run и продвижение clock1000ms без старого evaluating, reset проверены. TypeScript без incremental/ESLint теста/production build прошли; ESLint API baseline/current подтверждает те же4 исходных ошибки страницы и2 warnings. [Трассировка320px](ux-ui/screens/large-firewall-trace-320.png) просмотрена. Алгоритм вычисления не изменён: instant по-прежнему делает обновления полного steps на каждом правиле, CPU complexity не принята нагрузочным тестом. Данные остаются полными в памяти. + +Следующий блок12.3.47: Firewall lint cleanup четырёх старых эффектов без потери draft/URL/filter/focus. Затем OSPF таблица+topology/optimizer; ошибки localStorage и большой multi-server sort отдельно.10 manualPagination:true остаются. Общая main/context после delta и внешняя12.5 отдельно. Коммит локальный без push. + diff --git a/docs/ux-ui/screens/large-firewall-trace-320.png b/docs/ux-ui/screens/large-firewall-trace-320.png new file mode 100644 index 00000000..a50e7bb3 Binary files /dev/null and b/docs/ux-ui/screens/large-firewall-trace-320.png differ diff --git a/tests/e2e/large-firewall-scenario.spec.ts b/tests/e2e/large-firewall-scenario.spec.ts index ce68e1a2..a286cce1 100644 --- a/tests/e2e/large-firewall-scenario.spec.ts +++ b/tests/e2e/large-firewall-scenario.spec.ts @@ -52,6 +52,36 @@ test("Сценарий1000: граница страниц, полный draft и await page.getByRole("button", { name: "Запустить", exact: true }).click() await expect(page.getByText("— правило #999", { exact: true })).toBeVisible() await expect(page.getByText("Пакет заблокирован", { exact: false })).toBeVisible() + const trace = page.getByRole("region", { name: "Трассировка Firewall", exact: true }) + const traceRows = trace.locator("[data-firewall-trace-step]") + await expect(traceRows).toHaveCount(50) + await expect(traceRows.first()).toHaveAttribute("data-firewall-trace-step", "1") console.log("Scenario simulation nodes", await page.locator("*").count()) + await trace.getByRole("button", { name: "Следующая страница трассировки", exact: true }).click() + await expect(traceRows.first()).toHaveAttribute("data-firewall-trace-step", "51") + await trace.getByRole("button", { name: "К правилу результата", exact: true }).focus(); await page.keyboard.press("Enter") + await expect(traceRows).toHaveCount(49) + await expect(traceRows.last()).toHaveAttribute("data-firewall-trace-step", "999") + await expect(trace.getByRole("status")).toHaveText("Шаги 951–999 из 999") + await expect(trace.getByRole("button", { name: "Следующая страница трассировки", exact: true })).toBeDisabled() + expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(320) + expect((await page.evaluate(async () => (window as unknown as { axe: typeof axe }).axe.run(document.querySelector('[aria-label="Трассировка Firewall"]')!, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] } }))).violations.map(v => ({ id: v.id, nodes: v.nodes.map(n => n.target) }))).toEqual([]) + await traceRows.last().scrollIntoViewIfNeeded() + await page.screenshot({ path: info.outputPath("trace-320.png"), animations: "disabled" }) + await page.clock.install() + await page.getByRole("button", { name: "Быстро", exact: true }).click() + await page.getByRole("button", { name: "Снова", exact: true }).click() + await expect(traceRows.first()).toHaveAttribute("data-firewall-trace-step", "1") + await page.getByRole("button", { name: "Стоп", exact: true }).click() + await page.getByRole("button", { name: "Мгновенно", exact: true }).click() + await page.getByRole("button", { name: "Снова", exact: true }).click() + await expect(page.getByText("— правило #999", { exact: true })).toBeVisible() + await expect(traceRows.first()).toHaveAttribute("data-firewall-trace-step", "1") + await page.clock.runFor(1000) + await expect(trace.getByText("проверяется…", { exact: true })).toHaveCount(0) + await expect(page.getByText("— правило #999", { exact: true })).toBeVisible() + await page.getByRole("button", { name: "Сбросить", exact: true }).click() + await expect(trace).toHaveCount(0) + await expect(page.getByText("— правило #999", { exact: true })).toHaveCount(0) expect(writes).toBe(0) })