2026-02-16 23:32:28 +08:00

266 lines
7.2 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 { Settings, SubnetInfo, AppEdge, AppNode } from '../types/graph';
import { useReactFlow} from '@xyflow/react';
import './FormEditor.css';
import Folder from './Folder';
import toast from 'react-hot-toast';
import { IPUtils } from '../utils/iputils';
import Select from 'react-select';
interface SettingEditorProps {
settings: Settings;
onUpdate: (data: Settings) => void;
onClose: () => void;
}
interface SelectionOption {
value: string;
label: string;
}
interface SubnetProps {
subnets: SubnetInfo[],
setSubnets: (subnets: SubnetInfo[]) => void,
index: number,
}
function Validate(updateData: Settings) : string[] {
const errors: string[] = [];
const {mtu, listenPort} = updateData;
if(isNaN(listenPort)) {
errors.push("监听端口不是数字");
} else if(listenPort < 30000 || listenPort > 49151) {
errors.push("监听端口不在范围内:[30000, 49151]");
}
if(isNaN(mtu)) {
errors.push("mtu不是数字");
} else if(mtu < 1200) {
errors.push("mtu过小小于1200");
}
return errors;
}
function SubnetItem({index, subnets, setSubnets} : SubnetProps) : ReactNode {
const subnetInfo = subnets[index];
const [selectedOption, setSelectedOption] = useState<SelectionOption | null>(null);
const [nodeSubnet, setNodeSubnet] = useState<string>("");
const { getNodes, getNode } = useReactFlow<AppNode, AppEdge>();
const options : SelectionOption[] = getNodes().map(node => ({
value: node.id,
label: node.data.label
}));
const handleAddNodeSubnet = () => {
if(!selectedOption) {
toast.error("没有选择有效的节点");
return ;
}
const nodeId = selectedOption.value;
const nodecidr = (() => {
if(nodeSubnet.includes("/")) return nodeSubnet;
return `${nodeSubnet}/${nodeSubnet.includes(".") ? "32" : "128"}`;
})();
const result = IPUtils.parse(nodecidr);
const cidr = result.cidr;
if(!cidr) {
toast.error(`无法解析子网:${result.error}`);
return ;
}
if(!subnetInfo.subnet.contains(cidr)) {
toast.error("不在子网范围内");
return ;
}
if(subnetInfo.nodes.some(node => node.nodeId === nodeId)) {
toast.error(`节点已添加`);
return;
}
setSubnets(subnets.map((info, idx) => {
if(idx === index) {
return {...info, nodes: [...info.nodes, {nodeId: nodeId, cidr: cidr}]}
}
return info;
}));
}
const handleRemoveNodeSubnet = (nodeId: string) => {
setSubnets(subnets.map((info, idx) => {
if(idx === index) {
return {...info, nodes: info.nodes.filter(node => node.nodeId != nodeId)};
}
return info;
}));
}
const handleRemoveSubnet = () => {
setSubnets(subnets.filter((_, idx) => idx != index));
}
return (<Folder title={subnetInfo.subnet.toString()}>
<div>
<Select
onChange={setSelectedOption}
options={options}
placeholder="请选择节点..."
/>
<input
type="text"
placeholder="输入子网"
value={nodeSubnet}
onChange={e => {setNodeSubnet(e.target.value)}}/>
<button onClick={_ => handleAddNodeSubnet()}>+</button>
</div>
{subnetInfo.nodes.map((nodeInfo, _) => {
const node = getNode(nodeInfo.nodeId);
if(!node) return <></>
return (
<div>
<label>- {`${node.data.label}: ${nodeInfo.cidr?.toString()}`}</label>
<button onClick={_ => handleRemoveNodeSubnet(nodeInfo.nodeId)}></button>
</div>
)
})}
<div>
<button onClick={_ => handleRemoveSubnet()}></button>
</div>
</Folder>
);
}
export default function SettingsEditor({
settings,
onUpdate,
onClose
}: SettingEditorProps): ReactNode {
const [errors, setErrors] = useState<string[]>([]);
const [listenPort, setListenPort] = useState<number>(settings.listenPort);
const [mtu, setmtu] = useState<number>(settings.mtu);
const [subnets, setSubnets] = useState<SubnetInfo[]>(settings.subnets);
const [subnetInput, setSubnetInput] = useState<string>("");
const handleAddSubnet = (cidrStr: string) => {
const result = IPUtils.parse(cidrStr);
const cidr = result.cidr;
if(!cidr) {
toast.error(`无效的CIDR格式: ${result.error}`);
return;
}
if(subnets.some(s => IPUtils.equal(s.subnet, cidr))) {
toast.error("该CIDR已存在");
return;
}
setSubnets([...subnets, {subnet: cidr, nodes: []}]);
};
const handleSave = (): void => {
const updateData = {
listenPort: listenPort,
mtu: mtu,
subnets: subnets
};
const validation = Validate(updateData);
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></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="number"
min="30000"
max="49151"
step="1"
value={listenPort || ""}
onChange={e => setListenPort(e.target.valueAsNumber)}
/>
</div>
<div className="form-group">
<label>mtu</label>
<input
type="number"
min="1200"
step="1"
value={mtu || ''}
onChange={e => setmtu(e.target.valueAsNumber)}
/>
</div>
<div className="form-group">
<label></label>
<div className="add-subnet">
<input
type="text"
className="subnet-input"
placeholder="CIDR格式的子网例如192.168.1.0/24"
value={subnetInput}
onChange={e => setSubnetInput(e.target.value)}
/>
<button className="btn-add" onClick={e => {
e.stopPropagation();
handleAddSubnet(subnetInput);
}}>+</button>
</div>
<div className="subnet-list">
<label></label>
{subnets.map((_, idx) =>
<SubnetItem
key={idx}
index={idx}
subnets={subnets}
setSubnets={setSubnets}/>)}
</div>
</div>
<div className="editor-actions">
<button className="btn-save" onClick={handleSave}></button>
<button className="btn-cancel" onClick={onClose}></button>
</div>
</div>
</div>
);
}