修复出口协议无法选择,加入自定义转发端口,增加arm支持

This commit is contained in:
qaq
2025-11-19 16:32:48 +08:00
parent 9146eb0375
commit 4fef8eeb32
14 changed files with 157 additions and 47 deletions
+44 -15
View File
@@ -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
+2 -2
View File
@@ -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:
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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();
} }
} }
+1 -1
View File
@@ -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 = () => {
+28
View File
@@ -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'
}; };
@@ -1606,6 +1616,24 @@ export default function ForwardPage() {
))} ))}
</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="远程地址"
placeholder="请输入远程地址,多个地址用换行分隔&#10;例如:&#10;192.168.1.100:8080&#10;example.com:3000" placeholder="请输入远程地址,多个地址用换行分隔&#10;例如:&#10;192.168.1.100:8080&#10;example.com:3000"
+56 -17
View File
@@ -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"