diff --git a/components/data-grids/filters-data-grid.tsx b/components/data-grids/filters-data-grid.tsx index 09e28bd4..bc1e4dfe 100644 --- a/components/data-grids/filters-data-grid.tsx +++ b/components/data-grids/filters-data-grid.tsx @@ -1,7 +1,7 @@ "use client" -import { useMemo } from "react" -import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" +import { useEffect, useMemo } from "react" +import { DataGridContainer, DataGridScrollArea, DataGridTable, dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" import { type ColumnDef, useTable, @@ -154,11 +154,12 @@ function FilterRowActions({ onDelete: () => void }) { return ( -
+
@@ -253,18 +256,19 @@ function FiltersDataGrid({ onClick={() => onMoveDown(row.original.id)} aria-label={`Опустить правило ${row.original.community}`} disabled={mutationsLocked || index < 0 || isLast} - className="text-muted-foreground/50 hover:text-foreground disabled:opacity-20" + className="size-11 shrink-0 inline-flex items-center justify-center rounded text-muted-foreground hover:bg-muted disabled:opacity-40" >
) }, - size: 28, + size: 104, meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "community", + size: 120, accessorKey: "community", header: ({ column }) => , enableSorting, @@ -301,6 +305,7 @@ function FiltersDataGrid({ }, { id: "gateway", + size: 192, accessorKey: "gateway", header: ({ column }) => , enableSorting, @@ -316,6 +321,7 @@ function FiltersDataGrid({ }, { id: "description", + size: 240, accessorKey: "description", header: ({ column }) => , enableSorting, @@ -335,13 +341,14 @@ function FiltersDataGrid({ onDelete={() => onDelete(row.original.id)} /> ), - size: 64, + size: 112, meta: { headerClassName: DATA_GRID_CELL_PAD_LAST, cellClassName: DATA_GRID_CELL_PAD_LAST }, }, ], [ communityNameMap, orderedRules, + orderedIndex, mutationsLocked, enableSorting, onDelete, @@ -356,12 +363,21 @@ function FiltersDataGrid({ const table = useTable({ features: dataGridFeatures, - manualPagination: true, + manualPagination: false, + autoResetPageIndex: false, + initialState: { pagination: { pageIndex: 0, pageSize: 50 }, columnPinning: { start: ["priority", "community"], end: [] } }, + manualSorting: !enableSorting, data: rules, 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]) + if (rules.length === 0) { return ( - + 50 || pageSize !== 50} tableLayout={{ dense: true, columnsPinnable: true }}> + + + +
Последнее правило имеет наивысший приоритет в RouterOS diff --git a/docs/ux-ui-performance.md b/docs/ux-ui-performance.md index e46eb5d9..3f5268fd 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 —13 файлов после12.3.40. Это не15 дефектов: есть фиксированный источник, demo, неподключённый компонент и ограниченные срезы. Файлы ниже сопоставлены с действующими JSX-потребителями; ограничения установлены чтением кода, не нагрузочной приёмкой API. +Аудит12.3.36 от2026-10-04: точный поиск `manualPagination: true` в components/data-grids —12 файлов после12.3.42. Это не12 доказанных дефектов: есть фиксированный источник, 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 не соответствует источнику. | -| [filters-data-grid.tsx](../components/data-grids/filters-data-grid.tsx) | currentRules/filteredRules + orderedRules: move up/down относятся ко всему исходному порядку. Проверить границу страниц и точный target. | | [firewall-rules-data-grid.tsx](../components/data-grids/firewall-rules-data-grid.tsx) | filteredRules; reorderable отключает сортировку, DnD dataIds включает исходный массив. Ограничение DOM требует отдельной проверки cross-page порядка. | | [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 нет. | @@ -33,7 +32,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 и связывание с графиками. | -Очередь: Filters порядок → Firewall/DnD → OSPF таблица+topology → optimizer preview/комбинаторика. Scheduler11, traffic cap50 и commRecs24 проверять на реальной границе, не искусственно подставлять1000 в недоступный DTO. Demo и неиспользуемый Subusers отделены от live приёмки. Все новые профили должны сохранять search/sort/refresh/clamp, точный target и права,320px и URL scope. +Очередь: Firewall/DnD → 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). @@ -82,3 +81,5 @@ Statistics pivot в backend/src/services/statistics-aggregate.ts огранич 12.3.40: recursive1000 групп→50 rows/2118 nodes/1457ms; одна группа1000 endpoints→50 cards/1126 nodes.35 browser/type/lint/build прошли; большой RouteSheet пока вне принятого performance scope.13 manualPagination:true остаются. 12.3.41: recursive RouteSheet1000 endpoints ×1000 gateways →10 cards/1569 modal nodes, максимум5 gateway choices на карточку; полный PUT1001 сохранён.22+1 browser/type/lint/build. Расчёт/валидация полного draft остаётся на клиенте. + +12.3.42: Filters1000/120894 JSON bytes →50 rows/2266 nodes/1880ms dev ready. Global order/граница страниц/полный PUT/403/поиск/сортировка/refresh/cache/clamp/320px/axe проверены;10+1 browser/type/lint/build прошли.12 manualPagination:true остаются. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 99e0717a..d2745fe5 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: Filters pagination/порядок и полная 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: Firewall pagination/DnD, затем полная 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 8ad94727..1492ac1f 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.42 — большой список Filters (2026-10-04) + +ReUI pagination50/clamp/закреплённые номер и community; доступные действия44px без hover. Индекс и крайние позиции берутся из полного orderedRules через Map. Поиск сортирует весь срез, очистка поиска возвращает исходный порядок (manualSorting), перемещение отправляет все правила. + +10 профильных browser и финальный large1/1 прошли:1000/120894 JSON bytes →50 rows/2266 nodes/1880ms dev ready. Проверены down r50 через границу страницы, keyboard up обратно, полный PUT1000/403 rollback, search1000/global sort/clear, refresh/page/cache503/блокировка действий/1000→1,320px/ScrollArea/axe. TypeScript без incremental/ESLint и production build прошли. [Filters320px](ux-ui/screens/large-filters-320.png) просмотрен. DTO целиком остаётся в памяти; реальные RouterOS операции не проверены. Старое уведомление ошибки перемещения сохраняется после refresh; это отдельное состояние операции, не успех чтения. + +Осталось12 manualPagination:true specialized grids. Следующий блок12.3.43: Firewall1000/pagination/порядок DnD и действия, точный RouterOS destination через границу страниц. Общая main/context после delta и внешняя12.5 отдельно. Коммит локальный без push. + ## 12.3.41 — большая форма рекурсивного маршрута (2026-10-04) RouteSheet показывает10 endpoints, но хранит весь draft. Предыдущая/следующая страница и прямой переход по номеру endpoint доступны с клавиатуры; add открывает страницу нового элемента, удаление корректирует индекс. Валидация ищет ошибки во всём draft и открывает страницу первого невалидного endpoint. RecursiveGatewayChoices сохраняет прежние карточки/семантику статуса; показывает5 вариантов (выбранный первым) и ищет по всему каталогу IP/name. Ручной gateway ввод сохранён. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index a78a7c2f..a38de27b 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -524,3 +524,12 @@ RouteSheet показывает10 endpoints, но хранит весь draft. Следующий блок12.3.42: Filters1000/порядок через границу страниц/whole-catalog PUT/search/sort/refresh/cache/320px.13 manualPagination:true specialized grids остаются. Общая main/context после delta и внешняя12.5 отдельно. Коммит локальный без push. + +## 12.3.42 — большой список Filters (2026-10-04) + +ReUI pagination50/clamp/закреплённые номер и community; доступные действия44px без hover. Индекс и крайние позиции берутся из полного orderedRules через Map. Поиск сортирует весь срез, очистка поиска возвращает исходный порядок (manualSorting), перемещение отправляет все правила. + +10 профильных browser и финальный large1/1 прошли:1000/120894 JSON bytes →50 rows/2266 nodes/1880ms dev ready. Проверены down r50 через границу страницы, keyboard up обратно, полный PUT1000/403 rollback, search1000/global sort/clear, refresh/page/cache503/блокировка действий/1000→1,320px/ScrollArea/axe. TypeScript без incremental/ESLint и production build прошли. [Filters320px](ux-ui/screens/large-filters-320.png) просмотрен. DTO целиком остаётся в памяти; реальные RouterOS операции не проверены. Старое уведомление ошибки перемещения сохраняется после refresh; это отдельное состояние операции, не успех чтения. + +Осталось12 manualPagination:true specialized grids. Следующий блок12.3.43: Firewall1000/pagination/порядок DnD и действия, точный RouterOS destination через границу страниц. Общая main/context после delta и внешняя12.5 отдельно. Коммит локальный без push. + diff --git a/docs/ux-ui/screens/large-filters-320.png b/docs/ux-ui/screens/large-filters-320.png new file mode 100644 index 00000000..fdf0c4b6 Binary files /dev/null and b/docs/ux-ui/screens/large-filters-320.png differ diff --git a/tests/e2e/large-filters.spec.ts b/tests/e2e/large-filters.spec.ts new file mode 100644 index 00000000..1b24dc12 --- /dev/null +++ b/tests/e2e/large-filters.spec.ts @@ -0,0 +1,31 @@ +import { test, expect } from "@playwright/test" +import type { FilterRule } from "../../lib/data" +import axe from "axe-core" +test("Filters1000: global order/page boundary/full PUT/search/sort/cache/320px",async({page},info)=>{ + test.setTimeout(90000) + let rules:FilterRule[]=Array.from({length:1000},(_,i)=>({id:"r"+(i+1),community:"65001:"+String(i+1).padStart(5,"0"),gateway:"",gatewayTunnelId:"",action:"blackhole",description:"rule-"+String(i+1).padStart(4,"0")})) + let reads=0,failed=false,writeFail=false;const payloads:FilterRule[][]=[] + await page.route("**/health",r=>r.fulfill({json:{ok:true}})) + await page.route("**/api/**",r=>{const p=new URL(r.request().url()).pathname + if(p==="/api/filters/rules"&&r.request().method()==="PUT"){const body=r.request().postDataJSON();expect(body.serverId).toBe("1");expect(body.source).toBe("apply");payloads.push(body.rules);if(writeFail)return r.fulfill({status:403,json:{error:"Перемещение запрещено тест1000"}});rules=body.rules;return r.fulfill({json:{ok:true,rules}})} + if(p==="/api/filters/rules"){reads++;return r.fulfill(failed?{status:503,json:{error:"Каталог Filters недоступен тест"}}:{json:{rulesets:[{serverId:"1",rules}],greTunnels:[],live:true}})} + return r.fulfill({json:p==="/api/auth/config"?{required:false}:p==="/api/servers"?[{id:1,name:"Роутер",host:"192.0.2.1",country:"RU",type:"exit-node",enabled:true,status:null}]:p==="/api/filters/gre-tunnels"?{tunnels:[]}:p==="/api/recursive-routes"?{routes:[]}:{} }) + }) + const started=Date.now();await page.goto("/filters");const rows=page.locator("tr[data-row-id]");await expect(rows).toHaveCount(50) + const profile={count:1000,jsonBytes:Buffer.byteLength(JSON.stringify(rules)),rows:await rows.count(),nodes:await page.locator("*").count(),readyMs:Date.now()-started};console.log("Filters profile",JSON.stringify(profile));await info.attach("profile",{body:JSON.stringify(profile),contentType:"application/json"}) + await expect(rows.first().getByRole("button",{name:"Поднять правило 65001:00001",exact:true})).toBeDisabled() + await rows.last().getByRole("button",{name:"Опустить правило 65001:00050",exact:true}).click();await expect.poll(()=>payloads.length).toBe(1) + expect(payloads[0]).toHaveLength(1000);expect(payloads[0].slice(48,52).map(r=>r.id)).toEqual(["r49","r51","r50","r52"]);await expect(rows.last()).toHaveAttribute("data-row-id","r51") + const next=page.getByRole("button",{name:"Следующая страница",exact:true});await next.click();await expect(rows.first()).toHaveAttribute("data-row-id","r50") + await rows.first().getByRole("button",{name:"Поднять правило 65001:00050",exact:true}).focus();await page.keyboard.press("Enter");await expect.poll(()=>payloads.length).toBe(2);expect(payloads[1].slice(48,52).map(r=>r.id)).toEqual(["r49","r50","r51","r52"]);await expect(rows.first()).toHaveAttribute("data-row-id","r51") + writeFail=true;await rows.first().getByRole("button",{name:"Поднять правило 65001:00051",exact:true}).click();await expect(page.getByRole("alert").filter({hasText:"Перемещение запрещено тест1000"})).toBeVisible();await expect(rows.first()).toHaveAttribute("data-row-id","r51");writeFail=false + const before=reads;await page.getByRole("button",{name:"Обновить",exact:true}).click();await expect.poll(()=>reads).toBeGreaterThan(before);await expect(rows.first()).toHaveAttribute("data-row-id","r51") + const search=page.getByPlaceholder("Community, gateway, описание…");await search.fill("rule-1000");await expect(rows).toHaveCount(1);await expect(rows.first().getByRole("button",{name:"Опустить правило 65001:01000",exact:true})).toBeDisabled();await expect(rows.first().getByLabel("Наивысший приоритет",{exact:true})).toBeVisible() + await search.fill("rule");await expect(rows).toHaveCount(50);await page.getByRole("table").getByRole("button",{name:"Community",exact:true}).click();await page.getByRole("table").getByRole("button",{name:"Community",exact:true}).click();await expect(rows.first()).toHaveAttribute("data-row-id","r1000") + await search.fill("");await expect(rows.first()).toHaveAttribute("data-row-id","r1");await expect(page.getByRole("table").getByRole("button",{name:"Community",exact:true})).toHaveCount(0) + await next.click();const previous=await rows.first().getAttribute("data-row-id");failed=true;await page.getByRole("button",{name:"Обновить",exact:true}).click();await expect(page.getByText("Роутер недоступен — показан кэш. Изменения заблокированы, пока не удастся прочитать CHR.",{exact:true})).toBeVisible();await expect(rows.first()).toHaveAttribute("data-row-id",previous!);await expect(rows.first().getByRole("button",{name:"Редактировать правило",exact:true})).toBeDisabled() + await page.setViewportSize({width:320,height:1000});const edit=rows.first().getByRole("button",{name:"Редактировать правило",exact:true});await edit.scrollIntoViewIfNeeded();await page.locator('[data-slot="data-grid"] [data-slot="scroll-area-viewport"]').evaluate(el=>{el.scrollLeft=el.scrollWidth});await expect(edit).toBeInViewport();await expect.poll(()=>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('[data-slot="data-grid"]')!,{runOnly:{type:"tag",values:["wcag2a","wcag2aa","wcag21a","wcag21aa"]}}))).violations).toEqual([]) + await page.addStyleTag({content:"nextjs-portal{display:none!important}"});await page.screenshot({path:info.outputPath("filters-320.png"),animations:"disabled"}) + failed=false;rules=[rules[999]];await page.getByRole("button",{name:"Обновить",exact:true}).click();await expect(rows).toHaveCount(1);await expect(rows.first()).toHaveAttribute("data-row-id","r1000");expect(payloads).toHaveLength(3) +})