敲定结构

This commit is contained in:
limil 2026-02-15 01:02:29 +08:00
parent b18e21b620
commit 155fd2724a
5 changed files with 94 additions and 8 deletions

View File

@ -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;
}

View File

@ -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">

View File

@ -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>

View File

@ -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);

View File

@ -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' };