实现全局设置编辑窗口和参数校验
This commit is contained in:
parent
aeda4b5179
commit
e01c91a1a3
5
TODO.md
5
TODO.md
@ -2,12 +2,13 @@
|
||||
|
||||
- [x] 完成节点的编辑窗口
|
||||
- [x] 完成边的编辑窗口
|
||||
- [ ] 完成全局设置编辑窗口
|
||||
- [ ] 完成参数校验以及参数联动逻辑
|
||||
- [x] 完成全局设置编辑窗口
|
||||
- [x] 完成参数校验以及参数联动逻辑
|
||||
- [ ] 实现配置生成逻辑,并验证有效
|
||||
|
||||
- [ ] 实现子网路由功能,并验证有效
|
||||
|
||||
- [ ] 实现配置保存和加载功能
|
||||
- [ ] 实现加密功能(完全加密和只加密私钥)
|
||||
- [ ] 添加测试用例
|
||||
- [ ] 完成!
|
||||
|
||||
@ -109,7 +109,7 @@ function FlowContent(): ReactNode {
|
||||
|
||||
const handleAddNode = (): void => {
|
||||
const result = generateNodeData(nodes.length);
|
||||
if(result == null) return;
|
||||
if(!result) return;
|
||||
const newNode: AppNode = {
|
||||
id: result.id,
|
||||
position: { x: 0, y: 0 },
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useState, ReactNode } from 'react';
|
||||
import { NodeData, Settings, NodeDataUpdate } from '../types/graph';
|
||||
import { generateWireGuardPrivateKey } from '../utils/wireguardConfig'
|
||||
import { IPNetwork} from '../utils/iputils'
|
||||
import './FormEditor.css';
|
||||
import Folder from './Folder'
|
||||
|
||||
@ -11,6 +12,43 @@ interface NodeEditorProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
|
||||
class Validation {
|
||||
constructor(
|
||||
public readonly isValid: boolean,
|
||||
public readonly errors: string[],
|
||||
) {}
|
||||
}
|
||||
|
||||
function Validate(updateData : NodeDataUpdate, settings : Settings) : Validation {
|
||||
const errors: string[] = [];
|
||||
|
||||
const ipv4Subnet = settings.ipv4Subnet;
|
||||
const ipv4Address = updateData.ipv4Address;
|
||||
if(ipv4Subnet) {
|
||||
const cidr = IPNetwork.parse(ipv4Subnet);
|
||||
if(!ipv4Address) {
|
||||
errors.push("需要设置IPv4地址");
|
||||
} else if(!cidr.contains(IPNetwork.parse(`${ipv4Address}/32`))) {
|
||||
errors.push("IPv4不在子网范围中");
|
||||
}
|
||||
}
|
||||
|
||||
const ipv6Subnet = settings.ipv6Subnet;
|
||||
const ipv6Address = updateData.ipv6Address;
|
||||
if(ipv6Subnet) {
|
||||
const cidr = IPNetwork.parse(ipv6Subnet);
|
||||
if(!ipv6Address) {
|
||||
errors.push("需要设置IPv6地址");
|
||||
} else if(!cidr.contains(IPNetwork.parse(`${ipv6Address}/128`))) {
|
||||
errors.push("IPv6不在子网范围中");
|
||||
}
|
||||
}
|
||||
|
||||
const isValid : boolean = errors.length === 0;
|
||||
return new Validation(isValid, errors);
|
||||
}
|
||||
|
||||
export default function NodeEditor({
|
||||
node,
|
||||
settings,
|
||||
@ -33,9 +71,7 @@ export default function NodeEditor({
|
||||
const [notes, setNotes] = useState(node.notes)
|
||||
|
||||
const handleSave = (): void => {
|
||||
// todo: 校验
|
||||
setErrors([]);
|
||||
onUpdate({
|
||||
const updateData : NodeDataUpdate = {
|
||||
label: label,
|
||||
privateKey: privateKey,
|
||||
ipv4Address: ipv4Address,
|
||||
@ -47,7 +83,16 @@ export default function NodeEditor({
|
||||
listenPort: listenPort,
|
||||
dnsServers: dnsServers,
|
||||
notes: notes
|
||||
});
|
||||
}
|
||||
|
||||
const validation = Validate(updateData, settings);
|
||||
if(!validation.isValid) {
|
||||
setErrors(validation.errors);
|
||||
return ;
|
||||
}
|
||||
|
||||
setErrors([]);
|
||||
onUpdate(updateData);
|
||||
onClose();
|
||||
};
|
||||
|
||||
|
||||
@ -26,13 +26,17 @@ export default function SettingsEditor({
|
||||
if(ipv4Subnet) {
|
||||
const result = IPNetwork.parse(ipv4Subnet)
|
||||
if(!result.isValid) {
|
||||
errorInfo.push(result.error ?? "ipv4子网不合法")
|
||||
errorInfo.push("IPv4子网:" + (result.error ?? "ipv4子网不合法"))
|
||||
} else if(result.version != 'IPv4') {
|
||||
errorInfo.push("IPv4子网:" + "非IPv4 CIDR");
|
||||
}
|
||||
}
|
||||
if(ipv6Subnet) {
|
||||
const result = IPNetwork.parse(ipv6Subnet)
|
||||
if(!result.isValid) {
|
||||
errorInfo.push(result.error ?? "ipv6子网不合法")
|
||||
errorInfo.push("IPv6子网:" + (result.error ?? "子网不合法"));
|
||||
} else if(result.version != 'IPv6') {
|
||||
errorInfo.push("IPv6子网:" + "非IPv6 CIDR");
|
||||
}
|
||||
}
|
||||
if(errorInfo.length > 0) {
|
||||
|
||||
@ -1,18 +1,27 @@
|
||||
export type IPVersion = 'IPv4' | 'IPv6' | 'invalid';
|
||||
|
||||
export interface IPResult {
|
||||
isValid: boolean;
|
||||
version: IPVersion;
|
||||
binary: string;
|
||||
mask: number;
|
||||
error?: string;
|
||||
export class CIDR {
|
||||
constructor(
|
||||
public isValid: boolean,
|
||||
public version: IPVersion,
|
||||
public binary: string,
|
||||
public mask: number,
|
||||
public error?: string
|
||||
) {}
|
||||
|
||||
contains(cidr: CIDR) : boolean {
|
||||
if(!cidr.isValid || !this.isValid) return false;
|
||||
if(this.version !== cidr.version) return false;
|
||||
if(this.mask > cidr.mask) return false;
|
||||
return this.binary.slice(0, this.mask) === cidr.binary.slice(0, this.mask);
|
||||
}
|
||||
}
|
||||
|
||||
export class IPNetwork {
|
||||
/**
|
||||
* 解析 CIDR,返回结果对象(不抛出异常)
|
||||
*/
|
||||
static parse(cidr: string): IPResult {
|
||||
static parse(cidr: string): CIDR {
|
||||
const parts = cidr.split('/');
|
||||
if (parts.length !== 2) {
|
||||
return this.invalid('格式错误,缺少掩码 (如 /24)');
|
||||
@ -34,7 +43,7 @@ export class IPNetwork {
|
||||
const binary = version === 'IPv4' ? this.ipv4ToBinary(ip) : this.ipv6ToBinary(ip);
|
||||
if (!binary) return this.invalid('IP 地址数值非法');
|
||||
|
||||
return { isValid: true, version, binary, mask };
|
||||
return new CIDR(true, version, binary, mask);
|
||||
} catch (e) {
|
||||
return this.invalid('解析过程中出错');
|
||||
}
|
||||
@ -84,8 +93,8 @@ export class IPNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
private static invalid(msg: string): IPResult {
|
||||
return { isValid: false, version: 'invalid', binary: '', mask: -1, error: msg };
|
||||
private static invalid(msg: string): CIDR {
|
||||
return new CIDR(false, 'invalid', '', -1, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user