"use client" import { useMemo, useState } from "react" import { PageHeader } from "@/components/page-header" import { EmptyState } from "@/components/empty-state" import { servers } from "@/lib/data" import type { WireGuardInterface, WireGuardPeer } from "@/lib/data" import { Flag } from "@/components/flag" import { Card, CardContent } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { cn } from "@/lib/utils" import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFooter, SheetClose, } from "@/components/ui/sheet" import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, } from "@/components/ui/dropdown-menu" import { ShieldCheckIcon, PlusIcon, SearchIcon, KeyRoundIcon, ChevronDownIcon, ChevronRightIcon, MoreHorizontalIcon, PencilIcon, Trash2Icon, PowerIcon, CopyIcon, CheckIcon, CodeXmlIcon, UsersIcon, ActivityIcon, ArrowDownIcon, ArrowUpIcon, } from "lucide-react" // ─── collect all WireGuard interfaces from all servers ──────────────────────── interface WgIfaceWithServer extends WireGuardInterface { serverId: string serverName: string serverCountry: string } function collectInterfaces(): WgIfaceWithServer[] { const result: WgIfaceWithServer[] = [] for (const srv of servers) { for (const wg of srv.wireGuardIfaces ?? []) { result.push({ ...wg, serverId: srv.id, serverName: srv.name, serverCountry: srv.country, }) } } return result } // ─── helpers ────────────────────────────────────────────────────────────────── function fmtBytes(n: number | undefined): string { if (!n) return "—" if (n >= 1_000_000_000) return `${(n / 1_000_000_000).toFixed(1)} ГБ` if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)} МБ` if (n >= 1_000) return `${(n / 1_000).toFixed(0)} КБ` return `${n} Б` } function truncKey(key: string): string { if (key.length <= 20) return key return `${key.slice(0, 8)}…${key.slice(-8)}` } // ─── RSC generator ──────────────────────────────────────────────────────────── function generateWgRsc(iface: WgIfaceWithServer): string { const lines: string[] = [] lines.push(`# WireGuard — ${iface.name} · ${iface.serverName}`) lines.push(`# RouterOS 7.x`) lines.push(``) lines.push(`/interface wireguard add \\`) lines.push(` name=${iface.name} \\`) lines.push(` listen-port=${iface.listenPort} \\`) lines.push(` mtu=${iface.mtu} \\`) if (iface.comment) lines.push(` comment="${iface.comment}" \\`) if (!iface.enabled) lines.push(` disabled=yes \\`) lines.push(``) for (const p of iface.peers) { lines.push(`/interface wireguard peers add \\`) lines.push(` interface=${iface.name} \\`) lines.push(` public-key="${p.publicKey}" \\`) lines.push(` allowed-address=${p.allowedIps.join(",")} \\`) if (p.endpoint) lines.push(` endpoint-address=${p.endpoint.split(":")[0]} \\`) if (p.endpoint) lines.push(` endpoint-port=${p.endpoint.split(":")[1] ?? "13231"} \\`) if (p.persistent) lines.push(` persistent-keepalive=25 \\`) if (p.comment) lines.push(` comment="${p.comment}" \\`) lines.push(``) } return lines.join("\n") } // ─── Peer row ───────────────────────────────────────────────────────────────── function PeerRow({ peer }: { peer: WireGuardPeer }) { return (
{/* public key */}
{truncKey(peer.publicKey)}
{/* allowed IPs */}
{peer.allowedIps.join(", ")}
{/* handshake */} {peer.latestHandshake ?? "нет рукопожатия"} {/* rx / tx */}
{fmtBytes(peer.transferRx)} {fmtBytes(peer.transferTx)}
{/* endpoint */} {peer.endpoint ?? "—"}
) } // ─── Interface card ─────────────────────────────────────────────────────────── function IfaceRow({ iface, expanded, onToggleExpand, onExport, }: { iface: WgIfaceWithServer expanded: boolean onToggleExpand: () => void onExport: () => void }) { const onlinePeers = iface.peers.filter((p) => !!p.latestHandshake).length return (
{/* expand */} {/* name + server */}
{iface.name}
{iface.serverName}
{/* port */}

Порт

{iface.listenPort}

{/* MTU */}

MTU

{iface.mtu}

{/* peers */}

Пиров

{onlinePeers} /{iface.peers.length}

