From e66762ca2e42f17f3707baddb95997287d98f2e6 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sun, 13 Sep 2026 21:01:23 +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=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D1=8B=20=D0=BD?= =?UTF-8?q?=D0=B0=20ReUI=20v9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/data-grids/asns-data-grid.tsx | 24 ++-- .../data-grids/communities-data-grid.tsx | 24 ++-- components/data-grids/compact-data-grid.tsx | 24 ++-- components/data-grids/domains-data-grid.tsx | 24 ++-- components/data-grids/ip-ranges-data-grid.tsx | 24 ++-- .../data-grids/shared/data-grid-shell.tsx | 2 + .../data-grids/traffic-flows-data-grid.tsx | 16 ++- .../data-grids/uptime-resources-data-grid.tsx | 23 ++-- .../uptime-speed-history-data-grid.tsx | 22 ++-- components/traffic/flow-analytics-panel.tsx | 24 ++-- components/traffic/flow-traffic-map.tsx | 16 ++- docs/ux-ui-progress.md | 29 ++++- lib/data-grid-labels-ru.ts | 35 ++++++ tests/e2e/migrated-tables.spec.ts | 110 ++++++++++++++++++ 14 files changed, 287 insertions(+), 110 deletions(-) create mode 100644 lib/data-grid-labels-ru.ts create mode 100644 tests/e2e/migrated-tables.spec.ts diff --git a/components/data-grids/asns-data-grid.tsx b/components/data-grids/asns-data-grid.tsx index 8b41c3b1..10236842 100644 --- a/components/data-grids/asns-data-grid.tsx +++ b/components/data-grids/asns-data-grid.tsx @@ -1,21 +1,20 @@ "use client" +import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" + import { useMemo } from "react" import { type ColumnDef, - getCoreRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table-v8" + useTable, +} from "@tanstack/react-table" import type { Asn } from "@/lib/data" -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 { FilterIcon, NetworkIcon } from "lucide-react" @@ -26,7 +25,7 @@ interface AsnsDataGridProps { } function AsnsDataGrid({ asns, isLoading, pagination = false }: AsnsDataGridProps) { - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { id: "asn", @@ -115,12 +114,11 @@ function AsnsDataGrid({ asns, isLoading, pagination = false }: AsnsDataGridProps [], ) - const table = useReactTable({ + const table = useTable({ + features: dataGridFeatures, + manualPagination: !pagination, data: asns, columns, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - ...(pagination ? { getPaginationRowModel: getPaginationRowModel() } : {}), getRowId: (row) => row.id, }) diff --git a/components/data-grids/communities-data-grid.tsx b/components/data-grids/communities-data-grid.tsx index 665612e1..29d43587 100644 --- a/components/data-grids/communities-data-grid.tsx +++ b/components/data-grids/communities-data-grid.tsx @@ -1,20 +1,20 @@ "use client" +import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" + import { useMemo } from "react" import { type ColumnDef, - getCoreRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table-v8" + useTable, +} from "@tanstack/react-table" import { cn } from "@/lib/utils" -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 { CheckIcon, @@ -77,7 +77,7 @@ function CommunitiesDataGrid({ onSelect, onCopy, }: CommunitiesDataGridProps) { - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { id: "value", @@ -222,15 +222,15 @@ function CommunitiesDataGrid({ ) const rowSelection = useMemo( - () => (selectedId ? { [selectedId]: true } : {}), + () => (selectedId ? { [selectedId]: true as const } : {}), [selectedId], ) - const table = useReactTable({ + const table = useTable({ + features: dataGridFeatures, + manualPagination: true, data: communities, columns, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), getRowId: (row) => row.id, enableRowSelection: true, state: { rowSelection }, diff --git a/components/data-grids/compact-data-grid.tsx b/components/data-grids/compact-data-grid.tsx index 4e83edc4..e5b547f1 100644 --- a/components/data-grids/compact-data-grid.tsx +++ b/components/data-grids/compact-data-grid.tsx @@ -1,20 +1,19 @@ "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" -import { DataGridShell } from "@/components/data-grids/shared/legacy-data-grid-shell" + useTable, +} from "@tanstack/react-table" +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 { InboxIcon } from "lucide-react" @@ -47,7 +46,7 @@ function CompactDataGrid({ onRowClick, getExpandedContent, }: CompactDataGridProps) { - const columnDefs = useMemo[]>( + const columnDefs = useMemo[]>( () => columns.map((col, index) => ({ id: col.id, @@ -81,12 +80,11 @@ function CompactDataGrid({ [columns, getExpandedContent], ) - const table = useReactTable({ + const table = useTable({ + features: dataGridFeatures, + manualPagination: true, data, columns: columnDefs, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - ...(getExpandedContent ? { getExpandedRowModel: getExpandedRowModel() } : {}), getRowId: (row) => row.id, ...(getExpandedContent ? { getRowCanExpand: () => true } : {}), }) diff --git a/components/data-grids/domains-data-grid.tsx b/components/data-grids/domains-data-grid.tsx index 5a0a8bfd..fc9d3ea7 100644 --- a/components/data-grids/domains-data-grid.tsx +++ b/components/data-grids/domains-data-grid.tsx @@ -1,21 +1,20 @@ "use client" +import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" + import { useMemo } from "react" import { type ColumnDef, - getCoreRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table-v8" + useTable, +} from "@tanstack/react-table" import type { Domain } from "@/lib/data" -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 { FilterIcon, GlobeIcon } from "lucide-react" @@ -26,7 +25,7 @@ interface DomainsDataGridProps { } function DomainsDataGrid({ domains, isLoading, pagination = false }: DomainsDataGridProps) { - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { id: "domain", @@ -124,12 +123,11 @@ function DomainsDataGrid({ domains, isLoading, pagination = false }: DomainsData [], ) - const table = useReactTable({ + const table = useTable({ + features: dataGridFeatures, + manualPagination: !pagination, data: domains, columns, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - ...(pagination ? { getPaginationRowModel: getPaginationRowModel() } : {}), getRowId: (row) => row.id, }) diff --git a/components/data-grids/ip-ranges-data-grid.tsx b/components/data-grids/ip-ranges-data-grid.tsx index 19a8a9a3..ba5e1504 100644 --- a/components/data-grids/ip-ranges-data-grid.tsx +++ b/components/data-grids/ip-ranges-data-grid.tsx @@ -1,21 +1,20 @@ "use client" +import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" + import { useMemo } from "react" import { type ColumnDef, - getCoreRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table-v8" + useTable, +} from "@tanstack/react-table" import type { IpRange } from "@/lib/data" -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 { FilterIcon, NetworkIcon } from "lucide-react" @@ -26,7 +25,7 @@ interface IpRangesDataGridProps { } function IpRangesDataGrid({ ipRanges, isLoading, pagination = false }: IpRangesDataGridProps) { - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { id: "cidr", @@ -126,12 +125,11 @@ function IpRangesDataGrid({ ipRanges, isLoading, pagination = false }: IpRangesD [], ) - const table = useReactTable({ + const table = useTable({ + features: dataGridFeatures, + manualPagination: !pagination, data: ipRanges, columns, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - ...(pagination ? { getPaginationRowModel: getPaginationRowModel() } : {}), getRowId: (row) => row.id, }) diff --git a/components/data-grids/shared/data-grid-shell.tsx b/components/data-grids/shared/data-grid-shell.tsx index bee56cd7..b4a5f7bf 100644 --- a/components/data-grids/shared/data-grid-shell.tsx +++ b/components/data-grids/shared/data-grid-shell.tsx @@ -2,6 +2,7 @@ import type { ReactNode } from "react" import type { Table } from "@tanstack/react-table" +import { DATA_GRID_LABELS_RU } from "@/lib/data-grid-labels-ru" import { DataGrid, DataGridContainer, @@ -44,6 +45,7 @@ function DataGridShell({ return ( []>( + const columns = useMemo[]>( () => [ { id: "client", @@ -169,10 +171,12 @@ function TrafficFlowsDataGrid({ [], ) - const table = useReactTable({ + const table = useTable({ + features: dataGridFeatures, + manualPagination: true, + manualSorting: true, data: rows, columns, - getCoreRowModel: getCoreRowModel(), getRowId: (row, i) => `${row.serverId}-${row.src}-${row.dst}-${row.proto}-${row.srcPort}-${row.dstPort}-${i}`, }) diff --git a/components/data-grids/uptime-resources-data-grid.tsx b/components/data-grids/uptime-resources-data-grid.tsx index 4c96c40f..243d604d 100644 --- a/components/data-grids/uptime-resources-data-grid.tsx +++ b/components/data-grids/uptime-resources-data-grid.tsx @@ -1,31 +1,30 @@ "use client" +import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" + import { useMemo } from "react" import { type ColumnDef, - getCoreRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table-v8" + useTable, +} from "@tanstack/react-table" import type { Server } from "@/lib/data" import { Flag } from "@/components/flag" import { Sparkline } from "@/components/sparkline" import { StatusDot } from "@/components/status-dot" import { cn } from "@/lib/utils" -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 { AlertCircleIcon, ClockIcon, CpuIcon, HardDriveIcon, - ServerIcon, SearchIcon, ThermometerIcon, } from "lucide-react" @@ -114,7 +113,7 @@ interface UptimeResourcesDataGridProps { } function UptimeResourcesDataGrid({ rows }: UptimeResourcesDataGridProps) { - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { id: "name", @@ -348,11 +347,11 @@ function UptimeResourcesDataGrid({ rows }: UptimeResourcesDataGridProps) { [], ) - const table = useReactTable({ + const table = useTable({ + features: dataGridFeatures, + manualPagination: true, data: rows, columns, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), getRowId: (row) => row.serverId, }) diff --git a/components/data-grids/uptime-speed-history-data-grid.tsx b/components/data-grids/uptime-speed-history-data-grid.tsx index 104cd81f..709c0045 100644 --- a/components/data-grids/uptime-speed-history-data-grid.tsx +++ b/components/data-grids/uptime-speed-history-data-grid.tsx @@ -1,21 +1,21 @@ "use client" +import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" + import { useMemo } from "react" import { type ColumnDef, - getCoreRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table-v8" + useTable, +} from "@tanstack/react-table" import type { Server } from "@/lib/data" import { Flag } from "@/components/flag" -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 { cn } from "@/lib/utils" import { ArrowRightIcon, RefreshCwIcon } from "lucide-react" @@ -43,7 +43,7 @@ interface UptimeSpeedHistoryDataGridProps { } function UptimeSpeedHistoryDataGrid({ runs, servers }: UptimeSpeedHistoryDataGridProps) { - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { id: "startedAt", @@ -221,11 +221,11 @@ function UptimeSpeedHistoryDataGrid({ runs, servers }: UptimeSpeedHistoryDataGri [servers], ) - const table = useReactTable({ + const table = useTable({ + features: dataGridFeatures, + manualPagination: true, data: runs, columns, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), getRowId: (row) => row.id, initialState: { sorting: [{ id: "startedAt", desc: true }] }, }) diff --git a/components/traffic/flow-analytics-panel.tsx b/components/traffic/flow-analytics-panel.tsx index 969cff0a..5f181a27 100644 --- a/components/traffic/flow-analytics-panel.tsx +++ b/components/traffic/flow-analytics-panel.tsx @@ -1,7 +1,9 @@ "use client" +import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" + import { useMemo, useState } from "react" -import { type ColumnDef, getCoreRowModel, useReactTable } from "@tanstack/react-table-v8" +import { type ColumnDef, useTable } from "@tanstack/react-table" import type { FlowAnalyticsDto, FlowBreakdownRow, FlowEntityCard, FlowPathRow, FlowTalkerDto } from "@mmapp/contracts/traffic-flow" import { ArrowDownIcon, ArrowUpIcon, GitBranchIcon, GlobeIcon, LayersIcon, NetworkIcon, RouteIcon, ShieldIcon, UsersIcon } from "lucide-react" import { Badge } from "@/components/reui/badge" @@ -10,12 +12,12 @@ import { TrafficRxTxChart } from "@/components/reui-kit/traffic-rx-tx-chart" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Switch } from "@/components/ui/switch" import { Label } from "@/components/ui/label" -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" +} from "@/components/data-grids/shared/data-grid-layout" import { TrafficFlowsDataGrid } from "@/components/data-grids/traffic-flows-data-grid" import { FlowTrafficMap } from "@/components/traffic/flow-traffic-map" import { StatusDot } from "@/components/status-dot" @@ -142,7 +144,7 @@ function FlowBreakdownGrid({ country?: boolean onPick?: (row: FlowBreakdownRow) => void }) { - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { id: "label", @@ -195,10 +197,12 @@ function FlowBreakdownGrid({ [country], ) - const table = useReactTable({ + const table = useTable({ + features: dataGridFeatures, + manualPagination: true, + manualSorting: true, data: rows, columns, - getCoreRowModel: getCoreRowModel(), getRowId: (row) => row.id, }) @@ -221,7 +225,7 @@ function FlowPathsGrid({ empty?: string onPick?: (row: FlowPathRow) => void }) { - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { id: "client", @@ -290,10 +294,12 @@ function FlowPathsGrid({ [], ) - const table = useReactTable({ + const table = useTable({ + features: dataGridFeatures, + manualPagination: true, + manualSorting: true, data: rows, columns, - getCoreRowModel: getCoreRowModel(), getRowId: (row) => row.id, }) diff --git a/components/traffic/flow-traffic-map.tsx b/components/traffic/flow-traffic-map.tsx index 0908e296..71e7c574 100644 --- a/components/traffic/flow-traffic-map.tsx +++ b/components/traffic/flow-traffic-map.tsx @@ -1,15 +1,17 @@ "use client" +import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" + import { useMemo, useState } from "react" -import { type ColumnDef, getCoreRowModel, useReactTable } from "@tanstack/react-table-v8" +import { type ColumnDef, useTable } from "@tanstack/react-table" import type { FlowMapEdge } from "@mmapp/contracts/traffic-flow" import { Frame, FramePanel } from "@/components/reui/frame" -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" +} from "@/components/data-grids/shared/data-grid-layout" import { Flag } from "@/components/flag" import { fmtRate } from "@/lib/fmt-rate" @@ -50,7 +52,7 @@ function FlowTrafficMap({ return { srcPos, dstPos, maxBytes } }, [edges]) - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { id: "from", @@ -106,10 +108,12 @@ function FlowTrafficMap({ [], ) - const table = useReactTable({ + const table = useTable({ + features: dataGridFeatures, + manualPagination: true, + manualSorting: true, data: edges, columns, - getCoreRowModel: getCoreRowModel(), getRowId: (row, i) => `${row.fromId}-${row.toCountry}-${i}`, }) diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 8a747be9..efcf1e34 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -17,7 +17,7 @@ | 0 | Исходная база, карта миграции, проверки и обзор экранов | Завершён 2026-09-13 | | 1 | Достоверность сохранения, удаления, включения и опроса серверов | Завершён 2026-09-13 | | 2 | Общая оболочка Data Grid/TanStack v9, Filters и эталон серверов | Завершён 2026-09-13, с перечисленными ограничениями приёмки | -| 3 | Основные таблицы и справочники | Следующий, отдельное окно | +| 3 | Основные таблицы и справочники | Завершён 2026-09-13, границы проверок ниже | | 4 | Остальные таблицы, удаление старых API, общая регрессия | Не начат | | 5 | Состояния данных, свежесть, частичные сбои, гонки polling | Не начат | | 6 | Навигация и дашборд | Не начат | @@ -181,7 +181,32 @@ flowchart LR Следующий шаг: проверить квоту и состояние файлов; выполнить только этап 3 (обзор, мониторинг, трафик, статистика и справочники) в этой ветке, перечислить оставшихся legacy-потребителей и сделать локальный коммит без push. Перед миграцией виртуализированных таблиц добавить профильные проверки указанного выше риска контекста. Этапы 2–4 не выпускать до общей регрессии этапа 4. -## Карта миграции +## Этап 3 — основные таблицы и справочники + +Перенесены десять прямых потребителей: `asns-data-grid`, `domains-data-grid`, `ip-ranges-data-grid`, `communities-data-grid`, `compact-data-grid`, `uptime-resources-data-grid`, `uptime-speed-history-data-grid`, `traffic-flows-data-grid`, `traffic/flow-analytics-panel`, `traffic/flow-traffic-map`. Через CompactDataGrid новую основу получили таблицы дашборда, статистики и снимков состояния. Серверы уже перенесены на этапе 2. + +Используются `useTable`, `dataGridFeatures` и типы v9. Сохранены доменные props, стабильные идентификаторы, раскрытие и обработчики действий. Для списков без панели явно отключена клиентская пагинация; для каталогов EvoBGP она сохранена. Таблицы потоков сохраняют исходный порядок данных. Состояние выбора Communities приведено к типу v9. Общая оболочка получила русские служебные подписи через документированный `i18n`, без изменения установленных примитивов ReUI. + +| Референс | Схема сохранённого сценария | +| --- | --- | +| [Data Grid Filtering 2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Поиск → страница таблицы → сортировка / выбор строки → детали | +| [API Data Grid](https://reui.io/docs/components/base/data-grid?ref=mcp) | Типизированные данные → useTable v9 → DataGridShell → таблица и опциональная пагинация | + +Проверки: TypeScript и целевой ESLint — без ошибок и предупреждений. 17 браузерных тестов серверов прошли. Добавлены 6 сценариев `tests/e2e/migrated-tables.spec.ts`: пагинация и поиск с последней страницы в трёх справочниках, числовая сортировка через границу страницы, выбор одной из 12 Communities, все 12 строк статистики и переход в URL-срез. Первый старт Next.js превысил 120 секунд; повторный старт успешен. Два теста сначала упали из-за неточных селекторов, исправлены и повторно прошли. Все запросы перехватывались HTTP-моками. + +ReUI audit checklist получен. Сохранены Frame/base-nova, композиция и типизированные источники; новые props сверены с установленными файлами. Просмотрен экран Communities: таблица и выбранная строка отображаются, но правая панель деталей обрезается при ширине 1440 px. Исправить размещение таблицы/деталей при профильной переработке экранов; общую доступность и адаптивность не считать принятой. Существующие неработающие действия справочников также не входят в приёмку миграции. Браузерные проверки всех комбинаций мониторинга, traffic-map и snapshot-деталей остаются для общей регрессии этапа 4 и профильных этапов 7–10. Виртуализатор эти десять компонентов не используют; нагрузочный профиль общей основы остаётся этапу 12. + +Production build прошёл: `npm run build`, 34 статические страницы. Последняя проверка квоты перед завершением блока: использовано 37% пятичасового окна и 50% недельного; это общий расход аккаунта, не оценка стоимости этапа. + +### Остаток для этапа 4 + +- Доменные legacy-таблицы: backups, bgp-sessions, certificates, data-collection-scheduler, filters, firewall-rules, firewall-scenario-rules, gre-pools, gre-tunnels, ospf-bfd, ospf-neighbors, ospf-routes, probes-schedule, probes-speed-probes, recursive-routes, route-optimizer-comm-recs, route-optimizer-full-routes, route-optimizer-ospf-preview, route-optimizer-wan-matrix, subusers, users, vxlan, wireguard, wireguard-peers, ipsec-users-grid. +- Общая основа: `components/data-table.tsx`, `components/data-grids/shared/legacy-*`, `components/reui/legacy-data-grid/*`; ссылки на legacy layout в DataPageCard и DataPageToolbar. Проверить отсутствие потребителей перед удалением. +- Старые Filters: статистика, BGP, backups, backups-history и fallback DataPageToolbar; старые определения полей/вычислитель. Статистика передаёт в API только равенство скалярных срезов: при переносе нельзя принимать OR/отрицание и молча превращать их в равенство. Сохранять незавершённый черновик отдельно от URL. + +Следующий шаг: проверить квоту и git status; начать этап 4 в `codex/reui-v9-migration`, переносить оставшихся потребителей ограниченными группами, затем удалить v8/старые Filters и выполнить общую регрессию. Ветку до этой проверки не выпускать. Коммит этапа 3 — только локальный, без push; настройки skills/AGENTS/.codex других работ не включать. + +## Карта миграции (исходная инвентаризация этапа 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/lib/data-grid-labels-ru.ts b/lib/data-grid-labels-ru.ts new file mode 100644 index 00000000..a8a866c3 --- /dev/null +++ b/lib/data-grid-labels-ru.ts @@ -0,0 +1,35 @@ +import type { DataGridI18nLabels } from "@/components/reui/data-grid/data-grid-i18n" + +export const DATA_GRID_LABELS_RU: DataGridI18nLabels = { + sortAscending: "По возрастанию", + sortDescending: "По убыванию", + pinColumnStart: "Закрепить слева", + pinColumnEnd: "Закрепить справа", + moveColumnStart: "Переместить влево", + moveColumnEnd: "Переместить вправо", + columnsMenu: "Столбцы", + unpinColumn: title => `Открепить столбец ${title}`, + toggleColumns: "Настроить столбцы", + rowCreate: "Добавить строку", + pinRow: "Закрепить строку", + unpinRow: "Открепить строку", + selectRow: "Выбрать строку", + selectAll: "Выбрать все", + expandRow: "Раскрыть строку", + collapseRow: "Свернуть строку", + dragToReorder: "Перетащите для изменения порядка", + dragToReorderRow: "Перетащите для изменения порядка строк", + reorderingUnavailable: "Изменение порядка недоступно", + loading: "Загрузка…", + empty: "Нет данных", + allRowsLoaded: "Все записи загружены", + rowsPerPage: "Строк на странице", + paginationInfo: ({ from, to, count }) => `${from}–${to} из ${count}`, + previousPage: "Предыдущая страница", + nextPage: "Следующая страница", + goToPage: page => `Перейти на страницу ${page}`, + paginationEllipsis: "…", + filterSelectedCount: count => `Выбрано: ${count}`, + filterNoResults: "Ничего не найдено", + filterClear: "Очистить фильтры", +} diff --git a/tests/e2e/migrated-tables.spec.ts b/tests/e2e/migrated-tables.spec.ts new file mode 100644 index 00000000..96219c12 --- /dev/null +++ b/tests/e2e/migrated-tables.spec.ts @@ -0,0 +1,110 @@ +import { test, expect, type Page } from "@playwright/test" +import type { EvoBgpCatalogSnapshot } from "../../lib/evobgp-context" +import { statisticsDtoSchema } from "../../packages/contracts/src/statistics" + +const catalog: EvoBgpCatalogSnapshot = { + fetchedAt: "2026-09-13T00:00:00Z", modules: [], + asns: Array.from({ length: 12 }, (_, i) => ({ + id: String(i + 1), asn: `AS${65001 + i}`, org: `Организация ${i + 1}`, + country: "RU", prefixes: i + 1, filter: "", updated: "2026-09-13", enabled: true, + })), + domains: Array.from({ length: 12 }, (_, i) => ({ + id: String(i + 1), domain: `test${i + 1}.example`, resolvedIp: `192.0.2.${i + 1}`, + asn: "AS65001", purpose: "Тест", filter: "", updated: "2026-09-13", enabled: true, + })), + ipRanges: Array.from({ length: 12 }, (_, i) => ({ + id: String(i + 1), cidr: `192.0.${i + 1}.0/24`, asn: "AS65001", country: "RU", + purpose: "Тест", filter: "", updated: "2026-09-13", enabled: true, + })), + communities: Array.from({ length: 12 }, (_, i) => ({ + id: String(i + 1), value: `65001:${i + 1}`, name: `Community ${i + 1}`, + description: `Описание ${i + 1}`, type: "standard", filterIds: [], + serverCount: i + 1, prefixCount: i + 1, action: "permit", enabled: true, + })), +} + +async function setup(page: Page, path: string) { + await page.route("**/health", route => route.fulfill({ json: { ok: true } })) + await page.route("**/api/**", route => { + const path = new URL(route.request().url()).pathname + const json = path === "/api/auth/config" ? { required: false } + : path === "/api/evobgp/settings" ? { enabled: true, baseUrl: "", secretConfigured: true } + : path === "/api/evobgp/catalog" ? catalog + : path === "/api/servers" ? [] : {} + return route.fulfill({ json }) + }) + await page.goto(path) +} + +for (const [path, placeholder, last] of [ + ["/asns", "Поиск по ASN, имени, префиксам…", "AS65012"], + ["/domains", "Поиск по домену…", "test12.example"], + ["/ip-ranges", "Поиск по CIDR, ASN…", "192.0.12.0/24"], +]) { + test(`${path}: страницы и поиск с последней страницы`, async ({ page }) => { + const errors: string[] = [] + page.on("pageerror", e => errors.push(e.message)) + await setup(page, path) + const rows = page.locator("tr[data-row-id]") + await expect(page.getByText("1–10 из 12", { exact: true })).toBeVisible() + await expect(rows).toHaveCount(10) + await page.getByRole("button", { name: "Следующая страница", exact: true }).click() + await expect(rows).toHaveCount(2) + await expect(rows.last()).toContainText(last) + await page.getByPlaceholder(placeholder).fill(last) + await expect(rows).toHaveCount(1) + await expect(rows.first()).toContainText(last) + await expect(page.getByText("1–1 из 1", { exact: true })).toBeVisible() + await page.getByPlaceholder(placeholder).fill("несуществующее значение") + await expect(rows).toHaveCount(0) + await page.getByPlaceholder(placeholder).fill("") + await expect(rows).toHaveCount(10) + expect(errors).toEqual([]) + }) +} + +test("ASN: числовая сортировка охватывает обе страницы", async ({ page }) => { + await setup(page, "/asns") + await expect(page.getByText("1–10 из 12", { exact: true })).toBeVisible() + await page.getByRole("button", { name: "Префиксов", exact: true }).click() + await expect(page.locator("tr[data-row-id]").first()).toContainText("AS65012") + await page.getByRole("button", { name: "Следующая страница", exact: true }).click() + await expect(page.locator("tr[data-row-id]").last()).toContainText("AS65001") +}) + +test("Communities: все 12 строк и выбор деталей", async ({ page }) => { + await setup(page, "/communities") + await expect(page.locator("tr[data-row-id]")).toHaveCount(12) + await page.locator('tr[data-row-id="12"]').click() + await expect(page.locator('tr[data-row-id="12"]')).toHaveAttribute("data-state", "selected") + await expect(page.getByText("Описание 12", { exact: true })).toHaveCount(2) + await expect(page.getByText("Описание 12", { exact: true }).last()).toBeVisible() + await page.screenshot({ path: "test-results/communities-migrated.png", fullPage: true }) +}) + +test("Статистика: CompactDataGrid сохраняет 12 строк, сортировку и переход в срез", async ({ page }) => { + const dto = statisticsDtoSchema.parse({ + from: "2026-09-01", to: "2026-09-13", grain: "day", + kpis: { bytes: 7800, packets: 78, avgBps: 100, users: 12, servers: 1, ifaces: 1, topCountry: "RU", topService: "https" }, + series: [], servers: [], interfaces: [], countries: [], services: [], asns: [], + users: Array.from({ length: 12 }, (_, i) => ({ + id: `user-${i + 1}`, label: `Пользователь ${i + 1}`, bytes: (i + 1) * 100, + packets: i + 1, bps: i + 1, percent: i + 1, + })), + }) + await page.route("**/api/statistics?**", route => route.fulfill({ json: dto })) + // The general API handler falls back to the previously registered statistics route. + await page.route("**/health", route => route.fulfill({ json: { ok: true } })) + await page.route("**/api/**", route => { + const path = new URL(route.request().url()).pathname + if (path === "/api/statistics") return route.fallback() + return route.fulfill({ json: path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? [] : {} }) + }) + await page.goto("/statistics?from=2026-09-01&to=2026-09-13&dim=users") + const rows = page.locator("tr[data-row-id]") + await expect(rows).toHaveCount(12) + await page.getByRole("button", { name: "Байты", exact: true }).first().click() + await expect(rows.first()).toContainText("Пользователь 12") + await rows.first().click() + await expect(page).toHaveURL(/userId=user-12/) +})