敲定结构
This commit is contained in:
parent
b18e21b620
commit
155fd2724a
@ -98,4 +98,12 @@
|
||||
transform: translateY(1px) scale(0.995);
|
||||
background-color: #0f62d6; /* slightly darker on press */
|
||||
box-shadow: inset 0 1px 2px rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 8px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@ -216,6 +216,7 @@ export default function CustomNode({
|
||||
}
|
||||
};
|
||||
|
||||
const empty = !(data.ipv4Address || data.ipv6Address);
|
||||
|
||||
return (
|
||||
<div className={`custom-node ${selected ? 'selected' : ''}`}>
|
||||
@ -224,20 +225,22 @@ export default function CustomNode({
|
||||
</div>
|
||||
|
||||
<div className='node-info'>
|
||||
<div className="info-item">
|
||||
{empty && <div className="empty-tip">未配置地址信息</div>}
|
||||
|
||||
{data.ipv4Address && <div className="info-item">
|
||||
<span className="label">IPv4地址:</span>
|
||||
<span className="value">{data.ipv4Address || "未设置"}</span>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
<div className="info-item">
|
||||
{data.ipv6Address && <div className="info-item">
|
||||
<span className="label">IPv6地址:</span>
|
||||
<span className="value">{data.ipv6Address || "未设置"}</span>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
<div className="info-item">
|
||||
{data.listenAddress && <div className="info-item">
|
||||
<span className="label">监听地址:</span>
|
||||
<span className="value">{data.listenAddress || "未设置"}</span>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
<div className="node-actions">
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { useState, ReactNode } from 'react';
|
||||
import { Settings } from '../types/graph';
|
||||
import { Settings, SubnetInfo } from '../types/graph';
|
||||
import './FormEditor.css';
|
||||
import {IPUtils} from '../utils/iputils'
|
||||
import Folder from './Folder';
|
||||
import toast from 'react-hot-toast';
|
||||
import { IPUtils } from '../utils/iputils';
|
||||
|
||||
interface SettingEditorProps {
|
||||
settings: Settings;
|
||||
@ -38,11 +40,28 @@ export default function SettingsEditor({
|
||||
|
||||
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);
|
||||
@ -100,6 +119,49 @@ export default function SettingsEditor({
|
||||
/>
|
||||
</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((subnetInfo, idx) => (
|
||||
<Folder key={idx} title={subnetInfo.subnet.toString()}>
|
||||
<div className="add-node-to-subnet">
|
||||
<select>
|
||||
<option value="">请选择</option>
|
||||
<option value="apple">苹果</option>
|
||||
<option value="banana">香蕉</option>
|
||||
<option value="orange">橘子</option>
|
||||
</select>
|
||||
<input type="text" placeholder="输入节点ID" />
|
||||
<button>+</button>
|
||||
</div>
|
||||
|
||||
<ul className="subnet-nodes-list">
|
||||
<li>
|
||||
<span>节点ID: example-node-1, IP:</span>
|
||||
<button className="remove-node-btn">×</button>
|
||||
</li>
|
||||
</ul>
|
||||
</Folder>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="editor-actions">
|
||||
<button className="btn-save" onClick={handleSave}>保存</button>
|
||||
<button className="btn-cancel" onClick={onClose}>取消</button>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Node, Edge } from '@xyflow/react';
|
||||
import { createContext } from 'react';
|
||||
import { CIDR } from '../utils/iputils';
|
||||
|
||||
export type AppNode = Node<NodeData>;
|
||||
|
||||
@ -33,14 +34,22 @@ export type EdgeDataUpdate = {
|
||||
persistentKeepalive?: number;
|
||||
}
|
||||
|
||||
export interface SubnetInfo {
|
||||
subnet: CIDR;
|
||||
nodes: Record<string, CIDR | undefined>;
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
listenPort: number;
|
||||
mtu: number;
|
||||
|
||||
subnets: SubnetInfo[];
|
||||
}
|
||||
|
||||
export const initialSettings : Settings = {
|
||||
listenPort: 38894,
|
||||
mtu: 1420,
|
||||
subnets: []
|
||||
};
|
||||
|
||||
export const SettingsContext = createContext<Settings>(initialSettings);
|
||||
@ -125,6 +125,10 @@ export class IPUtils {
|
||||
];
|
||||
}
|
||||
|
||||
static equal(c1: CIDR, c2: CIDR): boolean {
|
||||
return c1.version === c2.version && c1.binary === c2.binary && c1.mask === c2.mask;
|
||||
}
|
||||
|
||||
static parse(cidrStr: string): CIDRParseResult {
|
||||
const parts = cidrStr.split('/');
|
||||
if (parts.length !== 2) return { error: 'Invalid CIDR format' };
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user