WG/src/App.tsx
2026-01-29 21:17:30 +08:00

161 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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