解决类型报错

This commit is contained in:
limil 2026-01-27 23:03:48 +08:00
parent e2a361b0f7
commit 9c869d7bb4
7 changed files with 35 additions and 206 deletions

View File

@ -7,36 +7,18 @@ import {
Background, Background,
Controls, Controls,
ReactFlowProvider, ReactFlowProvider,
Node, NodeTypes,
Edge,
NodeChange, NodeChange,
EdgeChange,
Connection,
OnNodesChange,
OnEdgesChange, OnEdgesChange,
OnConnect, OnConnect,
} from '@xyflow/react'; } from '@xyflow/react';
import '@xyflow/react/dist/style.css'; import '@xyflow/react/dist/style.css';
import { AppNode, AppEdge, NodeData } from './types/graph';
import CustomNode from './components/CustomNode'; import CustomNode from './components/CustomNode';
import NodeEditor from './components/NodeEditor'; import NodeEditor from './components/NodeEditor';
import ConfigViewer from './components/ConfigViewer'; import ConfigViewer from './components/ConfigViewer';
import './App.css'; import './App.css';
interface NodeData extends Record<string, unknown> {
label: string;
ipAddress: string;
listenPort: string;
privateKey: string;
publicKey: string;
endpoint?: string;
dnsServers?: string;
persistentKeepalive?: string;
}
type AppNode = Node<NodeData>;
type AppEdge = Edge;
const initialNodes: AppNode[] = [ const initialNodes: AppNode[] = [
{ {
id: 'n1', id: 'n1',
@ -53,7 +35,8 @@ const initialNodes: AppNode[] = [
]; ];
const initialEdges: AppEdge[] = []; const initialEdges: AppEdge[] = [];
const nodeTypes = {
const nodeTypes : NodeTypes = {
custom: CustomNode, custom: CustomNode,
}; };
@ -63,8 +46,8 @@ function FlowContent(): ReactNode {
const [editingNode, setEditingNode] = useState<NodeData | null>(null); const [editingNode, setEditingNode] = useState<NodeData | null>(null);
const [showConfigViewer, setShowConfigViewer] = useState(false); const [showConfigViewer, setShowConfigViewer] = useState(false);
const onNodesChange = useCallback<OnNodesChange>( const onNodesChange = useCallback(
(changes) => setNodes((nodesSnapshot) => applyNodeChanges(changes, nodesSnapshot)), (changes: NodeChange<AppNode>[]) => setNodes((nds) => applyNodeChanges<AppNode>(changes, nds)),
[], [],
); );

View File

@ -1,23 +1,11 @@
import { ReactNode, useState } from 'react'; import { ReactNode, useState } from 'react';
import { Node, Edge } from '@xyflow/react';
import { generateAllConfigs, downloadConfig } from '../utils/wireguardConfig'; import { generateAllConfigs, downloadConfig } from '../utils/wireguardConfig';
import {AppNode, AppEdge} from '../types/graph';
import './ConfigViewer.css'; import './ConfigViewer.css';
interface NodeData extends Record<string, unknown> {
id: string;
label: string;
ipAddress: string;
listenPort: string;
privateKey: string;
publicKey: string;
endpoint?: string;
dnsServers?: string;
persistentKeepalive?: string;
}
interface ConfigViewerProps { interface ConfigViewerProps {
nodes: Node<NodeData>[]; nodes: AppNode[];
edges: Edge[]; edges: AppEdge[];
onClose: () => void; onClose: () => void;
} }

View File

@ -1,23 +1,12 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Handle, Position, NodeProps } from '@xyflow/react'; import { Handle, Position, NodeProps } from '@xyflow/react';
import { AppNode } from '../types/graph';
import './CustomNode.css'; 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({ export default function CustomNode({
data, data,
isConnecting,
selected selected
}: NodeProps<NodeData>): ReactNode { }: NodeProps<AppNode>): ReactNode {
return ( return (
<div className={`custom-node ${selected ? 'selected' : ''}`}> <div className={`custom-node ${selected ? 'selected' : ''}`}>
<div className="node-header"> <div className="node-header">

View File

@ -1,18 +1,8 @@
import { useState, ReactNode } from 'react'; import { useState, ReactNode } from 'react';
import { validateNodeConfig } from '../utils/wireguardConfig'; import { validateNodeConfig } from '../utils/wireguardConfig';
import { NodeData } from '../types/graph';
import './NodeEditor.css'; import './NodeEditor.css';
interface NodeData {
label: string;
ipAddress: string;
listenPort: string;
privateKey: string;
publicKey: string;
endpoint?: string;
dnsServers?: string;
persistentKeepalive?: string;
}
interface NodeEditorProps { interface NodeEditorProps {
node: NodeData; node: NodeData;
onUpdate: (data: NodeData) => void; onUpdate: (data: NodeData) => void;

16
src/types/graph.ts Normal file
View File

@ -0,0 +1,16 @@
import { Node, Edge } from '@xyflow/react';
export type AppNode = Node<NodeData>;
export type AppEdge = Edge;
export type NodeData = {
label: string;
ipAddress: string;
listenPort: string;
privateKey: string;
publicKey: string;
endpoint?: string;
dnsServers?: string;
persistentKeepalive?: string;
}

View File

@ -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;
});
}

View File

@ -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 { interface ValidationResult {
isValid: boolean; isValid: boolean;
@ -21,9 +10,9 @@ interface ValidationResult {
* WireGuard格式的配置文件 * WireGuard格式的配置文件
*/ */
export function generateWireGuardConfig( export function generateWireGuardConfig(
node: Node<NodeData>, node: AppNode,
allNodes: Node<NodeData>[], allNodes: AppNode[],
allEdges: Edge[] allEdges: AppEdge[]
): string { ): string {
const config: string[] = []; const config: string[] = [];
@ -80,8 +69,8 @@ export function generateWireGuardConfig(
* *
*/ */
export function generateAllConfigs( export function generateAllConfigs(
nodes: Node<NodeData>[], nodes: AppNode[],
edges: Edge[] edges: AppEdge[]
): Record<string, { name: string; config: string }> { ): Record<string, { name: string; config: string }> {
const configs: Record<string, { name: string; config: string }> = {}; const configs: Record<string, { name: string; config: string }> = {};