补充参数
This commit is contained in:
parent
6a45efffe9
commit
e3fed713ff
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -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;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user