2026-02-12 19:35:42 +08:00

251 lines
7.3 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, 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>
);
}