"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] ?? STATUS_MAP.degraded return ( {s.label} ) } function IpsecBadge({ secured }: { secured: boolean }) { return secured ? ( IPsec ) : ( Открытый ) } interface GreTunnelsDataGridProps { tunnels: GreTunnel[] servers: Server[] pools: GrePool[] onCodePreview: (tunnel: GreTunnel) => void onEdit?: (tunnel: GreTunnel) => void onToggle?: (tunnel: GreTunnel) => void onDelete?: (tunnel: GreTunnel) => void mutationsLocked?: boolean } function GreTunnelsDataGrid({ tunnels, servers, pools, onCodePreview, onEdit, onToggle, onDelete, mutationsLocked = false, }: 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 const enc = t.ipsec.encAlg ? ENC_LABELS[t.ipsec.encAlg] : undefined const auth = t.ipsec.authAlg ? AUTH_LABELS[t.ipsec.authAlg] : undefined const dh = t.ipsec.dhGroup ? DH_LABELS[t.ipsec.dhGroup] : undefined const ike = t.ipsec.ikeVersion ? IKE_LABELS[t.ipsec.ikeVersion] : undefined if (!enc && !auth && !dh && !ike) { return PSK · auto } return (
{[enc, auth].filter(Boolean).join(" / ") || "PSK"} {[dh?.split(" ")[0], ike].filter(Boolean).join(" · ")} {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)}> Просмотр кода onEdit?.(t)}> Редактировать onToggle?.(t)}> {t.enabled ? "Выключить" : "Включить"} onDelete?.(t)} > Удалить туннель
) }, size: 88, meta: { headerClassName: DATA_GRID_CELL_PAD_LAST, cellClassName: DATA_GRID_CELL_PAD_LAST }, }, ], [onCodePreview, onEdit, onToggle, onDelete, mutationsLocked, 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 }