251 lines
7.3 KiB
TypeScript
251 lines
7.3 KiB
TypeScript
import { useState, ReactNode } from 'react';
|
||
import { NodeData, Settings, NodeDataUpdate } from '../types/graph';
|
||
import { generateWireGuardPrivateKey } from '../utils/wireguardConfig'
|
||
import { IPNetwork} from '../utils/iputils'
|
||
import './FormEditor.css';
|
||
import Folder from './Folder'
|
||
|
||
interface NodeEditorProps {
|
||
node: NodeData;
|
||
settings: Settings;
|
||
onUpdate: (data: NodeDataUpdate) => void;
|
||
onClose: () => void;
|
||
}
|
||
|
||
function Validate(updateData : NodeDataUpdate, settings : Settings) : string[] {
|
||
const errors: string[] = [];
|
||
const {ipv4Address, ipv6Address, mtu, listenPort} = updateData;
|
||
|
||
if(!updateData.label) {
|
||
errors.push("Label不能是空");
|
||
}
|
||
|
||
if(!updateData.privateKey) {
|
||
errors.push("privateKey不能是空");
|
||
}
|
||
|
||
if(ipv4Address) {
|
||
const result = IPNetwork.parse(ipv4Address);
|
||
if(!result.cidr || result.cidr.version !== 'IPv4') {
|
||
errors.push("IPv4地址无效");
|
||
}
|
||
}
|
||
|
||
if(ipv6Address) {
|
||
const result = IPNetwork.parse(ipv6Address);
|
||
if(!result.cidr || result.cidr.version !== 'IPv6') {
|
||
errors.push("IPv6地址无效");
|
||
}
|
||
}
|
||
|
||
if(listenPort !== undefined) {
|
||
if(isNaN(listenPort)) {
|
||
errors.push("监听端口不是数字");
|
||
} else if(listenPort < 30000 || listenPort > 49151) {
|
||
errors.push("监听端口不在范围内:[30000, 49151]");
|
||
}
|
||
}
|
||
|
||
if(mtu !== undefined) {
|
||
if(isNaN(mtu)) {
|
||
errors.push("mtu不是数字");
|
||
} else if(mtu < 1200) {
|
||
errors.push("mtu过小(小于1200)");
|
||
}
|
||
}
|
||
|
||
return errors;
|
||
}
|
||
|
||
export default function NodeEditor({
|
||
node,
|
||
settings,
|
||
onUpdate,
|
||
onClose
|
||
}: NodeEditorProps): ReactNode {
|
||
|
||
const [errors, setErrors] = useState<string[]>([]);
|
||
|
||
const [label, setLabel] = useState<string>(node.label);
|
||
const [privateKey, setPrivateKey] = useState<string>(node.privateKey);
|
||
const [ipv4Address, setIpv4Address] = useState(node.ipv4Address);
|
||
const [ipv6Address, setIpv6Address] = useState(node.ipv6Address);
|
||
const [disallowIPs, setDisallowIPs] = useState(node.disallowIPs);
|
||
const [listenPort, setListenPort] = useState(node.listenPort);
|
||
const [mtu, setmtu] = useState(node.mtu);
|
||
const [dnsServers, setdnsServers] = useState(node.dnsServers)
|
||
const [postUp, setPostUp] = useState(node.postUp)
|
||
const [postDown, setPostDown] = useState(node.postDown)
|
||
const [notes, setNotes] = useState(node.notes)
|
||
|
||
const handleSave = (): void => {
|
||
const updateData : NodeDataUpdate = {
|
||
label: label,
|
||
privateKey: privateKey,
|
||
ipv4Address: ipv4Address,
|
||
ipv6Address: ipv6Address,
|
||
disallowIPs: disallowIPs,
|
||
postUp: postUp,
|
||
postDown: postDown,
|
||
mtu: mtu,
|
||
listenPort: listenPort,
|
||
dnsServers: dnsServers,
|
||
notes: notes
|
||
}
|
||
|
||
const validation = Validate(updateData, settings);
|
||
setErrors(validation);
|
||
if(validation.length > 0) {
|
||
return ;
|
||
}
|
||
|
||
onUpdate(updateData);
|
||
onClose();
|
||
};
|
||
|
||
return (
|
||
<div className="node-editor-overlay">
|
||
<div className="node-editor">
|
||
<div className="editor-header">
|
||
<h2>编辑节点: {label}</h2>
|
||
<button className="close-btn" onClick={onClose}>×</button>
|
||
</div>
|
||
|
||
{errors.length > 0 && (
|
||
<div className="error-box">
|
||
<div
|
||
className="error-close-btn"
|
||
onClick={() => setErrors([])} // 点击清空错误数组
|
||
title="关闭提示"
|
||
>×</div>
|
||
|
||
{errors.map((error, idx) => (
|
||
<p key={idx} className="error-message">• {error}</p>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
<div className="form-group">
|
||
<label>节点名称</label>
|
||
<input
|
||
type="text"
|
||
value={label}
|
||
onChange={e => setLabel(e.target.value)}
|
||
placeholder="例如: Node-A"
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>私钥</label>
|
||
<div className="item-group">
|
||
<input
|
||
value={privateKey}
|
||
readOnly
|
||
/>
|
||
<button className="btn-interect"
|
||
onClick={_ => setPrivateKey(generateWireGuardPrivateKey())}>重新生成</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>IPv4地址</label>
|
||
<input
|
||
type="text"
|
||
value={ipv4Address || ''}
|
||
onChange={e => setIpv4Address(e.target.value)}
|
||
placeholder={`例如:172.29.0.1/16,留空代表不使用ipv4`}
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>IPv6地址</label>
|
||
<input
|
||
type="text"
|
||
value={ipv6Address || ''}
|
||
onChange={e => setIpv6Address(e.target.value)}
|
||
placeholder={`例如:fd23:23:23::1/64,留空代表不使用ipv6`}
|
||
/>
|
||
</div>
|
||
|
||
<Folder title='高级'>
|
||
<div className="form-group">
|
||
<label>子网黑名单</label>
|
||
<textarea
|
||
rows={2}
|
||
value={disallowIPs || ''}
|
||
onChange={e => setDisallowIPs(e.target.value)}
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>监听端口</label>
|
||
<input
|
||
type="number"
|
||
min="30000"
|
||
max="49151"
|
||
step="1"
|
||
value={listenPort || ""}
|
||
onChange={e => setListenPort(e.target.valueAsNumber)}
|
||
placeholder='范围:[30000, 49151]'
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>mtu</label>
|
||
<input
|
||
type="number"
|
||
min="1200"
|
||
step="1"
|
||
value={mtu || ''}
|
||
onChange={e => setmtu(e.target.valueAsNumber)}
|
||
placeholder='需要大于1200'
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>DNS服务器</label>
|
||
<input
|
||
type="text"
|
||
value={dnsServers || ''}
|
||
onChange={(e) => setdnsServers(e.target.value)}
|
||
placeholder="例如: 8.8.8.8,1.1.1.1"
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>PostUp</label>
|
||
<textarea
|
||
rows={2}
|
||
value={postUp || ''}
|
||
onChange={(e) => setPostUp(e.target.value)}
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>PostDown</label>
|
||
<textarea
|
||
rows={2}
|
||
value={postDown || ''}
|
||
onChange={(e) => setPostDown(e.target.value)}
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>备注</label>
|
||
<textarea
|
||
rows={4}
|
||
value={notes || ''}
|
||
onChange={(e) => setNotes(e.target.value)}
|
||
/>
|
||
</div>
|
||
</Folder>
|
||
|
||
<div className="editor-actions">
|
||
<button className="btn-save" onClick={handleSave}>保存</button>
|
||
<button className="btn-cancel" onClick={onClose}>取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|