diff --git a/components/data-grids/gre-tunnels-data-grid.tsx b/components/data-grids/gre-tunnels-data-grid.tsx index e13216c7..d24524b5 100644 --- a/components/data-grids/gre-tunnels-data-grid.tsx +++ b/components/data-grids/gre-tunnels-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, @@ -129,6 +129,7 @@ function GreTunnelsDataGrid({ () => [ { id: "name", + size: 160, accessorKey: "name", header: ({ column }) => ( @@ -138,7 +139,7 @@ function GreTunnelsDataGrid({ const srv = serverMap.get(t.serverId) return (
-

{t.name}

+

{t.name}

{srv && } {srv?.name ?? t.serverId} @@ -154,6 +155,7 @@ function GreTunnelsDataGrid({ }, { id: "endpoints", + size: 144, header: () => Эндпоинты, enableSorting: false, cell: ({ row }) => { @@ -175,6 +177,7 @@ function GreTunnelsDataGrid({ }, { id: "innerIp", + size: 136, header: () => Внутренний IP, enableSorting: false, cell: ({ row }) => { @@ -190,6 +193,7 @@ function GreTunnelsDataGrid({ }, { id: "pool", + size: 96, accessorKey: "poolId", header: ({ column }) => , cell: ({ row }) => ( @@ -201,6 +205,7 @@ function GreTunnelsDataGrid({ }, { id: "ipsec", + size: 112, header: () => IPsec, enableSorting: false, cell: ({ row }) => , @@ -208,6 +213,7 @@ function GreTunnelsDataGrid({ }, { id: "encryption", + size: 160, header: () => Шифрование, enableSorting: false, cell: ({ row }) => { @@ -236,6 +242,7 @@ function GreTunnelsDataGrid({ }, { id: "mtu", + size: 72, accessorKey: "mtu", header: ({ column }) => , cell: ({ row }) => ( @@ -249,6 +256,7 @@ function GreTunnelsDataGrid({ }, { id: "keepalive", + size: 104, header: () => Keepalive, enableSorting: false, cell: ({ row }) => { @@ -263,6 +271,7 @@ function GreTunnelsDataGrid({ }, { id: "status", + size: 112, accessorKey: "status", header: ({ column }) => , cell: ({ row }) => unknownServerIds?.has(row.original.serverId) ? Неизвестно : , @@ -281,9 +290,10 @@ function GreTunnelsDataGrid({ variant="ghost" size="icon" className={cn( - "size-7 opacity-0 transition-opacity group-hover/row:opacity-100 focus-visible:opacity-100", + "size-11 shrink-0", )} title="Предпросмотр кода RouterOS" + aria-label={`Просмотр кода: ${t.name}`} onClick={(e) => { e.stopPropagation() onCodePreview(t) @@ -299,8 +309,8 @@ function GreTunnelsDataGrid({ variant="outline" size="icon" className={cn( - "size-8 shrink-0 border-border/60 bg-background/80 text-muted-foreground shadow-none", - "opacity-0 transition-opacity group-hover/row:opacity-100 focus-visible:opacity-100", + "size-11 shrink-0 border-border/60 bg-background/80 text-muted-foreground shadow-none", + "opacity-100", "data-popup-open:opacity-100", )} aria-label={`Действия: ${t.name}`} @@ -338,7 +348,7 @@ function GreTunnelsDataGrid({

) }, - size: 88, + size: 112, meta: { headerClassName: DATA_GRID_CELL_PAD_LAST, cellClassName: DATA_GRID_CELL_PAD_LAST }, }, ], @@ -347,12 +357,20 @@ function GreTunnelsDataGrid({ const table = useTable({ features: dataGridFeatures, - manualPagination: true, + manualPagination: false, + autoResetPageIndex: false, + initialState: { pagination: { pageIndex: 0, pageSize: 50 }, columnPinning: { start: ["name"], end: [] } }, data: tunnels, columns, getRowId: row => JSON.stringify([row.serverId, row.id]), }) + const { pageIndex, pageSize } = table.state.pagination + useEffect(() => { + const lastPage = Math.max(0, Math.ceil(tunnels.length / pageSize) - 1) + if (pageIndex > lastPage) table.setPageIndex(lastPage) + }, [tunnels.length, pageIndex, pageSize, table]) + if (tunnels.length === 0) { return ( 50 || pageSize !== 50} + tableLayout={{ dense: true, columnsPinnable: true }} tableClassNames={{ headerRow: "border-b border-border", bodyRow: "group/row" }} - /> + > + + + + ) } diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 2baad0eb..f989a3aa 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: большой каталог GRE tunnels; далее network grids и полная 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: карта оставшихся специализированных источников и performance рисков; далее network grids и полная 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 3d3bc9bd..b3d89e8b 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -29,7 +29,15 @@ | 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.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 принят; остаются GRE/network grids/общая регрессия после delta/внешняя приёмка | +| 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.35 — большой каталог GRE-туннелей (2026-10-04) + +GET /api/gre/tunnels возвращает полный каталог без ограничения строк. ReUI client pagination50 ограничивает DOM; refresh сохраняет страницу, уменьшение/поиск корректирует её, MTU сортируется по всему каталогу. На320px имя закреплено, обе кнопки кода/меню видны без hover и имеют44px; composite identity и существующие permissions/mutation guards сохранены. + +Профиль1000 туннелей/351894 JSON bytes:50 строк/2748 DOM nodes/1609ms ready в dev с HTTP-моками.20 browser (large/links/freshness/toggle/delete/IPsec-GRE tables/layouts) прошли; large повторён1/1 с проверкой disabled edit/delete после503. Поиск записи1000, page20/refresh/global MTU sort, cache/page/unknown,320px/axe/pinning, устаревший export snapshot и1000→1 с точным MTU2399 проверены. TypeScript без incremental/ESLint прошли; production frontend build прошёл. [Снимок320px](ux-ui/screens/large-gre-320.png) просмотрен. + +Осталось15 manualPagination:true специализированных таблиц; GRE pools в live всегда пусты и доступны только как demo, поэтому их пагинация не приоритет для live. Firewall использует DnD исходного порядка; OSPF neighbors дополнительно рисует SVG topology. Следующий блок12.3.36: карта оставшихся источников/ограничений и безопасная последовательность performance доработок. Поиск GRE пока локальный; реальные RouterOS/PG и внешняя12.5 не проверены. Общая main/context регрессия после delta остаётся отдельной. Коммит локальный без push. ## 12.3.34 — большой каталог VXLAN (2026-10-04) diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 4675f392..97c86a83 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -463,3 +463,12 @@ Scope proof ограничен HTTP-моками и read/toggle context сцен Осталось16 manualPagination:true specialized grids, включая bounded/demo источники. VXLAN поиск пока локальный; server/tunnel URL контекст сохранён. Следующий блок12.3.35: GRE tunnels1000/50 DOM/search/sort/refresh/действия/320px. OSPF neighbors сопровождаются большим SVG графом, поэтому ограничение одной таблицы не докажет производительность всего экрана; отдельный аудит topology остаётся. Общая main/context после delta и внешняя12.5 отдельно. Коммит локальный без push. + +## 12.3.35 — большой каталог GRE-туннелей (2026-10-04) + +GET /api/gre/tunnels возвращает полный каталог без ограничения строк. ReUI client pagination50 ограничивает DOM; refresh сохраняет страницу, уменьшение/поиск корректирует её, MTU сортируется по всему каталогу. На320px имя закреплено, обе кнопки кода/меню видны без hover и имеют44px; composite identity и существующие permissions/mutation guards сохранены. + +Профиль1000 туннелей/351894 JSON bytes:50 строк/2748 DOM nodes/1609ms ready в dev с HTTP-моками.20 browser (large/links/freshness/toggle/delete/IPsec-GRE tables/layouts) прошли; large повторён1/1 с проверкой disabled edit/delete после503. Поиск записи1000, page20/refresh/global MTU sort, cache/page/unknown,320px/axe/pinning, устаревший export snapshot и1000→1 с точным MTU2399 проверены. TypeScript без incremental/ESLint прошли; production frontend build прошёл. [Снимок320px](ux-ui/screens/large-gre-320.png) просмотрен. + +Осталось15 manualPagination:true специализированных таблиц; GRE pools в live всегда пусты и доступны только как demo, поэтому их пагинация не приоритет для live. Firewall использует DnD исходного порядка; OSPF neighbors дополнительно рисует SVG topology. Следующий блок12.3.36: карта оставшихся источников/ограничений и безопасная последовательность performance доработок. Поиск GRE пока локальный; реальные RouterOS/PG и внешняя12.5 не проверены. Общая main/context регрессия после delta остаётся отдельной. Коммит локальный без push. + diff --git a/docs/ux-ui/screens/large-gre-320.png b/docs/ux-ui/screens/large-gre-320.png new file mode 100644 index 00000000..41103da8 Binary files /dev/null and b/docs/ux-ui/screens/large-gre-320.png differ diff --git a/tests/e2e/large-gre.spec.ts b/tests/e2e/large-gre.spec.ts new file mode 100644 index 00000000..39fc18b5 --- /dev/null +++ b/tests/e2e/large-gre.spec.ts @@ -0,0 +1,31 @@ +import { test, expect } from "@playwright/test" +import { greResponseSchema } from "../../lib/gre-response" +import axe from "axe-core" +test("GRE1000: page/refresh/full search/sort/cache/export/320px",async({page},info)=>{ + test.setTimeout(90000) + let tunnels=Array.from({length:1000},(_,i)=>({id:"*"+(i+1),name:"gre-test-"+String(i+1).padStart(4,"0"),serverId:"1",localAddress:"192.0.2.1",remoteAddress:"198.51.100.1",localInnerIp:"10.0.0.1/30",remoteInnerIp:"10.0.0.2/30",poolId:"live",ipsec:null,mtu:1400+i,keepaliveInterval:10,keepaliveRetries:3,dscp:"inherit",clampTcpMss:true,allowFastPath:true,comment:"",enabled:true,status:"up"})) + let failed=false,calls=0;const writes:string[]=[] + await page.route("**/health",r=>r.fulfill({json:{ok:true}})) + await page.route("**/api/**",r=>{const p=new URL(r.request().url()).pathname;if(r.request().method()!=="GET")writes.push(p);if(p==="/api/gre/tunnels"){calls++;return r.fulfill(failed?{status:503,json:{error:"Опрос GRE недоступен"}}:{json:greResponseSchema.parse({tunnels})})}return r.fulfill({json:p==="/api/auth/config"?{required:false}:p==="/api/servers"?[{id:1,name:"Роутер",host:"192.0.2.1",country:"RU",enabled:true,status:null}]:{}})}) + const started=Date.now();await page.goto("/gre?server=1&keep=1") + const rows=page.locator("tr[data-row-id]");await expect(rows).toHaveCount(50) + const profile={count:tunnels.length,jsonBytes:Buffer.byteLength(JSON.stringify(tunnels)),rows:await rows.count(),nodes:await page.locator("*").count(),readyMs:Date.now()-started};console.log("GRE profile",JSON.stringify(profile));await info.attach("profile",{body:JSON.stringify(profile),contentType:"application/json"}) + const next=page.getByRole("button",{name:"Следующая страница",exact:true}) + for(let i=0;i<19;i++)await next.click() + await expect(next).toBeDisabled();await expect(rows.first()).toContainText("gre-test-0951") + const before=calls;await page.getByRole("button",{name:"Обновить",exact:true}).click();await expect.poll(()=>calls).toBeGreaterThan(before);await expect(rows.first()).toContainText("gre-test-0951") + const search=page.getByPlaceholder("Поиск по имени, IP…");await search.fill("gre-test-1000");await expect(rows).toHaveCount(1);await expect(rows.first()).toContainText("gre-test-1000");await search.fill("");await expect(rows).toHaveCount(50) + await page.getByRole("table").getByRole("button",{name:"MTU",exact:true}).click();await expect(rows.first()).toContainText("gre-test-1000") + 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.getByRole("alert").filter({hasText:"Опрос GRE недоступен"})).toBeVisible();await expect(rows).toHaveCount(50);await expect(rows.first()).toHaveAttribute("data-row-id",previous!);await expect(rows.first()).toContainText("Неизвестно") + await page.setViewportSize({width:320,height:1000}) + const action=rows.first().getByRole("button",{name:/Действия:/}),scroll=page.locator('[data-slot="data-grid"] [data-slot="scroll-area-viewport"]') + await action.scrollIntoViewIfNeeded();await scroll.evaluate(el=>{el.scrollLeft=el.scrollWidth});await expect(action).toBeInViewport() + expect(await rows.first().getByRole("cell").first().evaluate(el=>{const a=el.getBoundingClientRect(),v=el.closest('[data-slot="scroll-area-viewport"]')!.getBoundingClientRect();return a.left>=v.left-1&&a.right<=v.right+1})).toBe(true) + 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("gre-320.png"),animations:"disabled"}) + await action.click();await expect(page.getByRole("menuitem",{name:"Редактировать",exact:true})).toBeDisabled();await expect(page.getByRole("menuitem",{name:"Удалить туннель",exact:true})).toBeDisabled();await page.getByRole("menuitem",{name:"Просмотр кода",exact:true}).click();await expect(page.getByRole("dialog")).toContainText("актуальность не подтверждена");await page.keyboard.press("Escape") + failed=false;tunnels=[tunnels[999]];await page.getByRole("button",{name:"Обновить",exact:true}).click();await expect(rows).toHaveCount(1);await expect(rows.first()).toContainText("gre-test-1000") + await action.click();await page.getByRole("menuitem",{name:"Просмотр кода",exact:true}).click();await expect(page.getByRole("dialog")).toContainText("2399");await expect(page.getByRole("dialog")).toContainText("gre-test-1000");expect(writes).toEqual([]) +})