diff --git a/app/(main)/wireguard/page.tsx b/app/(main)/wireguard/page.tsx index c2bcb68e..24c36fac 100644 --- a/app/(main)/wireguard/page.tsx +++ b/app/(main)/wireguard/page.tsx @@ -206,6 +206,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf const [exportPeerId, setExportPeerId] = useState(null) const [peerIface, setPeerIface] = useState(null) const [pendingDelete, setPendingDelete] = useState(null) + const pendingPeer = pendingDelete?.kind === "peer" ? pendingDelete.iface.peers.find((peer, index) => peerRowId(pendingDelete.iface, peer, index) === pendingDelete.peerId) : undefined const deleteLock = useRef(false) const [deleteBusy, setDeleteBusy] = useState(false) const [deleteError, setDeleteError] = useState(null) @@ -973,11 +974,11 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf {pendingDelete?.kind === "peer" ? "Удалить пира?" : "Удалить интерфейс?"} - + {pendingDelete?.kind === "iface" ? `${pendingDelete.iface.name} на ${pendingDelete.iface.serverName}. Вместе с интерфейсом будут удалены связанные пиры на роутере.` : pendingDelete - ? `Пир на ${pendingDelete.iface.name} (${pendingDelete.iface.serverName}).` + ? `Пир «${pendingPeer?.name || pendingPeer?.publicKey || pendingDelete.peerId}» (ID ${pendingDelete.peerId}) на ${pendingDelete.iface.name} (${pendingDelete.iface.serverName}).` : null} diff --git a/components/data-grids/wireguard-peers-grid.tsx b/components/data-grids/wireguard-peers-grid.tsx index f744b278..cd55f6cc 100644 --- a/components/data-grids/wireguard-peers-grid.tsx +++ b/components/data-grids/wireguard-peers-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, type ReactNode } from "react" +import { useEffect, useMemo, type ReactNode } from "react" import { type ColumnDef, useTable, @@ -57,6 +57,7 @@ function WireguardPeersGrid({ const cols: ColumnDef[] = [ { id: "peer", + size: 144, accessorKey: "publicKey", header: ({ column }) => ( @@ -80,6 +81,7 @@ function WireguardPeersGrid({ }, { id: "iface", + size: 120, accessorKey: "ifaceName", header: ({ column }) => , cell: ({ row }) => ( @@ -96,6 +98,7 @@ function WireguardPeersGrid({ if (!compactServer) { cols.push({ id: "server", + size: 128, accessorKey: "serverName", header: ({ column }) => , cell: ({ row }) => ( @@ -115,6 +118,7 @@ function WireguardPeersGrid({ cols.push( { id: "allowedIps", + size: 136, accessorFn: (row) => row.allowedIps.join(", "), header: ({ column }) => , cell: ({ row }) => ( @@ -130,6 +134,7 @@ function WireguardPeersGrid({ }, { id: "handshake", + size: 144, accessorKey: "latestHandshake", header: ({ column }) => , cell: ({ row }) => ( @@ -150,6 +155,7 @@ function WireguardPeersGrid({ }, { id: "transfer", + size: 160, header: () => ( RX / TX ), @@ -174,6 +180,7 @@ function WireguardPeersGrid({ }, { id: "endpoint", + size: 144, accessorKey: "endpoint", header: ({ column }) => , cell: ({ row }) => ( @@ -191,7 +198,7 @@ function WireguardPeersGrid({ id: "actions", header: () => Действия, enableSorting: false, - size: 72, + size: 112, cell: ({ row }) => { const peer = row.original return ( @@ -201,7 +208,7 @@ function WireguardPeersGrid({ type="button" variant="ghost" size="icon" - className="size-7" + className="size-11" aria-label="Экспорт peer .conf" onClick={() => onExportPeer(peer)} > @@ -213,7 +220,7 @@ function WireguardPeersGrid({ type="button" variant="ghost" size="icon" - className="size-7 text-destructive" + className="size-11 text-destructive" aria-label="Удалить пира" onClick={() => onDeletePeer(peer)} > @@ -235,12 +242,20 @@ function WireguardPeersGrid({ const table = useTable({ features: dataGridFeatures, - manualPagination: true, + manualPagination: false, + autoResetPageIndex: false, + initialState: { pagination: { pageIndex: 0, pageSize: 50 }, columnPinning: { start: ["peer"], end: [] } }, data: peers, columns, getRowId: (row) => JSON.stringify([row.serverId, row.ifaceId, row.id]), }) + const { pageIndex, pageSize } = table.state.pagination + useEffect(() => { + const lastPage = Math.max(0, Math.ceil(peers.length / pageSize) - 1) + if (pageIndex > lastPage) table.setPageIndex(lastPage) + }, [peers.length, pageIndex, pageSize, table]) + if (peers.length === 0) { return ( 50 || pageSize !== 50} + tableLayout={{ dense: true, columnsPinnable: true }} tableClassNames={{ headerRow: "border-b border-border", bodyRow: cn("group/row"), }} - /> + > + + + + ) } diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 4443223f..4ca0fb12 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: большой peer grid; далее 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: большое раскрытие пиров; далее 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 b94337b3..faa04af9 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -36,6 +36,14 @@ +## 12.3.29 — большой каталог пиров WireGuard (2026-10-04) + +Peer grid теперь использует client pagination50, clamp и сохранение страницы при refresh. Полный поиск/сортировка выполняются до страницы, composite server/interface/peer ID сохранён. Штатные ReUI ScrollArea/start pinning удерживают идентификатор на320px, действия имеют44px цель. Подтверждение удаления теперь показывает имя и точный ID пира, а не только интерфейс. + +Профиль1000 пиров одного интерфейса/119347 JSON bytes:50 строк/2596 DOM nodes/1369ms ready в локальном dev с HTTP-моками. Проверены page20/refresh, поиск записи1000, сортировка по всему каталогу, выбранные server/interface/keep в URL,503/cache/page, zero/missing RX/TX,320px/axe grid,1000→1 и точный пир в dialog.29 старых browser прошли; после уточнения dialog повтор большого профиля и обоих delete-сценариев3/3. TypeScript без incremental/ESLint и production frontend build прошли. [Снимок320px](ux-ui/screens/large-wireguard-peers-320.png) просмотрен. + +Осталось17 manualPagination:true specialized grids, включая bounded/demo источники. Следующий блок12.3.30: ограничить DOM раскрытия большого списка пиров одного интерфейса. Восстановление WireGuard search/tab/status из URL, аудит подписи «онлайн» по факту handshake и внешняя12.5 остаются; presence старого handshake не доказывает текущую доступность. Общая main/context после delta целиком ещё не повторена. Коммит локальный без push. + ## 12.3.28 — большой каталог интерфейсов WireGuard (2026-10-04) Полный API /wireguard не ограничивает каталог страницей. Таблица теперь использует client pagination50, глобальную сортировку, сохранение page/expanded при обновлении и clamp после сокращения списка. Composite ID server/interface сохранён. ReUI DataGridScrollArea и штатное start pinning удерживают имя на320px; длинные имена переносятся. Кнопка раскрытия доступна с клавиатуры, меню действий видно без hover и имеет44px цель. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index d8f28496..fde99649 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -407,3 +407,12 @@ UI проверяет Ping-каталог до публикации, сохра Поиск/вкладка/статус WireGuard пока локальные, сервер/interface контекст в URL сохраняется; восстановление остальных фильтров записано отдельным остатком. Осталось18 manualPagination:true specialized grids, включая bounded/demo источники; это не18 доказанных дефектов. Следующий блок12.3.29: полный peer grid1000/50 DOM/действия/320px. Unbounded раскрытие peers одного интерфейса и внешняя12.5 остаются. Коммит локальный без push. + +## 12.3.29 — большой каталог пиров WireGuard (2026-10-04) + +Peer grid теперь использует client pagination50, clamp и сохранение страницы при refresh. Полный поиск/сортировка выполняются до страницы, composite server/interface/peer ID сохранён. Штатные ReUI ScrollArea/start pinning удерживают идентификатор на320px, действия имеют44px цель. Подтверждение удаления теперь показывает имя и точный ID пира, а не только интерфейс. + +Профиль1000 пиров одного интерфейса/119347 JSON bytes:50 строк/2596 DOM nodes/1369ms ready в локальном dev с HTTP-моками. Проверены page20/refresh, поиск записи1000, сортировка по всему каталогу, выбранные server/interface/keep в URL,503/cache/page, zero/missing RX/TX,320px/axe grid,1000→1 и точный пир в dialog.29 старых browser прошли; после уточнения dialog повтор большого профиля и обоих delete-сценариев3/3. TypeScript без incremental/ESLint и production frontend build прошли. [Снимок320px](ux-ui/screens/large-wireguard-peers-320.png) просмотрен. + +Осталось17 manualPagination:true specialized grids, включая bounded/demo источники. Следующий блок12.3.30: ограничить DOM раскрытия большого списка пиров одного интерфейса. Восстановление WireGuard search/tab/status из URL, аудит подписи «онлайн» по факту handshake и внешняя12.5 остаются; presence старого handshake не доказывает текущую доступность. Общая main/context после delta целиком ещё не повторена. Коммит локальный без push. + diff --git a/docs/ux-ui/screens/large-wireguard-peers-320.png b/docs/ux-ui/screens/large-wireguard-peers-320.png new file mode 100644 index 00000000..e9f862fc Binary files /dev/null and b/docs/ux-ui/screens/large-wireguard-peers-320.png differ diff --git a/tests/e2e/large-wireguard-peers.spec.ts b/tests/e2e/large-wireguard-peers.spec.ts new file mode 100644 index 00000000..f6e24a93 --- /dev/null +++ b/tests/e2e/large-wireguard-peers.spec.ts @@ -0,0 +1,54 @@ +import { test, expect } from "@playwright/test" +import { wgListResponseSchema } from "../../packages/contracts/src/wireguard" +import axe from "axe-core" +test("WireGuard1000 peers: full search/sort/page/cache/target/320px", async ({ page }, info) => { + test.setTimeout(90000) + let peers = Array.from({ length: 1000 }, (_, i) => ({ id: "p" + (i + 1), rosId: "*P" + (i + 1), publicKey: "key-" + String(i + 1).padStart(4, "0"), name: "Пир " + String(i + 1).padStart(4, "0"), allowedIps: ["10." + Math.floor(i/256) + "." + (i%256) + ".1/32"], transferRx: 0 })) + const iface = { id: "*1", rosId: "*1", name: "wg-main", serverId: "1", serverName: "Роутер", serverCountry: "RU", listenPort: 13231, mtu: 1420, 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 path = new URL(r.request().url()).pathname + if (r.request().method() !== "GET") writes.push(path) + if (path === "/api/wireguard") { calls++; return r.fulfill(failed ? { status: 503, json: { error: "Каталог WireGuard недоступен" } } : { json: wgListResponseSchema.parse({ interfaces: [{ ...iface, peers }] }) }) } + return r.fulfill({ json: path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? [{ id: 1, name: "Роутер", host: "192.0.2.1", country: "RU", enabled: true, status: null }] : {} }) + }) + const started = Date.now(); await page.goto("/wireguard?server=1&interface=*1&keep=1") + await page.getByRole("tab", { name: /Пиры/ }).click() + const rows = page.locator("tr[data-row-id]"); await expect(rows).toHaveCount(50) + const profile = { count: peers.length, jsonBytes: Buffer.byteLength(JSON.stringify(peers)), rows: await rows.count(), nodes: await page.locator("*").count(), readyMs: Date.now()-started } + console.log("WG peers 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("Пир 0951") + const before=calls; await page.getByRole("button", { name: "Обновить", exact: true }).click() + await expect.poll(()=>calls).toBeGreaterThan(before); await expect(rows.first()).toContainText("Пир 0951") + const search=page.getByPlaceholder("Поиск по ключу, IP, endpoint…") + await search.fill("key-1000"); await expect(rows).toHaveCount(1); await expect(rows.first()).toContainText("Пир 1000") + await expect(page).toHaveURL(url=>url.searchParams.get("server")==="1"&&url.searchParams.get("interface")==="*1"&&url.searchParams.get("keep")==="1") + await search.fill(""); await expect(rows).toHaveCount(50) + await page.getByRole("table").getByRole("button", { name: "Пир", exact: true }).click() + await expect(rows.first()).toContainText("Пир 0001") + await page.getByRole("table").getByRole("button", { name: "Пир", exact: true }).click() + await expect(rows.first()).toContainText("Пир 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("status", { name: "WireGuard по ссылке", exact:true })).toContainText("сохранённые данные могут быть устаревшими") + await expect(rows).toHaveCount(50); await expect(rows.first()).toHaveAttribute("data-row-id",previous!) + await expect(rows.first().getByLabel("RX",{exact:true})).toHaveText("0 Б"); await expect(rows.first().getByLabel("TX",{exact:true})).toHaveText("—") + await page.setViewportSize({width:320,height:1000}) + const scroll=page.locator('[data-slot="data-grid"] [data-slot="scroll-area-viewport"]') + await rows.first().scrollIntoViewIfNeeded(); await scroll.evaluate(el=>{el.scrollLeft=el.scrollWidth}) + await expect.poll(()=>scroll.evaluate(el=>el.scrollLeft)).toBeGreaterThan(0) + 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(rows.first().getByRole("button",{name:"Экспорт peer .conf",exact:true})).toBeInViewport() + expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true) + 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("wireguard-peers-320.png"),animations:"disabled"}) + failed=false;peers=[peers[999]];await page.getByRole("button",{name:"Обновить",exact:true}).click() + await expect(rows).toHaveCount(1);await expect(rows.first()).toContainText("Пир 1000") + await rows.first().getByRole("button",{name:"Удалить пира",exact:true}).click() + await expect(page.getByRole("alertdialog")).toContainText("Пир 1000");await page.getByRole("alertdialog").getByRole("button",{name:"Отмена",exact:true}).click() + expect(writes).toEqual([]) +})