"use client" import { DataGridContainer, DataGridScrollArea, DataGridTable, dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" import { createContext, useContext, useEffect, useMemo, type ReactNode } from "react" import { type ColumnDef, useTable, } from "@tanstack/react-table" import type { WireGuardInterface } from "@/lib/data" import { Flag } from "@/components/flag" import { cn } from "@/lib/utils" import { Badge } from "@/components/reui/badge" 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 { WireGuardPeersDetail } from "@/components/data-grids/wireguard-peers-detail" import { EmptyState } from "@/components/empty-state" import { ChevronDownIcon, ChevronRightIcon, CodeXmlIcon, MoreHorizontalIcon, PlusIcon, PowerIcon, ShieldCheckIcon, Trash2Icon, } from "lucide-react" export interface WgIfaceWithServer extends WireGuardInterface { serverId: string serverName: string serverCountry: string } interface WireguardDataGridProps { interfaces: WgIfaceWithServer[] compactServer?: boolean canWrite?: boolean emptyAction?: ReactNode onExport: (iface: WgIfaceWithServer) => void onAddPeer?: (iface: WgIfaceWithServer) => void toggleBusy?: boolean onToggle?: (iface: WgIfaceWithServer) => void onDelete?: (iface: WgIfaceWithServer) => void onDeletePeer?: (iface: WgIfaceWithServer, peerId: string) => void onExportPeer?: (iface: WgIfaceWithServer, peerId: string) => void } type WireguardActions = Pick const WireguardActionsContext = createContext(null) // Preserve menu state while callbacks and the current row data update. function WireguardActionsCell({ row }: { row: { original: WgIfaceWithServer } }) { const actions = useContext(WireguardActionsContext) if (!actions) throw new Error("WireGuard actions context is missing") const { onExport, onAddPeer, onToggle, toggleBusy, onDelete, canWrite = true } = actions const iface = row.original return (
e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} > e.stopPropagation()} aria-label={`Действия: ${iface.name}`} > } /> onExport(iface)}> Экспорт {onAddPeer && ( onAddPeer(iface)}> Добавить пира )} {onToggle && ( <> onToggle(iface)}> {iface.enabled ? "Отключить" : "Включить"} )} {onDelete && ( <> onDelete(iface)}> Удалить )}
) } function WireguardDataGrid({ interfaces, compactServer = false, canWrite = true, emptyAction, onExport, onAddPeer, onToggle, toggleBusy = false, onDelete, onDeletePeer, onExportPeer, }: WireguardDataGridProps) { const columns = useMemo[]>( () => [ { id: "name", size: 176, accessorKey: "name", header: ({ column }) => ( ), cell: ({ row }) => { const iface = row.original const expanded = row.getIsExpanded() return (
{iface.name}
{!compactServer ? (
{iface.serverName}
) : null}

{iface.peers.length} пиров

) }, meta: { headerTitle: compactServer ? "Интерфейс" : "Интерфейс / Сервер", headerClassName: DATA_GRID_CELL_PAD_FIRST, cellClassName: DATA_GRID_CELL_PAD_FIRST, expandedContent: (row: WgIfaceWithServer) => ( onDeletePeer(row, peerId) : undefined} onExportPeer={onExportPeer ? (peerId) => onExportPeer(row, peerId) : undefined} /> ), }, }, { id: "listenPort", size: 100, accessorKey: "listenPort", header: ({ column }) => , cell: ({ row }) => ( {row.original.listenPort} ), meta: { headerTitle: "Порт", headerClassName: DATA_GRID_CELL_PAD, cellClassName: cn(DATA_GRID_CELL_PAD, "text-center"), }, }, { id: "mtu", size: 80, 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: "peers", size: 80, header: () => ( Пиры ), enableSorting: false, cell: ({ row }) => { const iface = row.original return ( {iface.peers.length} ) }, meta: { headerTitle: "Пиры", headerClassName: DATA_GRID_CELL_PAD, cellClassName: cn(DATA_GRID_CELL_PAD, "text-center"), }, }, { id: "status", size: 104, accessorKey: "status", header: ({ column }) => , cell: ({ row }) => ( {row.original.status === "up" ? "UP" : "DOWN"} ), meta: { headerTitle: "Статус", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD, }, }, { id: "actions", header: () => Действия, cell: WireguardActionsCell, enableSorting: false, size: 72, meta: { headerClassName: DATA_GRID_CELL_PAD_LAST, cellClassName: DATA_GRID_CELL_PAD_LAST, }, }, ], [compactServer, onDeletePeer, onExportPeer], ) const table = useTable({ features: dataGridFeatures, manualPagination: false, autoResetPageIndex: false, autoResetExpanded: false, initialState: { pagination: { pageIndex: 0, pageSize: 50 }, columnPinning: { start: ["name"], end: [] } }, data: interfaces, columns, getRowId: (row) => JSON.stringify([row.serverId, row.id]), getRowCanExpand: () => true, }) const { pageIndex, pageSize } = table.state.pagination useEffect(() => { const lastPage = Math.max(0, Math.ceil(interfaces.length / pageSize) - 1) if (pageIndex > lastPage) table.setPageIndex(lastPage) }, [interfaces.length, pageIndex, pageSize, table]) if (interfaces.length === 0) { return ( } title="Нет WireGuard интерфейсов" description="Добавьте первый интерфейс или сбросьте фильтры" action={emptyAction} className="border-0 py-16" /> ) } return ( 50 || pageSize !== 50} tableLayout={{ dense: true, columnsPinnable: true }} onRowClick={(row) => table.getRow(JSON.stringify([row.serverId, row.id])).toggleExpanded()} tableClassNames={{ headerRow: "border-b border-border", bodyRow: cn("group/row", "[&[data-disabled=true]]:opacity-50"), }} > ) } export { WireguardDataGrid, type WireguardDataGridProps }