重构子网生成逻辑
This commit is contained in:
parent
9933d56250
commit
260dcbb652
14
package-lock.json
generated
14
package-lock.json
generated
@ -61,6 +61,7 @@
|
|||||||
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.28.6",
|
"@babel/code-frame": "^7.28.6",
|
||||||
"@babel/generator": "^7.28.6",
|
"@babel/generator": "^7.28.6",
|
||||||
@ -1637,6 +1638,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz",
|
||||||
"integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==",
|
"integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@ -1719,6 +1721,7 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@ -1858,6 +1861,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@ -2003,7 +2007,8 @@
|
|||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/d3-color": {
|
"node_modules/d3-color": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
@ -2062,6 +2067,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
@ -2230,6 +2236,7 @@
|
|||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@ -3048,6 +3055,7 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@ -3120,6 +3128,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@ -3129,6 +3138,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@ -3514,6 +3524,7 @@
|
|||||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@ -3635,6 +3646,7 @@
|
|||||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,14 @@
|
|||||||
import { ReactNode, useContext } from 'react';
|
import { ReactNode, useContext } from 'react';
|
||||||
import { Handle, Position, NodeProps, useReactFlow} from '@xyflow/react';
|
import { Handle, Position, NodeProps, useReactFlow} from '@xyflow/react';
|
||||||
import { AppNode, AppEdge, SettingsContext, NodeData, Settings } from '../types/graph';
|
import { AppNode, AppEdge, AppGraph, NodeData } from '../types/graph';
|
||||||
|
import {Settings, SettingsContext} from '../types/settings'
|
||||||
|
import StringBuilder from '../utils/StringBuilder';
|
||||||
|
|
||||||
import { CIDR, IPUtils } from '../utils/iputils';
|
import { CIDR, IPUtils } from '../utils/iputils';
|
||||||
import { tryDerivePublicKey } from '../utils/wireguardConfig'
|
import { tryDerivePublicKey } from '../utils/wireguardConfig'
|
||||||
import './CustomNode.css';
|
import './CustomNode.css';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
class StringBuilder {
|
|
||||||
private lines: string[] = [];
|
|
||||||
|
|
||||||
appendLine(value: string = "") {
|
|
||||||
this.lines.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
return this.lines.join('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConfigResult {
|
class ConfigResult {
|
||||||
constructor(
|
constructor(
|
||||||
public success: boolean,
|
public success: boolean,
|
||||||
@ -26,32 +17,27 @@ class ConfigResult {
|
|||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetEdge = (id: string) => AppEdge | undefined;
|
// function mapAddressToCIDR(nodeIds : string[], getAddress: GetAddress) : Record<string, CIDR> {
|
||||||
type GetNode = (id: string) => AppNode | undefined;
|
// const nodeIdToCIDR : Record<string, CIDR> = {};
|
||||||
type GetEdges = () => AppEdge[];
|
// for(const nodeId of nodeIds) {
|
||||||
type GetAddress = (nodeId: string) => string | undefined;
|
// const address = getAddress(nodeId);
|
||||||
|
// if(!address) continue;
|
||||||
|
// const result = IPUtils.parse(address);
|
||||||
|
// const cidr = result.cidr;
|
||||||
|
// if(!cidr) {
|
||||||
|
// throw new Error("节点地址无效");
|
||||||
|
// }
|
||||||
|
// if(cidr.version === 'IPv4') {
|
||||||
|
// cidr.mask = 32;
|
||||||
|
// } else if(cidr.version === 'IPv6') {
|
||||||
|
// cidr.mask = 128;
|
||||||
|
// }
|
||||||
|
// nodeIdToCIDR[nodeId] = cidr;
|
||||||
|
// }
|
||||||
|
// return nodeIdToCIDR;
|
||||||
|
// }
|
||||||
|
|
||||||
function mapAddressToCIDR(nodeIds : string[], getAddress: GetAddress) : Record<string, CIDR> {
|
function generateInterfaceConfig(settings: Settings, data: NodeData) : StringBuilder {
|
||||||
const nodeIdToCIDR : Record<string, CIDR> = {};
|
|
||||||
for(const nodeId of nodeIds) {
|
|
||||||
const address = getAddress(nodeId);
|
|
||||||
if(!address) continue;
|
|
||||||
const result = IPUtils.parse(address);
|
|
||||||
const cidr = result.cidr;
|
|
||||||
if(!cidr) {
|
|
||||||
throw new Error("节点地址无效");
|
|
||||||
}
|
|
||||||
if(cidr.version === 'IPv4') {
|
|
||||||
cidr.mask = 32;
|
|
||||||
} else if(cidr.version === 'IPv6') {
|
|
||||||
cidr.mask = 128;
|
|
||||||
}
|
|
||||||
nodeIdToCIDR[nodeId] = cidr;
|
|
||||||
}
|
|
||||||
return nodeIdToCIDR;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateInterfaceConfig(settings: Settings,data: NodeData) : StringBuilder {
|
|
||||||
const address = [data.ipv4Address, data.ipv6Address].flatMap(p => p ? [p] : []).join(', ');
|
const address = [data.ipv4Address, data.ipv6Address].flatMap(p => p ? [p] : []).join(', ');
|
||||||
const config = new StringBuilder();
|
const config = new StringBuilder();
|
||||||
config.appendLine(`[Interface]`);
|
config.appendLine(`[Interface]`);
|
||||||
@ -66,20 +52,9 @@ function generateInterfaceConfig(settings: Settings,data: NodeData) : StringBuil
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateConfig(settings: Settings, data: NodeData, getEdges: GetEdges, getEdge: GetEdge, getNode: GetNode) : ConfigResult {
|
function generateConfig(settings: Settings, data: NodeData, graph: AppGraph) : ConfigResult {
|
||||||
const config = generateInterfaceConfig(settings, data);
|
const config = generateInterfaceConfig(settings, data);
|
||||||
|
|
||||||
const getNearEdges = (node: AppNode) : AppEdge[] => {
|
|
||||||
return getEdges().filter(edge => edge.source === node.id || edge.target === node.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getNextNode = (edge: AppEdge, node: AppNode) : AppNode => {
|
|
||||||
const nextNodeId = edge.source === node.id ? edge.target : edge.source;
|
|
||||||
return getNode(nextNodeId)!;
|
|
||||||
};
|
|
||||||
|
|
||||||
const node = getNode(data.id)!;
|
|
||||||
|
|
||||||
const disallowCIDRs : CIDR[] = [];
|
const disallowCIDRs : CIDR[] = [];
|
||||||
if(data.disallowIPs) {
|
if(data.disallowIPs) {
|
||||||
const disallowList = data.disallowIPs.split(',').map(ip => ip.trim()).filter(ip => ip.length > 0);
|
const disallowList = data.disallowIPs.split(',').map(ip => ip.trim()).filter(ip => ip.length > 0);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useState, ReactNode } from 'react';
|
import { useState, ReactNode } from 'react';
|
||||||
import { Settings, SubnetInfo, AppEdge, AppNode } from '../types/graph';
|
import { AppEdge, AppNode } from '../types/graph';
|
||||||
|
import {Settings, SubnetInfo} from '../types/settings'
|
||||||
import { useReactFlow} from '@xyflow/react';
|
import { useReactFlow} from '@xyflow/react';
|
||||||
import './FormEditor.css';
|
import './FormEditor.css';
|
||||||
import './SettingsEditor.css';
|
import './SettingsEditor.css';
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import { Node, Edge } from '@xyflow/react';
|
import { Node, Edge } from '@xyflow/react';
|
||||||
import { createContext } from 'react';
|
|
||||||
import { CIDR } from '../utils/iputils';
|
|
||||||
|
|
||||||
export type AppNode = Node<NodeData>;
|
export type AppNode = Node<NodeData>;
|
||||||
|
|
||||||
@ -34,22 +32,58 @@ export type EdgeDataUpdate = {
|
|||||||
persistentKeepalive?: number;
|
persistentKeepalive?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SubnetInfo {
|
type GetEdges = () => AppEdge[];
|
||||||
subnet: CIDR;
|
type GetNodes = () => AppNode[];
|
||||||
nodes: Array<{nodeId: string, cidr: CIDR | undefined}>;
|
|
||||||
|
|
||||||
|
export class AppGraph {
|
||||||
|
private readonly _getNextNodeIds = new Map<string, string[]>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly getEdges: GetEdges,
|
||||||
|
public readonly getNodes: GetNodes
|
||||||
|
) {
|
||||||
|
const getNextNodeIds = this._getNextNodeIds;
|
||||||
|
const nodeIds = getNodes().map(node => node.id);
|
||||||
|
|
||||||
|
for(const edge of getEdges()) {
|
||||||
|
if(!nodeIds.includes(edge.source) || !nodeIds.includes(edge.target)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const sourceChildren = getNextNodeIds.get(edge.source) ?? [];
|
||||||
|
const targetChildren = getNextNodeIds.get(edge.target) ?? [];
|
||||||
|
getNextNodeIds.set(edge.source, [...sourceChildren, edge.target]);
|
||||||
|
getNextNodeIds.set(edge.target, [...targetChildren, edge.source]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static checkConnected(graph: AppGraph): boolean {
|
||||||
|
const nodes = graph.getNodes();
|
||||||
|
if(nodes.length === 0) return true;
|
||||||
|
|
||||||
|
const visited = new Set<string>();
|
||||||
|
const queue = [];
|
||||||
|
const first = nodes[0];
|
||||||
|
queue.push(first.id);
|
||||||
|
visited.add(first.id);
|
||||||
|
|
||||||
|
const getNextNodeIds = graph._getNextNodeIds;
|
||||||
|
while(queue.length > 0) {
|
||||||
|
const curr = queue.shift()!;
|
||||||
|
const next = getNextNodeIds.get(curr);
|
||||||
|
if(!next) continue;
|
||||||
|
for(const nextId of next) {
|
||||||
|
if(visited.has(nextId)) continue;
|
||||||
|
visited.add(nextId);
|
||||||
|
queue.push(nextId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return visited.size === nodes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
getConnectedSubgraph(nodeIds: string[]): AppGraph | undefined {
|
||||||
|
const node
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Settings {
|
|
||||||
listenPort: number;
|
|
||||||
mtu: number;
|
|
||||||
|
|
||||||
subnets: SubnetInfo[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const initialSettings : Settings = {
|
|
||||||
listenPort: 38894,
|
|
||||||
mtu: 1420,
|
|
||||||
subnets: []
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SettingsContext = createContext<Settings>(initialSettings);
|
|
||||||
22
src/types/settings.ts
Normal file
22
src/types/settings.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
import { CIDR } from '../utils/iputils';
|
||||||
|
|
||||||
|
export interface SubnetInfo {
|
||||||
|
subnet: CIDR;
|
||||||
|
nodes: Array<{nodeId: string, cidr: 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);
|
||||||
11
src/utils/StringBuilder.ts
Normal file
11
src/utils/StringBuilder.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export default class StringBuilder {
|
||||||
|
private lines: string[] = [];
|
||||||
|
|
||||||
|
appendLine(value: string = "") {
|
||||||
|
this.lines.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return this.lines.join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user