mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-08 10:46:37 +08:00
feat: add custom IP selection for nodes, tunnels, and forwards
- Add extra_ips field to nodes for multi-IP servers - Add connect_ip field to chain_tunnel for specifying connection address - Add in_ip field to forward_port for specifying listen address - Frontend: add UI controls for extra IPs on node form - Frontend: add connect IP input for tunnel chain nodes - Frontend: add listen IP input for forward form - Backend: resolve forward ingress with custom listen IP priority Entire-Checkpoint: 557563462c16
This commit is contained in:
@@ -127,6 +127,7 @@ interface ForwardForm {
|
||||
name: string;
|
||||
tunnelId: number | null;
|
||||
inPort: number | null;
|
||||
inIp: string;
|
||||
remoteAddr: string;
|
||||
interfaceName?: string;
|
||||
strategy: string;
|
||||
@@ -546,6 +547,7 @@ export default function ForwardPage() {
|
||||
name: "",
|
||||
tunnelId: null,
|
||||
inPort: null,
|
||||
inIp: "",
|
||||
remoteAddr: "",
|
||||
interfaceName: "",
|
||||
strategy: "fifo",
|
||||
@@ -1164,6 +1166,7 @@ export default function ForwardPage() {
|
||||
name: "",
|
||||
tunnelId: null,
|
||||
inPort: null,
|
||||
inIp: "",
|
||||
remoteAddr: "",
|
||||
interfaceName: "",
|
||||
strategy: "fifo",
|
||||
@@ -1182,6 +1185,7 @@ export default function ForwardPage() {
|
||||
name: forward.name,
|
||||
tunnelId: forward.tunnelId,
|
||||
inPort: forward.inPort,
|
||||
inIp: forward.inIp || "",
|
||||
remoteAddr: forward.remoteAddr.split(",").join("\n"),
|
||||
interfaceName: forward.interfaceName || "",
|
||||
strategy: forward.strategy || "fifo",
|
||||
@@ -1263,6 +1267,7 @@ export default function ForwardPage() {
|
||||
name: form.name,
|
||||
tunnelId: form.tunnelId,
|
||||
inPort: form.inPort,
|
||||
inIp: form.inIp || null,
|
||||
remoteAddr: processedRemoteAddr,
|
||||
strategy: addressCount > 1 ? form.strategy : "fifo",
|
||||
speedId: normalizeSpeedId(form.speedId),
|
||||
@@ -1270,11 +1275,11 @@ export default function ForwardPage() {
|
||||
|
||||
res = await updateForward(updateData);
|
||||
} else {
|
||||
// 创建时不需要id和userId(后端会自动设置)
|
||||
const createData = {
|
||||
name: form.name,
|
||||
tunnelId: form.tunnelId,
|
||||
inPort: form.inPort,
|
||||
inIp: form.inIp || null,
|
||||
remoteAddr: processedRemoteAddr,
|
||||
strategy: addressCount > 1 ? form.strategy : "fifo",
|
||||
speedId: normalizeSpeedId(form.speedId),
|
||||
@@ -4023,6 +4028,17 @@ export default function ForwardPage() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<Input
|
||||
description="留空使用默认入口IP,多IP节点可指定监听地址"
|
||||
label="监听IP"
|
||||
placeholder="留空使用默认"
|
||||
value={form.inIp}
|
||||
variant="bordered"
|
||||
onChange={(e) =>
|
||||
setForm((prev) => ({ ...prev, inIp: e.target.value }))
|
||||
}
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
description="格式: IP:端口 或 域名:端口,支持多个地址(每行一个)"
|
||||
errorMessage={errors.remoteAddr}
|
||||
|
||||
@@ -76,6 +76,7 @@ interface Node {
|
||||
port: string;
|
||||
tcpListenAddr?: string;
|
||||
udpListenAddr?: string;
|
||||
extraIPs?: string;
|
||||
version?: string;
|
||||
http?: number; // 0 关 1 开
|
||||
tls?: number; // 0 关 1 开
|
||||
@@ -99,7 +100,7 @@ interface Node {
|
||||
rollbackLoading?: boolean;
|
||||
}
|
||||
|
||||
interface NodeForm {
|
||||
interface NodeForm {
|
||||
id: number | null;
|
||||
name: string;
|
||||
serverHost: string;
|
||||
@@ -109,6 +110,7 @@ interface NodeForm {
|
||||
tcpListenAddr: string;
|
||||
udpListenAddr: string;
|
||||
interfaceName: string;
|
||||
extraIPs: string;
|
||||
http: number; // 0 关 1 开
|
||||
tls: number; // 0 关 1 开
|
||||
socks: number; // 0 关 1 开
|
||||
@@ -183,6 +185,7 @@ export default function NodePage() {
|
||||
tcpListenAddr: "[::]",
|
||||
udpListenAddr: "[::]",
|
||||
interfaceName: "",
|
||||
extraIPs: "",
|
||||
http: 0,
|
||||
tls: 0,
|
||||
socks: 0,
|
||||
@@ -590,6 +593,7 @@ export default function NodePage() {
|
||||
tcpListenAddr: node.tcpListenAddr || "[::]",
|
||||
udpListenAddr: node.udpListenAddr || "[::]",
|
||||
interfaceName: (node as any).interfaceName || "",
|
||||
extraIPs: node.extraIPs || "",
|
||||
http: typeof node.http === "number" ? node.http : 1,
|
||||
tls: typeof node.tls === "number" ? node.tls : 1,
|
||||
socks: typeof node.socks === "number" ? node.socks : 1,
|
||||
@@ -817,6 +821,7 @@ export default function NodePage() {
|
||||
const { serverHost, ...rest } = form;
|
||||
const data = {
|
||||
...rest,
|
||||
extraIPs: form.extraIPs,
|
||||
serverIp:
|
||||
form.serverIpV4?.trim() ||
|
||||
form.serverIpV6?.trim() ||
|
||||
@@ -880,6 +885,7 @@ export default function NodePage() {
|
||||
tcpListenAddr: "[::]",
|
||||
udpListenAddr: "[::]",
|
||||
interfaceName: "",
|
||||
extraIPs: "",
|
||||
http: 0,
|
||||
tls: 0,
|
||||
socks: 0,
|
||||
@@ -1637,6 +1643,17 @@ export default function NodePage() {
|
||||
}
|
||||
/>
|
||||
|
||||
<Input
|
||||
description="多IP服务器可填写额外IP地址,逗号分隔"
|
||||
label="额外IP地址"
|
||||
placeholder="例如: 192.168.1.100, 10.0.0.5"
|
||||
value={form.extraIPs}
|
||||
variant="bordered"
|
||||
onChange={(e) =>
|
||||
setForm((prev) => ({ ...prev, extraIPs: e.target.value }))
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Input
|
||||
errorMessage={errors.tcpListenAddr}
|
||||
|
||||
@@ -70,6 +70,7 @@ interface ChainTunnel {
|
||||
strategy?: string; // 'fifo' | 'round' | 'rand' - 仅转发链需要
|
||||
chainType?: number; // 1: 入口, 2: 转发链, 3: 出口
|
||||
inx?: number; // 转发链序号
|
||||
connectIp?: string; // 连接IP(多IP节点指定连接地址)
|
||||
}
|
||||
|
||||
interface Tunnel {
|
||||
@@ -343,6 +344,20 @@ export default function TunnelPage() {
|
||||
});
|
||||
};
|
||||
|
||||
// 更新某一跳的所有节点的连接IP
|
||||
const updateChainConnectIp = (groupIndex: number, connectIp: string) => {
|
||||
setForm((prev) => {
|
||||
const chainNodes = [...(prev.chainNodes || [])];
|
||||
|
||||
chainNodes[groupIndex] = (chainNodes[groupIndex] || []).map((node) => ({
|
||||
...node,
|
||||
connectIp,
|
||||
}));
|
||||
|
||||
return { ...prev, chainNodes };
|
||||
});
|
||||
};
|
||||
|
||||
// 获取所有转发链中已选择的节点ID列表
|
||||
const getSelectedChainNodeIds = (): number[] => {
|
||||
return (form.chainNodes || []).flatMap((group) =>
|
||||
@@ -1741,6 +1756,29 @@ export default function TunnelPage() {
|
||||
<SelectItem key="rand">随机</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 连接IP - 转发链节点 */}
|
||||
<Input
|
||||
classNames={{
|
||||
label: "text-xs",
|
||||
}}
|
||||
description="多IP节点可指定连接地址,留空使用默认"
|
||||
label="连接IP"
|
||||
placeholder="留空使用默认"
|
||||
size="sm"
|
||||
value={
|
||||
groupNodes.length > 0
|
||||
? groupNodes[0].connectIp || ""
|
||||
: ""
|
||||
}
|
||||
variant="bordered"
|
||||
onChange={(e) => {
|
||||
updateChainConnectIp(
|
||||
groupIndex,
|
||||
e.target.value,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -2002,6 +2040,50 @@ export default function TunnelPage() {
|
||||
<SelectItem key="rand">随机</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 连接IP - 出口节点 */}
|
||||
<Input
|
||||
classNames={{
|
||||
label: "text-xs",
|
||||
}}
|
||||
description="多IP节点可指定连接地址,留空使用默认"
|
||||
label="连接IP"
|
||||
placeholder="留空使用默认"
|
||||
size="sm"
|
||||
value={
|
||||
form.outNodeId && form.outNodeId.length > 0
|
||||
? form.outNodeId[0].connectIp || ""
|
||||
: ""
|
||||
}
|
||||
variant="bordered"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setForm((prev) => {
|
||||
const currentOutNodes = prev.outNodeId || [];
|
||||
if (currentOutNodes.length === 0) {
|
||||
return {
|
||||
...prev,
|
||||
outNodeId: [
|
||||
{
|
||||
nodeId: -1,
|
||||
chainType: 3,
|
||||
protocol: "tls",
|
||||
strategy: "round",
|
||||
connectIp: value,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
outNodeId: currentOutNodes.map((ct) => ({
|
||||
...ct,
|
||||
connectIp: value,
|
||||
})),
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user