解决类型报错
This commit is contained in:
parent
e2a361b0f7
commit
9c869d7bb4
29
src/App.tsx
29
src/App.tsx
@ -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)),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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
16
src/types/graph.ts
Normal 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;
|
||||||
|
}
|
||||||
@ -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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -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 }> = {};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user