From 9c869d7bb448acfa61fd6ac0423f13e78958ec54 Mon Sep 17 00:00:00 2001 From: limil Date: Tue, 27 Jan 2026 23:03:48 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E7=B1=BB=E5=9E=8B=E6=8A=A5?= =?UTF-8?q?=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 29 ++------ src/components/ConfigViewer.tsx | 18 +---- src/components/CustomNode.tsx | 17 +---- src/components/NodeEditor.tsx | 12 +-- src/types/graph.ts | 16 ++++ src/utils/wireguardConfig.js | 126 -------------------------------- src/utils/wireguardConfig.ts | 23 ++---- 7 files changed, 35 insertions(+), 206 deletions(-) create mode 100644 src/types/graph.ts delete mode 100644 src/utils/wireguardConfig.js diff --git a/src/App.tsx b/src/App.tsx index dbe92bf..b751400 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,36 +7,18 @@ import { Background, Controls, ReactFlowProvider, - Node, - Edge, + NodeTypes, NodeChange, - EdgeChange, - Connection, - OnNodesChange, OnEdgesChange, OnConnect, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; +import { AppNode, AppEdge, NodeData } from './types/graph'; import CustomNode from './components/CustomNode'; import NodeEditor from './components/NodeEditor'; import ConfigViewer from './components/ConfigViewer'; import './App.css'; - -interface NodeData extends Record { - label: string; - ipAddress: string; - listenPort: string; - privateKey: string; - publicKey: string; - endpoint?: string; - dnsServers?: string; - persistentKeepalive?: string; -} - -type AppNode = Node; -type AppEdge = Edge; - const initialNodes: AppNode[] = [ { id: 'n1', @@ -53,7 +35,8 @@ const initialNodes: AppNode[] = [ ]; const initialEdges: AppEdge[] = []; -const nodeTypes = { + +const nodeTypes : NodeTypes = { custom: CustomNode, }; @@ -63,8 +46,8 @@ function FlowContent(): ReactNode { const [editingNode, setEditingNode] = useState(null); const [showConfigViewer, setShowConfigViewer] = useState(false); - const onNodesChange = useCallback( - (changes) => setNodes((nodesSnapshot) => applyNodeChanges(changes, nodesSnapshot)), + const onNodesChange = useCallback( + (changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)), [], ); diff --git a/src/components/ConfigViewer.tsx b/src/components/ConfigViewer.tsx index 3720059..2fdeed4 100644 --- a/src/components/ConfigViewer.tsx +++ b/src/components/ConfigViewer.tsx @@ -1,23 +1,11 @@ import { ReactNode, useState } from 'react'; -import { Node, Edge } from '@xyflow/react'; import { generateAllConfigs, downloadConfig } from '../utils/wireguardConfig'; +import {AppNode, AppEdge} from '../types/graph'; import './ConfigViewer.css'; -interface NodeData extends Record { - id: string; - label: string; - ipAddress: string; - listenPort: string; - privateKey: string; - publicKey: string; - endpoint?: string; - dnsServers?: string; - persistentKeepalive?: string; -} - interface ConfigViewerProps { - nodes: Node[]; - edges: Edge[]; + nodes: AppNode[]; + edges: AppEdge[]; onClose: () => void; } diff --git a/src/components/CustomNode.tsx b/src/components/CustomNode.tsx index 83eecb5..88f87f6 100644 --- a/src/components/CustomNode.tsx +++ b/src/components/CustomNode.tsx @@ -1,23 +1,12 @@ import { ReactNode } from 'react'; import { Handle, Position, NodeProps } from '@xyflow/react'; +import { AppNode } from '../types/graph'; import './CustomNode.css'; -interface NodeData { - label: string; - ipAddress?: string; - listenPort?: string; - privateKey?: string; - publicKey?: string; - endpoint?: string; - dnsServers?: string; - persistentKeepalive?: string; -} - export default function CustomNode({ data, - isConnecting, - selected -}: NodeProps): ReactNode { + selected +}: NodeProps): ReactNode { return (
diff --git a/src/components/NodeEditor.tsx b/src/components/NodeEditor.tsx index c99a7e8..3ddefb2 100644 --- a/src/components/NodeEditor.tsx +++ b/src/components/NodeEditor.tsx @@ -1,18 +1,8 @@ import { useState, ReactNode } from 'react'; import { validateNodeConfig } from '../utils/wireguardConfig'; +import { NodeData } from '../types/graph'; import './NodeEditor.css'; -interface NodeData { - label: string; - ipAddress: string; - listenPort: string; - privateKey: string; - publicKey: string; - endpoint?: string; - dnsServers?: string; - persistentKeepalive?: string; -} - interface NodeEditorProps { node: NodeData; onUpdate: (data: NodeData) => void; diff --git a/src/types/graph.ts b/src/types/graph.ts new file mode 100644 index 0000000..6fa8909 --- /dev/null +++ b/src/types/graph.ts @@ -0,0 +1,16 @@ +import { Node, Edge } from '@xyflow/react'; + +export type AppNode = Node; + +export type AppEdge = Edge; + +export type NodeData = { + label: string; + ipAddress: string; + listenPort: string; + privateKey: string; + publicKey: string; + endpoint?: string; + dnsServers?: string; + persistentKeepalive?: string; +} \ No newline at end of file diff --git a/src/utils/wireguardConfig.js b/src/utils/wireguardConfig.js deleted file mode 100644 index 6c67a8b..0000000 --- a/src/utils/wireguardConfig.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * WireGuard配置生成工具 - */ - -// 生成WireGuard格式的配置文件 -export function generateWireGuardConfig(node, allNodes, allEdges) { - const config = []; - - // [Interface] 部分 - config.push('[Interface]'); - config.push(`PrivateKey = ${node.privateKey}`); - config.push(`Address = ${node.ipAddress}/32`); - - if (node.listenPort) { - config.push(`ListenPort = ${node.listenPort}`); - } - - if (node.dnsServers) { - config.push(`DNS = ${node.dnsServers}`); - } - - config.push(''); - - // 找到与该节点相连的所有节点 - const connectedNodeIds = new Set(); - allEdges.forEach(edge => { - if (edge.source === node.id) { - connectedNodeIds.add(edge.target); - } - if (edge.target === node.id) { - connectedNodeIds.add(edge.source); - } - }); - - // 为每个连接的节点添加 [Peer] 部分 - connectedNodeIds.forEach(peerId => { - const peerNode = allNodes.find(n => n.id === peerId); - if (peerNode && peerNode.publicKey && peerNode.ipAddress) { - config.push('[Peer]'); - config.push(`PublicKey = ${peerNode.publicKey}`); - config.push(`AllowedIPs = ${peerNode.ipAddress}/32`); - - if (peerNode.endpoint) { - config.push(`Endpoint = ${peerNode.endpoint}`); - } - - if (peerNode.persistentKeepalive) { - config.push(`PersistentKeepalive = ${peerNode.persistentKeepalive}`); - } - - config.push(''); - } - }); - - return config.join('\n'); -} - -// 生成所有节点的配置 -export function generateAllConfigs(nodes, edges) { - const configs = {}; - - nodes.forEach(node => { - if (node.id && node.privateKey && node.ipAddress) { - configs[node.id] = { - name: node.label || node.id, - config: generateWireGuardConfig(node, nodes, edges) - }; - } - }); - - return configs; -} - -// 下载配置文件 -export function downloadConfig(nodeId, nodeName, configContent) { - const element = document.createElement('a'); - element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(configContent)); - element.setAttribute('download', `${nodeId}-${nodeName}.conf`); - element.style.display = 'none'; - - document.body.appendChild(element); - element.click(); - document.body.removeChild(element); -} - -// 验证节点配置是否完整 -export function validateNodeConfig(node) { - const errors = []; - - if (!node.label || node.label.trim() === '') { - errors.push('节点名称不能为空'); - } - - if (!node.ipAddress || node.ipAddress.trim() === '') { - errors.push('IP地址不能为空'); - } else if (!isValidIP(node.ipAddress)) { - errors.push('IP地址格式无效'); - } - - if (!node.privateKey || node.privateKey.trim() === '') { - errors.push('私钥不能为空'); - } - - if (!node.publicKey || node.publicKey.trim() === '') { - errors.push('公钥不能为空'); - } - - return { - isValid: errors.length === 0, - errors - }; -} - -// 验证IP地址格式 -function isValidIP(ip) { - const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; - if (!ipv4Regex.test(ip)) { - return false; - } - - const parts = ip.split('.'); - return parts.every(part => { - const num = parseInt(part, 10); - return num >= 0 && num <= 255; - }); -} diff --git a/src/utils/wireguardConfig.ts b/src/utils/wireguardConfig.ts index 7f6edee..57a2356 100644 --- a/src/utils/wireguardConfig.ts +++ b/src/utils/wireguardConfig.ts @@ -1,16 +1,5 @@ -import { Node, Edge } from '@xyflow/react'; +import { AppNode, AppEdge, NodeData } from "../types/graph"; -interface NodeData { - id: string; - label: string; - ipAddress: string; - listenPort: string; - privateKey: string; - publicKey: string; - endpoint?: string; - dnsServers?: string; - persistentKeepalive?: string; -} interface ValidationResult { isValid: boolean; @@ -21,9 +10,9 @@ interface ValidationResult { * 生成WireGuard格式的配置文件 */ export function generateWireGuardConfig( - node: Node, - allNodes: Node[], - allEdges: Edge[] + node: AppNode, + allNodes: AppNode[], + allEdges: AppEdge[] ): string { const config: string[] = []; @@ -80,8 +69,8 @@ export function generateWireGuardConfig( * 生成所有节点的配置 */ export function generateAllConfigs( - nodes: Node[], - edges: Edge[] + nodes: AppNode[], + edges: AppEdge[] ): Record { const configs: Record = {};