fix(ui): перенести основные таблицы на ReUI v9
This commit is contained in:
1 parent
3d3c45e4d2
commit
e66762ca2e
14 files changed
+287
-110
No files matched your search
@@ -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<ColumnDef<Asn>[]>(
|
||||
const columns = useMemo<ColumnDef<DataGridFeatures, Asn>[]>(
|
||||
() => [
|
||||
{
|
||||
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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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<ColumnDef<CommunityRow>[]>(
|
||||
const columns = useMemo<ColumnDef<DataGridFeatures, CommunityRow>[]>(
|
||||
() => [
|
||||
{
|
||||
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 },
|
||||
|
||||
@@ -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<T extends { id: string }>({
|
||||
onRowClick,
|
||||
getExpandedContent,
|
||||
}: CompactDataGridProps<T>) {
|
||||
const columnDefs = useMemo<ColumnDef<T>[]>(
|
||||
const columnDefs = useMemo<ColumnDef<DataGridFeatures, T>[]>(
|
||||
() =>
|
||||
columns.map((col, index) => ({
|
||||
id: col.id,
|
||||
@@ -81,12 +80,11 @@ function CompactDataGrid<T extends { id: string }>({
|
||||
[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 } : {}),
|
||||
})
|
||||
|
||||
@@ -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<ColumnDef<Domain>[]>(
|
||||
const columns = useMemo<ColumnDef<DataGridFeatures, Domain>[]>(
|
||||
() => [
|
||||
{
|
||||
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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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<ColumnDef<IpRange>[]>(
|
||||
const columns = useMemo<ColumnDef<DataGridFeatures, IpRange>[]>(
|
||||
() => [
|
||||
{
|
||||
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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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<TData extends object>({
|
||||
return (
|
||||
<DataGrid
|
||||
className="w-full"
|
||||
i18n={{ labels: DATA_GRID_LABELS_RU }}
|
||||
table={table}
|
||||
recordCount={recordCount}
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
"use client"
|
||||
|
||||
import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid"
|
||||
|
||||
import { useMemo } from "react"
|
||||
import { type ColumnDef, getCoreRowModel, useReactTable } from "@tanstack/react-table-v8"
|
||||
import { type ColumnDef, useTable } from "@tanstack/react-table"
|
||||
import type { FlowTalkerDto } from "@mmapp/contracts/traffic-flow"
|
||||
import { ArrowDownIcon, ArrowUpIcon, MinusIcon } from "lucide-react"
|
||||
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 { fmtRate } from "@/lib/fmt-rate"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
@@ -61,7 +63,7 @@ function TrafficFlowsDataGrid({
|
||||
rows: FlowTalkerDto[]
|
||||
emptyHint?: string
|
||||
}) {
|
||||
const columns = useMemo<ColumnDef<FlowTalkerDto>[]>(
|
||||
const columns = useMemo<ColumnDef<DataGridFeatures, FlowTalkerDto>[]>(
|
||||
() => [
|
||||
{
|
||||
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}`,
|
||||
})
|
||||
|
||||
|
||||
@@ -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<ColumnDef<UptimeResourceRow>[]>(
|
||||
const columns = useMemo<ColumnDef<DataGridFeatures, UptimeResourceRow>[]>(
|
||||
() => [
|
||||
{
|
||||
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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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<ColumnDef<SpeedTestRunRow>[]>(
|
||||
const columns = useMemo<ColumnDef<DataGridFeatures, SpeedTestRunRow>[]>(
|
||||
() => [
|
||||
{
|
||||
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 }] },
|
||||
})
|
||||
|
||||
@@ -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<ColumnDef<FlowBreakdownRow>[]>(
|
||||
const columns = useMemo<ColumnDef<DataGridFeatures, FlowBreakdownRow>[]>(
|
||||
() => [
|
||||
{
|
||||
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<ColumnDef<FlowPathRow>[]>(
|
||||
const columns = useMemo<ColumnDef<DataGridFeatures, FlowPathRow>[]>(
|
||||
() => [
|
||||
{
|
||||
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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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<ColumnDef<FlowMapEdge>[]>(
|
||||
const columns = useMemo<ColumnDef<DataGridFeatures, FlowMapEdge>[]>(
|
||||
() => [
|
||||
{
|
||||
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}`,
|
||||
})
|
||||
|
||||
|
||||
+27
-2
@@ -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.
|
||||
|
||||
|
||||
@@ -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: "Очистить фильтры",
|
||||
}
|
||||
@@ -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/)
|
||||
})
|
||||
Reference in new issue
Block a user