更新节点
This commit is contained in:
parent
49d20d3dbd
commit
aeda4b5179
5
TODO.md
5
TODO.md
@ -5,6 +5,9 @@
|
||||
- [ ] 完成全局设置编辑窗口
|
||||
- [ ] 完成参数校验以及参数联动逻辑
|
||||
- [ ] 实现配置生成逻辑,并验证有效
|
||||
|
||||
- [ ] 实现子网路由功能,并验证有效
|
||||
|
||||
- [ ] 实现配置保存和加载功能
|
||||
- [ ] 实现加密功能(完全加密和只加密私钥)
|
||||
- [ ] 完成!奖励自己
|
||||
- [ ] 完成!
|
||||
|
||||
@ -207,7 +207,7 @@ function FlowContent(): ReactNode {
|
||||
{editSettings && (
|
||||
<SettingsEditor
|
||||
settings={settings}
|
||||
onUpdate={settingsUpdate => {}}
|
||||
onUpdate={settingsUpdate => {setSettings(settingsUpdate)}}
|
||||
onClose={() => setEditSettings(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -4,12 +4,6 @@ import { generateWireGuardPrivateKey } from '../utils/wireguardConfig'
|
||||
import './FormEditor.css';
|
||||
import Folder from './Folder'
|
||||
|
||||
|
||||
interface Validation {
|
||||
isValid: boolean,
|
||||
errors: string[]
|
||||
}
|
||||
|
||||
interface NodeEditorProps {
|
||||
node: NodeData;
|
||||
settings: Settings;
|
||||
@ -39,11 +33,7 @@ export default function NodeEditor({
|
||||
const [notes, setNotes] = useState(node.notes)
|
||||
|
||||
const handleSave = (): void => {
|
||||
// const validation = validateNodeConfig(formData);
|
||||
// if (!validation.isValid) {
|
||||
// setErrors(validation.errors);
|
||||
// return;
|
||||
// }
|
||||
// todo: 校验
|
||||
setErrors([]);
|
||||
onUpdate({
|
||||
label: label,
|
||||
@ -112,6 +102,7 @@ export default function NodeEditor({
|
||||
type="text"
|
||||
value={ipv4Address || ''}
|
||||
onChange={e => setIpv4Address(e.target.value)}
|
||||
placeholder={`当前子网:${settings.ipv4Subnet}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -123,6 +114,7 @@ export default function NodeEditor({
|
||||
type="text"
|
||||
value={ipv6Address || ''}
|
||||
onChange={e => setIpv6Address(e.target.value)}
|
||||
placeholder={`当前子网:${settings.ipv6Subnet}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -141,7 +133,7 @@ export default function NodeEditor({
|
||||
<label>侦听端口</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1024"
|
||||
min="30000"
|
||||
max="49151"
|
||||
step="1"
|
||||
value={listenPort || ''}
|
||||
@ -157,7 +149,7 @@ export default function NodeEditor({
|
||||
<label>mtu</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
min="1200"
|
||||
step="1"
|
||||
value={mtu || ''}
|
||||
onChange={e => {
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { useState, ReactNode } from 'react';
|
||||
import { Settings } from '../types/graph';
|
||||
import './FormEditor.css';
|
||||
import {IPNetwork} from '../utils/iputils'
|
||||
|
||||
interface SettingEditorProps {
|
||||
settings: Settings;
|
||||
onUpdate?: (data: Settings) => void;
|
||||
onUpdate: (data: Settings) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
@ -13,9 +14,39 @@ export default function SettingsEditor({
|
||||
onUpdate,
|
||||
onClose
|
||||
}: SettingEditorProps): ReactNode {
|
||||
const [errors, setErrors] = useState<string[]>([]);
|
||||
|
||||
const [listenPort, setListenPort] = useState<number>(settings.listenPort);
|
||||
const [mtu, setmtu] = useState<number>(settings.mtu);
|
||||
const [ipv4Subnet, setIpv4Subnet] = useState(settings.ipv4Subnet)
|
||||
const [ipv6Subnet, setIpv6Subnet] = useState(settings.ipv6Subnet)
|
||||
|
||||
const handleSave = (): void => {
|
||||
// onUpdate();
|
||||
const errorInfo : string[] = [];
|
||||
if(ipv4Subnet) {
|
||||
const result = IPNetwork.parse(ipv4Subnet)
|
||||
if(!result.isValid) {
|
||||
errorInfo.push(result.error ?? "ipv4子网不合法")
|
||||
}
|
||||
}
|
||||
if(ipv6Subnet) {
|
||||
const result = IPNetwork.parse(ipv6Subnet)
|
||||
if(!result.isValid) {
|
||||
errorInfo.push(result.error ?? "ipv6子网不合法")
|
||||
}
|
||||
}
|
||||
if(errorInfo.length > 0) {
|
||||
setErrors(errorInfo);
|
||||
return ;
|
||||
}
|
||||
|
||||
setErrors([]);
|
||||
onUpdate({
|
||||
listenPort: listenPort,
|
||||
mtu: mtu,
|
||||
ipv4Subnet: ipv4Subnet,
|
||||
ipv6Subnet: ipv6Subnet
|
||||
});
|
||||
onClose();
|
||||
};
|
||||
|
||||
@ -27,6 +58,69 @@ export default function SettingsEditor({
|
||||
<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 => {
|
||||
const value = e.target.valueAsNumber;
|
||||
setListenPort(isNaN(value) ? 38894 : value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>mtu</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1200"
|
||||
step="1"
|
||||
value={mtu || ''}
|
||||
onChange={e => {
|
||||
const value = e.target.valueAsNumber;
|
||||
setmtu(isNaN(value) ? 1420 : value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>ipv4子网</label>
|
||||
<input
|
||||
type="text"
|
||||
value={ipv4Subnet || ''}
|
||||
onChange={e => setIpv4Subnet(e.target.value)}
|
||||
placeholder='例如:172.29.0.0/16,留空代表不使用ipv4'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>ipv6子网</label>
|
||||
<input
|
||||
type="text"
|
||||
value={ipv6Subnet || ''}
|
||||
onChange={e => setIpv6Subnet(e.target.value)}
|
||||
placeholder='例如:fd23:23:23::/64,留空代表不使用ipv6'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="editor-actions">
|
||||
<button className="btn-save" onClick={handleSave}>保存</button>
|
||||
<button className="btn-cancel" onClick={onClose}>取消</button>
|
||||
|
||||
@ -31,22 +31,10 @@ export type EdgeDataUpdate = {
|
||||
persistentKeepalive?: number;
|
||||
}
|
||||
|
||||
export class SubNetRouter {
|
||||
private _nodes : Record<string, string | undefined> = {};
|
||||
|
||||
constructor(
|
||||
public subnet: string,
|
||||
public readonly kind: 'ipv4' | 'ipv6'
|
||||
) {}
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
listenPort: number;
|
||||
mtu: number;
|
||||
|
||||
ipv4Subnet?: string;
|
||||
ipv4SubNetRouter?: SubNetRouter;
|
||||
|
||||
ipv6Subnet?: string;
|
||||
ipv6SubnetRouter?: SubNetRouter;
|
||||
}
|
||||
107
src/utils/iputils.ts
Normal file
107
src/utils/iputils.ts
Normal file
@ -0,0 +1,107 @@
|
||||
export type IPVersion = 'IPv4' | 'IPv6' | 'invalid';
|
||||
|
||||
export interface IPResult {
|
||||
isValid: boolean;
|
||||
version: IPVersion;
|
||||
binary: string;
|
||||
mask: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export class IPNetwork {
|
||||
/**
|
||||
* 解析 CIDR,返回结果对象(不抛出异常)
|
||||
*/
|
||||
static parse(cidr: string): IPResult {
|
||||
const parts = cidr.split('/');
|
||||
if (parts.length !== 2) {
|
||||
return this.invalid('格式错误,缺少掩码 (如 /24)');
|
||||
}
|
||||
|
||||
const [ip, maskStr] = parts;
|
||||
const mask = parseInt(maskStr, 10);
|
||||
const version = this.getVersion(ip);
|
||||
|
||||
// 基础校验
|
||||
if (version === 'invalid') return this.invalid('非法的 IP 格式');
|
||||
if (isNaN(mask)) return this.invalid('掩码必须是数字');
|
||||
|
||||
// 掩码范围校验
|
||||
if (version === 'IPv4' && (mask < 0 || mask > 32)) return this.invalid('IPv4 掩码范围应为 0-32');
|
||||
if (version === 'IPv6' && (mask < 0 || mask > 128)) return this.invalid('IPv6 掩码范围应为 0-128');
|
||||
|
||||
try {
|
||||
const binary = version === 'IPv4' ? this.ipv4ToBinary(ip) : this.ipv6ToBinary(ip);
|
||||
if (!binary) return this.invalid('IP 地址数值非法');
|
||||
|
||||
return { isValid: true, version, binary, mask };
|
||||
} catch (e) {
|
||||
return this.invalid('解析过程中出错');
|
||||
}
|
||||
}
|
||||
|
||||
private static getVersion(ip: string): IPVersion {
|
||||
if (/^(\d{1,3}\.){3}\d{1,3}$/.test(ip)) return 'IPv4';
|
||||
if (ip.includes(':')) return 'IPv6'; // IPv6 逻辑较复杂,在转换函数中进一步精确校验
|
||||
return 'invalid';
|
||||
}
|
||||
|
||||
private static ipv4ToBinary(ip: string): string | null {
|
||||
const octets = ip.split('.');
|
||||
let binary = '';
|
||||
for (const o of octets) {
|
||||
const n = parseInt(o, 10);
|
||||
if (n < 0 || n > 255 || isNaN(n)) return null;
|
||||
binary += n.toString(2).padStart(8, '0');
|
||||
}
|
||||
return binary;
|
||||
}
|
||||
|
||||
private static ipv6ToBinary(ip: string): string | null {
|
||||
let fullIP = ip;
|
||||
try {
|
||||
if (ip.includes('::')) {
|
||||
const parts = ip.split('::');
|
||||
if (parts.length > 2) return null; // 只能有一个 ::
|
||||
const left = parts[0].split(':').filter(x => x.length > 0);
|
||||
const right = parts[1].split(':').filter(x => x.length > 0);
|
||||
const missing = 8 - (left.length + right.length);
|
||||
if (missing < 0) return null;
|
||||
fullIP = [...left, ...Array(missing).fill('0'), ...right].join(':');
|
||||
}
|
||||
const groups = fullIP.split(':');
|
||||
if (groups.length !== 8) return null;
|
||||
|
||||
let binary = '';
|
||||
for (const hex of groups) {
|
||||
const n = parseInt(hex, 16);
|
||||
if (isNaN(n) || n < 0 || n > 0xFFFF) return null;
|
||||
binary += n.toString(2).padStart(16, '0');
|
||||
}
|
||||
return binary;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static invalid(msg: string): IPResult {
|
||||
return { isValid: false, version: 'invalid', binary: '', mask: -1, error: msg };
|
||||
}
|
||||
|
||||
/**
|
||||
* 二进制转回 CIDR 字符串
|
||||
*/
|
||||
static fromBinary(binary: string, mask: number, version: IPVersion): string {
|
||||
if (version === 'IPv4' && binary.length === 32) {
|
||||
const octets = [];
|
||||
for (let i = 0; i < 32; i += 8) octets.push(parseInt(binary.slice(i, i + 8), 2));
|
||||
return `${octets.join('.')}/${mask}`;
|
||||
}
|
||||
if (version === 'IPv6' && binary.length === 128) {
|
||||
const segments = [];
|
||||
for (let i = 0; i < 128; i += 16) segments.push(parseInt(binary.slice(i, i + 16), 2).toString(16));
|
||||
return `${segments.join(':')}/${mask}`;
|
||||
}
|
||||
return 'invalid-input';
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user