From e7d45d194ded457861311bd8227cf1c85603a86b Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sun, 13 Sep 2026 21:17:48 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=B8=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D1=8B?= =?UTF-8?q?=20WireGuard=20=D0=BD=D0=B0=20ReUI=20v9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/data-grids/wireguard-data-grid.tsx | 24 +++++----- .../data-grids/wireguard-peers-grid.tsx | 24 +++++----- docs/ux-ui-progress.md | 12 ++++- tests/e2e/wireguard-tables.spec.ts | 47 +++++++++++++++++++ 4 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 tests/e2e/wireguard-tables.spec.ts diff --git a/components/data-grids/wireguard-data-grid.tsx b/components/data-grids/wireguard-data-grid.tsx index d37e1806..28a6085f 100644 --- a/components/data-grids/wireguard-data-grid.tsx +++ b/components/data-grids/wireguard-data-grid.tsx @@ -1,13 +1,12 @@ "use client" +import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" + import { useMemo, type ReactNode } from "react" import { type ColumnDef, - getCoreRowModel, - getExpandedRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table-v8" + useTable, +} from "@tanstack/react-table" import type { WireGuardInterface } from "@/lib/data" import { Flag } from "@/components/flag" import { cn } from "@/lib/utils" @@ -20,13 +19,13 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" -import { DataGridShell } from "@/components/data-grids/shared/legacy-data-grid-shell" +import { DataGridShell } from "@/components/data-grids/shared/data-grid-shell" import { DATA_GRID_CELL_PAD, DATA_GRID_CELL_PAD_FIRST, DATA_GRID_CELL_PAD_LAST, -} from "@/components/data-grids/shared/legacy-data-grid-layout" -import { DataGridSortHeader } from "@/components/data-grids/shared/legacy-data-grid-sort-header" +} from "@/components/data-grids/shared/data-grid-layout" +import { DataGridSortHeader } from "@/components/data-grids/shared/data-grid-sort-header" import { WireGuardPeersDetail } from "@/components/data-grids/wireguard-peers-detail" import { EmptyState } from "@/components/empty-state" import { @@ -69,7 +68,7 @@ function WireguardDataGrid({ onDeletePeer, onExportPeer, }: WireguardDataGridProps) { - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { id: "name", @@ -269,12 +268,11 @@ function WireguardDataGrid({ [compactServer, onExport, onAddPeer, onToggle, onDelete, onDeletePeer, onExportPeer], ) - const table = useReactTable({ + const table = useTable({ + features: dataGridFeatures, + manualPagination: true, data: interfaces, columns, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - getExpandedRowModel: getExpandedRowModel(), getRowId: (row) => row.id, getRowCanExpand: () => true, }) diff --git a/components/data-grids/wireguard-peers-grid.tsx b/components/data-grids/wireguard-peers-grid.tsx index 3be65c17..8e0d2c16 100644 --- a/components/data-grids/wireguard-peers-grid.tsx +++ b/components/data-grids/wireguard-peers-grid.tsx @@ -1,23 +1,23 @@ "use client" +import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" + import { useMemo, type ReactNode } from "react" import { type ColumnDef, - getCoreRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table-v8" + useTable, +} from "@tanstack/react-table" import type { WireGuardPeer } from "@/lib/data" import { Flag } from "@/components/flag" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" -import { DataGridShell } from "@/components/data-grids/shared/legacy-data-grid-shell" +import { DataGridShell } from "@/components/data-grids/shared/data-grid-shell" import { DATA_GRID_CELL_PAD, DATA_GRID_CELL_PAD_FIRST, DATA_GRID_CELL_PAD_LAST, -} from "@/components/data-grids/shared/legacy-data-grid-layout" -import { DataGridSortHeader } from "@/components/data-grids/shared/legacy-data-grid-sort-header" +} from "@/components/data-grids/shared/data-grid-layout" +import { DataGridSortHeader } from "@/components/data-grids/shared/data-grid-sort-header" import { EmptyState } from "@/components/empty-state" import { fmtBytes, truncKey } from "@/components/data-grids/wireguard-peers-detail" import { @@ -53,8 +53,8 @@ function WireguardPeersGrid({ onDeletePeer, onExportPeer, }: WireguardPeersGridProps) { - const columns = useMemo[]>(() => { - const cols: ColumnDef[] = [ + const columns = useMemo[]>(() => { + const cols: ColumnDef[] = [ { id: "peer", accessorKey: "publicKey", @@ -233,11 +233,11 @@ function WireguardPeersGrid({ return cols }, [compactServer, onDeletePeer, onExportPeer]) - const table = useReactTable({ + const table = useTable({ + features: dataGridFeatures, + manualPagination: true, data: peers, columns, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), getRowId: (row) => row.id, }) diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index ef2fa739..4ccc739f 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -18,7 +18,7 @@ | 1 | Достоверность сохранения, удаления, включения и опроса серверов | Завершён 2026-09-13 | | 2 | Общая оболочка Data Grid/TanStack v9, Filters и эталон серверов | Завершён 2026-09-13, с перечисленными ограничениями приёмки | | 3 | Основные таблицы и справочники | Завершён 2026-09-13, границы проверок ниже | -| 4 | Остальные таблицы, удаление старых API, общая регрессия | В работе: блок BGP/OSPF перенесён | +| 4 | Остальные таблицы, удаление старых API, общая регрессия | В работе: BGP/OSPF и WireGuard перенесены | | 5 | Состояния данных, свежесть, частичные сбои, гонки polling | Не начат | | 6 | Навигация и дашборд | Не начат | | 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Не начат | @@ -218,6 +218,16 @@ ReUI checklist получен: сохранены композиция, props у Точный следующий шаг: проверить git status и квоту, перенести VPN-группу (`wireguard-data-grid`, `wireguard-peers-grid`, `ipsec-users-grid`, `gre-pools-data-grid`, `gre-tunnels-data-grid`, `vxlan-data-grid`) с профильными проверками. Затем перейти к оставшимся таблицам управления/диагностики и миграции Filters. Старые зависимости удалять только после последнего потребителя. Этот блок сохранить отдельным локальным коммитом без push; ветку не выпускать. +## Этап 4 — блок 2: WireGuard + +Перенесены `wireguard-data-grid` и `wireguard-peers-grid` на установленную основу ReUI v9. Сохранены внешние props, идентификаторы, обработчики операций и раскрытие интерфейса. Явное `manualPagination: true` сохраняет все строки без скрытого ограничения в десять записей. Общие примитивы и сетевые операции не менялись. + +Проверки: TypeScript (`--incremental false`) и целевой ESLint прошли без замечаний. Два браузерных сценария `tests/e2e/wireguard-tables.spec.ts` прошли: 12 интерфейсов, сортировка по порту, раскрытие/сворачивание пиров выбранного интерфейса, 12 строк отдельной вкладки пиров, сортировка и пустые состояния обеих вкладок. Фикстура проверяется схемой `wgListResponseSchema`, ответы подменяются на HTTP-уровне. Реальные операции RouterOS и права этими тестами не проверены; их приёмка остаётся этапу 9. Применён уже полученный ReUI checklist: сохранены композиция, публичный API и оформление; полная доступность и адаптивность ещё не приняты. + +Квота на старте: использовано 67% пятичасового окна; после браузерных проверок — 74% (остаток 26%). Новый блок не начинать на границе резерва. Полная сборка и общая регрессия остаются завершению этапа 4, общая основа в этом блоке не менялась. + +Следующий шаг после проверки квоты: перенести оставшиеся четыре VPN-таблицы `ipsec-users-grid`, `gre-pools-data-grid`, `gre-tunnels-data-grid`, `vxlan-data-grid`. WireGuard, BGP и три OSPF-таблицы уже исключены из остатка. Остальные таблицы управления/диагностики и старые Filters ещё не перенесены; этап 4 не завершён, ветку не выпускать. Блок WireGuard сохранить локальным коммитом без push. + ## Карта миграции (исходная инвентаризация этапа 0) Текущая связка: @tanstack/react-table ^8.21.3, @tanstack/react-virtual ^3.14.4, base-nova. Актуальный ReUI MCP ранее подтвердил useTable/dataGridFeatures для Data Grid и дерево FilterQuery вместо filters/onChange для Filters. Перед установкой перепроверить совместимые версии, сохранить семантику существующих фильтров (простые условия → AND) и закрепить lockfile. diff --git a/tests/e2e/wireguard-tables.spec.ts b/tests/e2e/wireguard-tables.spec.ts new file mode 100644 index 00000000..d8942135 --- /dev/null +++ b/tests/e2e/wireguard-tables.spec.ts @@ -0,0 +1,47 @@ +import { test, expect, type Page } from "@playwright/test" +import { wgListResponseSchema } from "../../packages/contracts/src/wireguard" + +const fixture = wgListResponseSchema.parse({ interfaces: Array.from({ length: 12 }, (_, i) => ({ + id: `wg-${i + 1}`, rosId: `*${i + 1}`, name: `wg-test-${i + 1}`, serverId: "1", + serverName: "Тестовый роутер", serverCountry: "RU", listenPort: 13231 + i, mtu: 1420, + comment: "Тест", enabled: true, status: "up", + peers: [{ id: `peer-${i + 1}`, rosId: `*${i + 20}`, publicKey: `fixture-public-key-${i + 1}`, + name: `Пир ${i + 1}`, allowedIps: [`10.0.0.${i + 1}/32`], transferRx: i + 1, transferTx: i + 1 }], +})) }) + +async function setup(page: Page, empty = false) { + await page.route("**/health", route => route.fulfill({ json: { ok: true } })) + await page.route("**/api/**", route => { + const path = new URL(route.request().url()).pathname + return route.fulfill({ json: path === "/api/wireguard" ? (empty ? { interfaces: [] } : fixture) + : path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? [] : {} }) + }) + await page.goto("/wireguard") +} + +test("WireGuard: 12 интерфейсов, сортировка порта, раскрытие и список пиров", async ({ page }) => { + const errors: string[] = [] + page.on("pageerror", e => errors.push(e.message)) + await setup(page) + const rows = page.locator("tr[data-row-id]") + await expect(rows).toHaveCount(12) + await page.getByRole("button", { name: "Порт", exact: true }).click() + await expect(rows.first()).toContainText("wg-test-12") + await rows.first().click() + await expect(page.getByText("10.0.0.12/32", { exact: true })).toBeVisible() + await rows.first().click() + await expect(page.getByText("10.0.0.12/32", { exact: true })).toBeHidden() + await page.getByRole("tab", { name: /Пиры/ }).click() + await expect(rows).toHaveCount(12) + await expect(rows.last()).toContainText("10.0.0.12/32") + await page.getByRole("button", { name: "Пир", exact: true }).click() + await expect(rows.first()).toContainText("Пир 1") + expect(errors).toEqual([]) +}) + +test("WireGuard: пустые интерфейсы и пиры", async ({ page }) => { + await setup(page, true) + await expect(page.getByText("Нет WireGuard интерфейсов", { exact: true })).toBeVisible() + await page.getByRole("tab", { name: /Пиры/ }).click() + await expect(page.getByText("Нет пиров", { exact: true })).toBeVisible() +})