From 9231597d88ec979e2300c97ed373613481625b26 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sun, 4 Oct 2026 16:38:19 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D0=BE=D0=B3=D1=80=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=87=D0=B8=D1=82=D1=8C=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=20=D1=81=D1=86=D0=B5=D0=BD=D0=B0=D1=80=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B1=D0=B5=D0=B7=20=D0=BF=D0=BE=D1=82=D0=B5=D1=80=D0=B8=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/firewall/page.tsx | 8 ++- .../firewall-scenario-rules-data-grid.tsx | 43 ++++++++++--- docs/ux-ui-performance.md | 7 ++- docs/ux-ui-plan.md | 2 +- docs/ux-ui-progress.md | 8 +++ docs/ux-ui-stage-12.md | 9 +++ .../screens/large-firewall-scenario-320.png | Bin 0 -> 54072 bytes tests/e2e/large-firewall-scenario.spec.ts | 57 ++++++++++++++++++ 8 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 docs/ux-ui/screens/large-firewall-scenario-320.png create mode 100644 tests/e2e/large-firewall-scenario.spec.ts diff --git a/app/(main)/firewall/page.tsx b/app/(main)/firewall/page.tsx index be356f6b..27eae6b4 100644 --- a/app/(main)/firewall/page.tsx +++ b/app/(main)/firewall/page.tsx @@ -1160,7 +1160,7 @@ function ScenarioSheet({ open, onClose, initial, onSave }: { return ( { if (!v) onClose() }}> - + {isNew ? "Новый сценарий" : `Редактировать: ${initial?.name}`} Набор правил firewall/mangle/nat + тестовый пакет @@ -1552,11 +1552,13 @@ function SimulatorTab({ rules: allRules }: { rules: FirewallRule[] }) { diff --git a/components/data-grids/firewall-scenario-rules-data-grid.tsx b/components/data-grids/firewall-scenario-rules-data-grid.tsx index a83a4c8c..07b886cd 100644 --- a/components/data-grids/firewall-scenario-rules-data-grid.tsx +++ b/components/data-grids/firewall-scenario-rules-data-grid.tsx @@ -1,8 +1,8 @@ "use client" -import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" +import { DataGridContainer, DataGridScrollArea, DataGridTable, dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" -import { useMemo } from "react" +import { useEffect, useMemo } from "react" import { type ColumnDef, useTable, @@ -10,6 +10,7 @@ import { import { ActionBadge, ChainBadge } from "@/components/data-grids/firewall-rules-data-grid" import { DataGridShell } from "@/components/data-grids/shared/data-grid-shell" import { + DATA_GRID_CONTAINER_CLASS, DATA_GRID_CELL_PAD, DATA_GRID_CELL_PAD_FIRST, DATA_GRID_CELL_PAD_LAST, @@ -74,6 +75,7 @@ function FirewallScenarioRulesDataGrid({ }, { id: "chain", + size: 112, accessorKey: "chain", header: () => Цепочка, enableSorting: false, @@ -86,6 +88,7 @@ function FirewallScenarioRulesDataGrid({ }, { id: "action", + size: 176, accessorKey: "action", header: () => Действие, enableSorting: false, @@ -94,6 +97,7 @@ function FirewallScenarioRulesDataGrid({ }, { id: "src", + size: 176, accessorKey: "src", header: () => Src, enableSorting: false, @@ -106,6 +110,7 @@ function FirewallScenarioRulesDataGrid({ }, { id: "dst", + size: 176, accessorKey: "dst", header: () => Dst, enableSorting: false, @@ -118,6 +123,7 @@ function FirewallScenarioRulesDataGrid({ }, { id: "port", + size: 112, accessorKey: "port", header: () => Порт, enableSorting: false, @@ -128,6 +134,7 @@ function FirewallScenarioRulesDataGrid({ }, { id: "iface", + size: 160, accessorKey: "iface", header: () => Iface, enableSorting: false, @@ -138,6 +145,7 @@ function FirewallScenarioRulesDataGrid({ }, { id: "comment", + size: 192, accessorKey: "comment", header: () => Комментарий, enableSorting: false, @@ -150,17 +158,18 @@ function FirewallScenarioRulesDataGrid({ }, { id: "actions", + size: 208, header: () => null, enableSorting: false, cell: ({ row }) => { const index = row.original._index - 1 return ( -
+
@@ -169,7 +178,7 @@ function FirewallScenarioRulesDataGrid({ onClick={() => onMoveUp(row.original.id)} aria-label={`Поднять правило ${index + 1}`} disabled={index === 0} - className="p-0.5 text-muted-foreground/40 hover:text-foreground disabled:opacity-20 transition-colors" + className="size-11 shrink-0 inline-flex items-center justify-center rounded text-muted-foreground hover:text-foreground disabled:opacity-40 transition-colors" > ▲ @@ -178,7 +187,7 @@ function FirewallScenarioRulesDataGrid({ onClick={() => onMoveDown(row.original.id)} aria-label={`Опустить правило ${index + 1}`} disabled={index === rules.length - 1} - className="p-0.5 text-muted-foreground/40 hover:text-foreground disabled:opacity-20 transition-colors" + className="size-11 shrink-0 inline-flex items-center justify-center rounded text-muted-foreground hover:text-foreground disabled:opacity-40 transition-colors" > ▼ @@ -186,7 +195,7 @@ function FirewallScenarioRulesDataGrid({ type="button" onClick={() => onRemove(row.original.id)} aria-label={`Удалить правило ${index + 1}`} - className="p-0.5 ml-0.5 text-muted-foreground/40 hover:text-red-500 transition-colors" + className="size-11 shrink-0 inline-flex items-center justify-center rounded text-muted-foreground hover:text-red-500 transition-colors" > @@ -201,21 +210,35 @@ function FirewallScenarioRulesDataGrid({ const table = useTable({ features: dataGridFeatures, - manualPagination: true, + manualPagination: false, + autoResetPageIndex: false, + initialState: { pagination: { pageIndex: 0, pageSize: 50 }, columnPinning: { start: ["index", "chain"], end: [] } }, manualSorting: true, data: indexedRules, columns, getRowId: (row) => row.id, }) + const { pageIndex, pageSize } = table.state.pagination + useEffect(() => { + const lastPage = Math.max(0, Math.ceil(rules.length / pageSize) - 1) + if (pageIndex > lastPage) table.setPageIndex(lastPage) + }, [rules.length, pageIndex, pageSize, table]) + return ( 50 || pageSize !== 50} + tableLayout={{ dense: true, columnsPinnable: true }} tableClassNames={{ bodyRow: cn("group/row text-xs", "hover:bg-muted/20 transition-colors"), }} - /> + > + + + + ) } diff --git a/docs/ux-ui-performance.md b/docs/ux-ui-performance.md index 59db4782..414e0ee5 100644 --- a/docs/ux-ui-performance.md +++ b/docs/ux-ui-performance.md @@ -15,12 +15,11 @@ ## Оставшиеся специализированные grids -Аудит12.3.36 от2026-10-04: точный поиск `manualPagination: true` в components/data-grids —11 файлов после12.3.43. Это не11 доказанных дефектов: есть фиксированный источник, demo, неподключённый компонент и ограниченные срезы. Файлы ниже сопоставлены с действующими JSX-потребителями; ограничения установлены чтением кода, не нагрузочной приёмкой API. +Аудит12.3.36 от2026-10-04: точный поиск `manualPagination: true` в components/data-grids —10 файлов после12.3.45. Это не10 доказанных дефектов: есть фиксированный источник, demo, неподключённый компонент и ограниченные срезы. Файлы ниже сопоставлены с действующими JSX-потребителями; ограничения установлены чтением кода, не нагрузочной приёмкой API. | Файл | Источник и необходимая проверка | | --- | --- | | [data-collection-scheduler-data-grid.tsx](../components/data-grids/data-collection-scheduler-data-grid.tsx) | 11 фиксированных SCHEDULER_JOB_KEYS (lib/scheduler-settings.ts); UI map этих ключей. Fixture1000 не соответствует источнику. | -| [firewall-scenario-rules-data-grid.tsx](../components/data-grids/firewall-scenario-rules-data-grid.tsx) | Локальный редактор сценария, динамический массив; не путать с live правилами. Проверить сохранение/симуляцию всего сценария. | | [gre-pools-data-grid.tsx](../components/data-grids/gre-pools-data-grid.tsx) | В live displayPools=[]; только mockGrePools в demo. Приоритета live1000 нет. | | [ospf-neighbors-data-grid.tsx](../components/data-grids/ospf-neighbors-data-grid.tsx) | Полный /ospf/catalog; одновременно SVG topology с distinct router IDs. Нужен профиль всего экрана, не только таблицы. | | [probes-schedule-data-grid.tsx](../components/data-grids/probes-schedule-data-grid.tsx) | Только demo ScheduleTab/INIT_RULES3; live использует ProbesSpeedSchedule. Demo список расширяется пользователем; не доказательство live API. | @@ -31,7 +30,7 @@ | [subusers-data-grid.tsx](../components/data-grids/subusers-data-grid.tsx) | Поиск импортов/JSX в app/components не нашёл потребителя: компонент экспортируется, но не подключён. Не доказывает дефект экрана /users. | | [traffic-flows-data-grid.tsx](../components/data-grids/traffic-flows-data-grid.tsx) | FlowAnalyticsPanel/conversationsList: backend traffic-flow-analytics.ts top=min(50,max(10,settings.topN)), slice(0,top). Это top slice, не полный каталог/серверная pagination. Проверить сообщение об ограничении50 и связывание с графиками. | -Очередь: Firewall stale guard/редактор сценария → OSPF таблица+topology → optimizer preview/комбинаторика. Scheduler11, traffic cap50 и commRecs24 проверять на реальной границе, не искусственно подставлять1000 в недоступный DTO. Demo и неиспользуемый Subusers отделены от live приёмки. Все новые профили должны сохранять search/sort/refresh/clamp, точный target и права,320px и URL scope. +Очередь: Firewall trace/cleanup → OSPF таблица+topology → optimizer preview/комбинаторика. Scheduler11, traffic cap50 и commRecs24 проверять на реальной границе, не искусственно подставлять1000 в недоступный DTO. Demo и неиспользуемый Subusers отделены от live приёмки. Все новые профили должны сохранять search/sort/refresh/clamp, точный target и права,320px и URL scope. Принятые после ранней карты: OSPF routes/BFD, WireGuard interfaces/peers/detail, VXLAN/GRE tunnels. Pagination сохраняет полный DTO в памяти и не доказывает серверную производительность. Текущий журнал: [ux-ui-progress.md](ux-ui-progress.md). @@ -86,3 +85,5 @@ Statistics pivot в backend/src/services/statistics-aggregate.ts огранич 12.3.43: Firewall1000/253248 JSON bytes →50 rows/3054 nodes/2082ms dev ready. Cross-page кнопки/точный RouterOS destination/DnD page2/403/search/cache/clamp/320px/axe проверены;35+1 main/context4/type/lint/build прошли. Stale operations после полного GET failure требуют12.3.44; большой multi-server sort отдельно не проверен.11 manualPagination:true остаются. 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 остаются. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 8baf8574..a80856be 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: сценарий Firewall; затем 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/15529 nodes; затем 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 2f2e8616..247dad0b 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.45 — большой редактор сценария Firewall (2026-10-04) + +ReUI pagination50/clamp/ScrollArea, закреплённые номер и chain, действия44px без hover; номера и up/down края считаются по полному draft. ScenarioSheet320px занимает всю ширину viewport. Кнопки редактирования/удаления сценария получили44px и доступные имена. Локальный сценарий не отправляет операции RouterOS. + +Финальные6/6 browser (tables и large scenario) прошли.1000 правил →50 rows/1847 modal nodes. Проверены down50 через страницу/up51 обратно/delete51, полный сохранённый localStorage draft999/точный порядок/последнее правило неизменно,320px/полная кнопка44px/axe. Симуляция вычисляет скрытое последнее правило #999 и блокирует пакет; API writes0. TypeScript без incremental, ESLint grid/test и production build прошли. [Сценарий320px](ux-ui/screens/large-firewall-scenario-320.png) просмотрен. Четыре исходных lint errors страницы/2 warnings из12.3.44 остаются; отказ записи localStorage отдельно не проверен. + +Обнаружено: 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.44 — блокировка устаревших операций Firewall (2026-10-04) readTrusted отделён от текста dataError: после HTTP/DTO failure кэш сохраняется, но toggle/edit/delete/create/move/address-list/history restore заблокированы. Очистка ошибки при retry не разблокирует действия до корректного ответа. Существующие partial failures дополнительно сохраняют scoped блокировку; поздний read после мутации не сбрасывает подтверждение. Статус объясняет блокировку и сохранённый список, операции RouterOS не изменены. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 6d6095ac..dd162789 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -551,3 +551,12 @@ readTrusted отделён от текста dataError: после HTTP/DTO fail Следующий блок12.3.45: большой редактор локального сценария Firewall — pagination/глобальный порядок/сохранение всего draft/симуляция/320px. Затем OSPF таблица+topology/optimizer.11 manualPagination:true остаются. Общая main/context после delta, lint cleanup Firewall и внешняя12.5 отдельно. Коммит локальный без push. + +## 12.3.45 — большой редактор сценария Firewall (2026-10-04) + +ReUI pagination50/clamp/ScrollArea, закреплённые номер и chain, действия44px без hover; номера и up/down края считаются по полному draft. ScenarioSheet320px занимает всю ширину viewport. Кнопки редактирования/удаления сценария получили44px и доступные имена. Локальный сценарий не отправляет операции RouterOS. + +Финальные6/6 browser (tables и large scenario) прошли.1000 правил →50 rows/1847 modal nodes. Проверены down50 через страницу/up51 обратно/delete51, полный сохранённый localStorage draft999/точный порядок/последнее правило неизменно,320px/полная кнопка44px/axe. Симуляция вычисляет скрытое последнее правило #999 и блокирует пакет; API writes0. TypeScript без incremental, ESLint grid/test и production build прошли. [Сценарий320px](ux-ui/screens/large-firewall-scenario-320.png) просмотрен. Четыре исходных lint errors страницы/2 warnings из12.3.44 остаются; отказ записи localStorage отдельно не проверен. + +Обнаружено: 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. + diff --git a/docs/ux-ui/screens/large-firewall-scenario-320.png b/docs/ux-ui/screens/large-firewall-scenario-320.png new file mode 100644 index 0000000000000000000000000000000000000000..c634053f75a01d99c639a3d349d44beae1d96809 GIT binary patch literal 54072 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 { + test.setTimeout(90000) + const packet = { chain: "forward", proto: "tcp", srcAddr: "10.10.0.100", dstAddr: "142.250.74.110", srcPort: "54321", dstPort: "443", inIface: "lan", outIface: "wan", connState: "new", srcAddrList: "", dstAddrList: "", tlsHost: "" } + const rules = Array.from({ length: 1000 }, (_, i) => ({ id: `s${i + 1}`, chain: "forward", action: i === 999 ? "drop" : "passthrough", proto: "all", src: "", dst: "", port: "", iface: "", comment: `scenario-${i + 1}`, enabled: true })) + const scenarios = [{ id: "big", name: "Сценарий1000", description: "", packet, rules, createdAt: 1 }] + await page.addInitScript(value => localStorage.setItem("routerlists-fw-scenarios", JSON.stringify(value)), scenarios) + let writes = 0 + await page.route("**/health", r => r.fulfill({ json: { ok: true } })) + await page.route("**/api/**", r => { + if (r.request().method() !== "GET") writes++ + const p = new URL(r.request().url()).pathname + return r.fulfill({ json: p === "/api/auth/config" ? { required: false } : p === "/api/firewall/all" ? { rules: [], addressLists: [] } : p === "/api/servers" ? [{ id: 1, name: "Роутер", host: "192.0.2.1", type: "exit-node", enabled: true, country: "RU", status: "online" }] : {} }) + }) + await page.goto("/firewall?server=1") + await page.getByRole("button", { name: "Симулятор", exact: true }).click() + await page.getByRole("button", { name: "Редактировать сценарий Сценарий1000", exact: true }).click() + const dialog = page.getByRole("dialog"), rows = dialog.locator("tr[data-row-id]") + await expect(rows).toHaveCount(50) + console.log("Scenario profile", JSON.stringify({ count: 1000, rows: await rows.count(), modalNodes: await dialog.locator("*").count() })) + await expect(dialog.getByRole("button", { name: "Поднять правило 1", exact: true })).toBeDisabled() + await dialog.getByRole("button", { name: "Опустить правило 50", exact: true }).click() + await expect(rows.last()).toHaveAttribute("data-row-id", "s51") + await dialog.getByRole("button", { name: "Следующая страница", exact: true }).click() + await expect(rows.first()).toHaveAttribute("data-row-id", "s50") + await dialog.getByRole("button", { name: "Поднять правило 51", exact: true }).focus(); await page.keyboard.press("Enter") + await expect(rows.first()).toHaveAttribute("data-row-id", "s51") + await dialog.getByRole("button", { name: "Удалить правило 51", exact: true }).click() + await expect(rows.first()).toHaveAttribute("data-row-id", "s52") + await page.setViewportSize({ width: 320, height: 1000 }) + const remove = rows.first().getByRole("button", { name: "Удалить правило 51", exact: true }) + await remove.scrollIntoViewIfNeeded() + await dialog.locator('[data-slot="scroll-area-viewport"]').evaluate(el => { el.scrollLeft = el.scrollWidth }) + await expect(remove).toBeInViewport(); const box = (await remove.boundingBox())! + expect(box.height).toBeGreaterThanOrEqual(44); expect(box.width).toBeGreaterThanOrEqual(44); expect(box.x + box.width).toBeLessThanOrEqual(320) + expect((await dialog.boundingBox())?.width).toBe(320) + expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(320) + await page.addScriptTag({ content: axe.source }) + expect((await page.evaluate(async () => (window as unknown as { axe: typeof axe }).axe.run(document.querySelector('[role="dialog"] [data-slot="data-grid"]')!, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] } }))).violations.map(v => ({ id: v.id, nodes: v.nodes.map(n => n.target) }))).toEqual([]) + await page.addStyleTag({ content: "nextjs-portal{display:none!important}" }) + await page.screenshot({ path: info.outputPath("scenario-320.png"), animations: "disabled" }) + await dialog.getByRole("button", { name: "Сохранить изменения", exact: true }).click() + await expect(dialog).toHaveCount(0) + const saved = await page.evaluate(() => JSON.parse(localStorage.getItem("routerlists-fw-scenarios")!)) + expect(saved[0].rules).toHaveLength(999) + expect(saved[0].rules.slice(48,52).map((r: { id: string }) => r.id)).toEqual(["s49", "s50", "s52", "s53"]) + expect(saved[0].rules[998]).toEqual(rules[999]) + await expect(page.getByRole("button", { name: "Активен", exact: true })).toBeVisible() + 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(page.getByText("Пакет заблокирован", { exact: false })).toBeVisible() + console.log("Scenario simulation nodes", await page.locator("*").count()) + expect(writes).toBe(0) +})