diff --git a/src/components/CustomNode.css b/src/components/CustomNode.css index 7d7e674..7897c2a 100644 --- a/src/components/CustomNode.css +++ b/src/components/CustomNode.css @@ -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; } \ No newline at end of file diff --git a/src/components/CustomNode.tsx b/src/components/CustomNode.tsx index 404d411..597c32a 100644 --- a/src/components/CustomNode.tsx +++ b/src/components/CustomNode.tsx @@ -216,6 +216,7 @@ export default function CustomNode({ } }; + const empty = !(data.ipv4Address || data.ipv6Address); return (
@@ -224,20 +225,22 @@ export default function CustomNode({
-
+ {empty &&
未配置地址信息
} + + {data.ipv4Address &&
IPv4地址: {data.ipv4Address || "未设置"} -
+
} -
+ {data.ipv6Address &&
IPv6地址: {data.ipv6Address || "未设置"} -
+
} -
+ {data.listenAddress &&
监听地址: {data.listenAddress || "未设置"} -
+
}
diff --git a/src/components/SettingsEditor.tsx b/src/components/SettingsEditor.tsx index 790de78..9e1e2c6 100644 --- a/src/components/SettingsEditor.tsx +++ b/src/components/SettingsEditor.tsx @@ -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(settings.listenPort); const [mtu, setmtu] = useState(settings.mtu); + const [subnets, setSubnets] = useState(settings.subnets); + const [subnetInput, setSubnetInput] = useState(""); + + 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({ />
+
+ +
+ setSubnetInput(e.target.value)} + /> + +
+ +
+ + {subnets.map((subnetInfo, idx) => ( + +
+ + + +
+ +
    +
  • + 节点ID: example-node-1, IP: + +
  • +
+
+ ))} +
+ +
+
diff --git a/src/types/graph.ts b/src/types/graph.ts index 6ada0f5..fb4043c 100644 --- a/src/types/graph.ts +++ b/src/types/graph.ts @@ -1,5 +1,6 @@ import { Node, Edge } from '@xyflow/react'; import { createContext } from 'react'; +import { CIDR } from '../utils/iputils'; export type AppNode = Node; @@ -33,14 +34,22 @@ export type EdgeDataUpdate = { persistentKeepalive?: number; } +export interface SubnetInfo { + subnet: CIDR; + nodes: Record; +} + export interface Settings { listenPort: number; mtu: number; + + subnets: SubnetInfo[]; } export const initialSettings : Settings = { listenPort: 38894, mtu: 1420, + subnets: [] }; export const SettingsContext = createContext(initialSettings); \ No newline at end of file diff --git a/src/utils/iputils.ts b/src/utils/iputils.ts index 370ee7b..e92438d 100644 --- a/src/utils/iputils.ts +++ b/src/utils/iputils.ts @@ -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' };