161 lines
4.2 KiB
TypeScript
161 lines
4.2 KiB
TypeScript
import { useState, useCallback, ReactNode } from 'react';
|
||
import {
|
||
ReactFlow,
|
||
applyNodeChanges,
|
||
applyEdgeChanges,
|
||
addEdge,
|
||
Background,
|
||
Controls,
|
||
ReactFlowProvider,
|
||
NodeTypes,
|
||
NodeChange,
|
||
OnEdgesChange,
|
||
MarkerType,
|
||
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 Toggle from "./components/Toggle"
|
||
import './App.css';
|
||
|
||
const initialNodes: AppNode[] = [
|
||
{
|
||
id: 'n1',
|
||
position: { x: 250, y: 50 },
|
||
data: {
|
||
label: 'Node-A',
|
||
ipAddress: '10.0.0.1',
|
||
listenPort: '51820',
|
||
privateKey: '',
|
||
publicKey: '',
|
||
},
|
||
type: 'custom',
|
||
},
|
||
];
|
||
const initialEdges: AppEdge[] = [];
|
||
|
||
|
||
const nodeTypes : NodeTypes = {
|
||
custom: CustomNode,
|
||
};
|
||
|
||
function FlowContent(): ReactNode {
|
||
const [nodes, setNodes] = useState<AppNode[]>(initialNodes);
|
||
const [edges, setEdges] = useState<AppEdge[]>(initialEdges);
|
||
const [editingNode, setEditingNode] = useState<NodeData | null>(null);
|
||
const [showConfigViewer, setShowConfigViewer] = useState(false);
|
||
|
||
const onNodesChange = useCallback(
|
||
(changes: NodeChange<AppNode>[]) => setNodes((nds) => applyNodeChanges<AppNode>(changes, nds)),
|
||
[],
|
||
);
|
||
|
||
const onEdgesChange = useCallback<OnEdgesChange>(
|
||
(changes) => setEdges((edgesSnapshot) => applyEdgeChanges(changes, edgesSnapshot)),
|
||
[],
|
||
);
|
||
|
||
const onConnect = useCallback<OnConnect>(
|
||
(params) => setEdges((edgesSnapshot) => addEdge(params, edgesSnapshot)),
|
||
[],
|
||
);
|
||
|
||
const onNodeClick = useCallback((_event: React.MouseEvent, node: AppNode) => {
|
||
setEditingNode(node.data);
|
||
}, []);
|
||
|
||
const handleAddNode = (): void => {
|
||
const newNodeId = `n${Date.now()}`;
|
||
const newNode: AppNode = {
|
||
id: newNodeId,
|
||
position: { x: Math.random() * 500, y: Math.random() * 500 },
|
||
data: {
|
||
label: `Node-${String.fromCharCode(65 + (nodes.length % 26))}`,
|
||
ipAddress: `10.0.0.${nodes.length + 2}`,
|
||
listenPort: `${51820 + nodes.length}`,
|
||
privateKey: '',
|
||
publicKey: '',
|
||
},
|
||
type: 'custom',
|
||
};
|
||
setNodes((prev) => [...prev, newNode]);
|
||
};
|
||
|
||
const handleUpdateNode = (updatedData: NodeData): void => {
|
||
setNodes((prev) =>
|
||
prev.map((node) => {
|
||
if (node.data.label === editingNode?.label) {
|
||
return { ...node, data: updatedData };
|
||
}
|
||
return node;
|
||
})
|
||
);
|
||
setEditingNode(null);
|
||
};
|
||
|
||
return (
|
||
<div style={{ width: '100vw', height: '100vh' }}>
|
||
<ReactFlow
|
||
nodes={nodes}
|
||
edges={edges}
|
||
onNodesChange={onNodesChange}
|
||
onEdgesChange={onEdgesChange}
|
||
onConnect={onConnect}
|
||
onNodeDoubleClick={onNodeClick}
|
||
nodeTypes={nodeTypes}
|
||
deleteKeyCode={["Delete"]}
|
||
defaultEdgeOptions={{
|
||
animated: true,
|
||
markerEnd: { type: MarkerType.ArrowClosed }
|
||
}}
|
||
fitView
|
||
>
|
||
<Background />
|
||
<Controls />
|
||
</ReactFlow>
|
||
|
||
<div className="toolbar">
|
||
<div className="toolbar-group">
|
||
<button className="toolbar-btn" onClick={handleAddNode} title="添加新节点">
|
||
➕ 添加节点
|
||
</button>
|
||
<button
|
||
className="toolbar-btn"
|
||
onClick={() => setShowConfigViewer(true)}
|
||
title="查看和下载WireGuard配置"
|
||
>
|
||
📋 配置
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{editingNode && (
|
||
<NodeEditor
|
||
node={editingNode}
|
||
onUpdate={handleUpdateNode}
|
||
onClose={() => setEditingNode(null)}
|
||
/>
|
||
)}
|
||
|
||
{showConfigViewer && (
|
||
<ConfigViewer
|
||
nodes={nodes}
|
||
edges={edges}
|
||
onClose={() => setShowConfigViewer(false)}
|
||
/>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default function App(): ReactNode {
|
||
return (
|
||
<ReactFlowProvider>
|
||
<FlowContent />
|
||
</ReactFlowProvider>
|
||
);
|
||
}
|