mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 07:36:38 +08:00
添加屏蔽协议到面板,免去手动操作
This commit is contained in:
@@ -5,7 +5,6 @@ go 1.23.0
|
||||
toolchain go1.23.4
|
||||
|
||||
require (
|
||||
github.com/apernet/OpenGFW v0.0.0-00010101000000-000000000000
|
||||
github.com/go-gost/core v0.3.1
|
||||
github.com/go-gost/x v0.5.3
|
||||
github.com/judwhite/go-svc v1.2.1
|
||||
@@ -149,4 +148,3 @@ require (
|
||||
|
||||
replace github.com/go-gost/x => ./x
|
||||
|
||||
replace github.com/apernet/OpenGFW => ./gfw
|
||||
|
||||
+1
-2
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
//"github.com/apernet/OpenGFW/cmd"
|
||||
"log"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
@@ -120,7 +119,7 @@ func main() {
|
||||
log := xlogger.NewLogger()
|
||||
logger.SetDefault(log)
|
||||
|
||||
wsReporter := socket.StartWebSocketReporterWithConfig(config.Addr, config.Secret, "1.2.2")
|
||||
wsReporter := socket.StartWebSocketReporterWithConfig(config.Addr, config.Secret, config.Http, config.Tls, config.Socks, "1.2.3")
|
||||
defer wsReporter.Stop()
|
||||
service.SetHTTPReportURL(config.Addr, config.Secret)
|
||||
|
||||
|
||||
@@ -52,6 +52,14 @@ var isSocks = 0
|
||||
|
||||
var needWrap = false
|
||||
|
||||
// SetProtocolBlock sets protocol blocking switches and recomputes wrapper need
|
||||
func SetProtocolBlock(httpOn int, tlsOn int, socksOn int) {
|
||||
isHttp = httpOn
|
||||
isTls = tlsOn
|
||||
isSocks = socksOn
|
||||
needWrap = isTls+isSocks+isHttp > 0
|
||||
}
|
||||
|
||||
type Option func(opts *options)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -8,17 +8,20 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync" // 新增:用于管理连接状态的互斥锁
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/service"
|
||||
"github.com/go-gost/x/internal/util/crypto"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
psnet "github.com/shirou/gopsutil/v3/net"
|
||||
"os"
|
||||
)
|
||||
|
||||
// SystemInfo 系统信息结构体
|
||||
@@ -524,6 +527,11 @@ func (w *WebSocketReporter) routeCommand(cmd CommandMessage) {
|
||||
response.Type = "TcpPingResponse"
|
||||
response.Data = tcpPingResult
|
||||
|
||||
// Protocol blocking switches
|
||||
case "SetProtocol":
|
||||
err = w.handleSetProtocol(cmd.Data)
|
||||
response.Type = "SetProtocolResponse"
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("未知命令类型: %s", cmd.Type)
|
||||
response.Type = "UnknownCommandResponse"
|
||||
@@ -765,6 +773,85 @@ func (w *WebSocketReporter) handleDeleteLimiter(data interface{}) error {
|
||||
return deleteLimiter(deleteReq)
|
||||
}
|
||||
|
||||
// handleSetProtocol 处理设置屏蔽协议的命令
|
||||
func (w *WebSocketReporter) handleSetProtocol(data interface{}) error {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化协议设置失败: %v", err)
|
||||
}
|
||||
|
||||
// 支持 {"http":0/1, "tls":0/1, "socks":0/1}
|
||||
var req struct {
|
||||
HTTP *int `json:"http"`
|
||||
TLS *int `json:"tls"`
|
||||
SOCKS *int `json:"socks"`
|
||||
}
|
||||
if err := json.Unmarshal(jsonData, &req); err != nil {
|
||||
return fmt.Errorf("解析协议设置失败: %v", err)
|
||||
}
|
||||
|
||||
// 读取当前值作为默认
|
||||
httpVal, tlsVal, socksVal := 0, 0, 0
|
||||
|
||||
if req.HTTP != nil {
|
||||
if *req.HTTP != 0 && *req.HTTP != 1 {
|
||||
return fmt.Errorf("http 取值必须为0或1")
|
||||
}
|
||||
httpVal = *req.HTTP
|
||||
}
|
||||
if req.TLS != nil {
|
||||
if *req.TLS != 0 && *req.TLS != 1 {
|
||||
return fmt.Errorf("tls 取值必须为0或1")
|
||||
}
|
||||
tlsVal = *req.TLS
|
||||
}
|
||||
if req.SOCKS != nil {
|
||||
if *req.SOCKS != 0 && *req.SOCKS != 1 {
|
||||
return fmt.Errorf("socks 取值必须为0或1")
|
||||
}
|
||||
socksVal = *req.SOCKS
|
||||
}
|
||||
|
||||
// 设置至 service,全量传递(未提供的值沿用0)
|
||||
service.SetProtocolBlock(httpVal, tlsVal, socksVal)
|
||||
|
||||
// 同步写入本地 config.json
|
||||
if err := updateLocalConfigJSON(httpVal, tlsVal, socksVal); err != nil {
|
||||
return fmt.Errorf("写入config.json失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateLocalConfigJSON 将 http/tls/socks 写入工作目录下的 config.json
|
||||
func updateLocalConfigJSON(httpVal int, tlsVal int, socksVal int) error {
|
||||
path := "config.json"
|
||||
|
||||
// 读取现有配置
|
||||
type LocalConfig struct {
|
||||
Addr string `json:"addr"`
|
||||
Secret string `json:"secret"`
|
||||
Http int `json:"http"`
|
||||
Tls int `json:"tls"`
|
||||
Socks int `json:"socks"`
|
||||
}
|
||||
|
||||
var cfg LocalConfig
|
||||
if b, err := os.ReadFile(path); err == nil {
|
||||
_ = json.Unmarshal(b, &cfg)
|
||||
}
|
||||
|
||||
cfg.Http = httpVal
|
||||
cfg.Tls = tlsVal
|
||||
cfg.Socks = socksVal
|
||||
|
||||
// 写回
|
||||
data, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
// handleCall 处理服务端的call回调消息
|
||||
func (w *WebSocketReporter) handleCall(data interface{}) error {
|
||||
// 解析call数据
|
||||
@@ -931,15 +1018,15 @@ func getMemoryInfo() MemoryInfo {
|
||||
return memInfo
|
||||
}
|
||||
|
||||
// StartWebSocketReporterWithConfig 使用配置启动WebSocket报告器
|
||||
func StartWebSocketReporterWithConfig(Addr string, Secret string, Version string) *WebSocketReporter {
|
||||
// StartWebSocketReporterWithConfig 使用配置字段启动WebSocket报告器
|
||||
func StartWebSocketReporterWithConfig(addr string, secret string, http int, tls int, socks int, version string) *WebSocketReporter {
|
||||
|
||||
// 构建包含本机IP的WebSocket URL
|
||||
var fullURL = "ws://" + Addr + "/system-info?type=1&secret=" + Secret + "&version=" + Version
|
||||
fullURL := "ws://" + addr + "/system-info?type=1&secret=" + secret + "&version=" + version + "&http=" + strconv.Itoa(http) + "&tls=" + strconv.Itoa(tls) + "&socks=" + strconv.Itoa(socks)
|
||||
|
||||
fmt.Printf("🔗 WebSocket连接URL: %s\n", fullURL)
|
||||
|
||||
reporter := NewWebSocketReporter(fullURL, Secret) // Pass Secret here
|
||||
reporter := NewWebSocketReporter(fullURL, secret)
|
||||
reporter.Start()
|
||||
return reporter
|
||||
}
|
||||
|
||||
@@ -61,6 +61,9 @@ CREATE TABLE `node` (
|
||||
`port_sta` int(10) NOT NULL,
|
||||
`port_end` int(10) NOT NULL,
|
||||
`version` varchar(100) DEFAULT NULL,
|
||||
`http` int(10) NOT NULL DEFAULT '0',
|
||||
`tls` int(10) NOT NULL DEFAULT '0',
|
||||
`socks` int(10) NOT NULL DEFAULT '0',
|
||||
`created_time` bigint(20) NOT NULL,
|
||||
`updated_time` bigint(20) DEFAULT NULL,
|
||||
`status` int(10) NOT NULL
|
||||
|
||||
@@ -537,6 +537,64 @@ UPDATE \`node\`
|
||||
SET \`port_sta\` = 1000, \`port_end\` = 65535
|
||||
WHERE \`port_sta\` IS NULL OR \`port_end\` IS NULL;
|
||||
|
||||
-- node 表:添加 http、tls、socks 字段(如果不存在)
|
||||
SET @sql = (
|
||||
SELECT IF(
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'node'
|
||||
AND column_name = 'http'
|
||||
),
|
||||
'ALTER TABLE \`node\` ADD COLUMN \`http\` INT(10) DEFAULT 0 COMMENT "HTTP 服务端口";',
|
||||
'SELECT "Column \`http\` already exists in \`node\`";'
|
||||
)
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql = (
|
||||
SELECT IF(
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'node'
|
||||
AND column_name = 'tls'
|
||||
),
|
||||
'ALTER TABLE \`node\` ADD COLUMN \`tls\` INT(10) DEFAULT 0 COMMENT "TLS 服务端口";',
|
||||
'SELECT "Column \`tls\` already exists in \`node\`";'
|
||||
)
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql = (
|
||||
SELECT IF(
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'node'
|
||||
AND column_name = 'socks'
|
||||
),
|
||||
'ALTER TABLE \`node\` ADD COLUMN \`socks\` INT(10) DEFAULT 0 COMMENT "SOCKS 服务端口";',
|
||||
'SELECT "Column \`socks\` already exists in \`node\`";'
|
||||
)
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 为现有节点设置 http、tls、socks 默认值
|
||||
UPDATE \`node\`
|
||||
SET \`http\` = IFNULL(\`http\`, 0),
|
||||
\`tls\` = IFNULL(\`tls\`, 0),
|
||||
\`socks\` = IFNULL(\`socks\`, 0);
|
||||
|
||||
-- tunnel 表:删除废弃字段(如果存在)
|
||||
SET @sql = (
|
||||
SELECT IF(
|
||||
|
||||
@@ -31,4 +31,8 @@ public class NodeUpdateDto {
|
||||
@Min(value = 1, message = "结束端口必须大于0")
|
||||
@Max(value = 65535, message = "结束端口不能超过65535")
|
||||
private Integer portEnd;
|
||||
|
||||
private Integer http;
|
||||
private Integer tls;
|
||||
private Integer socks;
|
||||
}
|
||||
@@ -224,6 +224,9 @@ public class WebSocketServer extends TextWebSocketHandler {
|
||||
// 客户端节点连接
|
||||
Long nodeId = Long.valueOf(id);
|
||||
String version = (String) session.getAttributes().get("nodeVersion");
|
||||
String http = (String) session.getAttributes().get("http");
|
||||
String tls = (String) session.getAttributes().get("tls");
|
||||
String socks = (String) session.getAttributes().get("socks");
|
||||
|
||||
log.info("节点 {} 尝试连接,开始处理连接逻辑", nodeId);
|
||||
|
||||
@@ -256,6 +259,16 @@ public class WebSocketServer extends TextWebSocketHandler {
|
||||
if (version != null) {
|
||||
node.setVersion(version);
|
||||
}
|
||||
if (http != null) {
|
||||
node.setHttp(Integer.parseInt(http));
|
||||
}
|
||||
if (tls != null) {
|
||||
node.setTls(Integer.parseInt(tls));
|
||||
}
|
||||
if (socks != null) {
|
||||
node.setSocks(Integer.parseInt(socks));
|
||||
}
|
||||
|
||||
boolean updateResult = nodeService.updateById(node);
|
||||
|
||||
if (updateResult) {
|
||||
|
||||
@@ -38,6 +38,9 @@ public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
|
||||
String secret = serverHttpRequest.getServletRequest().getParameter("secret");
|
||||
String type = serverHttpRequest.getServletRequest().getParameter("type");
|
||||
String version = serverHttpRequest.getServletRequest().getParameter("version");
|
||||
String http = serverHttpRequest.getServletRequest().getParameter("http");
|
||||
String tls = serverHttpRequest.getServletRequest().getParameter("tls");
|
||||
String socks = serverHttpRequest.getServletRequest().getParameter("socks");
|
||||
if (Objects.equals(type, "1")) {
|
||||
System.out.println("type: " + type + " - version: " + version + " - secret: " + secret + " - IP: " + getClientIp(request));
|
||||
Node node = nodeService.getOne(new QueryWrapper<Node>().eq("secret", secret));
|
||||
@@ -48,6 +51,9 @@ public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
|
||||
attributes.put("id", node.getId());
|
||||
attributes.put("nodeSecret", secret);
|
||||
attributes.put("nodeVersion", version);
|
||||
attributes.put("http",http);
|
||||
attributes.put("tls",tls);
|
||||
attributes.put("socks",socks);
|
||||
log.info("节点 {} 通过验证,版本: {}", node.getId(), version);
|
||||
// 不在这里更新状态,等到连接建立后再统一更新
|
||||
}else {
|
||||
|
||||
@@ -32,4 +32,10 @@ public class Node extends BaseEntity {
|
||||
|
||||
private Integer portEnd;
|
||||
|
||||
private Integer http;
|
||||
|
||||
private Integer tls;
|
||||
|
||||
private Integer socks;
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package com.admin.service.impl;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.admin.common.dto.GostDto;
|
||||
import com.admin.common.dto.NodeDto;
|
||||
import com.admin.common.dto.NodeUpdateDto;
|
||||
import com.admin.common.lang.R;
|
||||
import com.admin.common.utils.WebSocketServer;
|
||||
import com.admin.entity.Node;
|
||||
import com.admin.entity.Tunnel;
|
||||
import com.admin.entity.ViteConfig;
|
||||
@@ -13,6 +15,7 @@ import com.admin.mapper.TunnelMapper;
|
||||
import com.admin.service.NodeService;
|
||||
import com.admin.service.TunnelService;
|
||||
import com.admin.service.ViteConfigService;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
@@ -22,6 +25,8 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
/**
|
||||
@@ -114,10 +119,34 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
|
||||
@Override
|
||||
public R updateNode(NodeUpdateDto nodeUpdateDto) {
|
||||
// 1. 验证节点是否存在
|
||||
if (!isNodeExists(nodeUpdateDto.getId())) {
|
||||
Node node = this.getById(nodeUpdateDto.getId());
|
||||
if (node == null) {
|
||||
return R.err(ERROR_NODE_NOT_FOUND);
|
||||
}
|
||||
|
||||
//1.1 如果节点在线 且传入更新的 http/tls/socks 任意一项与数据库不一致,则通过 WS 通知节点更新设置
|
||||
boolean online = node.getStatus() != null && node.getStatus() == 1;
|
||||
Integer newHttp = nodeUpdateDto.getHttp();
|
||||
Integer newTls = nodeUpdateDto.getTls();
|
||||
Integer newSocks = nodeUpdateDto.getSocks();
|
||||
|
||||
boolean httpChanged = newHttp != null && !newHttp.equals(node.getHttp());
|
||||
boolean tlsChanged = newTls != null && !newTls.equals(node.getTls());
|
||||
boolean socksChanged = newSocks != null && !newSocks.equals(node.getSocks());
|
||||
|
||||
if (online && (httpChanged || tlsChanged || socksChanged)) {
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("http", newHttp);
|
||||
req.put("tls", newTls);
|
||||
req.put("socks", newSocks);
|
||||
|
||||
GostDto gostResult = WebSocketServer.send_msg(node.getId(), req, "SetProtocol");
|
||||
if (!Objects.equals(gostResult.getMsg(), "OK")){
|
||||
return R.err(gostResult.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 2. 构建更新对象并执行更新
|
||||
Node updateNode = buildUpdateNode(nodeUpdateDto);
|
||||
boolean result = this.updateById(updateNode);
|
||||
@@ -153,7 +182,8 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
|
||||
@Override
|
||||
public R deleteNode(Long id) {
|
||||
// 1. 验证节点是否存在
|
||||
if (!isNodeExists(id)) {
|
||||
Node node = this.getById(id);
|
||||
if (node == null) {
|
||||
return R.err(ERROR_NODE_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -225,7 +255,9 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
|
||||
node.setServerIp(nodeUpdateDto.getServerIp());
|
||||
node.setPortSta(nodeUpdateDto.getPortSta());
|
||||
node.setPortEnd(nodeUpdateDto.getPortEnd());
|
||||
|
||||
node.setHttp(nodeUpdateDto.getHttp());
|
||||
node.setTls(nodeUpdateDto.getTls());
|
||||
node.setSocks(nodeUpdateDto.getSocks());
|
||||
// 验证端口范围
|
||||
validatePortRange(node.getPortSta(), node.getPortEnd());
|
||||
|
||||
@@ -242,15 +274,6 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
|
||||
nodeList.forEach(node -> node.setSecret(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查节点是否存在
|
||||
*
|
||||
* @param nodeId 节点ID
|
||||
* @return 节点是否存在
|
||||
*/
|
||||
private boolean isNodeExists(Long nodeId) {
|
||||
return this.getById(nodeId) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查节点使用情况
|
||||
|
||||
@@ -1 +1 @@
|
||||
VITE_API_BASE=http://192.168.100.11:6365
|
||||
VITE_API_BASE=http://127.0.0.1:6365
|
||||
|
||||
@@ -4,7 +4,7 @@ export type SiteConfig = typeof siteConfig;
|
||||
|
||||
// 缓存相关常量
|
||||
const CACHE_PREFIX = 'vite_config_';
|
||||
const VERSION = "1.4.0";
|
||||
const VERSION = "1.4.1";
|
||||
const APP_VERSION = "1.0.3";
|
||||
|
||||
const getInitialConfig = () => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Input } from "@heroui/input";
|
||||
import { Textarea } from "@heroui/input";
|
||||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/modal";
|
||||
import { Chip } from "@heroui/chip";
|
||||
import { Switch } from "@heroui/switch";
|
||||
import { Spinner } from "@heroui/spinner";
|
||||
import { Alert } from "@heroui/alert";
|
||||
import { Progress } from "@heroui/progress";
|
||||
@@ -28,6 +29,9 @@ interface Node {
|
||||
portSta: number;
|
||||
portEnd: number;
|
||||
version?: string;
|
||||
http?: number; // 0 关 1 开
|
||||
tls?: number; // 0 关 1 开
|
||||
socks?: number; // 0 关 1 开
|
||||
status: number; // 1: 在线, 0: 离线
|
||||
connectionStatus: 'online' | 'offline';
|
||||
systemInfo?: {
|
||||
@@ -49,6 +53,9 @@ interface NodeForm {
|
||||
serverIp: string;
|
||||
portSta: number;
|
||||
portEnd: number;
|
||||
http: number; // 0 关 1 开
|
||||
tls: number; // 0 关 1 开
|
||||
socks: number; // 0 关 1 开
|
||||
}
|
||||
|
||||
export default function NodePage() {
|
||||
@@ -61,13 +68,18 @@ export default function NodePage() {
|
||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||
const [nodeToDelete, setNodeToDelete] = useState<Node | null>(null);
|
||||
const [protocolDisabled, setProtocolDisabled] = useState(false);
|
||||
const [protocolDisabledReason, setProtocolDisabledReason] = useState('');
|
||||
const [form, setForm] = useState<NodeForm>({
|
||||
id: null,
|
||||
name: '',
|
||||
ipString: '',
|
||||
serverIp: '',
|
||||
portSta: 1000,
|
||||
portEnd: 65535
|
||||
portEnd: 65535,
|
||||
http: 0,
|
||||
tls: 0,
|
||||
socks: 0
|
||||
});
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
@@ -404,6 +416,8 @@ export default function NodePage() {
|
||||
setIsEdit(false);
|
||||
setDialogVisible(true);
|
||||
resetForm();
|
||||
setProtocolDisabled(true);
|
||||
setProtocolDisabledReason('节点未在线,等待节点上线后再设置');
|
||||
};
|
||||
|
||||
// 编辑节点
|
||||
@@ -416,8 +430,14 @@ export default function NodePage() {
|
||||
ipString: node.ip ? node.ip.split(',').map(ip => ip.trim()).join('\n') : '',
|
||||
serverIp: node.serverIp || '',
|
||||
portSta: node.portSta,
|
||||
portEnd: node.portEnd
|
||||
portEnd: node.portEnd,
|
||||
http: typeof node.http === 'number' ? node.http : 1,
|
||||
tls: typeof node.tls === 'number' ? node.tls : 1,
|
||||
socks: typeof node.socks === 'number' ? node.socks : 1
|
||||
});
|
||||
const offline = node.connectionStatus !== 'online';
|
||||
setProtocolDisabled(offline);
|
||||
setProtocolDisabledReason(offline ? '节点未在线,等待节点上线后再设置' : '');
|
||||
setDialogVisible(true);
|
||||
};
|
||||
|
||||
@@ -514,7 +534,10 @@ export default function NodePage() {
|
||||
ip: ipString,
|
||||
serverIp: form.serverIp,
|
||||
portSta: form.portSta,
|
||||
portEnd: form.portEnd
|
||||
portEnd: form.portEnd,
|
||||
http: form.http,
|
||||
tls: form.tls,
|
||||
socks: form.socks
|
||||
};
|
||||
|
||||
const res = await apiCall(data);
|
||||
@@ -530,7 +553,10 @@ export default function NodePage() {
|
||||
ip: ipString,
|
||||
serverIp: form.serverIp,
|
||||
portSta: form.portSta,
|
||||
portEnd: form.portEnd
|
||||
portEnd: form.portEnd,
|
||||
http: form.http,
|
||||
tls: form.tls,
|
||||
socks: form.socks
|
||||
} : n
|
||||
));
|
||||
} else {
|
||||
@@ -554,7 +580,10 @@ export default function NodePage() {
|
||||
ipString: '',
|
||||
serverIp: '',
|
||||
portSta: 1000,
|
||||
portEnd: 65535
|
||||
portEnd: 65535,
|
||||
http: 0,
|
||||
tls: 0,
|
||||
socks: 0
|
||||
});
|
||||
setErrors({});
|
||||
};
|
||||
@@ -869,9 +898,84 @@ export default function NodePage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 屏蔽协议 */}
|
||||
<div className="mt-1">
|
||||
<div className="text-sm font-medium text-default-700">屏蔽协议</div>
|
||||
<div className="text-xs text-default-500 mb-2">开启开关以屏蔽对应协议</div>
|
||||
{protocolDisabled && (
|
||||
<Alert
|
||||
color="warning"
|
||||
variant="flat"
|
||||
description={protocolDisabledReason || '等待节点上线后再设置'}
|
||||
className="mb-2"
|
||||
/>
|
||||
)}
|
||||
<div className={`grid grid-cols-1 sm:grid-cols-3 gap-3 bg-default-50 dark:bg-default-100 p-3 rounded-md border border-default-200 dark:border-default-100/30 ${protocolDisabled ? 'opacity-70' : ''}`}>
|
||||
{/* HTTP tile */}
|
||||
<div className="px-3 py-3 rounded-lg bg-white dark:bg-default-50 border border-default-200 dark:border-default-100/30 hover:border-primary-200 transition-colors">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<svg className="w-4 h-4 text-default-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M2 10h20"/></svg>
|
||||
<div className="text-sm font-medium text-default-700">HTTP</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-xs text-default-500">禁用/启用</div>
|
||||
<Switch
|
||||
size="sm"
|
||||
isSelected={form.http === 1}
|
||||
isDisabled={protocolDisabled}
|
||||
onValueChange={(v) => setForm(prev => ({ ...prev, http: v ? 1 : 0 }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-default-400">{form.http === 1 ? '已开启' : '已关闭'}</div>
|
||||
</div>
|
||||
|
||||
{/* TLS tile */}
|
||||
<div className="px-3 py-3 rounded-lg bg-white dark:bg-default-50 border border-default-200 dark:border-default-100/30 hover:border-primary-200 transition-colors">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<svg className="w-4 h-4 text-default-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M6 10V7a6 6 0 1 1 12 0v3"/><rect x="4" y="10" width="16" height="10" rx="2"/></svg>
|
||||
<div className="text-sm font-medium text-default-700">TLS</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-xs text-default-500">禁用/启用</div>
|
||||
<Switch
|
||||
size="sm"
|
||||
isSelected={form.tls === 1}
|
||||
isDisabled={protocolDisabled}
|
||||
onValueChange={(v) => setForm(prev => ({ ...prev, tls: v ? 1 : 0 }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-default-400">{form.tls === 1 ? '已开启' : '已关闭'}</div>
|
||||
</div>
|
||||
|
||||
{/* SOCKS tile */}
|
||||
<div className="px-3 py-3 rounded-lg bg-white dark:bg-default-50 border border-default-200 dark:border-default-100/30 hover:border-primary-200 transition-colors">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<svg className="w-4 h-4 text-default-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||
<div className="text-sm font-medium text-default-700">SOCKS</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-xs text-default-500">禁用/启用</div>
|
||||
<Switch
|
||||
size="sm"
|
||||
isSelected={form.socks === 1}
|
||||
isDisabled={protocolDisabled}
|
||||
onValueChange={(v) => setForm(prev => ({ ...prev, socks: v ? 1 : 0 }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-default-400">{form.socks === 1 ? '已开启' : '已关闭'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<Alert
|
||||
color="danger"
|
||||
variant="flat"
|
||||
description="请不要在出口节点执行屏蔽协议,否则可能影响转发;屏蔽协议仅需在入口节点执行。"
|
||||
className="mt-3"
|
||||
/>
|
||||
|
||||
<Alert
|
||||
color="primary"
|
||||
variant="flat"
|
||||
|
||||
Reference in New Issue
Block a user