"use client" import { useMemo } from "react" import { type ColumnDef, getCoreRowModel, getExpandedRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table" import type { Server, ServerType } from "@/lib/data" import { StatusBadge } from "@/components/status-badge" import { Flag } from "@/components/flag" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" 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 { ServerExpandedDetail } from "@/components/data-grids/server-expanded-detail" import { ChevronRightIcon, ChevronDownIcon, HomeIcon, ServerIcon, NetworkIcon, ShieldIcon, WifiIcon, MoreHorizontalIcon, ExternalLinkIcon, PencilIcon, RefreshCwIcon, PowerIcon, Trash2Icon, } from "lucide-react" const TYPE_LABELS: Record = { "jump-host": "JumpHost", "exit-node": "Exit Node", "home-router": "Home Router", } const TYPE_STYLES: Record = { "jump-host": "bg-violet-500/10 text-violet-400 border-violet-500/20", "exit-node": "bg-sky-500/10 text-sky-400 border-sky-500/20", "home-router": "bg-emerald-500/10 text-emerald-400 border-emerald-500/20", } function rosVer(os: string): number { const m = os.match(/(\d+)\.(\d+)/) if (!m) return 0 return parseInt(m[1], 10) * 100 + parseInt(m[2], 10) } function TypeBadge({ type }: { type: ServerType }) { const icon = type === "jump-host" ? : type === "exit-node" ? : return ( {icon}{TYPE_LABELS[type]} ) } function RosBadge({ os }: { os: string }) { const v = rosVer(os) const cls = v >= 715 ? "bg-[var(--status-online-bg)] text-[var(--status-online-fg)] border-current/20" : v >= 710 ? "bg-[var(--status-degraded-bg)] text-[var(--status-degraded-fg)] border-current/20" : "bg-[var(--status-offline-bg)] text-[var(--status-offline-fg)] border-current/20" return {os} } interface ServerRowActionsProps { server: Server isLive: boolean isPolling: boolean onEdit: (server: Server) => void onDelete: (id: string) => void onPoll: (id: string) => void onToggleStatus: (id: string) => void } function ServerRowActions({ server, isLive, isPolling, onEdit, onDelete, onPoll, onToggleStatus, }: ServerRowActionsProps) { return (
e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} > e.stopPropagation()} aria-label={`Действия: ${server.name}`} > } /> window.open(`https://${server.host}`, "_blank")}> Открыть WebFig onEdit(server)}> Редактировать {isLive && ( onPoll(server.id)} disabled={isPolling}> {isPolling ? "Опрос…" : "Опросить"} )} onToggleStatus(server.id)}> {server.status === "offline" ? "Включить" : "Отключить"} onDelete(server.id)}> Удалить
) } interface ServersDataGridProps { servers: Server[] isLive: boolean pollingIds: Set onPoll: (id: string) => void onEdit: (server: Server) => void onDelete: (id: string) => void onToggleStatus: (id: string) => void } function ServersDataGrid({ servers, isLive, pollingIds, onPoll, onEdit, onDelete, onToggleStatus, }: ServersDataGridProps) { const columns = useMemo[]>( () => [ { id: "name", accessorKey: "name", header: ({ column }) => ( ), cell: ({ row }) => { const s = row.original const expanded = row.getIsExpanded() return (
{expanded ? : }

{s.name}

{s.host}

{s.ipv6Address && (

{s.ipv6Address}

)}
) }, meta: { headerTitle: "Имя / Хост", headerClassName: DATA_GRID_CELL_PAD_FIRST, cellClassName: DATA_GRID_CELL_PAD_FIRST, expandedContent: (row: Server) => ( onPoll(row.id)} /> ), }, }, { id: "type", accessorKey: "type", header: ({ column }) => , cell: ({ row }) => , meta: { headerTitle: "Тип", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "model", accessorKey: "model", header: ({ column }) => , cell: ({ row }) => {row.original.model}, meta: { headerTitle: "Модель", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "os", accessorKey: "os", header: ({ column }) => , cell: ({ row }) => , meta: { headerTitle: "RouterOS", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "site", accessorKey: "site", header: ({ column }) => , cell: ({ row }) => (
{row.original.site}
), meta: { headerTitle: "Площадка", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "wan", header: () => ( WAN / LAN ), enableSorting: false, cell: ({ row }) => { const s = row.original if (s.type === "home-router" && s.wanUplinks?.length) { return (
{s.wanUplinks.map((w) => (
{w.name} {w.isp} ↓{w.maxDl}↑{w.maxUl}
))} {s.lanSubnet && (
LAN {s.lanSubnet}
)}
) } return (
{s.wireGuardIfaces && s.wireGuardIfaces.length > 0 && (
WG: {s.wireGuardIfaces.length} iface · {s.wireGuardIfaces.reduce((n, i) => n + i.peers.length, 0)} peers
)} {s.rpkiEnabled && (
RPKI ✓
)} {!s.wireGuardIfaces?.length && !s.rpkiEnabled && ( )}
) }, meta: { headerTitle: "WAN / LAN", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "latency", accessorKey: "latency", header: ({ column }) => ( ), cell: ({ row }) => { const s = row.original return ( 60 ? "text-[var(--status-degraded-fg)]" : "", )}> {s.latency == null ? "—" : `${s.latency} мс`} ) }, meta: { headerTitle: "Задержка", headerClassName: cn(DATA_GRID_CELL_PAD, "text-right"), cellClassName: cn(DATA_GRID_CELL_PAD, "text-right"), }, }, { id: "status", accessorKey: "status", header: ({ column }) => , cell: ({ row }) => , meta: { headerTitle: "Статус", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "actions", header: () => Действия, cell: ({ row }) => { const s = row.original return ( ) }, enableSorting: false, size: 56, meta: { headerClassName: DATA_GRID_CELL_PAD_LAST, cellClassName: DATA_GRID_CELL_PAD_LAST, }, }, ], [isLive, onDelete, onEdit, onPoll, onToggleStatus, pollingIds], ) const table = useReactTable({ data: servers, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getExpandedRowModel: getExpandedRowModel(), getRowId: (row) => row.id, getRowCanExpand: () => true, }) if (servers.length === 0) { return ( } title="Нет серверов" description="Добавьте первый MikroTik-сервер для мониторинга" className="border-0 py-12" /> ) } return ( table.getRow(row.id).toggleExpanded()} /> ) } export { ServersDataGrid, rosVer, RosBadge, TypeBadge }