Files
MikrotikManager/components/data-grids/uptime-speed-history-data-grid.tsx
T
Denozordec d3a2d38b37
Docker images / prepare-release (push) Successful in 6s
Docker images / backend-image (push) Successful in 1m37s
Docker images / frontend-image (push) Successful in 1m50s
Docker images / updater-image (push) Successful in 44s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 7s
fix(ui): заменить таблицы на карточки данных и улучшить функциональность поиска
2026-06-30 22:22:51 +07:00

243 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { useMemo } from "react"
import {
type ColumnDef,
getCoreRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
import type { Server } from "@/lib/data"
import { Flag } from "@/components/flag"
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/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"
export interface SpeedTestRunRow {
id: string
startedAt: number
srcServerId: string
dstServerId: string
srcInterface?: string
dstInterface?: string
protocol: "tcp" | "udp"
direction: "transmit" | "receive" | "both"
durationSec: number
txAvgMbps: number
rxAvgMbps: number
status: "running" | "done" | "error"
afterBtPing?: { rttMs: number | null; lossPct: number | null; error: string | null } | null
srcInterfaceAddress?: string | null
dstInterfaceAddress?: string | null
}
interface UptimeSpeedHistoryDataGridProps {
runs: SpeedTestRunRow[]
servers: Server[]
}
function UptimeSpeedHistoryDataGrid({ runs, servers }: UptimeSpeedHistoryDataGridProps) {
const columns = useMemo<ColumnDef<SpeedTestRunRow>[]>(
() => [
{
id: "startedAt",
accessorKey: "startedAt",
header: ({ column }) => <DataGridSortHeader column={column} title="Время" className="ml-1" />,
cell: ({ row }) => (
<span className="text-muted-foreground whitespace-nowrap tabular-nums font-mono text-xs">
{new Date(row.original.startedAt).toLocaleString("ru-RU", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
day: "2-digit",
month: "2-digit",
})}
</span>
),
meta: {
headerTitle: "Время",
headerClassName: DATA_GRID_CELL_PAD_FIRST,
cellClassName: DATA_GRID_CELL_PAD_FIRST,
},
},
{
id: "route",
accessorFn: (row) => `${row.srcServerId}-${row.dstServerId}`,
header: ({ column }) => <DataGridSortHeader column={column} title="Маршрут" />,
cell: ({ row }) => {
const run = row.original
const src = servers.find((s) => s.id === run.srcServerId)
const dst = servers.find((s) => s.id === run.dstServerId)
return (
<div className="font-mono whitespace-nowrap text-xs">
<div className="flex items-center gap-1.5">
<Flag code={src?.country ?? "UN"} size={13} />
<span>{src?.name ?? run.srcServerId}</span>
<ArrowRightIcon className="size-3 text-muted-foreground" />
<Flag code={dst?.country ?? "UN"} size={13} />
<span>{dst?.name ?? run.dstServerId}</span>
</div>
<div className="text-[10px] text-muted-foreground mt-0.5 font-mono">
{run.srcInterfaceAddress && run.dstInterfaceAddress
? `${run.srcInterfaceAddress}${run.dstInterfaceAddress}`
: "внутренние IP: auto/не указаны"}
</div>
</div>
)
},
meta: { headerTitle: "Маршрут", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "params",
accessorFn: (row) => `${row.protocol}-${row.direction}-${row.durationSec}`,
header: ({ column }) => <DataGridSortHeader column={column} title="Параметры" />,
cell: ({ row }) => {
const run = row.original
return (
<div className="flex items-center gap-1 text-muted-foreground whitespace-nowrap text-xs">
<span className="inline-flex items-center rounded border px-1.5 py-0.5 text-[10px] font-semibold bg-muted/60 border-border/60">
{run.protocol.toUpperCase()}
</span>
<span className="text-muted-foreground/60">·</span>
<span>{run.direction}</span>
<span className="text-muted-foreground/60">·</span>
<span>{run.durationSec}s</span>
</div>
)
},
meta: { headerTitle: "Параметры", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "status",
accessorKey: "status",
header: ({ column }) => <DataGridSortHeader column={column} title="Статус" />,
cell: ({ row }) => {
const status = row.original.status
if (status === "running") {
return (
<span className="inline-flex items-center gap-1 text-[var(--status-degraded-fg)] text-xs">
<RefreshCwIcon className="size-3 animate-spin" />
running
</span>
)
}
if (status === "error") {
return <span className="text-[var(--status-offline-fg)] text-xs">error</span>
}
return <span className="text-[var(--status-online-fg)] text-xs">done</span>
},
meta: { headerTitle: "Статус", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "txAvgMbps",
accessorKey: "txAvgMbps",
header: ({ column }) => <DataGridSortHeader column={column} title="TX avg" />,
cell: ({ row }) => {
const run = row.original
const maxVal = Math.max(run.txAvgMbps, run.rxAvgMbps, 1)
return (
<div className="flex items-center gap-2 min-w-[120px]">
<div className="w-16 h-1.5 rounded-full bg-muted overflow-hidden">
<div
className="h-full rounded-full bg-[var(--chart-tx)]"
style={{ width: `${(run.txAvgMbps / maxVal) * 100}%` }}
/>
</div>
<span className="font-mono tabular-nums text-[var(--chart-tx)] font-medium whitespace-nowrap text-xs">
{run.txAvgMbps} Мбит/с
</span>
</div>
)
},
meta: { headerTitle: "TX avg", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "rxAvgMbps",
accessorKey: "rxAvgMbps",
header: ({ column }) => <DataGridSortHeader column={column} title="RX avg" />,
cell: ({ row }) => {
const run = row.original
const maxVal = Math.max(run.txAvgMbps, run.rxAvgMbps, 1)
return (
<div className="flex items-center gap-2 min-w-[120px]">
<div className="w-16 h-1.5 rounded-full bg-muted overflow-hidden">
<div
className="h-full rounded-full bg-[var(--chart-rx)]"
style={{ width: `${(run.rxAvgMbps / maxVal) * 100}%` }}
/>
</div>
<span className="font-mono tabular-nums text-[var(--chart-rx)] font-medium whitespace-nowrap text-xs">
{run.rxAvgMbps} Мбит/с
</span>
</div>
)
},
meta: { headerTitle: "RX avg", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "afterBtPing",
accessorFn: (row) => row.afterBtPing?.rttMs ?? -1,
header: ({ column }) => <DataGridSortHeader column={column} title="Ping после BT" />,
cell: ({ row }) => {
const run = row.original
if (run.status !== "done") return <span className="text-xs"></span>
if (run.afterBtPing?.error) {
return (
<span className="text-[var(--status-offline-fg)] text-xs font-mono" title={run.afterBtPing.error}>
ошибка
</span>
)
}
if (run.afterBtPing?.rttMs != null) {
return (
<span className="font-mono tabular-nums whitespace-nowrap text-xs text-violet-600 dark:text-violet-400">
{run.afterBtPing.rttMs} мс
{run.afterBtPing.lossPct != null && run.afterBtPing.lossPct > 0 && (
<span className="text-amber-600 dark:text-amber-400"> · {run.afterBtPing.lossPct}%</span>
)}
</span>
)
}
return (
<span className="font-mono tabular-nums whitespace-nowrap text-xs text-amber-600 dark:text-amber-400">
timeout
{run.afterBtPing?.lossPct != null && <span> · {run.afterBtPing.lossPct}%</span>}
</span>
)
},
meta: {
headerTitle: "Ping после BT",
headerClassName: DATA_GRID_CELL_PAD_LAST,
cellClassName: DATA_GRID_CELL_PAD_LAST,
},
},
],
[servers],
)
const table = useReactTable({
data: runs,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getRowId: (row) => row.id,
initialState: { sorting: [{ id: "startedAt", desc: true }] },
})
return (
<DataGridShell
table={table}
recordCount={runs.length}
tableClassNames={{ bodyRow: cn("group/row text-xs") }}
/>
)
}
export { UptimeSpeedHistoryDataGrid, type UptimeSpeedHistoryDataGridProps }