151 lines
4.7 KiB
TypeScript
151 lines
4.7 KiB
TypeScript
"use client"
|
||
|
||
import { useMemo } from "react"
|
||
import { resolveWgClientEndpoint } from "@mmapp/contracts/wireguard"
|
||
import { selectWgExportPeer } from "@/lib/wg-export-peer"
|
||
import type { WgIfaceWithServer } from "@/components/data-grids/wireguard-data-grid"
|
||
import {
|
||
CodeExportSheet,
|
||
type CodeExportFormat,
|
||
} from "@/components/reui-kit/code-export-sheet"
|
||
import {
|
||
generateMikrotikRsc,
|
||
generateNativeConf,
|
||
generatePeerClientConf,
|
||
} from "@/lib/wg-config"
|
||
|
||
function WgExportSheet({
|
||
open,
|
||
iface,
|
||
onClose,
|
||
liveContent,
|
||
liveBusy,
|
||
onRequestLiveExport,
|
||
initialTab = "rsc",
|
||
peerId,
|
||
serverHost,
|
||
}: {
|
||
open: boolean
|
||
iface: WgIfaceWithServer | null
|
||
onClose: () => void
|
||
liveContent?: { rsc?: string; conf?: string; peerConf?: string } | null
|
||
liveBusy?: boolean
|
||
onRequestLiveExport?: (format: "rsc" | "conf" | "peer-conf") => void
|
||
initialTab?: "rsc" | "conf" | "peer"
|
||
/** Selected peer for Peer .conf (defaults to first peer) */
|
||
peerId?: string | null
|
||
serverHost?: string
|
||
}) {
|
||
const formats = useMemo((): CodeExportFormat[] => {
|
||
if (!iface) {
|
||
return [
|
||
{ id: "rsc", label: ".rsc", filename: "wg.rsc", code: "" },
|
||
{ id: "conf", label: ".conf", filename: "wg.conf", code: "" },
|
||
{
|
||
id: "peer",
|
||
label: "Peer",
|
||
filename: "wg-peer.conf",
|
||
code: "",
|
||
emptyMessage: "Интерфейс не выбран",
|
||
},
|
||
]
|
||
}
|
||
|
||
const base = {
|
||
name: iface.name,
|
||
listenPort: iface.listenPort,
|
||
mtu: iface.mtu,
|
||
comment: iface.comment,
|
||
enabled: iface.enabled,
|
||
privateKey: iface.privateKey,
|
||
publicKey: iface.publicKey,
|
||
address: iface.address,
|
||
serverName: iface.serverName,
|
||
peers: iface.peers.map((p) => ({
|
||
publicKey: p.publicKey,
|
||
allowedIps: p.allowedIps,
|
||
endpoint: p.endpoint,
|
||
persistentKeepalive: p.persistentKeepalive,
|
||
persistent: p.persistent,
|
||
comment: p.comment,
|
||
name: p.name,
|
||
clientAddress: p.clientAddress,
|
||
clientDns: p.clientDns,
|
||
clientEndpoint: p.clientEndpoint,
|
||
})),
|
||
}
|
||
|
||
const peer = selectWgExportPeer(iface.peers, peerId)
|
||
|
||
const localRsc = generateMikrotikRsc(base)
|
||
const localConf = generateNativeConf(base)
|
||
|
||
let peerConf = ""
|
||
let peerEmpty: string | undefined
|
||
if (peerId != null && !peer) {
|
||
peerEmpty = "Выбранный пир не найден. Обновите список и выберите пира заново."
|
||
} else if (!iface.publicKey) {
|
||
peerEmpty = "Нет public-key интерфейса для клиентского .conf"
|
||
} else if (!peer) {
|
||
peerEmpty = "Нет пиров для экспорта Peer .conf"
|
||
} else {
|
||
peerConf = generatePeerClientConf({
|
||
peerAddress: peer.clientAddress,
|
||
peerDns: peer.clientDns,
|
||
serverPublicKey: iface.publicKey,
|
||
endpoint: resolveWgClientEndpoint(peer.clientEndpoint, serverHost, iface.listenPort),
|
||
persistentKeepalive: peer.persistentKeepalive ?? 25,
|
||
})
|
||
}
|
||
|
||
return [
|
||
{
|
||
id: "rsc",
|
||
label: ".rsc",
|
||
filename: `${iface.name}.rsc`,
|
||
code: liveContent?.rsc ?? localRsc,
|
||
isLive: Boolean(liveContent?.rsc),
|
||
},
|
||
{
|
||
id: "conf",
|
||
label: ".conf",
|
||
filename: `${iface.name}.conf`,
|
||
code: liveContent?.conf ?? localConf,
|
||
isLive: Boolean(liveContent?.conf),
|
||
},
|
||
{
|
||
id: "peer",
|
||
label: "Peer",
|
||
filename: `${iface.name}-peer.conf`,
|
||
code: liveContent?.peerConf ?? peerConf,
|
||
emptyMessage: liveContent?.peerConf ? undefined : peerEmpty,
|
||
isLive: Boolean(liveContent?.peerConf),
|
||
},
|
||
]
|
||
}, [iface, liveContent, peerId, serverHost])
|
||
|
||
return (
|
||
<CodeExportSheet
|
||
open={open}
|
||
onClose={onClose}
|
||
title="Экспорт WireGuard"
|
||
description={iface ? `${iface.name} · ${iface.serverName}` : "—"}
|
||
formats={formats}
|
||
beforeCode={<p className="text-xs text-muted-foreground">Peer .conf — шаблон для клиента. Заполните его приватный ключ, адрес и сети AllowedIPs. При NAT укажите доступный извне client-endpoint; endpoint удалённого пира здесь не используется.</p>}
|
||
initialFormatId={initialTab}
|
||
liveBusy={liveBusy}
|
||
liveLabel="С роутера"
|
||
onLiveFetch={
|
||
onRequestLiveExport
|
||
? (formatId) =>
|
||
onRequestLiveExport(
|
||
formatId === "peer" ? "peer-conf" : formatId === "conf" ? "conf" : "rsc",
|
||
)
|
||
: undefined
|
||
}
|
||
/>
|
||
)
|
||
}
|
||
|
||
export { WgExportSheet }
|