{/* status badge */} {iface.status === "up" ? "UP" : "DOWN"} {/* menu */} e.stopPropagation()}> } /> { e.stopPropagation(); onExport() }}> Экспорт .rsc Редактировать Добавить пира {iface.enabled ? "Отключить" : "Включить"} Удалить
{/* expanded peers */} {expanded && iface.peers.length > 0 && (
Public Key Allowed IPs Последнее рукопожатие RX / TX Endpoint
{iface.peers.map((p) => )}
)} {expanded && iface.peers.length === 0 && (
Нет пиров
)}
) } // ─── Export Sheet ───────────────────────────────────────────────────────────── function ExportSheet({ open, iface, onClose }: { open: boolean; iface: WgIfaceWithServer | null; onClose: () => void }) { const [copied, setCopied] = useState(false) const code = useMemo(() => iface ? generateWgRsc(iface) : "", [iface]) function handleCopy() { navigator.clipboard.writeText(code).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2000) }) } return ( { if (!v) onClose() }}>
Экспорт WireGuard RouterOS 7.x · /interface wireguard + peers
            {code.split("\n").map((line, i) => {
              const isComment = line.startsWith("#")
              const isCmd = line.trimStart().startsWith("/interface")
              const isParam = /^\s+[a-z]/.test(line)
              return (
                
                  {line}{"\n"}
                
              )
            })}
          
}>Закрыть
) } // ════════════════════════════════════════════════════════════════════════════ export default function WireGuardPage() { const allIfaces = useMemo(() => collectInterfaces(), []) const [search, setSearch] = useState("") const [expandedIds, setExpandedIds] = useState>(new Set()) const [exportIface, setExportIface] = useState(null) const filtered = useMemo(() => { if (!search) return allIfaces const q = search.toLowerCase() return allIfaces.filter((i) => i.name.includes(q) || i.serverName.toLowerCase().includes(q) || i.peers.some((p) => p.allowedIps.some((a) => a.includes(q)) || (p.endpoint ?? "").includes(q)) ) }, [allIfaces, search]) const totalPeers = allIfaces.reduce((s, i) => s + i.peers.length, 0) const onlinePeers = allIfaces.reduce((s, i) => s + i.peers.filter((p) => !!p.latestHandshake).length, 0) const upIfaces = allIfaces.filter((i) => i.status === "up").length function toggleExpand(id: string) { setExpandedIds((prev) => { const next = new Set(prev) if (next.has(id)) next.delete(id); else next.add(id) return next }) } return (
} />
{/* KPI */}
{[ { label: "Интерфейсов", value: allIfaces.length, icon: }, { label: "Активных (UP)", value: upIfaces, icon: }, { label: "Всего пиров", value: totalPeers, icon: }, { label: "Пиров онлайн", value: `${onlinePeers}/${totalPeers}`, icon: }, ].map((s) => (

{s.label}

{s.value}

{s.icon}
))}
{/* Info banner */}

WireGuard — рекомендуемый туннельный протокол в RouterOS 7.x

Доступен с RouterOS 7.1+. Более высокая производительность и безопасность по сравнению с GRE+IPsec. Ключи генерируются командой /interface/wireguard/print.

{/* Search + table */}
setSearch(e.target.value)} />
{filtered.length} интерфейсов
{/* table header */}
Интерфейс / Сервер Порт MTU Пиры Статус
{filtered.length === 0 ? ( } title="Нет WireGuard интерфейсов" description="Добавьте первый интерфейс или проверьте поиск" className="border-0 py-16" /> ) : ( filtered.map((iface) => ( toggleExpand(iface.id)} onExport={() => setExportIface(iface)} /> )) )}
{/* RouterOS reference */}

RouterOS 7 · /interface wireguard — быстрые команды

{[ { title: "Создать интерфейс", lines: [ "/interface wireguard add \\", " name=wg0 \\", " listen-port=13231 \\", " mtu=1420", ], }, { title: "Добавить пира", lines: [ "/interface wireguard peers add \\", " interface=wg0 \\", ' public-key="<ключ>" \\', " allowed-address=10.0.0.2/32 \\", " endpoint-address=1.2.3.4 \\", " persistent-keepalive=25", ], }, { title: "Назначить IP", lines: [ "/ip address add \\", " address=10.210.0.1/30 \\", " interface=wg0", "", "# Статус:", "/interface wireguard print", ], }, ].map((b) => (

{b.title}

                      {b.lines.join("\n")}
                    
))}
setExportIface(null)} />
) }