补充参数

This commit is contained in:
fengfeng 2026-01-31 19:55:33 +08:00
parent 6a45efffe9
commit e3fed713ff
4 changed files with 23 additions and 349 deletions

View File

@ -20,7 +20,6 @@ import '@xyflow/react/dist/style.css';
import { AppNode, AppEdge, NodeData } from './types/graph'; 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 Toggle from "./components/Toggle" import Toggle from "./components/Toggle"
import './App.css'; import './App.css';
@ -147,8 +146,8 @@ function FlowContent(): ReactNode {
</button> </button>
<button className="toolbar-btn" onClick={() => setShowConfigViewer(true)} title="查看配置"> <button className="toolbar-btn" onClick={() => setShowConfigViewer(true)} title="置">
📋 📋
</button> </button>
</div> </div>
@ -162,13 +161,13 @@ function FlowContent(): ReactNode {
/> />
)} )}
{showConfigViewer && ( {/* {showConfigViewer && (
<ConfigViewer <ConfigViewer
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}
onClose={() => setShowConfigViewer(false)} onClose={() => setShowConfigViewer(false)}
/> />
)} )} */}
</div> </div>
); );
} }

View File

@ -1,219 +0,0 @@
.config-viewer-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1001;
}
.config-viewer {
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
max-width: 900px;
width: 95%;
max-height: 85vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.viewer-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #eee;
flex-shrink: 0;
}
.viewer-header h2 {
margin: 0;
font-size: 18px;
color: #333;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.close-btn:hover {
color: #333;
}
.viewer-content {
display: flex;
gap: 20px;
flex: 1;
overflow: hidden;
padding: 20px;
}
.config-list {
min-width: 200px;
max-width: 250px;
border-right: 1px solid #eee;
padding-right: 20px;
overflow-y: auto;
}
.config-list h3 {
margin: 0 0 10px 0;
font-size: 14px;
color: #333;
}
.config-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
margin-bottom: 8px;
background: #f5f5f5;
border-radius: 4px;
font-size: 13px;
gap: 8px;
}
.config-item span {
flex: 1;
word-break: break-all;
}
.download-btn {
background: none;
border: none;
cursor: pointer;
font-size: 16px;
padding: 0;
flex-shrink: 0;
}
.config-preview {
flex: 1;
display: flex;
flex-direction: column;
min-width: 400px;
overflow: hidden;
}
.preview-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.preview-header h3 {
margin: 0;
font-size: 14px;
color: #333;
}
.copy-btn {
padding: 6px 12px;
background: #1677ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
transition: background 0.3s;
}
.copy-btn:hover {
background: #0b66d4;
}
.config-text {
flex: 1;
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 4px;
padding: 12px;
overflow: auto;
font-size: 12px;
line-height: 1.5;
color: #333;
margin: 0;
font-family: 'Courier New', monospace;
}
.viewer-actions {
display: flex;
gap: 10px;
padding: 20px;
border-top: 1px solid #eee;
flex-shrink: 0;
}
.btn-download-all,
.btn-close {
padding: 10px 16px;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-download-all {
flex: 1;
background: #52c41a;
color: white;
}
.btn-download-all:hover:not(:disabled) {
background: #389e0d;
box-shadow: 0 2px 8px rgba(82, 196, 26, 0.3);
}
.btn-download-all:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-close {
flex: 1;
background: #f5f5f5;
color: #333;
}
.btn-close:hover {
background: #e6e6e6;
}
@media (max-width: 768px) {
.viewer-content {
flex-direction: column;
}
.config-list {
max-width: none;
border-right: none;
border-bottom: 1px solid #eee;
padding-right: 0;
padding-bottom: 15px;
}
.config-preview {
min-width: auto;
}
}

View File

@ -1,121 +0,0 @@
import { ReactNode, useState } from 'react';
import { generateAllConfigs, downloadConfig } from '../utils/wireguardConfig';
import {AppNode, AppEdge} from '../types/graph';
import './ConfigViewer.css';
interface ConfigViewerProps {
nodes: AppNode[];
edges: AppEdge[];
onClose: () => void;
}
interface ConfigItem {
name: string;
config: string;
}
interface Configs {
[key: string]: ConfigItem;
}
export default function ConfigViewer({
nodes,
edges,
onClose
}: ConfigViewerProps): ReactNode {
const configs: Configs = generateAllConfigs(nodes, edges);
const configIds = Object.keys(configs);
const [selectedId, setSelectedId] = useState(configIds[0] || null);
const handleDownload = (nodeId: string): void => {
const config = configs[nodeId];
if (config) {
downloadConfig(nodeId, config.name, config.config);
}
};
const handleDownloadAll = (): void => {
Object.keys(configs).forEach(nodeId => {
const config = configs[nodeId];
if (config) {
setTimeout(() => {
downloadConfig(nodeId, config.name, config.config);
}, 100);
}
});
};
if (!selectedId || !configs[selectedId]) {
return (
<div className="config-viewer-overlay">
<div className="config-viewer">
<div className="viewer-header">
<h2>WireGuard配置</h2>
<button className="close-btn" onClick={onClose}>×</button>
</div>
<p style={{ padding: '20px', color: '#666' }}>
WireGuard配置信息
</p>
</div>
</div>
);
}
const config = configs[selectedId];
return (
<div className="config-viewer-overlay">
<div className="config-viewer">
<div className="viewer-header">
<h2>WireGuard配置查看器</h2>
<button className="close-btn" onClick={onClose}>×</button>
</div>
<div className="viewer-content">
<div className="config-list">
<h3></h3>
{configIds.map(nodeId => (
<div key={nodeId} className="config-item">
<span>{configs[nodeId].name}</span>
<button
className="download-btn"
onClick={() => handleDownload(nodeId)}
title="下载此节点的配置"
>
</button>
</div>
))}
</div>
<div className="config-preview">
<div className="preview-header">
<h3>{config.name} </h3>
<button
className="copy-btn"
onClick={() => {
navigator.clipboard.writeText(config.config);
alert('已复制到剪贴板');
}}
>
📋
</button>
</div>
<pre className="config-text">{config.config}</pre>
</div>
</div>
<div className="viewer-actions">
<button
className="btn-download-all"
onClick={handleDownloadAll}
disabled={configIds.length === 0}
>
💾
</button>
<button className="btn-close" onClick={onClose}></button>
</div>
</div>
</div>
);
}

View File

@ -5,16 +5,31 @@ export type AppNode = Node<NodeData>;
export type AppEdge = Edge<EdgeData>; export type AppEdge = Edge<EdgeData>;
export type NodeData = { export type NodeData = {
// basic
label: string; label: string;
ipAddress: string; subnet: string;
listenPort: string;
privateKey: string; privateKey: string;
publicKey: string; publicKey: string;
endpoint?: string;
dnsServers?: string; // options
PostUp?: string;
PostDown?: string;
persistentKeepalive?: string; persistentKeepalive?: string;
dnsServers?: string;
disallowSubnet?: string;
allowIPs?: string;
MTU?: number
} }
export type EdgeData = { export type EdgeData = {
isTwoWayEdge: boolean isTwoWayEdge: boolean
}
export type Settings = {
v4SubNetPrefix: string;
v6SubNetPrefix: string;
listenPort: string;
// global options
MTU?: number;
} }