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
179 lines
5.6 KiB
TypeScript
179 lines
5.6 KiB
TypeScript
"use client"
|
|
|
|
import { useMemo } from "react"
|
|
import {
|
|
type ColumnDef,
|
|
getCoreRowModel,
|
|
getSortedRowModel,
|
|
useReactTable,
|
|
} from "@tanstack/react-table"
|
|
import { cn } from "@/lib/utils"
|
|
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 { EmptyState } from "@/components/empty-state"
|
|
import { ClockIcon } from "lucide-react"
|
|
|
|
export interface SpeedProbeApiRow {
|
|
id: string
|
|
srcServerId: string
|
|
dstServerId: string
|
|
srcInterface: string
|
|
dstInterface: string
|
|
protocol: string
|
|
direction: string
|
|
durationSec: string
|
|
enabled: boolean
|
|
lastRunAt: string | null
|
|
lastTxAvgMbps: number | null
|
|
lastRxAvgMbps: number | null
|
|
lastStatus: string | null
|
|
lastError: string | null
|
|
}
|
|
|
|
interface ProbesSpeedProbesDataGridProps {
|
|
rows: SpeedProbeApiRow[]
|
|
serverName: (id: string) => string
|
|
}
|
|
|
|
function ProbesSpeedProbesDataGrid({ rows, serverName }: ProbesSpeedProbesDataGridProps) {
|
|
const columns = useMemo<ColumnDef<SpeedProbeApiRow>[]>(
|
|
() => [
|
|
{
|
|
id: "src",
|
|
header: ({ column }) => <DataGridSortHeader column={column} title="Источник" className="ml-1" />,
|
|
accessorFn: (row) => row.srcServerId,
|
|
cell: ({ row }) => {
|
|
const r = row.original
|
|
return (
|
|
<span className="truncate font-mono text-xs block">
|
|
{serverName(r.srcServerId)}
|
|
{r.srcInterface ? ` · ${r.srcInterface}` : ""}
|
|
</span>
|
|
)
|
|
},
|
|
meta: {
|
|
headerTitle: "Источник",
|
|
headerClassName: DATA_GRID_CELL_PAD_FIRST,
|
|
cellClassName: DATA_GRID_CELL_PAD_FIRST,
|
|
},
|
|
},
|
|
{
|
|
id: "dst",
|
|
accessorFn: (row) => row.dstServerId,
|
|
header: ({ column }) => <DataGridSortHeader column={column} title="Назначение" />,
|
|
cell: ({ row }) => {
|
|
const r = row.original
|
|
return (
|
|
<span className="truncate font-mono text-xs block">
|
|
{serverName(r.dstServerId)}
|
|
{r.dstInterface ? ` · ${r.dstInterface}` : ""}
|
|
</span>
|
|
)
|
|
},
|
|
meta: {
|
|
headerTitle: "Назначение",
|
|
headerClassName: DATA_GRID_CELL_PAD,
|
|
cellClassName: DATA_GRID_CELL_PAD,
|
|
},
|
|
},
|
|
{
|
|
id: "protocol",
|
|
accessorKey: "protocol",
|
|
header: ({ column }) => <DataGridSortHeader column={column} title="Протокол" />,
|
|
cell: ({ row }) => <span className="text-xs">{row.original.protocol.toUpperCase()}</span>,
|
|
meta: {
|
|
headerTitle: "Протокол",
|
|
headerClassName: DATA_GRID_CELL_PAD,
|
|
cellClassName: DATA_GRID_CELL_PAD,
|
|
},
|
|
},
|
|
{
|
|
id: "durationSec",
|
|
accessorKey: "durationSec",
|
|
header: ({ column }) => <DataGridSortHeader column={column} title="Сек" />,
|
|
cell: ({ row }) => <span className="font-mono text-xs">{row.original.durationSec}s</span>,
|
|
meta: {
|
|
headerTitle: "Сек",
|
|
headerClassName: DATA_GRID_CELL_PAD,
|
|
cellClassName: DATA_GRID_CELL_PAD,
|
|
},
|
|
},
|
|
{
|
|
id: "enabled",
|
|
accessorKey: "enabled",
|
|
header: ({ column }) => <DataGridSortHeader column={column} title="Вкл" />,
|
|
cell: ({ row }) => <span className="text-xs">{row.original.enabled ? "да" : "нет"}</span>,
|
|
meta: {
|
|
headerTitle: "Вкл",
|
|
headerClassName: DATA_GRID_CELL_PAD,
|
|
cellClassName: DATA_GRID_CELL_PAD,
|
|
},
|
|
},
|
|
{
|
|
id: "lastRunAt",
|
|
accessorKey: "lastRunAt",
|
|
header: ({ column }) => <DataGridSortHeader column={column} title="Последний запуск" />,
|
|
cell: ({ row }) => {
|
|
const r = row.original
|
|
return (
|
|
<span className="text-xs text-muted-foreground truncate block">
|
|
{r.lastRunAt ?? "—"}
|
|
{r.lastStatus === "done" && r.lastTxAvgMbps != null && (
|
|
<span className="text-emerald-600 dark:text-emerald-400 ml-1">
|
|
TX≈{r.lastTxAvgMbps.toFixed(1)} RX≈{(r.lastRxAvgMbps ?? 0).toFixed(1)} Mb/s
|
|
</span>
|
|
)}
|
|
{r.lastStatus === "error" && r.lastError && (
|
|
<span className="text-destructive ml-1 truncate">{r.lastError}</span>
|
|
)}
|
|
</span>
|
|
)
|
|
},
|
|
meta: {
|
|
headerTitle: "Последний запуск",
|
|
headerClassName: DATA_GRID_CELL_PAD_LAST,
|
|
cellClassName: DATA_GRID_CELL_PAD_LAST,
|
|
},
|
|
},
|
|
],
|
|
[serverName],
|
|
)
|
|
|
|
const table = useReactTable({
|
|
data: rows,
|
|
columns,
|
|
getCoreRowModel: getCoreRowModel(),
|
|
getSortedRowModel: getSortedRowModel(),
|
|
getRowId: (row) => row.id,
|
|
})
|
|
|
|
if (rows.length === 0) {
|
|
return (
|
|
<EmptyState
|
|
icon={<ClockIcon className="size-4" />}
|
|
title="Нет записей speed-test"
|
|
description="Настраиваются через API PUT /api/uptime/speed-probes"
|
|
className="border-0 py-10"
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<DataGridShell
|
|
table={table}
|
|
recordCount={rows.length}
|
|
tableClassNames={{
|
|
headerRow: "border-b border-border",
|
|
bodyRow: cn("group/row", "[&[data-disabled=true]]:opacity-50"),
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export { ProbesSpeedProbesDataGrid, type ProbesSpeedProbesDataGridProps }
|