import { ReactNode, useContext } from 'react'; import { Handle, Position, NodeProps, useReactFlow} from '@xyflow/react'; import { AppNode, AppEdge, AppGraph, NodeData } from '../types/graph'; import {Settings, SettingsContext} from '../types/settings' import StringBuilder from '../utils/StringBuilder'; import { CIDR, IPUtils } from '../utils/iputils'; import { tryDerivePublicKey } from '../utils/wireguardConfig' import './CustomNode.css'; import toast from 'react-hot-toast'; class ConfigResult { constructor( public success: boolean, public config?: string, public error?: string ) {} } // function mapAddressToCIDR(nodeIds : string[], getAddress: GetAddress) : Record { // const nodeIdToCIDR : Record = {}; // for(const nodeId of nodeIds) { // const address = getAddress(nodeId); // if(!address) continue; // const result = IPUtils.parse(address); // const cidr = result.cidr; // if(!cidr) { // throw new Error("节点地址无效"); // } // if(cidr.version === 'IPv4') { // cidr.mask = 32; // } else if(cidr.version === 'IPv6') { // cidr.mask = 128; // } // nodeIdToCIDR[nodeId] = cidr; // } // return nodeIdToCIDR; // } function generateInterfaceConfig(settings: Settings, data: NodeData) : StringBuilder { const address = [data.ipv4Address, data.ipv6Address].flatMap(p => p ? [p] : []).join(', '); const config = new StringBuilder(); config.appendLine(`[Interface]`); config.appendLine(`# ${data.label}`); config.appendLine(`PrivateKey = ${data.privateKey}`); config.appendLine(`ListenPort = ${data.listenPort || settings.listenPort}`); config.appendLine(`MTU = ${data.mtu || settings.mtu}`); if(address) config.appendLine(`Address = ${address}`); if(data.postUp) config.appendLine(`PostUp = ${data.postUp}`); if(data.postDown) config.appendLine(`PostDown = ${data.postDown}`); if(data.dnsServers) config.appendLine(`DNS = ${data.dnsServers}`); return config; } function generateConfig(settings: Settings, data: NodeData, graph: AppGraph) : ConfigResult { const config = generateInterfaceConfig(settings, data); const disallowCIDRs : CIDR[] = []; if(data.disallowIPs) { const disallowList = data.disallowIPs.split(',').map(ip => ip.trim()).filter(ip => ip.length > 0); for(const ip of disallowList) { const result = IPUtils.parse(ip); if(!result.cidr) { return new ConfigResult(false, undefined, `无效的禁止访问IP: ${ip} (${result.error})`); } disallowCIDRs.push(result.cidr); } } const belongsToEdge : Record = {[node.id]: node.id}; const queue : AppNode[] = []; const nearEdges = getNearEdges(node); nearEdges.forEach(edge => { const nextNode = getNextNode(edge, node)!; belongsToEdge[nextNode.id] = edge.id; queue.push(nextNode); }); while(queue.length > 0) { const currentNode = queue.shift()!; const fromEdgeId = belongsToEdge[currentNode.id]; if(!fromEdgeId) continue; getNearEdges(currentNode).forEach(edge => { const nextNode = getNextNode(edge, currentNode)!; if(!belongsToEdge[nextNode.id]) { belongsToEdge[nextNode.id] = fromEdgeId; queue.push(nextNode); } }); } const groupedByEdge: Record = {}; const nodeIds : string[] = []; for (const nodeId in belongsToEdge) { const edgeId = belongsToEdge[nodeId]; if(edgeId === nodeId) continue; // 跳过起始节点 nodeIds.push(nodeId); if(!edgeId) continue; if (!groupedByEdge[edgeId]) { groupedByEdge[edgeId] = []; } groupedByEdge[edgeId].push(nodeId); } for(const edgeId in groupedByEdge) { const groupNodeIds = groupedByEdge[edgeId]!; const edge = getEdge(edgeId)!; const nextNode = getNextNode(edge, node)!; const nextNodeData = nextNode.data; const publicKey = tryDerivePublicKey(nextNodeData.privateKey); if(!publicKey) return new ConfigResult(false, undefined, "无法从私钥派生公钥"); config.appendLine(""); config.appendLine("[Peer]"); config.appendLine(`# ${nextNodeData.label}`); config.appendLine(`PublicKey = ${ publicKey}`); if(edge.data?.isTwoWayEdge || edge.source === node.id) { if(edge.data?.persistentKeepalive) { config.appendLine(`PersistentKeepalive = ${edge.data.persistentKeepalive}`); } if(!nextNodeData.listenAddress) { return new ConfigResult(false, undefined, `节点 ${nextNodeData.label} 未设置监听地址,无法生成配置`); } const parse = IPUtils.parse(`${nextNodeData.listenAddress}/0`); if(!parse.cidr) { return new ConfigResult(false, undefined, `节点 ${nextNodeData.label} 的监听地址无效`); } const listenAddress = parse.cidr.version === 'IPv4' ? nextNodeData.listenAddress : `[${nextNodeData.listenAddress}]`; const listenPort = nextNodeData.listenPort || settings.listenPort; config.appendLine(`EndPoint = ${listenAddress}:${listenPort}`); } const subnets : Record[] = []; try { subnets.push(mapAddressToCIDR(nodeIds, nodeId => node.data.ipv4Address && getNode(nodeId)?.data.ipv4Address)); subnets.push(mapAddressToCIDR(nodeIds, nodeId => node.data.ipv6Address && getNode(nodeId)?.data.ipv6Address)); } catch(e) { if(e instanceof Error) { return new ConfigResult(false, undefined, e.message); } } const allowIPs : string[] = []; for(const subnetMap of subnets) { const allCIDRs = nodeIds.flatMap(id => subnetMap[id] ? [subnetMap[id]] : []); const targetCIDRs = groupNodeIds.flatMap(id => { const cidr = subnetMap[id]; if(!cidr || disallowCIDRs.some(disallow => disallow.contains(cidr))) return []; return [cidr]; }); const mergeResult = IPUtils.mergeCIDRs(allCIDRs, targetCIDRs); if(!mergeResult) { return new ConfigResult(false, undefined, `无法生成路由配置`); } mergeResult.forEach(cidr => {allowIPs.push(cidr.toString())}); } if(allowIPs.length > 0) { config.appendLine(`AllowedIPs = ${allowIPs.join(', ')}`); } } // console.log(config.toString()); return new ConfigResult(true, config.toString()); } export default function CustomNode({ data, selected }: NodeProps): ReactNode { const settings = useContext(SettingsContext); const { getNode, getEdge, getEdges } = useReactFlow(); const handleGenerate = (node : NodeData) => { const result = generateConfig(settings, node, getEdges, getEdge, getNode); if(result.success && result.config) { navigator.clipboard.writeText(result.config).then(() => { toast.success("配置已复制到剪贴板"); }).catch(() => { toast.error("复制失败,请手动复制"); }); } else { toast.error("配置生成失败:" + (result.error || "未知错误")); } }; const empty = !(data.ipv4Address || data.ipv6Address); return (
{empty &&
未配置地址信息
} {data.ipv4Address &&
IPv4地址: {data.ipv4Address || "未设置"}
} {data.ipv6Address &&
IPv6地址: {data.ipv6Address || "未设置"}
} {data.listenAddress &&
监听地址: {data.listenAddress || "未设置"}
}
{[Position.Top, Position.Bottom, Position.Right, Position.Left].map((position) => ( (["target", "source"] as const).map((type) => ( )) ))}
); }