"use client" import { useMemo } from "react" import { type ColumnDef, getCoreRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table" import type { GrePool, GreTunnel, GreStatus, IpsecEncAlg, IpsecAuthAlg, IpsecDhGroup, IkeVersion, Server, } from "@/lib/data" import { Flag } from "@/components/flag" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, 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 { CodeXmlIcon, LockIcon, LockOpenIcon, MoreHorizontalIcon, NetworkIcon, PencilIcon, PowerIcon, Trash2Icon, } from "lucide-react" const ENC_LABELS: Record = { "aes-128": "AES-128", "aes-192": "AES-192", "aes-256": "AES-256", } const AUTH_LABELS: Record = { sha1: "SHA-1", sha256: "SHA-256", sha512: "SHA-512", } const DH_LABELS: Record = { modp1024: "DH-2 (1024)", modp2048: "DH-14 (2048)", modp4096: "DH-16 (4096)", ecp256: "ECP-256", ecp384: "ECP-384", ecp521: "ECP-521", } const IKE_LABELS: Record = { ikev1: "IKEv1", ikev2: "IKEv2" } const STATUS_MAP: Record = { up: { label: "Up", dot: "bg-emerald-500" }, degraded: { label: "Degraded", dot: "bg-amber-500" }, down: { label: "Down", dot: "bg-red-500" }, } function TunnelStatus({ status }: { status: GreStatus }) { const s = STATUS_MAP[status] return ( {s.label} ) } function IpsecBadge({ secured }: { secured: boolean }) { return secured ? ( IPsec ) : ( Открытый ) } interface GreTunnelsDataGridProps { tunnels: GreTunnel[] servers: Server[] pools: GrePool[] onCodePreview: (tunnel: GreTunnel) => void } function GreTunnelsDataGrid({ tunnels, servers, pools, onCodePreview, }: GreTunnelsDataGridProps) { const serverMap = useMemo(() => new Map(servers.map((s) => [s.id, s])), [servers]) const poolMap = useMemo(() => new Map(pools.map((p) => [p.id, p])), [pools]) const columns = useMemo[]>( () => [ { id: "name", accessorKey: "name", header: ({ column }) => ( ), cell: ({ row }) => { const t = row.original const srv = serverMap.get(t.serverId) return (

{t.name}

{srv && } {srv?.name ?? t.serverId}

) }, meta: { headerTitle: "Интерфейс / Сервер", headerClassName: DATA_GRID_CELL_PAD_FIRST, cellClassName: DATA_GRID_CELL_PAD_FIRST, }, }, { id: "endpoints", header: () => Эндпоинты, enableSorting: false, cell: ({ row }) => { const t = row.original return (

{t.localAddress === "0.0.0.0" ? ( авто ) : ( t.localAddress )}

→ {t.remoteAddress}

) }, meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "innerIp", header: () => Внутренний IP, enableSorting: false, cell: ({ row }) => { const t = row.original return (

{t.localInnerIp}

{t.remoteInnerIp}

) }, meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "pool", accessorKey: "poolId", header: ({ column }) => , cell: ({ row }) => ( {poolMap.get(row.original.poolId)?.name ?? "—"} ), meta: { headerTitle: "Пул", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "ipsec", header: () => IPsec, enableSorting: false, cell: ({ row }) => , meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "encryption", header: () => Шифрование, enableSorting: false, cell: ({ row }) => { const t = row.original if (!t.ipsec) return return (
{ENC_LABELS[t.ipsec.encAlg]} / {AUTH_LABELS[t.ipsec.authAlg]} {DH_LABELS[t.ipsec.dhGroup].split(" ")[0]} · {IKE_LABELS[t.ipsec.ikeVersion]} {t.ipsec.pfs && " · PFS"}
) }, meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "mtu", accessorKey: "mtu", header: ({ column }) => , cell: ({ row }) => ( {row.original.mtu} ), meta: { headerTitle: "MTU", headerClassName: DATA_GRID_CELL_PAD, cellClassName: cn(DATA_GRID_CELL_PAD, "text-center"), }, }, { id: "keepalive", header: () => Keepalive, enableSorting: false, cell: ({ row }) => { const t = row.original return ( {t.keepaliveInterval === 0 ? "откл." : `${t.keepaliveInterval}с / ${t.keepaliveRetries}`} ) }, meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "status", accessorKey: "status", header: ({ column }) => , cell: ({ row }) => , meta: { headerTitle: "Статус", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "actions", header: () => Действия, enableSorting: false, cell: ({ row }) => { const t = row.original return (
} /> {t.name} onCodePreview(t)}> Просмотр кода Редактировать {t.enabled ? "Выключить" : "Включить"} Удалить туннель
) }, size: 88, meta: { headerClassName: DATA_GRID_CELL_PAD_LAST, cellClassName: DATA_GRID_CELL_PAD_LAST }, }, ], [onCodePreview, poolMap, serverMap], ) const table = useReactTable({ data: tunnels, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getRowId: (row, index) => `${row.id}:${row.serverId}:${row.name}:${index}`, }) if (tunnels.length === 0) { return ( } title="Нет GRE-туннелей" description="Измените фильтр или добавьте туннель" className="border-0 py-16" /> ) } return ( ) } export { GreTunnelsDataGrid, type GreTunnelsDataGridProps }