Files
MikrotikManager/components/data-grids/route-optimizer-ospf-preview-data-grid.tsx
T

163 lines
5.7 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 { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid"
import { useMemo } from "react"
import type {OspfOptimizerObserved} from "@mmapp/contracts"
import {
type ColumnDef,
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/data-grid-layout"
import { DataGridSortHeader } from "@/components/data-grids/shared/data-grid-sort-header"
import { EmptyState } from "@/components/empty-state"
import { NetworkIcon } from "lucide-react"
function describeObserved(metric:OspfOptimizerObserved["ping"]|undefined,unit:string){
if(!metric || metric.value===null)return "Измерение: —"
const source=metric.source==="speed-probe"?"Проба":"Трафик интерфейса"
const date=metric.measuredAt?new Date(metric.measuredAt).toLocaleString("ru-RU"):"дата неизвестна"
return source+": "+metric.value+" "+unit+" · "+date
}
export interface OspfPreviewInterfaceRow {
observed?: OspfOptimizerObserved
id: string
interface: string
currentCost: number
optimalCost: number
score: number
pingMs: number
dlMbps: number
ulMbps: number
}
interface RouteOptimizerOspfPreviewDataGridProps {
rows: OspfPreviewInterfaceRow[]
error?: string | null
loading?: boolean
}
function RouteOptimizerOspfPreviewDataGrid({
rows,
error,
loading,
}: RouteOptimizerOspfPreviewDataGridProps) {
const columns = useMemo<ColumnDef<DataGridFeatures, OspfPreviewInterfaceRow>[]>(
() => [
{
id: "interface",
accessorKey: "interface",
header: ({ column }) => (
<DataGridSortHeader column={column} title="Интерфейс" className="ml-1" />
),
cell: ({ row }) => <span className="font-mono">{row.original.interface}</span>,
meta: {
headerTitle: "Интерфейс",
headerClassName: DATA_GRID_CELL_PAD_FIRST,
cellClassName: DATA_GRID_CELL_PAD_FIRST,
},
},
{
id: "cost",
accessorKey: "currentCost",
header: ({ column }) => <DataGridSortHeader column={column} title="Cost" />,
cell: ({ row }) => {
const r = row.original
return (
<span className="font-mono">
<span className="text-info ">{r.currentCost}</span>
{" → "}
<span
className={
r.currentCost === r.optimalCost
? "text-success "
: "text-warning "
}
>
{r.optimalCost}
</span>
</span>
)
},
meta: { headerTitle: "Cost", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "score",
accessorKey: "score",
header: ({ column }) => <DataGridSortHeader column={column} title="Score" />,
cell: ({ row }) => <span className="font-mono">{row.original.score}</span>,
meta: { headerTitle: "Score", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "pingMs",
accessorKey: "pingMs",
header: ({ column }) => <DataGridSortHeader column={column} title="Ping модели" />,
cell: ({ row }) => <div><span className="font-mono">{row.original.pingMs}ms</span><p className="text-[10px] text-muted-foreground">{describeObserved(row.original.observed?.ping,"мс")}</p></div>,
meta: { headerTitle: "Ping модели", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "speed",
header: () => <span className="text-xs font-medium text-muted-foreground">Speed (dl/ul) · модель</span>,
enableSorting: false,
cell: ({ row }) => {
const r = row.original
return (
<span className="font-mono">
↓{r.dlMbps} / ↑{r.ulMbps}<span className="block text-[10px] text-muted-foreground">↓ {describeObserved(r.observed?.dl,"Мбит/с")}<br />↑ {describeObserved(r.observed?.ul,"Мбит/с")}</span>
</span>
)
},
meta: { headerClassName: DATA_GRID_CELL_PAD_LAST, cellClassName: DATA_GRID_CELL_PAD_LAST },
},
],
[],
)
const table = useTable({
features: dataGridFeatures,
manualPagination: true,
data: rows,
columns,
getRowId: (row) => row.id,
})
if (error && rows.length === 0) {
return (
<p role="alert" className="px-3 py-2 text-xs text-destructive">Ошибка preview: {error}</p>
)
}
if (!loading && rows.length === 0) {
return (
<EmptyState
icon={<NetworkIcon aria-hidden="true" className="size-4" />}
title="Интерфейсы OSPF не найдены для выбранного сервера"
className="border-0 py-6 text-xs"
/>
)
}
return (
<>
{error && <p role="alert" className="px-3 py-2 text-xs text-warning">Ошибка preview: {error}. Последний снимок; актуальность неизвестна.</p>}
{loading && rows.length > 0 && <p role="status" className="px-3 py-2 text-xs text-muted-foreground">Обновление preview OSPF…</p>}
<DataGridShell
table={table}
recordCount={rows.length}
isLoading={loading && rows.length === 0}
loadingMode="skeleton"
tableClassNames={{ headerRow: "border-b bg-muted/30", bodyRow: "group/row" }}
tableLayout={{ dense: true }}
/>
</>
)
}
export { RouteOptimizerOspfPreviewDataGrid, type RouteOptimizerOspfPreviewDataGridProps }