补充参数
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 CustomNode from './components/CustomNode';
|
||||
import NodeEditor from './components/NodeEditor';
|
||||
import ConfigViewer from './components/ConfigViewer';
|
||||
import Toggle from "./components/Toggle"
|
||||
import './App.css';
|
||||
|
||||
@ -147,8 +146,8 @@ function FlowContent(): ReactNode {
|
||||
➕ 添加节点
|
||||
</button>
|
||||
|
||||
<button className="toolbar-btn" onClick={() => setShowConfigViewer(true)} title="查看配置">
|
||||
📋 配置
|
||||
<button className="toolbar-btn" onClick={() => setShowConfigViewer(true)} title="设置">
|
||||
📋 设置
|
||||
</button>
|
||||
|
||||
</div>
|
||||
@ -162,13 +161,13 @@ function FlowContent(): ReactNode {
|
||||
/>
|
||||
)}
|
||||
|
||||
{showConfigViewer && (
|
||||
{/* {showConfigViewer && (
|
||||
<ConfigViewer
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onClose={() => setShowConfigViewer(false)}
|
||||
/>
|
||||
)}
|
||||
)} */}
|
||||
</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 NodeData = {
|
||||
// basic
|
||||
label: string;
|
||||
ipAddress: string;
|
||||
listenPort: string;
|
||||
subnet: string;
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
endpoint?: string;
|
||||
dnsServers?: string;
|
||||
|
||||
// options
|
||||
PostUp?: string;
|
||||
PostDown?: string;
|
||||
persistentKeepalive?: string;
|
||||
dnsServers?: string;
|
||||
disallowSubnet?: string;
|
||||
allowIPs?: string;
|
||||
MTU?: number
|
||||
}
|
||||
|
||||
export type EdgeData = {
|
||||
isTwoWayEdge: boolean
|
||||
}
|
||||
|
||||
export type Settings = {
|
||||
v4SubNetPrefix: string;
|
||||
v6SubNetPrefix: string;
|
||||
listenPort: string;
|
||||
|
||||
// global options
|
||||
MTU?: number;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user