mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-06 18:06:36 +08:00
修复出口协议无法选择,加入自定义转发端口,增加arm支持
This commit is contained in:
@@ -2,7 +2,7 @@ name: Build and Push Images Based on Version
|
|||||||
|
|
||||||
# 在这里定义统一版本号
|
# 在这里定义统一版本号
|
||||||
env:
|
env:
|
||||||
VERSION: "2.0.1-beta"
|
VERSION: "2.0.2-beta"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # 获取所有历史和 tags
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Display version
|
- name: Display version
|
||||||
run: |
|
run: |
|
||||||
@@ -67,22 +67,38 @@ jobs:
|
|||||||
sudo mv upx-4.2.1-amd64_linux/upx /usr/local/bin/
|
sudo mv upx-4.2.1-amd64_linux/upx /usr/local/bin/
|
||||||
rm -rf upx-4.2.1-amd64_linux*
|
rm -rf upx-4.2.1-amd64_linux*
|
||||||
|
|
||||||
- name: Build GOST binary
|
- name: Build GOST binary (AMD64)
|
||||||
working-directory: ./go-gost
|
working-directory: ./go-gost
|
||||||
run: |
|
run: |
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o gost
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o gost
|
||||||
|
|
||||||
- name: Compress with UPX
|
- name: Build GOST binary (ARM64)
|
||||||
|
working-directory: ./go-gost
|
||||||
|
run: |
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o gost-arm64
|
||||||
|
|
||||||
|
- name: Compress with UPX (AMD64)
|
||||||
working-directory: ./go-gost
|
working-directory: ./go-gost
|
||||||
run: |
|
run: |
|
||||||
upx --best --lzma gost
|
upx --best --lzma gost
|
||||||
|
|
||||||
- name: Upload GOST artifact
|
- name: Compress with UPX (ARM64)
|
||||||
|
working-directory: ./go-gost
|
||||||
|
run: |
|
||||||
|
upx --best --lzma gost-arm64
|
||||||
|
|
||||||
|
- name: Upload GOST AMD64 artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: gost-binary
|
name: gost-binary-amd64
|
||||||
path: ./go-gost/gost
|
path: ./go-gost/gost
|
||||||
|
|
||||||
|
- name: Upload GOST ARM64 artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: gost-binary-arm64
|
||||||
|
path: ./go-gost/gost-arm64
|
||||||
|
|
||||||
build-vite:
|
build-vite:
|
||||||
name: Build & Push Vite Frontend
|
name: Build & Push Vite Frontend
|
||||||
needs: check-version
|
needs: check-version
|
||||||
@@ -109,7 +125,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
VERSION="${{ needs.check-version.outputs.version }}"
|
VERSION="${{ needs.check-version.outputs.version }}"
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--platform linux/amd64 \
|
--platform linux/amd64,linux/arm64 \
|
||||||
--push \
|
--push \
|
||||||
-t bqlpfy/vite-frontend:latest \
|
-t bqlpfy/vite-frontend:latest \
|
||||||
-t bqlpfy/vite-frontend:${VERSION} \
|
-t bqlpfy/vite-frontend:${VERSION} \
|
||||||
@@ -153,7 +169,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
VERSION="${{ needs.check-version.outputs.version }}"
|
VERSION="${{ needs.check-version.outputs.version }}"
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--platform linux/amd64 \
|
--platform linux/amd64,linux/arm64 \
|
||||||
--push \
|
--push \
|
||||||
-t bqlpfy/springboot-backend:latest \
|
-t bqlpfy/springboot-backend:latest \
|
||||||
-t bqlpfy/springboot-backend:${VERSION} \
|
-t bqlpfy/springboot-backend:${VERSION} \
|
||||||
@@ -169,11 +185,22 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Download GOST binary
|
- name: Download GOST AMD64 binary
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: gost-binary
|
name: gost-binary-amd64
|
||||||
path: ./artifacts
|
path: ./artifacts/amd64
|
||||||
|
|
||||||
|
- name: Download GOST ARM64 binary
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: gost-binary-arm64
|
||||||
|
path: ./artifacts/arm64
|
||||||
|
|
||||||
|
- name: Rename binaries
|
||||||
|
run: |
|
||||||
|
mv ./artifacts/amd64/gost ./artifacts/gost
|
||||||
|
mv ./artifacts/arm64/gost-arm64 ./artifacts/gost-arm64
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
env:
|
env:
|
||||||
@@ -202,9 +229,10 @@ jobs:
|
|||||||
- **Commit**: [\`${GITHUB_SHA:0:7}\`](https://github.com/${{ github.repository }}/commit/${{ github.sha }})
|
- **Commit**: [\`${GITHUB_SHA:0:7}\`](https://github.com/${{ github.repository }}/commit/${{ github.sha }})
|
||||||
|
|
||||||
## 📦 Components
|
## 📦 Components
|
||||||
- **GOST Binary** (Linux AMD64, compressed with UPX)
|
- **GOST Binary AMD64** - \`gost\` (Linux AMD64, compressed with UPX)
|
||||||
- **Vite Frontend** Docker Image: \`bqlpfy/vite-frontend:${VERSION}\` / \`bqlpfy/vite-frontend:latest\`
|
- **GOST Binary ARM64** - \`gost-arm64\` (Linux ARM64, compressed with UPX)
|
||||||
- **Spring Boot Backend** Docker Image: \`bqlpfy/springboot-backend:${VERSION}\` / \`bqlpfy/springboot-backend:latest\`
|
- **Vite Frontend** Docker Image: \`bqlpfy/vite-frontend:${VERSION}\` / \`bqlpfy/vite-frontend:latest\` (支持 AMD64/ARM64)
|
||||||
|
- **Spring Boot Backend** Docker Image: \`bqlpfy/springboot-backend:${VERSION}\` / \`bqlpfy/springboot-backend:latest\` (支持 AMD64/ARM64)
|
||||||
|
|
||||||
## 📥 Installation Files
|
## 📥 Installation Files
|
||||||
- \`install.sh\` - GOST 客户端安装脚本
|
- \`install.sh\` - GOST 客户端安装脚本
|
||||||
@@ -218,8 +246,9 @@ jobs:
|
|||||||
--repo ${{ github.repository }}
|
--repo ${{ github.repository }}
|
||||||
|
|
||||||
# 上传所有文件到 release
|
# 上传所有文件到 release
|
||||||
echo "📤 上传 GOST 二进制..."
|
echo "📤 上传 GOST 二进制文件..."
|
||||||
gh release upload "${VERSION}" ./artifacts/gost --clobber
|
gh release upload "${VERSION}" ./artifacts/gost --clobber
|
||||||
|
gh release upload "${VERSION}" ./artifacts/gost-arm64 --clobber
|
||||||
|
|
||||||
echo "📤 上传安装脚本..."
|
echo "📤 上传安装脚本..."
|
||||||
gh release upload "${VERSION}" ./install.sh --clobber
|
gh release upload "${VERSION}" ./install.sh --clobber
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
image: bqlpfy/springboot-backend:2.0.1-beta
|
image: bqlpfy/springboot-backend:2.0.2-beta
|
||||||
container_name: springboot-backend
|
container_name: springboot-backend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
@@ -23,7 +23,7 @@ services:
|
|||||||
start_period: 60s
|
start_period: 60s
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: bqlpfy/vite-frontend:2.0.1-beta
|
image: bqlpfy/vite-frontend:2.0.2-beta
|
||||||
container_name: vite-frontend
|
container_name: vite-frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
image: bqlpfy/springboot-backend:2.0.1-beta
|
image: bqlpfy/springboot-backend:2.0.2-beta
|
||||||
container_name: springboot-backend
|
container_name: springboot-backend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
@@ -23,7 +23,7 @@ services:
|
|||||||
start_period: 60s
|
start_period: 60s
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: bqlpfy/vite-frontend:2.0.1-beta
|
image: bqlpfy/vite-frontend:2.0.2-beta
|
||||||
container_name: vite-frontend
|
container_name: vite-frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# 下载地址
|
# 下载地址
|
||||||
DOWNLOAD_URL="https://github.com/bqlpfy/flux-panel/releases/download/2.0.1-beta/gost"
|
DOWNLOAD_URL="https://github.com/bqlpfy/flux-panel/releases/download/2.0.2-beta/gost"
|
||||||
INSTALL_DIR="/etc/flux_agent"
|
INSTALL_DIR="/etc/flux_agent"
|
||||||
COUNTRY=$(curl -s https://ipinfo.io/country)
|
COUNTRY=$(curl -s https://ipinfo.io/country)
|
||||||
if [ "$COUNTRY" = "CN" ]; then
|
if [ "$COUNTRY" = "CN" ]; then
|
||||||
|
|||||||
+2
-2
@@ -8,8 +8,8 @@ export LC_ALL=C
|
|||||||
|
|
||||||
|
|
||||||
# 全局下载地址配置
|
# 全局下载地址配置
|
||||||
DOCKER_COMPOSEV4_URL="https://github.com/bqlpfy/flux-panel/releases/download/2.0.1-beta/docker-compose-v4.yml"
|
DOCKER_COMPOSEV4_URL="https://github.com/bqlpfy/flux-panel/releases/download/2.0.2-beta/docker-compose-v4.yml"
|
||||||
DOCKER_COMPOSEV6_URL="https://github.com/bqlpfy/flux-panel/releases/download/2.0.1-beta/docker-compose-v6.yml"
|
DOCKER_COMPOSEV6_URL="https://github.com/bqlpfy/flux-panel/releases/download/2.0.2-beta/docker-compose-v6.yml"
|
||||||
|
|
||||||
COUNTRY=$(curl -s https://ipinfo.io/country)
|
COUNTRY=$(curl -s https://ipinfo.io/country)
|
||||||
if [ "$COUNTRY" = "CN" ]; then
|
if [ "$COUNTRY" = "CN" ]; then
|
||||||
|
|||||||
@@ -21,4 +21,6 @@ public class ForwardDto {
|
|||||||
private String remoteAddr;
|
private String remoteAddr;
|
||||||
|
|
||||||
private String strategy;
|
private String strategy;
|
||||||
|
|
||||||
|
private Integer inPort;
|
||||||
}
|
}
|
||||||
@@ -25,4 +25,6 @@ public class ForwardUpdateDto {
|
|||||||
|
|
||||||
private String strategy;
|
private String strategy;
|
||||||
|
|
||||||
|
private Integer inPort;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -57,5 +57,5 @@ public interface TunnelService extends IService<Tunnel> {
|
|||||||
*/
|
*/
|
||||||
R diagnoseTunnel(Long tunnelId);
|
R diagnoseTunnel(Long tunnelId);
|
||||||
|
|
||||||
Integer getNodePort(Long nodeId, Integer type);
|
Integer getNodePort(Long nodeId, Integer type, Integer port);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
|
|||||||
|
|
||||||
List<ChainTunnel> chainTunnels = chainTunnelService.list(new QueryWrapper<ChainTunnel>().eq("tunnel_id", tunnel.getId()).eq("chain_type", 1));
|
List<ChainTunnel> chainTunnels = chainTunnelService.list(new QueryWrapper<ChainTunnel>().eq("tunnel_id", tunnel.getId()).eq("chain_type", 1));
|
||||||
for (ChainTunnel chainTunnel : chainTunnels) {
|
for (ChainTunnel chainTunnel : chainTunnels) {
|
||||||
Integer nodePort = tunnelService.getNodePort(chainTunnel.getNodeId(), 2);
|
Integer nodePort = tunnelService.getNodePort(chainTunnel.getNodeId(), 2, forwardDto.getInPort());
|
||||||
|
|
||||||
ForwardPort forwardPort = new ForwardPort();
|
ForwardPort forwardPort = new ForwardPort();
|
||||||
forwardPort.setForwardId(forward.getId());
|
forwardPort.setForwardId(forward.getId());
|
||||||
@@ -205,7 +205,7 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
|
|||||||
return R.err(permissionResult.getErrorMessage());
|
return R.err(permissionResult.getErrorMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
UserTunnel userTunnel = null;
|
UserTunnel userTunnel;
|
||||||
if (currentUser.getRoleId() != 0) {
|
if (currentUser.getRoleId() != 0) {
|
||||||
userTunnel = getUserTunnel(currentUser.getUserId(), tunnel.getId().intValue());
|
userTunnel = getUserTunnel(currentUser.getUserId(), tunnel.getId().intValue());
|
||||||
if (userTunnel == null) {
|
if (userTunnel == null) {
|
||||||
@@ -236,6 +236,13 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
|
|||||||
if (forwardPort == null){
|
if (forwardPort == null){
|
||||||
return R.err("部分节点不存在1");
|
return R.err("部分节点不存在1");
|
||||||
}
|
}
|
||||||
|
if (forwardUpdateDto.getInPort() != null && !forwardUpdateDto.getInPort().equals(forwardPort.getPort())) {
|
||||||
|
Integer nodePort = tunnelService.getNodePort(forwardPort.getNodeId(), 2, forwardUpdateDto.getInPort());
|
||||||
|
if (Objects.equals(nodePort, forwardUpdateDto.getInPort())) {
|
||||||
|
forwardPort.setPort(nodePort);
|
||||||
|
forwardPortService.updateById(forwardPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
GostUtil.AddAndUpdateService(serviceName, limiter, node, existForward, forwardPort, tunnel, "UpdateService");
|
GostUtil.AddAndUpdateService(serviceName, limiter, node, existForward, forwardPort, tunnel, "UpdateService");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
|
|||||||
ViteConfig viteConfig = viteConfigService.getOne(new QueryWrapper<ViteConfig>().eq("name", "ip"));
|
ViteConfig viteConfig = viteConfigService.getOne(new QueryWrapper<ViteConfig>().eq("name", "ip"));
|
||||||
if (viteConfig == null) return R.err("请先前往网站配置中设置ip");
|
if (viteConfig == null) return R.err("请先前往网站配置中设置ip");
|
||||||
StringBuilder command = new StringBuilder();
|
StringBuilder command = new StringBuilder();
|
||||||
command.append("curl -L https://github.com/bqlpfy/flux-panel/releases/download/2.0.1-beta/install.sh")
|
command.append("curl -L https://github.com/bqlpfy/flux-panel/releases/download/2.0.2-beta/install.sh")
|
||||||
.append(" -o ./install.sh && chmod +x ./install.sh && ");
|
.append(" -o ./install.sh && chmod +x ./install.sh && ");
|
||||||
String processedServerAddr = processServerAddress(viteConfig.getValue());
|
String processedServerAddr = processServerAddress(viteConfig.getValue());
|
||||||
command.append("./install.sh")
|
command.append("./install.sh")
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
|||||||
Node node = nodeService.getById(chain_node.getNodeId());
|
Node node = nodeService.getById(chain_node.getNodeId());
|
||||||
if (node == null) return R.err("节点不存在");
|
if (node == null) return R.err("节点不存在");
|
||||||
nodes.put(node.getId(), node);
|
nodes.put(node.getId(), node);
|
||||||
Integer nodePort = getNodePort(chain_node.getNodeId(), 1);
|
Integer nodePort = getNodePort(chain_node.getNodeId(), 1, null);
|
||||||
chain_node.setPort(nodePort);
|
chain_node.setPort(nodePort);
|
||||||
chain_node.setInx(inx); // 设置转发链序号
|
chain_node.setInx(inx); // 设置转发链序号
|
||||||
chainTunnels.add(chain_node);
|
chainTunnels.add(chain_node);
|
||||||
@@ -93,7 +93,7 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
|||||||
Node node = nodeService.getById(out_node.getNodeId());
|
Node node = nodeService.getById(out_node.getNodeId());
|
||||||
if (node == null) return R.err("节点不存在");
|
if (node == null) return R.err("节点不存在");
|
||||||
nodes.put(node.getId(), node);
|
nodes.put(node.getId(), node);
|
||||||
Integer nodePort = getNodePort(out_node.getNodeId(), 1);
|
Integer nodePort = getNodePort(out_node.getNodeId(), 1, null);
|
||||||
out_node.setPort(nodePort);
|
out_node.setPort(nodePort);
|
||||||
chainTunnels.add(out_node);
|
chainTunnels.add(out_node);
|
||||||
}
|
}
|
||||||
@@ -472,7 +472,7 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer getNodePort(Long nodeId,Integer type) {
|
public Integer getNodePort(Long nodeId,Integer type, Integer port) {
|
||||||
|
|
||||||
Node node = nodeService.getById(nodeId);
|
Node node = nodeService.getById(nodeId);
|
||||||
if (node == null){
|
if (node == null){
|
||||||
@@ -508,6 +508,9 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
|||||||
if (type == 1) {
|
if (type == 1) {
|
||||||
return availablePorts.getLast();
|
return availablePorts.getLast();
|
||||||
}else {
|
}else {
|
||||||
|
if (port != null && availablePorts.contains(port)) {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
return availablePorts.getFirst();
|
return availablePorts.getFirst();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export type SiteConfig = typeof siteConfig;
|
|||||||
|
|
||||||
// 缓存相关常量
|
// 缓存相关常量
|
||||||
const CACHE_PREFIX = 'vite_config_';
|
const CACHE_PREFIX = 'vite_config_';
|
||||||
const VERSION = "2.0.1-beta";
|
const VERSION = "2.0.2-beta";
|
||||||
const APP_VERSION = "1.0.3";
|
const APP_VERSION = "1.0.3";
|
||||||
|
|
||||||
const getInitialConfig = () => {
|
const getInitialConfig = () => {
|
||||||
|
|||||||
@@ -406,6 +406,14 @@ export default function ForwardPage() {
|
|||||||
newErrors.tunnelId = '请选择关联隧道';
|
newErrors.tunnelId = '请选择关联隧道';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证入口端口(可选,如果填写则验证)
|
||||||
|
if (form.inPort !== null && form.inPort !== undefined) {
|
||||||
|
const port = Number(form.inPort);
|
||||||
|
if (isNaN(port) || port < 1 || port > 65535) {
|
||||||
|
newErrors.inPort = '端口必须在 1-65535 之间';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!form.remoteAddr.trim()) {
|
if (!form.remoteAddr.trim()) {
|
||||||
newErrors.remoteAddr = '请输入远程地址';
|
newErrors.remoteAddr = '请输入远程地址';
|
||||||
} else {
|
} else {
|
||||||
@@ -526,6 +534,7 @@ export default function ForwardPage() {
|
|||||||
userId: form.userId,
|
userId: form.userId,
|
||||||
name: form.name,
|
name: form.name,
|
||||||
tunnelId: form.tunnelId,
|
tunnelId: form.tunnelId,
|
||||||
|
inPort: form.inPort,
|
||||||
remoteAddr: processedRemoteAddr,
|
remoteAddr: processedRemoteAddr,
|
||||||
strategy: addressCount > 1 ? form.strategy : 'fifo'
|
strategy: addressCount > 1 ? form.strategy : 'fifo'
|
||||||
};
|
};
|
||||||
@@ -535,6 +544,7 @@ export default function ForwardPage() {
|
|||||||
const createData = {
|
const createData = {
|
||||||
name: form.name,
|
name: form.name,
|
||||||
tunnelId: form.tunnelId,
|
tunnelId: form.tunnelId,
|
||||||
|
inPort: form.inPort,
|
||||||
remoteAddr: processedRemoteAddr,
|
remoteAddr: processedRemoteAddr,
|
||||||
strategy: addressCount > 1 ? form.strategy : 'fifo'
|
strategy: addressCount > 1 ? form.strategy : 'fifo'
|
||||||
};
|
};
|
||||||
@@ -1605,6 +1615,24 @@ export default function ForwardPage() {
|
|||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label="入口端口"
|
||||||
|
placeholder="留空则自动分配可用端口"
|
||||||
|
type="number"
|
||||||
|
value={form.inPort !== null ? form.inPort.toString() : ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setForm(prev => ({
|
||||||
|
...prev,
|
||||||
|
inPort: value ? parseInt(value) : null
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
isInvalid={!!errors.inPort}
|
||||||
|
errorMessage={errors.inPort}
|
||||||
|
variant="bordered"
|
||||||
|
description="指定入口端口,留空则从节点可用端口中自动分配"
|
||||||
|
/>
|
||||||
|
|
||||||
<Textarea
|
<Textarea
|
||||||
label="远程地址"
|
label="远程地址"
|
||||||
|
|||||||
@@ -357,6 +357,9 @@ export default function TunnelPage() {
|
|||||||
.map(group => group.filter(node => node.nodeId !== -1))
|
.map(group => group.filter(node => node.nodeId !== -1))
|
||||||
.filter(group => group.length > 0); // 移除空组
|
.filter(group => group.length > 0); // 移除空组
|
||||||
|
|
||||||
|
// 过滤掉出口节点中的占位节点
|
||||||
|
const cleanedOutNodeId = (form.outNodeId || []).filter(node => node.nodeId !== -1);
|
||||||
|
|
||||||
// 将换行符分隔的IP转换为逗号分隔
|
// 将换行符分隔的IP转换为逗号分隔
|
||||||
const inIpString = form.inIp
|
const inIpString = form.inIp
|
||||||
.split('\n')
|
.split('\n')
|
||||||
@@ -367,6 +370,7 @@ export default function TunnelPage() {
|
|||||||
const data = {
|
const data = {
|
||||||
...form,
|
...form,
|
||||||
inIp: inIpString,
|
inIp: inIpString,
|
||||||
|
outNodeId: cleanedOutNodeId,
|
||||||
chainNodes: cleanedChainNodes
|
chainNodes: cleanedChainNodes
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1052,7 +1056,7 @@ export default function TunnelPage() {
|
|||||||
label="节点"
|
label="节点"
|
||||||
placeholder="请选择出口节点(可多选)"
|
placeholder="请选择出口节点(可多选)"
|
||||||
selectionMode="multiple"
|
selectionMode="multiple"
|
||||||
selectedKeys={form.outNodeId ? form.outNodeId.map(ct => ct.nodeId.toString()) : []}
|
selectedKeys={form.outNodeId ? form.outNodeId.filter(ct => ct.nodeId !== -1).map(ct => ct.nodeId.toString()) : []}
|
||||||
disabledKeys={[
|
disabledKeys={[
|
||||||
...nodes.filter(node => node.status !== 1).map(node => node.id.toString()),
|
...nodes.filter(node => node.status !== 1).map(node => node.id.toString()),
|
||||||
...form.inNodeId.map(ct => ct.nodeId.toString()),
|
...form.inNodeId.map(ct => ct.nodeId.toString()),
|
||||||
@@ -1061,12 +1065,17 @@ export default function TunnelPage() {
|
|||||||
onSelectionChange={(keys) => {
|
onSelectionChange={(keys) => {
|
||||||
const selectedIds = Array.from(keys).map(key => parseInt(key as string));
|
const selectedIds = Array.from(keys).map(key => parseInt(key as string));
|
||||||
const currentOutNodes = form.outNodeId || [];
|
const currentOutNodes = form.outNodeId || [];
|
||||||
const protocol = currentOutNodes.length > 0 ? currentOutNodes[0].protocol || 'tls' : 'tls';
|
|
||||||
const strategy = currentOutNodes.length > 0 ? currentOutNodes[0].strategy || 'round' : 'round';
|
|
||||||
|
|
||||||
|
let protocol = 'tls';
|
||||||
|
let strategy = 'round';
|
||||||
|
if (currentOutNodes.length > 0) {
|
||||||
|
protocol = currentOutNodes[0].protocol || 'tls';
|
||||||
|
strategy = currentOutNodes[0].strategy || 'round';
|
||||||
|
}
|
||||||
|
|
||||||
|
const realNodes = currentOutNodes.filter(ct => ct.nodeId !== -1);
|
||||||
const newOutNodeId: ChainTunnel[] = selectedIds.map(nodeId => {
|
const newOutNodeId: ChainTunnel[] = selectedIds.map(nodeId => {
|
||||||
// 保留已有的配置或使用默认值
|
const existing = realNodes.find(ct => ct.nodeId === nodeId);
|
||||||
const existing = currentOutNodes.find(ct => ct.nodeId === nodeId);
|
|
||||||
return existing || { nodeId, chainType: 3, protocol, strategy };
|
return existing || { nodeId, chainType: 3, protocol, strategy };
|
||||||
});
|
});
|
||||||
setForm(prev => ({ ...prev, outNodeId: newOutNodeId }));
|
setForm(prev => ({ ...prev, outNodeId: newOutNodeId }));
|
||||||
@@ -1116,14 +1125,30 @@ export default function TunnelPage() {
|
|||||||
<Select
|
<Select
|
||||||
label="协议"
|
label="协议"
|
||||||
placeholder="选择协议"
|
placeholder="选择协议"
|
||||||
selectedKeys={[form.outNodeId && form.outNodeId.length > 0 ? form.outNodeId[0].protocol || 'tls' : 'tls']}
|
selectedKeys={[(() => {
|
||||||
|
if (!form.outNodeId || form.outNodeId.length === 0) return 'tls';
|
||||||
|
return form.outNodeId[0].protocol || 'tls';
|
||||||
|
})()]}
|
||||||
onSelectionChange={(keys) => {
|
onSelectionChange={(keys) => {
|
||||||
const selectedKey = Array.from(keys)[0] as string;
|
const selectedKey = Array.from(keys)[0] as string;
|
||||||
if (selectedKey && form.outNodeId) {
|
if (selectedKey) {
|
||||||
setForm(prev => ({
|
setForm(prev => {
|
||||||
...prev,
|
const currentOutNodes = prev.outNodeId || [];
|
||||||
outNodeId: (prev.outNodeId || []).map(ct => ({ ...ct, protocol: selectedKey }))
|
const currentStrategy = currentOutNodes.length > 0 ? currentOutNodes[0].strategy || 'round' : 'round';
|
||||||
}));
|
|
||||||
|
if (currentOutNodes.length === 0) {
|
||||||
|
// 如果还没有出口节点,创建一个占位节点保存设置
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
outNodeId: [{ nodeId: -1, chainType: 3, protocol: selectedKey, strategy: currentStrategy }]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 更新所有出口节点的协议
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
outNodeId: currentOutNodes.map(ct => ({ ...ct, protocol: selectedKey }))
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
isInvalid={!!errors.protocol}
|
isInvalid={!!errors.protocol}
|
||||||
@@ -1147,14 +1172,28 @@ export default function TunnelPage() {
|
|||||||
<Select
|
<Select
|
||||||
label="负载策略"
|
label="负载策略"
|
||||||
placeholder="选择策略"
|
placeholder="选择策略"
|
||||||
selectedKeys={[form.outNodeId && form.outNodeId.length > 0 ? form.outNodeId[0].strategy || 'round' : 'round']}
|
selectedKeys={[(() => {
|
||||||
|
if (!form.outNodeId || form.outNodeId.length === 0) return 'round';
|
||||||
|
return form.outNodeId[0].strategy || 'round';
|
||||||
|
})()]}
|
||||||
onSelectionChange={(keys) => {
|
onSelectionChange={(keys) => {
|
||||||
const selectedKey = Array.from(keys)[0] as string;
|
const selectedKey = Array.from(keys)[0] as string;
|
||||||
if (selectedKey && form.outNodeId) {
|
if (selectedKey) {
|
||||||
setForm(prev => ({
|
setForm(prev => {
|
||||||
...prev,
|
const currentOutNodes = prev.outNodeId || [];
|
||||||
outNodeId: (prev.outNodeId || []).map(ct => ({ ...ct, strategy: selectedKey }))
|
const currentProtocol = currentOutNodes.length > 0 ? currentOutNodes[0].protocol || 'tls' : 'tls';
|
||||||
}));
|
|
||||||
|
if (currentOutNodes.length === 0) {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
outNodeId: [{ nodeId: -1, chainType: 3, protocol: currentProtocol, strategy: selectedKey }]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
outNodeId: currentOutNodes.map(ct => ({ ...ct, strategy: selectedKey }))
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
|
|||||||
Reference in New Issue
Block a user