mirror of
https://github.com/VaalaCat/frp-panel.git
synced 2025-09-27 03:36:10 +08:00
feat: frps can have multi endpoint
This commit is contained in:
@@ -73,6 +73,7 @@ func GetClientHandler(ctx *app.Context, req *pb.GetClientRequest) (*pb.GetClient
|
|||||||
ServerId: lo.ToPtr(client.ServerID),
|
ServerId: lo.ToPtr(client.ServerID),
|
||||||
Stopped: lo.ToPtr(client.Stopped),
|
Stopped: lo.ToPtr(client.Stopped),
|
||||||
Comment: lo.ToPtr(client.Comment),
|
Comment: lo.ToPtr(client.Comment),
|
||||||
|
FrpsUrl: lo.ToPtr(client.FRPsUrl),
|
||||||
ClientIds: nil,
|
ClientIds: nil,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/VaalaCat/frp-panel/common"
|
"github.com/VaalaCat/frp-panel/common"
|
||||||
"github.com/VaalaCat/frp-panel/models"
|
"github.com/VaalaCat/frp-panel/models"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"github.com/VaalaCat/frp-panel/services/dao"
|
"github.com/VaalaCat/frp-panel/services/dao"
|
||||||
"github.com/VaalaCat/frp-panel/utils/logger"
|
"github.com/VaalaCat/frp-panel/utils/logger"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
"github.com/spf13/cast"
|
||||||
"github.com/tiendc/go-deepcopy"
|
"github.com/tiendc/go-deepcopy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -113,3 +115,34 @@ func ChildClientForServer(c *app.Context, serverID string, clientEntity *models.
|
|||||||
|
|
||||||
return copiedClient, nil
|
return copiedClient, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidFrpsScheme(scheme string) bool {
|
||||||
|
return scheme == "tcp" ||
|
||||||
|
scheme == "kcp" || scheme == "quic" ||
|
||||||
|
scheme == "websocket" || scheme == "wss"
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateFrpsUrl(urlStr string) (*url.URL, error) {
|
||||||
|
parsedFrpsUrl, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse frps url error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ValidFrpsScheme(parsedFrpsUrl.Scheme) {
|
||||||
|
return nil, fmt.Errorf("invalid frps scheme")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parsedFrpsUrl.Host) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid frps host")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parsedFrpsUrl.Hostname()) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid frps hostname")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cast.ToInt(parsedFrpsUrl.Port()) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid frps port")
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedFrpsUrl, nil
|
||||||
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/VaalaCat/frp-panel/common"
|
"github.com/VaalaCat/frp-panel/common"
|
||||||
"github.com/VaalaCat/frp-panel/defs"
|
"github.com/VaalaCat/frp-panel/defs"
|
||||||
@@ -16,6 +17,7 @@ import (
|
|||||||
"github.com/VaalaCat/frp-panel/utils/logger"
|
"github.com/VaalaCat/frp-panel/utils/logger"
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
"github.com/spf13/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UpdateFrpcHander(c *app.Context, req *pb.UpdateFRPCRequest) (*pb.UpdateFRPCResponse, error) {
|
func UpdateFrpcHander(c *app.Context, req *pb.UpdateFRPCRequest) (*pb.UpdateFRPCResponse, error) {
|
||||||
@@ -77,6 +79,13 @@ func UpdateFrpcHander(c *app.Context, req *pb.UpdateFRPCRequest) (*pb.UpdateFRPC
|
|||||||
}, fmt.Errorf("cannot get server")
|
}, fmt.Errorf("cannot get server")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(srv.ConfigContent) == 0 {
|
||||||
|
logger.Logger(c).Errorf("cannot get server, server is not prepared, id: [%s]", req.GetServerId())
|
||||||
|
return &pb.UpdateFRPCResponse{
|
||||||
|
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_INVALID, Message: "server is not prepared, pls update server config first"},
|
||||||
|
}, fmt.Errorf("server is not prepared, pls update server config first")
|
||||||
|
}
|
||||||
|
|
||||||
srvConf, err := srv.GetConfigContent()
|
srvConf, err := srv.GetConfigContent()
|
||||||
if srvConf == nil || err != nil {
|
if srvConf == nil || err != nil {
|
||||||
logger.Logger(c).WithError(err).Errorf("cannot get server, id: [%s]", serverID)
|
logger.Logger(c).WithError(err).Errorf("cannot get server, id: [%s]", serverID)
|
||||||
@@ -85,8 +94,6 @@ func UpdateFrpcHander(c *app.Context, req *pb.UpdateFRPCRequest) (*pb.UpdateFRPC
|
|||||||
|
|
||||||
cliCfg.ServerAddr = srv.ServerIP
|
cliCfg.ServerAddr = srv.ServerIP
|
||||||
switch cliCfg.Transport.Protocol {
|
switch cliCfg.Transport.Protocol {
|
||||||
case "tcp":
|
|
||||||
cliCfg.ServerPort = srvConf.BindPort
|
|
||||||
case "kcp":
|
case "kcp":
|
||||||
cliCfg.ServerPort = srvConf.KCPBindPort
|
cliCfg.ServerPort = srvConf.KCPBindPort
|
||||||
case "quic":
|
case "quic":
|
||||||
@@ -95,6 +102,41 @@ func UpdateFrpcHander(c *app.Context, req *pb.UpdateFRPCRequest) (*pb.UpdateFRPC
|
|||||||
cliCfg.ServerPort = srvConf.BindPort
|
cliCfg.ServerPort = srvConf.BindPort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(req.GetFrpsUrl()) > 0 || len(cli.FRPsUrl) > 0 {
|
||||||
|
// 有一个有就需要覆盖,优先请求的url
|
||||||
|
var (
|
||||||
|
parsedFrpsUrl *url.URL
|
||||||
|
err error
|
||||||
|
urlToParse string
|
||||||
|
)
|
||||||
|
if len(req.GetFrpsUrl()) > 0 {
|
||||||
|
parsedFrpsUrl, err = ValidateFrpsUrl(req.GetFrpsUrl())
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(c).WithError(err).Errorf("invalid frps url, url: [%s]", req.GetFrpsUrl())
|
||||||
|
return &pb.UpdateFRPCResponse{
|
||||||
|
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_INVALID, Message: err.Error()},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
urlToParse = req.GetFrpsUrl()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cli.FRPsUrl) > 0 && parsedFrpsUrl == nil {
|
||||||
|
parsedFrpsUrl, err = ValidateFrpsUrl(cli.FRPsUrl)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(c).WithError(err).Errorf("invalid old frps url, url: [%s]", cli.FRPsUrl)
|
||||||
|
return &pb.UpdateFRPCResponse{
|
||||||
|
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_INVALID, Message: err.Error()},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
urlToParse = cli.FRPsUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
cliCfg.ServerAddr = parsedFrpsUrl.Hostname()
|
||||||
|
cliCfg.ServerPort = cast.ToInt(parsedFrpsUrl.Port())
|
||||||
|
cliCfg.Transport.Protocol = parsedFrpsUrl.Scheme
|
||||||
|
cli.FRPsUrl = urlToParse
|
||||||
|
}
|
||||||
|
|
||||||
cliCfg.User = userInfo.GetUserName()
|
cliCfg.User = userInfo.GetUserName()
|
||||||
|
|
||||||
if cliCfg.Metadatas == nil {
|
if cliCfg.Metadatas == nil {
|
||||||
|
@@ -34,11 +34,12 @@ func GetServerHandler(c *app.Context, req *pb.GetServerRequest) (*pb.GetServerRe
|
|||||||
return &pb.GetServerResponse{
|
return &pb.GetServerResponse{
|
||||||
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"},
|
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"},
|
||||||
Server: &pb.Server{
|
Server: &pb.Server{
|
||||||
Id: lo.ToPtr(serverEntity.ServerID),
|
Id: lo.ToPtr(serverEntity.ServerID),
|
||||||
Config: lo.ToPtr(string(serverEntity.ConfigContent)),
|
Config: lo.ToPtr(string(serverEntity.ConfigContent)),
|
||||||
Secret: lo.ToPtr(serverEntity.ConnectSecret),
|
Secret: lo.ToPtr(serverEntity.ConnectSecret),
|
||||||
Comment: lo.ToPtr(serverEntity.Comment),
|
Comment: lo.ToPtr(serverEntity.Comment),
|
||||||
Ip: lo.ToPtr(serverEntity.ServerIP),
|
Ip: lo.ToPtr(serverEntity.ServerIP),
|
||||||
|
FrpsUrls: serverEntity.FRPsUrls,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@@ -49,11 +49,12 @@ func ListServersHandler(c *app.Context, req *pb.ListServersRequest) (*pb.ListSer
|
|||||||
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"},
|
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"},
|
||||||
Servers: lo.Map(servers, func(c *models.ServerEntity, _ int) *pb.Server {
|
Servers: lo.Map(servers, func(c *models.ServerEntity, _ int) *pb.Server {
|
||||||
return &pb.Server{
|
return &pb.Server{
|
||||||
Id: lo.ToPtr(c.ServerID),
|
Id: lo.ToPtr(c.ServerID),
|
||||||
Config: lo.ToPtr(string(c.ConfigContent)),
|
Config: lo.ToPtr(string(c.ConfigContent)),
|
||||||
Secret: lo.ToPtr(c.ConnectSecret),
|
Secret: lo.ToPtr(c.ConnectSecret),
|
||||||
Ip: lo.ToPtr(c.ServerIP),
|
Ip: lo.ToPtr(c.ServerIP),
|
||||||
Comment: lo.ToPtr(c.Comment),
|
Comment: lo.ToPtr(c.Comment),
|
||||||
|
FrpsUrls: c.FRPsUrls,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Total: lo.ToPtr(int32(serverCounts)),
|
Total: lo.ToPtr(int32(serverCounts)),
|
||||||
|
@@ -52,6 +52,10 @@ func UpdateFrpsHander(c *app.Context, req *pb.UpdateFRPSRequest) (*pb.UpdateFRPS
|
|||||||
srv.ServerIP = req.GetServerIp()
|
srv.ServerIP = req.GetServerIp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(req.GetFrpsUrls()) > 0 {
|
||||||
|
srv.FRPsUrls = req.GetFrpsUrls()
|
||||||
|
}
|
||||||
|
|
||||||
if err := dao.NewQuery(c).UpdateServer(userInfo, srv); err != nil {
|
if err := dao.NewQuery(c).UpdateServer(userInfo, srv); err != nil {
|
||||||
logger.Logger(context.Background()).WithError(err).Errorf("cannot update server, id: [%s]", serverID)
|
logger.Logger(context.Background()).WithError(err).Errorf("cannot update server, id: [%s]", serverID)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -35,6 +35,11 @@ type CommonArgs struct {
|
|||||||
func buildCommand() *cobra.Command {
|
func buildCommand() *cobra.Command {
|
||||||
cfg := conf.NewConfig()
|
cfg := conf.NewConfig()
|
||||||
|
|
||||||
|
logger.UpdateLoggerOpt(
|
||||||
|
cfg.Logger.FRPLoggerLevel,
|
||||||
|
cfg.Logger.DefaultLoggerLevel,
|
||||||
|
)
|
||||||
|
|
||||||
return NewRootCmd(
|
return NewRootCmd(
|
||||||
NewMasterCmd(cfg),
|
NewMasterCmd(cfg),
|
||||||
NewClientCmd(cfg),
|
NewClientCmd(cfg),
|
||||||
@@ -392,7 +397,7 @@ func patchConfig(appInstance app.Application, commonArgs CommonArgs) conf.Config
|
|||||||
|
|
||||||
func warnDepParam(cmd *cobra.Command) {
|
func warnDepParam(cmd *cobra.Command) {
|
||||||
if appSecret, _ := cmd.Flags().GetString("app"); len(appSecret) != 0 {
|
if appSecret, _ := cmd.Flags().GetString("app"); len(appSecret) != 0 {
|
||||||
logger.Logger(context.Background()).Fatalf(
|
logger.Logger(context.Background()).Errorf(
|
||||||
"\n⚠️\n\n-a / -app / APP_SECRET 参数已停止使用,请删除该参数重新启动\n\n" +
|
"\n⚠️\n\n-a / -app / APP_SECRET 参数已停止使用,请删除该参数重新启动\n\n" +
|
||||||
"The -a / -app / APP_SECRET parameter is deprecated. Please remove it and restart.\n\n")
|
"The -a / -app / APP_SECRET parameter is deprecated. Please remove it and restart.\n\n")
|
||||||
}
|
}
|
||||||
|
@@ -53,6 +53,10 @@ type Config struct {
|
|||||||
TLSInsecureSkipVerify bool `env:"TLS_INSECURE_SKIP_VERIFY" env-default:"true" env-description:"skip tls verify"`
|
TLSInsecureSkipVerify bool `env:"TLS_INSECURE_SKIP_VERIFY" env-default:"true" env-description:"skip tls verify"`
|
||||||
} `env-prefix:"CLIENT_"`
|
} `env-prefix:"CLIENT_"`
|
||||||
IsDebug bool `env:"IS_DEBUG" env-default:"false" env-description:"is debug mode"`
|
IsDebug bool `env:"IS_DEBUG" env-default:"false" env-description:"is debug mode"`
|
||||||
|
Logger struct {
|
||||||
|
DefaultLoggerLevel string `env:"DEFAULT_LOGGER_LEVEL" env-default:"info" env-description:"frp-panel internal default logger level"`
|
||||||
|
FRPLoggerLevel string `env:"FRP_LOGGER_LEVEL" env-default:"info" env-description:"frp logger level"`
|
||||||
|
} `env-prefix:"LOGGER_"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig() Config {
|
func NewConfig() Config {
|
||||||
|
3
go.mod
3
go.mod
@@ -27,8 +27,10 @@ require (
|
|||||||
github.com/shirou/gopsutil/v4 v4.24.11
|
github.com/shirou/gopsutil/v4 v4.24.11
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/sourcegraph/conc v0.3.0
|
github.com/sourcegraph/conc v0.3.0
|
||||||
|
github.com/spf13/cast v1.7.1
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
|
github.com/tidwall/pretty v1.2.1
|
||||||
github.com/tiendc/go-deepcopy v1.2.0
|
github.com/tiendc/go-deepcopy v1.2.0
|
||||||
go.uber.org/fx v1.23.0
|
go.uber.org/fx v1.23.0
|
||||||
golang.org/x/crypto v0.37.0
|
golang.org/x/crypto v0.37.0
|
||||||
@@ -114,7 +116,6 @@ require (
|
|||||||
github.com/stretchr/testify v1.10.0 // indirect
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
github.com/templexxx/cpu v0.1.1 // indirect
|
github.com/templexxx/cpu v0.1.1 // indirect
|
||||||
github.com/templexxx/xorsimd v0.4.3 // indirect
|
github.com/templexxx/xorsimd v0.4.3 // indirect
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
|
||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
|
4
go.sum
4
go.sum
@@ -49,6 +49,8 @@ github.com/fatedier/frp v0.62.0 h1:0ti+kNLCRcs8TmFyxUKiXuyCDRF+hkuh7MI3yrhGcEs=
|
|||||||
github.com/fatedier/frp v0.62.0/go.mod h1:Kfctx9dDnV+8vr2BYfwmKIbNPSbgyvIcWsVSkHWq2jM=
|
github.com/fatedier/frp v0.62.0/go.mod h1:Kfctx9dDnV+8vr2BYfwmKIbNPSbgyvIcWsVSkHWq2jM=
|
||||||
github.com/fatedier/golib v0.5.1 h1:hcKAnaw5mdI/1KWRGejxR+i1Hn/NvbY5UsMKDr7o13M=
|
github.com/fatedier/golib v0.5.1 h1:hcKAnaw5mdI/1KWRGejxR+i1Hn/NvbY5UsMKDr7o13M=
|
||||||
github.com/fatedier/golib v0.5.1/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ=
|
github.com/fatedier/golib v0.5.1/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
@@ -242,6 +244,8 @@ github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D
|
|||||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
|
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||||
|
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
@@ -49,6 +49,7 @@ message UpdateFRPCRequest {
|
|||||||
optional string server_id = 2;
|
optional string server_id = 2;
|
||||||
optional bytes config = 3;
|
optional bytes config = 3;
|
||||||
optional string comment = 4;
|
optional string comment = 4;
|
||||||
|
optional string frps_url = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateFRPCResponse {
|
message UpdateFRPCResponse {
|
||||||
|
@@ -49,6 +49,7 @@ message UpdateFRPSRequest {
|
|||||||
optional bytes config = 2;
|
optional bytes config = 2;
|
||||||
optional string comment = 3;
|
optional string comment = 3;
|
||||||
optional string server_ip = 4;
|
optional string server_ip = 4;
|
||||||
|
repeated string frps_urls = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateFRPSResponse {
|
message UpdateFRPSResponse {
|
||||||
|
@@ -42,6 +42,7 @@ message Client {
|
|||||||
optional bool stopped = 7;
|
optional bool stopped = 7;
|
||||||
repeated string client_ids = 8; // some client can connected to more than one server, make a shadow client to handle this
|
repeated string client_ids = 8; // some client can connected to more than one server, make a shadow client to handle this
|
||||||
optional string origin_client_id = 9;
|
optional string origin_client_id = 9;
|
||||||
|
optional string frps_url = 10; // 客户端用于连接frps的url,解决 frp 在 CDN 后的问题,格式类似 [tcp/ws/wss/quic/kcp]://example.com:7000
|
||||||
}
|
}
|
||||||
|
|
||||||
message Server {
|
message Server {
|
||||||
@@ -50,6 +51,7 @@ message Server {
|
|||||||
optional string ip = 3;
|
optional string ip = 3;
|
||||||
optional string config = 4; // 在定义上,ip和port只是为了方便使用
|
optional string config = 4; // 在定义上,ip和port只是为了方便使用
|
||||||
optional string comment = 5; // 用户自定义的备注
|
optional string comment = 5; // 用户自定义的备注
|
||||||
|
repeated string frps_urls = 6; // 客户端用于连接frps的url,解决 frp 在 CDN 后的问题,格式类似 [tcp/ws/wss/quic/kcp]://example.com:7000,可以有多个
|
||||||
}
|
}
|
||||||
|
|
||||||
message User {
|
message User {
|
||||||
|
@@ -25,6 +25,7 @@ type ClientEntity struct {
|
|||||||
Comment string `json:"comment"`
|
Comment string `json:"comment"`
|
||||||
IsShadow bool `json:"is_shadow" gorm:"index"`
|
IsShadow bool `json:"is_shadow" gorm:"index"`
|
||||||
OriginClientID string `json:"origin_client_id" gorm:"index"`
|
OriginClientID string `json:"origin_client_id" gorm:"index"`
|
||||||
|
FRPsUrl string `json:"frps_url" gorm:"index"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
@@ -15,13 +15,14 @@ type Server struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ServerEntity struct {
|
type ServerEntity struct {
|
||||||
ServerID string `json:"client_id" gorm:"uniqueIndex;not null;primaryKey"`
|
ServerID string `json:"client_id" gorm:"uniqueIndex;not null;primaryKey"`
|
||||||
TenantID int `json:"tenant_id" gorm:"not null"`
|
TenantID int `json:"tenant_id" gorm:"not null"`
|
||||||
UserID int `json:"user_id" gorm:"not null"`
|
UserID int `json:"user_id" gorm:"not null"`
|
||||||
ServerIP string `json:"server_ip"`
|
ServerIP string `json:"server_ip"`
|
||||||
ConfigContent []byte `json:"config_content"`
|
ConfigContent []byte `json:"config_content"`
|
||||||
ConnectSecret string `json:"connect_secret" gorm:"not null"`
|
ConnectSecret string `json:"connect_secret" gorm:"not null"`
|
||||||
Comment string `json:"comment"`
|
Comment string `json:"comment"`
|
||||||
|
FRPsUrls GormArray[string] `json:"frps_urls"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
28
models/types.go
Normal file
28
models/types.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GormArray[T any] []T
|
||||||
|
|
||||||
|
func (p GormArray[T]) Value() (driver.Value, error) {
|
||||||
|
return json.Marshal(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GormArray[T]) Scan(data interface{}) error {
|
||||||
|
return json.Unmarshal(data.([]byte), &p)
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSON[T any] struct {
|
||||||
|
Data T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j JSON[T]) Value() (driver.Value, error) {
|
||||||
|
return json.Marshal(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JSON[T]) Scan(value interface{}) error {
|
||||||
|
return json.Unmarshal(value.([]byte), &j)
|
||||||
|
}
|
@@ -435,6 +435,7 @@ type UpdateFRPCRequest struct {
|
|||||||
ServerId *string `protobuf:"bytes,2,opt,name=server_id,json=serverId,proto3,oneof" json:"server_id,omitempty"`
|
ServerId *string `protobuf:"bytes,2,opt,name=server_id,json=serverId,proto3,oneof" json:"server_id,omitempty"`
|
||||||
Config []byte `protobuf:"bytes,3,opt,name=config,proto3,oneof" json:"config,omitempty"`
|
Config []byte `protobuf:"bytes,3,opt,name=config,proto3,oneof" json:"config,omitempty"`
|
||||||
Comment *string `protobuf:"bytes,4,opt,name=comment,proto3,oneof" json:"comment,omitempty"`
|
Comment *string `protobuf:"bytes,4,opt,name=comment,proto3,oneof" json:"comment,omitempty"`
|
||||||
|
FrpsUrl *string `protobuf:"bytes,5,opt,name=frps_url,json=frpsUrl,proto3,oneof" json:"frps_url,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -497,6 +498,13 @@ func (x *UpdateFRPCRequest) GetComment() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *UpdateFRPCRequest) GetFrpsUrl() string {
|
||||||
|
if x != nil && x.FrpsUrl != nil {
|
||||||
|
return *x.FrpsUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateFRPCResponse struct {
|
type UpdateFRPCResponse struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Status *Status `protobuf:"bytes,1,opt,name=status,proto3,oneof" json:"status,omitempty"`
|
Status *Status `protobuf:"bytes,1,opt,name=status,proto3,oneof" json:"status,omitempty"`
|
||||||
@@ -1534,19 +1542,21 @@ const file_api_client_proto_rawDesc = "" +
|
|||||||
"_client_id\"N\n" +
|
"_client_id\"N\n" +
|
||||||
"\x14DeleteClientResponse\x12+\n" +
|
"\x14DeleteClientResponse\x12+\n" +
|
||||||
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" +
|
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" +
|
||||||
"\a_status\"\xc6\x01\n" +
|
"\a_status\"\xf3\x01\n" +
|
||||||
"\x11UpdateFRPCRequest\x12 \n" +
|
"\x11UpdateFRPCRequest\x12 \n" +
|
||||||
"\tclient_id\x18\x01 \x01(\tH\x00R\bclientId\x88\x01\x01\x12 \n" +
|
"\tclient_id\x18\x01 \x01(\tH\x00R\bclientId\x88\x01\x01\x12 \n" +
|
||||||
"\tserver_id\x18\x02 \x01(\tH\x01R\bserverId\x88\x01\x01\x12\x1b\n" +
|
"\tserver_id\x18\x02 \x01(\tH\x01R\bserverId\x88\x01\x01\x12\x1b\n" +
|
||||||
"\x06config\x18\x03 \x01(\fH\x02R\x06config\x88\x01\x01\x12\x1d\n" +
|
"\x06config\x18\x03 \x01(\fH\x02R\x06config\x88\x01\x01\x12\x1d\n" +
|
||||||
"\acomment\x18\x04 \x01(\tH\x03R\acomment\x88\x01\x01B\f\n" +
|
"\acomment\x18\x04 \x01(\tH\x03R\acomment\x88\x01\x01\x12\x1e\n" +
|
||||||
|
"\bfrps_url\x18\x05 \x01(\tH\x04R\afrpsUrl\x88\x01\x01B\f\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"_client_idB\f\n" +
|
"_client_idB\f\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"_server_idB\t\n" +
|
"_server_idB\t\n" +
|
||||||
"\a_configB\n" +
|
"\a_configB\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\b_comment\"L\n" +
|
"\b_commentB\v\n" +
|
||||||
|
"\t_frps_url\"L\n" +
|
||||||
"\x12UpdateFRPCResponse\x12+\n" +
|
"\x12UpdateFRPCResponse\x12+\n" +
|
||||||
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" +
|
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" +
|
||||||
"\a_status\"C\n" +
|
"\a_status\"C\n" +
|
||||||
|
@@ -443,6 +443,7 @@ type UpdateFRPSRequest struct {
|
|||||||
Config []byte `protobuf:"bytes,2,opt,name=config,proto3,oneof" json:"config,omitempty"`
|
Config []byte `protobuf:"bytes,2,opt,name=config,proto3,oneof" json:"config,omitempty"`
|
||||||
Comment *string `protobuf:"bytes,3,opt,name=comment,proto3,oneof" json:"comment,omitempty"`
|
Comment *string `protobuf:"bytes,3,opt,name=comment,proto3,oneof" json:"comment,omitempty"`
|
||||||
ServerIp *string `protobuf:"bytes,4,opt,name=server_ip,json=serverIp,proto3,oneof" json:"server_ip,omitempty"`
|
ServerIp *string `protobuf:"bytes,4,opt,name=server_ip,json=serverIp,proto3,oneof" json:"server_ip,omitempty"`
|
||||||
|
FrpsUrls []string `protobuf:"bytes,5,rep,name=frps_urls,json=frpsUrls,proto3" json:"frps_urls,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -505,6 +506,13 @@ func (x *UpdateFRPSRequest) GetServerIp() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *UpdateFRPSRequest) GetFrpsUrls() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.FrpsUrls
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateFRPSResponse struct {
|
type UpdateFRPSResponse struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Status *Status `protobuf:"bytes,1,opt,name=status,proto3,oneof" json:"status,omitempty"`
|
Status *Status `protobuf:"bytes,1,opt,name=status,proto3,oneof" json:"status,omitempty"`
|
||||||
@@ -961,12 +969,13 @@ const file_api_server_proto_rawDesc = "" +
|
|||||||
"_server_id\"N\n" +
|
"_server_id\"N\n" +
|
||||||
"\x14DeleteServerResponse\x12+\n" +
|
"\x14DeleteServerResponse\x12+\n" +
|
||||||
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" +
|
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" +
|
||||||
"\a_status\"\xc6\x01\n" +
|
"\a_status\"\xe3\x01\n" +
|
||||||
"\x11UpdateFRPSRequest\x12 \n" +
|
"\x11UpdateFRPSRequest\x12 \n" +
|
||||||
"\tserver_id\x18\x01 \x01(\tH\x00R\bserverId\x88\x01\x01\x12\x1b\n" +
|
"\tserver_id\x18\x01 \x01(\tH\x00R\bserverId\x88\x01\x01\x12\x1b\n" +
|
||||||
"\x06config\x18\x02 \x01(\fH\x01R\x06config\x88\x01\x01\x12\x1d\n" +
|
"\x06config\x18\x02 \x01(\fH\x01R\x06config\x88\x01\x01\x12\x1d\n" +
|
||||||
"\acomment\x18\x03 \x01(\tH\x02R\acomment\x88\x01\x01\x12 \n" +
|
"\acomment\x18\x03 \x01(\tH\x02R\acomment\x88\x01\x01\x12 \n" +
|
||||||
"\tserver_ip\x18\x04 \x01(\tH\x03R\bserverIp\x88\x01\x01B\f\n" +
|
"\tserver_ip\x18\x04 \x01(\tH\x03R\bserverIp\x88\x01\x01\x12\x1b\n" +
|
||||||
|
"\tfrps_urls\x18\x05 \x03(\tR\bfrpsUrlsB\f\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"_server_idB\t\n" +
|
"_server_idB\t\n" +
|
||||||
"\a_configB\n" +
|
"\a_configB\n" +
|
||||||
|
@@ -289,6 +289,7 @@ type Client struct {
|
|||||||
Stopped *bool `protobuf:"varint,7,opt,name=stopped,proto3,oneof" json:"stopped,omitempty"`
|
Stopped *bool `protobuf:"varint,7,opt,name=stopped,proto3,oneof" json:"stopped,omitempty"`
|
||||||
ClientIds []string `protobuf:"bytes,8,rep,name=client_ids,json=clientIds,proto3" json:"client_ids,omitempty"` // some client can connected to more than one server, make a shadow client to handle this
|
ClientIds []string `protobuf:"bytes,8,rep,name=client_ids,json=clientIds,proto3" json:"client_ids,omitempty"` // some client can connected to more than one server, make a shadow client to handle this
|
||||||
OriginClientId *string `protobuf:"bytes,9,opt,name=origin_client_id,json=originClientId,proto3,oneof" json:"origin_client_id,omitempty"`
|
OriginClientId *string `protobuf:"bytes,9,opt,name=origin_client_id,json=originClientId,proto3,oneof" json:"origin_client_id,omitempty"`
|
||||||
|
FrpsUrl *string `protobuf:"bytes,10,opt,name=frps_url,json=frpsUrl,proto3,oneof" json:"frps_url,omitempty"` // 客户端用于连接frps的url,解决 frp 在 CDN 后的问题,格式类似 [tcp/ws/wss/quic/kcp]://example.com:7000
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -379,13 +380,21 @@ func (x *Client) GetOriginClientId() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Client) GetFrpsUrl() string {
|
||||||
|
if x != nil && x.FrpsUrl != nil {
|
||||||
|
return *x.FrpsUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Id *string `protobuf:"bytes,1,opt,name=id,proto3,oneof" json:"id,omitempty"`
|
Id *string `protobuf:"bytes,1,opt,name=id,proto3,oneof" json:"id,omitempty"`
|
||||||
Secret *string `protobuf:"bytes,2,opt,name=secret,proto3,oneof" json:"secret,omitempty"`
|
Secret *string `protobuf:"bytes,2,opt,name=secret,proto3,oneof" json:"secret,omitempty"`
|
||||||
Ip *string `protobuf:"bytes,3,opt,name=ip,proto3,oneof" json:"ip,omitempty"`
|
Ip *string `protobuf:"bytes,3,opt,name=ip,proto3,oneof" json:"ip,omitempty"`
|
||||||
Config *string `protobuf:"bytes,4,opt,name=config,proto3,oneof" json:"config,omitempty"` // 在定义上,ip和port只是为了方便使用
|
Config *string `protobuf:"bytes,4,opt,name=config,proto3,oneof" json:"config,omitempty"` // 在定义上,ip和port只是为了方便使用
|
||||||
Comment *string `protobuf:"bytes,5,opt,name=comment,proto3,oneof" json:"comment,omitempty"` // 用户自定义的备注
|
Comment *string `protobuf:"bytes,5,opt,name=comment,proto3,oneof" json:"comment,omitempty"` // 用户自定义的备注
|
||||||
|
FrpsUrls []string `protobuf:"bytes,6,rep,name=frps_urls,json=frpsUrls,proto3" json:"frps_urls,omitempty"` // 客户端用于连接frps的url,解决 frp 在 CDN 后的问题,格式类似 [tcp/ws/wss/quic/kcp]://example.com:7000,可以有多个
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -455,6 +464,13 @@ func (x *Server) GetComment() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Server) GetFrpsUrls() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.FrpsUrls
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
UserID *int64 `protobuf:"varint,1,opt,name=UserID,proto3,oneof" json:"UserID,omitempty"`
|
UserID *int64 `protobuf:"varint,1,opt,name=UserID,proto3,oneof" json:"UserID,omitempty"`
|
||||||
@@ -846,7 +862,7 @@ const file_common_proto_rawDesc = "" +
|
|||||||
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01\x12\x17\n" +
|
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01\x12\x17\n" +
|
||||||
"\x04data\x18\x02 \x01(\tH\x01R\x04data\x88\x01\x01B\t\n" +
|
"\x04data\x18\x02 \x01(\tH\x01R\x04data\x88\x01\x01B\t\n" +
|
||||||
"\a_statusB\a\n" +
|
"\a_statusB\a\n" +
|
||||||
"\x05_data\"\xdd\x02\n" +
|
"\x05_data\"\x8a\x03\n" +
|
||||||
"\x06Client\x12\x13\n" +
|
"\x06Client\x12\x13\n" +
|
||||||
"\x02id\x18\x01 \x01(\tH\x00R\x02id\x88\x01\x01\x12\x1b\n" +
|
"\x02id\x18\x01 \x01(\tH\x00R\x02id\x88\x01\x01\x12\x1b\n" +
|
||||||
"\x06secret\x18\x02 \x01(\tH\x01R\x06secret\x88\x01\x01\x12\x1b\n" +
|
"\x06secret\x18\x02 \x01(\tH\x01R\x06secret\x88\x01\x01\x12\x1b\n" +
|
||||||
@@ -856,7 +872,9 @@ const file_common_proto_rawDesc = "" +
|
|||||||
"\astopped\x18\a \x01(\bH\x05R\astopped\x88\x01\x01\x12\x1d\n" +
|
"\astopped\x18\a \x01(\bH\x05R\astopped\x88\x01\x01\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"client_ids\x18\b \x03(\tR\tclientIds\x12-\n" +
|
"client_ids\x18\b \x03(\tR\tclientIds\x12-\n" +
|
||||||
"\x10origin_client_id\x18\t \x01(\tH\x06R\x0eoriginClientId\x88\x01\x01B\x05\n" +
|
"\x10origin_client_id\x18\t \x01(\tH\x06R\x0eoriginClientId\x88\x01\x01\x12\x1e\n" +
|
||||||
|
"\bfrps_url\x18\n" +
|
||||||
|
" \x01(\tH\aR\afrpsUrl\x88\x01\x01B\x05\n" +
|
||||||
"\x03_idB\t\n" +
|
"\x03_idB\t\n" +
|
||||||
"\a_secretB\t\n" +
|
"\a_secretB\t\n" +
|
||||||
"\a_configB\n" +
|
"\a_configB\n" +
|
||||||
@@ -866,13 +884,15 @@ const file_common_proto_rawDesc = "" +
|
|||||||
"_server_idB\n" +
|
"_server_idB\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\b_stoppedB\x13\n" +
|
"\b_stoppedB\x13\n" +
|
||||||
"\x11_origin_client_id\"\xbb\x01\n" +
|
"\x11_origin_client_idB\v\n" +
|
||||||
|
"\t_frps_url\"\xd8\x01\n" +
|
||||||
"\x06Server\x12\x13\n" +
|
"\x06Server\x12\x13\n" +
|
||||||
"\x02id\x18\x01 \x01(\tH\x00R\x02id\x88\x01\x01\x12\x1b\n" +
|
"\x02id\x18\x01 \x01(\tH\x00R\x02id\x88\x01\x01\x12\x1b\n" +
|
||||||
"\x06secret\x18\x02 \x01(\tH\x01R\x06secret\x88\x01\x01\x12\x13\n" +
|
"\x06secret\x18\x02 \x01(\tH\x01R\x06secret\x88\x01\x01\x12\x13\n" +
|
||||||
"\x02ip\x18\x03 \x01(\tH\x02R\x02ip\x88\x01\x01\x12\x1b\n" +
|
"\x02ip\x18\x03 \x01(\tH\x02R\x02ip\x88\x01\x01\x12\x1b\n" +
|
||||||
"\x06config\x18\x04 \x01(\tH\x03R\x06config\x88\x01\x01\x12\x1d\n" +
|
"\x06config\x18\x04 \x01(\tH\x03R\x06config\x88\x01\x01\x12\x1d\n" +
|
||||||
"\acomment\x18\x05 \x01(\tH\x04R\acomment\x88\x01\x01B\x05\n" +
|
"\acomment\x18\x05 \x01(\tH\x04R\acomment\x88\x01\x01\x12\x1b\n" +
|
||||||
|
"\tfrps_urls\x18\x06 \x03(\tR\bfrpsUrlsB\x05\n" +
|
||||||
"\x03_idB\t\n" +
|
"\x03_idB\t\n" +
|
||||||
"\a_secretB\x05\n" +
|
"\a_secretB\x05\n" +
|
||||||
"\x03_ipB\t\n" +
|
"\x03_ipB\t\n" +
|
||||||
|
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
|
"github.com/fatedier/frp/pkg/featuregate"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"github.com/sourcegraph/conc"
|
"github.com/sourcegraph/conc"
|
||||||
)
|
)
|
||||||
@@ -32,6 +33,12 @@ func NewClientHandler(commonCfg *v1.ClientCommonConfig,
|
|||||||
visitorCfgs []v1.VisitorConfigurer) app.ClientHandler {
|
visitorCfgs []v1.VisitorConfigurer) app.ClientHandler {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if len(commonCfg.FeatureGates) > 0 {
|
||||||
|
if err := featuregate.SetFromMap(commonCfg.FeatureGates); err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("there's a feature gate settings, but set failed: %+v, skip", commonCfg.FeatureGates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
warning, err := validation.ValidateAllClientConfig(commonCfg, proxyCfgs, visitorCfgs)
|
warning, err := validation.ValidateAllClientConfig(commonCfg, proxyCfgs, visitorCfgs)
|
||||||
if warning != nil {
|
if warning != nil {
|
||||||
logger.Logger(ctx).WithError(err).Warnf("validate client config warning: %+v", warning)
|
logger.Logger(ctx).WithError(err).Warnf("validate client config warning: %+v", warning)
|
||||||
|
@@ -2,6 +2,7 @@ package logger
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -14,17 +15,16 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitFrpLogger() {
|
func initFrpLogger(frpLogLevel log.Level) {
|
||||||
frplog.Logger = log.New(
|
frplog.Logger = log.New(
|
||||||
log.WithCaller(true),
|
log.WithCaller(true),
|
||||||
log.AddCallerSkip(1),
|
log.AddCallerSkip(1),
|
||||||
log.WithLevel(log.InfoLevel),
|
log.WithLevel(frpLogLevel),
|
||||||
log.WithOutput(logger))
|
log.WithOutput(logger))
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitLogger() {
|
func InitLogger() {
|
||||||
// projectRoot, projectPkg, _ := findProjectRootAndModule()
|
// projectRoot, projectPkg, _ := findProjectRootAndModule()
|
||||||
InitFrpLogger()
|
|
||||||
|
|
||||||
Instance().SetReportCaller(true)
|
Instance().SetReportCaller(true)
|
||||||
Instance().SetFormatter(NewCustomFormatter(false, true))
|
Instance().SetFormatter(NewCustomFormatter(false, true))
|
||||||
@@ -34,6 +34,36 @@ func InitLogger() {
|
|||||||
logrus.SetFormatter(NewCustomFormatter(false, true))
|
logrus.SetFormatter(NewCustomFormatter(false, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateLoggerOpt(frpLogLevel string, logrusLevel string) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
frpLogLevel = strings.ToLower(frpLogLevel)
|
||||||
|
logrusLevel = strings.ToLower(logrusLevel)
|
||||||
|
|
||||||
|
if frpLogLevel == "" {
|
||||||
|
frpLogLevel = "info"
|
||||||
|
}
|
||||||
|
if logrusLevel == "" {
|
||||||
|
logrusLevel = "info"
|
||||||
|
}
|
||||||
|
|
||||||
|
frpLv, err := log.ParseLevel(frpLogLevel)
|
||||||
|
if err != nil {
|
||||||
|
Logger(ctx).WithError(err).Errorf("invalid frp log level: %s, use info", frpLogLevel)
|
||||||
|
frpLv = log.InfoLevel
|
||||||
|
}
|
||||||
|
logrusLv, err := logrus.ParseLevel(logrusLevel)
|
||||||
|
if err != nil {
|
||||||
|
Logger(ctx).WithError(err).Errorf("invalid logrus log level: %s, use info", logrusLevel)
|
||||||
|
logrusLv = logrus.InfoLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance().SetLevel(logrusLv)
|
||||||
|
logrus.SetLevel(logrusLv)
|
||||||
|
|
||||||
|
initFrpLogger(frpLv)
|
||||||
|
}
|
||||||
|
|
||||||
func NewCallerPrettyfier(projectRoot, projectPkg string) func(frame *runtime.Frame) (function string, file string) {
|
func NewCallerPrettyfier(projectRoot, projectPkg string) func(frame *runtime.Frame) (function string, file string) {
|
||||||
return func(frame *runtime.Frame) (function string, file string) {
|
return func(frame *runtime.Frame) (function string, file string) {
|
||||||
file = frame.File
|
file = frame.File
|
||||||
|
@@ -117,19 +117,19 @@ export const APITest = () => {
|
|||||||
<Separator />
|
<Separator />
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 my-4">
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 my-4">
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
// onClick={async () => {
|
||||||
console.log(
|
// console.log(
|
||||||
'attempting update frps:',
|
// 'attempting update frps:',
|
||||||
await updateFRPS({
|
// await updateFRPS({
|
||||||
serverId: serverID,
|
// serverId: serverID,
|
||||||
config: Buffer.from(
|
// config: Buffer.from(
|
||||||
JSON.stringify({
|
// JSON.stringify({
|
||||||
bindPort: 1122,
|
// bindPort: 1122,
|
||||||
} as ServerConfig),
|
// } as ServerConfig),
|
||||||
),
|
// ),
|
||||||
}),
|
// }),
|
||||||
)
|
// )
|
||||||
}}
|
// }}
|
||||||
>
|
>
|
||||||
update frps
|
update frps
|
||||||
</Button>
|
</Button>
|
||||||
@@ -181,11 +181,11 @@ export const APITest = () => {
|
|||||||
await updateFRPC({
|
await updateFRPC({
|
||||||
clientId: clientID,
|
clientId: clientID,
|
||||||
serverId: serverID,
|
serverId: serverID,
|
||||||
config: Buffer.from(
|
// config: Buffer.from(
|
||||||
JSON.stringify({
|
// JSON.stringify({
|
||||||
proxies: [{ name: 'test', type: 'tcp', localIP: '127.0.0.1', localPort: 1234, remotePort: 4321 }],
|
// proxies: [{ name: 'test', type: 'tcp', localIP: '127.0.0.1', localPort: 1234, remotePort: 4321 }],
|
||||||
} as ClientConfig),
|
// } as ClientConfig),
|
||||||
),
|
// ),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
@@ -3,14 +3,16 @@ import { Badge } from '../ui/badge';
|
|||||||
import { Input } from '../ui/input';
|
import { Input } from '../ui/input';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
interface StringListInputProps {
|
interface StringListInputProps {
|
||||||
value: string[];
|
value: string[];
|
||||||
onChange: React.Dispatch<React.SetStateAction<string[]>>;
|
onChange: React.Dispatch<React.SetStateAction<string[]>>;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StringListInput: React.FC<StringListInputProps> = ({ value, onChange, placeholder }) => {
|
const StringListInput: React.FC<StringListInputProps> = ({ value, onChange, placeholder, className }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ const StringListInput: React.FC<StringListInputProps> = ({ value, onChange, plac
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto">
|
<div className={cn("mx-auto", className)}>
|
||||||
<div className="flex items-center mb-4">
|
<div className="flex items-center mb-4">
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
|
@@ -37,7 +37,7 @@ export const ServerSelector: React.FC<ServerSelectorProps> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (serverID) {
|
if (serverID) {
|
||||||
setServer && setServer(serverList?.servers.find((server) => server.id == serverID) || {})
|
setServer && setServer(serverList?.servers.find((server) => server.id == serverID) || {frpsUrls: []})
|
||||||
}
|
}
|
||||||
}, [serverID])
|
}, [serverID])
|
||||||
|
|
||||||
|
144
www/components/base/suggestive-input.tsx
Normal file
144
www/components/base/suggestive-input.tsx
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
// src/components/ui/SuggestiveInput.tsx
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"; // 调整路径根据你的项目结构
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
||||||
|
interface SuggestiveInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'> {
|
||||||
|
/** 当前输入框的值 (受控) */
|
||||||
|
value: string;
|
||||||
|
/** 值改变时的回调函数 */
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
/** 建议选项列表 */
|
||||||
|
suggestions: string[];
|
||||||
|
/** 输入框的占位符 */
|
||||||
|
placeholder?: string;
|
||||||
|
/** 自定义 CSS 类名 */
|
||||||
|
className?: string;
|
||||||
|
/** Popover 内容的 CSS 类名 */
|
||||||
|
popoverClassName?: string;
|
||||||
|
/** 没有建议时的提示信息 */
|
||||||
|
emptyMessage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SuggestiveInput = React.forwardRef<HTMLInputElement, SuggestiveInputProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
suggestions,
|
||||||
|
placeholder,
|
||||||
|
className,
|
||||||
|
popoverClassName,
|
||||||
|
emptyMessage = "", // Default empty message
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||||
|
const [finalSuggestions, setFinalSuggestions] = React.useState(suggestions);
|
||||||
|
|
||||||
|
React.useImperativeHandle(ref, () => inputRef.current as HTMLInputElement);
|
||||||
|
|
||||||
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const newValue = event.target.value;
|
||||||
|
onChange(newValue);
|
||||||
|
setFinalSuggestions([newValue, ...suggestions]);
|
||||||
|
if (newValue && !open) {
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
if (!newValue && open) {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSuggestionSelect = (selectedValue: string) => {
|
||||||
|
if (selectedValue !== value) {
|
||||||
|
onChange(selectedValue);
|
||||||
|
}
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredSuggestions = React.useMemo(() => {
|
||||||
|
const lowerCaseValue = value?.toLowerCase() || "";
|
||||||
|
|
||||||
|
const validSuggestions = finalSuggestions.filter(s => s);
|
||||||
|
|
||||||
|
if (!lowerCaseValue) {
|
||||||
|
return validSuggestions;
|
||||||
|
}
|
||||||
|
return validSuggestions.filter(s =>
|
||||||
|
s.toLowerCase().includes(lowerCaseValue)
|
||||||
|
);
|
||||||
|
}, [value, finalSuggestions]);
|
||||||
|
|
||||||
|
const shouldShowPopover = ((filteredSuggestions.length > 0 || (value && filteredSuggestions.length === 0)) && open) ? true : false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={shouldShowPopover}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
value={value}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onFocus={() => {
|
||||||
|
// 仅当有建议或已有内容时才在聚焦时打开
|
||||||
|
if (finalSuggestions.length > 0) {
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className={cn("w-full text-sm", className)}
|
||||||
|
role="combobox" // Accessibility role
|
||||||
|
aria-expanded={shouldShowPopover} // Accessibility state
|
||||||
|
aria-autocomplete="list" // Accessibility hint
|
||||||
|
autoComplete="off" // Prevent browser's default autocomplete
|
||||||
|
{...props} // Pass down other standard input props like 'id', 'name', etc.
|
||||||
|
/>
|
||||||
|
</PopoverTrigger>
|
||||||
|
{shouldShowPopover && (
|
||||||
|
<PopoverContent
|
||||||
|
className={cn("w-[--radix-popover-trigger-width] p-1", popoverClassName)}
|
||||||
|
style={{ zIndex: 50 }}
|
||||||
|
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<Command shouldFilter={false}>
|
||||||
|
<CommandList className="pt-0">
|
||||||
|
{value && filteredSuggestions.length === 0 ? (
|
||||||
|
<CommandEmpty>{emptyMessage}</CommandEmpty>
|
||||||
|
) : null}
|
||||||
|
{filteredSuggestions.map((suggestion) => (
|
||||||
|
<CommandItem
|
||||||
|
key={suggestion}
|
||||||
|
value={suggestion}
|
||||||
|
onSelect={handleSuggestionSelect}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
|
{suggestion}
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
SuggestiveInput.displayName = "SuggestiveInput";
|
||||||
|
|
||||||
|
export { SuggestiveInput };
|
@@ -48,6 +48,7 @@ export type ClientTableSchema = {
|
|||||||
info?: string
|
info?: string
|
||||||
config?: string
|
config?: string
|
||||||
originClient: Client
|
originClient: Client
|
||||||
|
clientIds: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TableMetaType extends TableMeta<ClientTableSchema> {
|
export interface TableMetaType extends TableMeta<ClientTableSchema> {
|
||||||
|
@@ -15,6 +15,9 @@ import { TypedProxyConfig } from '@/types/proxy'
|
|||||||
import { ClientSelector } from '../base/client-selector'
|
import { ClientSelector } from '../base/client-selector'
|
||||||
import { ServerSelector } from '../base/server-selector'
|
import { ServerSelector } from '../base/server-selector'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Input } from '../ui/input'
|
||||||
|
import { Server } from '@/lib/pb/common'
|
||||||
|
import { SuggestiveInput } from '../base/suggestive-input'
|
||||||
|
|
||||||
export interface FRPCFormCardProps {
|
export interface FRPCFormCardProps {
|
||||||
clientID?: string
|
clientID?: string
|
||||||
@@ -32,6 +35,8 @@ export const FRPCFormCard: React.FC<FRPCFormCardProps> = ({
|
|||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const paramClientID = searchParams.get('clientID')
|
const paramClientID = searchParams.get('clientID')
|
||||||
const [clientProxyConfigs, setClientProxyConfigs] = useState<TypedProxyConfig[]>([])
|
const [clientProxyConfigs, setClientProxyConfigs] = useState<TypedProxyConfig[]>([])
|
||||||
|
const [frpsUrl, setFrpsUrl] = useState<string | undefined>()
|
||||||
|
const [selectedServer, setSelectedServer] = useState<Server | undefined>(undefined)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (defaultClientID) {
|
if (defaultClientID) {
|
||||||
@@ -69,6 +74,10 @@ export const FRPCFormCard: React.FC<FRPCFormCardProps> = ({
|
|||||||
if (clientConf != undefined && clientConf.proxies == undefined) {
|
if (clientConf != undefined && clientConf.proxies == undefined) {
|
||||||
setClientProxyConfigs([])
|
setClientProxyConfigs([])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client?.client?.frpsUrl) {
|
||||||
|
setFrpsUrl(client?.client?.frpsUrl)
|
||||||
|
}
|
||||||
}, [client, refetchClient, setClientProxyConfigs])
|
}, [client, refetchClient, setClientProxyConfigs])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -105,9 +114,12 @@ export const FRPCFormCard: React.FC<FRPCFormCardProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full pt-2 space-y-2">
|
<div className="flex flex-col w-full pt-2 space-y-2">
|
||||||
<Label className="text-sm font-medium">{t('frpc.form.server')}</Label>
|
<Label className="text-sm font-medium">{t('frpc.form.server')}</Label>
|
||||||
<ServerSelector serverID={serverID} setServerID={setServerID} />
|
<ServerSelector serverID={serverID} setServerID={setServerID} setServer={setSelectedServer} />
|
||||||
<Label className="text-sm font-medium">{t('frpc.form.client')}</Label>
|
<Label className="text-sm font-medium">{t('frpc.form.client')}</Label>
|
||||||
<ClientSelector clientID={clientID} setClientID={setClientID} />
|
<ClientSelector clientID={clientID} setClientID={setClientID} />
|
||||||
|
<Label className="text-sm font-medium">{t('frpc.form.frps_url.title')}</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">{t('frpc.form.frps_url.hint')}</p>
|
||||||
|
<SuggestiveInput value={frpsUrl || ''} onChange={setFrpsUrl} suggestions={selectedServer?.frpsUrls || []} />
|
||||||
</div>
|
</div>
|
||||||
{clientID && !advanceMode && <div className='flex flex-col w-full pt-2 space-y-2'>
|
{clientID && !advanceMode && <div className='flex flex-col w-full pt-2 space-y-2'>
|
||||||
<Label className="text-sm font-medium">{t('frpc.form.comment.title', { id: clientID })}</Label>
|
<Label className="text-sm font-medium">{t('frpc.form.comment.title', { id: clientID })}</Label>
|
||||||
@@ -120,14 +132,18 @@ export const FRPCFormCard: React.FC<FRPCFormCardProps> = ({
|
|||||||
clientConfig={JSON.parse(client?.client?.config || '{}') as ClientConfig} refetchClient={refetchClient}
|
clientConfig={JSON.parse(client?.client?.config || '{}') as ClientConfig} refetchClient={refetchClient}
|
||||||
clientID={clientID} serverID={serverID}
|
clientID={clientID} serverID={serverID}
|
||||||
clientProxyConfigs={clientProxyConfigs}
|
clientProxyConfigs={clientProxyConfigs}
|
||||||
setClientProxyConfigs={setClientProxyConfigs} />
|
setClientProxyConfigs={setClientProxyConfigs}
|
||||||
|
frpsUrl={frpsUrl}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
{clientID && serverID && advanceMode && <FRPCEditor
|
{clientID && serverID && advanceMode && <FRPCEditor
|
||||||
client={client?.client}
|
client={client?.client}
|
||||||
clientConfig={JSON.parse(client?.client?.config || '{}') as ClientConfig} refetchClient={refetchClient}
|
clientConfig={JSON.parse(client?.client?.config || '{}') as ClientConfig} refetchClient={refetchClient}
|
||||||
clientID={clientID} serverID={serverID}
|
clientID={clientID} serverID={serverID}
|
||||||
clientProxyConfigs={clientProxyConfigs}
|
clientProxyConfigs={clientProxyConfigs}
|
||||||
setClientProxyConfigs={setClientProxyConfigs} />
|
setClientProxyConfigs={setClientProxyConfigs}
|
||||||
|
frpsUrl={frpsUrl}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
@@ -9,7 +9,7 @@ import { RespCode } from '@/lib/pb/common'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
export const FRPCEditor: React.FC<FRPCFormProps> = ({ clientID, serverID, client, refetchClient }) => {
|
export const FRPCEditor: React.FC<FRPCFormProps> = ({ clientID, serverID, client, refetchClient, frpsUrl }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [configContent, setConfigContent] = useState<string>('{}')
|
const [configContent, setConfigContent] = useState<string>('{}')
|
||||||
@@ -25,6 +25,7 @@ export const FRPCEditor: React.FC<FRPCFormProps> = ({ clientID, serverID, client
|
|||||||
config: Buffer.from(editorValue),
|
config: Buffer.from(editorValue),
|
||||||
serverId: serverID,
|
serverId: serverID,
|
||||||
comment: clientComment,
|
comment: clientComment,
|
||||||
|
frpsUrl: frpsUrl,
|
||||||
})
|
})
|
||||||
if (res.status?.code !== RespCode.SUCCESS) {
|
if (res.status?.code !== RespCode.SUCCESS) {
|
||||||
toast(t('client.operation.update_failed', {
|
toast(t('client.operation.update_failed', {
|
||||||
|
@@ -24,12 +24,13 @@ export interface FRPCFormProps {
|
|||||||
serverID: string
|
serverID: string
|
||||||
client?: Client
|
client?: Client
|
||||||
clientConfig: ClientConfig
|
clientConfig: ClientConfig
|
||||||
|
frpsUrl?: string
|
||||||
refetchClient: (options?: RefetchOptions) => Promise<QueryObserverResult<GetClientResponse, Error>>
|
refetchClient: (options?: RefetchOptions) => Promise<QueryObserverResult<GetClientResponse, Error>>
|
||||||
clientProxyConfigs: TypedProxyConfig[]
|
clientProxyConfigs: TypedProxyConfig[]
|
||||||
setClientProxyConfigs: React.Dispatch<React.SetStateAction<TypedProxyConfig[]>>
|
setClientProxyConfigs: React.Dispatch<React.SetStateAction<TypedProxyConfig[]>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID, clientConfig, client, refetchClient, clientProxyConfigs, setClientProxyConfigs }) => {
|
export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID, clientConfig, client, refetchClient, clientProxyConfigs, setClientProxyConfigs, frpsUrl }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [proxyType, setProxyType] = useState<ProxyType>('http')
|
const [proxyType, setProxyType] = useState<ProxyType>('http')
|
||||||
const [proxyName, setProxyName] = useState<string | undefined>()
|
const [proxyName, setProxyName] = useState<string | undefined>()
|
||||||
@@ -84,6 +85,7 @@ export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID, clientCo
|
|||||||
),
|
),
|
||||||
serverId: serverID,
|
serverId: serverID,
|
||||||
clientId: clientID,
|
clientId: clientID,
|
||||||
|
frpsUrl: frpsUrl,
|
||||||
})
|
})
|
||||||
await refetchClient()
|
await refetchClient()
|
||||||
toast(t('proxy.status.update'), {
|
toast(t('proxy.status.update'), {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { use, useEffect, useState } from 'react'
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Label } from '@/components/ui/label'
|
import { Label } from '@/components/ui/label'
|
||||||
import { getServer } from '@/api/server'
|
import { getServer } from '@/api/server'
|
||||||
@@ -9,6 +9,7 @@ import FRPSForm from './frps_form'
|
|||||||
import { useSearchParams } from 'next/navigation'
|
import { useSearchParams } from 'next/navigation'
|
||||||
import { ServerSelector } from '../base/server-selector'
|
import { ServerSelector } from '../base/server-selector'
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import StringListInput from '../base/list-input'
|
||||||
|
|
||||||
export interface FRPSFormCardProps {
|
export interface FRPSFormCardProps {
|
||||||
serverID?: string
|
serverID?: string
|
||||||
@@ -18,6 +19,8 @@ export const FRPSFormCard: React.FC<FRPSFormCardProps> = ({ serverID: defaultSer
|
|||||||
const [serverID, setServerID] = useState<string | undefined>()
|
const [serverID, setServerID] = useState<string | undefined>()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const paramServerID = searchParams.get('serverID')
|
const paramServerID = searchParams.get('serverID')
|
||||||
|
const [frpsUrls, setFrpsUrls] = useState<string[]>([])
|
||||||
|
|
||||||
const { data: server, refetch: refetchServer } = useQuery({
|
const { data: server, refetch: refetchServer } = useQuery({
|
||||||
queryKey: ['getServer', serverID],
|
queryKey: ['getServer', serverID],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
@@ -26,6 +29,10 @@ export const FRPSFormCard: React.FC<FRPSFormCardProps> = ({ serverID: defaultSer
|
|||||||
})
|
})
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFrpsUrls(server?.server?.frpsUrls || [])
|
||||||
|
}, [server])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (defaultServerID) {
|
if (defaultServerID) {
|
||||||
setServerID(defaultServerID)
|
setServerID(defaultServerID)
|
||||||
@@ -64,13 +71,18 @@ export const FRPSFormCard: React.FC<FRPSFormCardProps> = ({ serverID: defaultSer
|
|||||||
</div>
|
</div>
|
||||||
<Switch onCheckedChange={setAdvanceMode} />
|
<Switch onCheckedChange={setAdvanceMode} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full pt-2">
|
<div className="flex flex-col w-full pt-2 gap-2">
|
||||||
<Label className="text-sm font-medium">{t('server.serverLabel')}</Label>
|
<Label className="text-sm font-medium">{t('server.serverLabel')}</Label>
|
||||||
<ServerSelector serverID={serverID} setServerID={handleServerChange} onOpenChange={refetchServer} />
|
<ServerSelector serverID={serverID} setServerID={handleServerChange} onOpenChange={refetchServer} />
|
||||||
|
<Label className="text-sm font-medium">{t('server.frpsUrl.title')}</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">{t('server.frpsUrl.description')}</p>
|
||||||
|
<StringListInput className='w-full' value={frpsUrls} onChange={setFrpsUrls} placeholder='eg. tcp://example.com:7000' />
|
||||||
</div>
|
</div>
|
||||||
{serverID && server && server.server && !advanceMode && <FRPSForm key={serverID} serverID={serverID} server={server.server} />}
|
{serverID && server && server.server && !advanceMode && (
|
||||||
|
<FRPSForm key={serverID} serverID={serverID} server={server.server} frpsUrls={frpsUrls} />
|
||||||
|
)}
|
||||||
{serverID && server && server.server && advanceMode && (
|
{serverID && server && server.server && advanceMode && (
|
||||||
<FRPSEditor serverID={serverID} server={server.server} />
|
<FRPSEditor serverID={serverID} server={server.server} frpsUrls={frpsUrls} />
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter></CardFooter>
|
<CardFooter></CardFooter>
|
||||||
|
@@ -10,7 +10,7 @@ import { RespCode } from '@/lib/pb/common'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
export const FRPSEditor: React.FC<FRPSFormProps> = ({ server, serverID }) => {
|
export const FRPSEditor: React.FC<FRPSFormProps> = ({ server, serverID, frpsUrls }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { data: serverResp, refetch: refetchServer } = useQuery({
|
const { data: serverResp, refetch: refetchServer } = useQuery({
|
||||||
queryKey: ['getServer', serverID],
|
queryKey: ['getServer', serverID],
|
||||||
@@ -27,6 +27,7 @@ export const FRPSEditor: React.FC<FRPSFormProps> = ({ server, serverID }) => {
|
|||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
let res = await updateFrps.mutateAsync({
|
let res = await updateFrps.mutateAsync({
|
||||||
|
frpsUrls: frpsUrls,
|
||||||
serverId: serverID,
|
serverId: serverID,
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
config: Buffer.from(editorValue),
|
config: Buffer.from(editorValue),
|
||||||
|
@@ -30,9 +30,10 @@ export const ServerConfigZodSchema = ServerConfigSchema
|
|||||||
export interface FRPSFormProps {
|
export interface FRPSFormProps {
|
||||||
serverID: string
|
serverID: string
|
||||||
server: Server
|
server: Server
|
||||||
|
frpsUrls: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const FRPSForm: React.FC<FRPSFormProps> = ({ serverID, server }) => {
|
const FRPSForm: React.FC<FRPSFormProps> = ({ serverID, server, frpsUrls }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const form = useForm<z.infer<typeof ServerConfigZodSchema>>({
|
const form = useForm<z.infer<typeof ServerConfigZodSchema>>({
|
||||||
resolver: zodResolver(ServerConfigZodSchema),
|
resolver: zodResolver(ServerConfigZodSchema),
|
||||||
@@ -54,6 +55,7 @@ const FRPSForm: React.FC<FRPSFormProps> = ({ serverID, server }) => {
|
|||||||
let resp = await updateFrps.mutateAsync({
|
let resp = await updateFrps.mutateAsync({
|
||||||
serverIp: publicHost,
|
serverIp: publicHost,
|
||||||
serverId: serverID,
|
serverId: serverID,
|
||||||
|
frpsUrls: frpsUrls,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
config: Buffer.from(
|
config: Buffer.from(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
@@ -46,6 +46,7 @@ export type ServerTableSchema = {
|
|||||||
info?: string
|
info?: string
|
||||||
ip: string
|
ip: string
|
||||||
config?: string
|
config?: string
|
||||||
|
frpsUrls: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const columns: ColumnDef<ServerTableSchema>[] = [
|
export const columns: ColumnDef<ServerTableSchema>[] = [
|
||||||
|
@@ -107,7 +107,7 @@ function VisitPreviewField({ row }: { row: Row<ProxyConfigTableSchema> }) {
|
|||||||
const typedProxyConfig = JSON.parse(row.original.config || '{}') as TypedProxyConfig
|
const typedProxyConfig = JSON.parse(row.original.config || '{}') as TypedProxyConfig
|
||||||
|
|
||||||
return <VisitPreview
|
return <VisitPreview
|
||||||
server={server?.server || {}}
|
server={server?.server || {frpsUrls: []}}
|
||||||
typedProxyConfig={typedProxyConfig} />
|
typedProxyConfig={typedProxyConfig} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -134,6 +134,10 @@
|
|||||||
"description": "Edit server raw configuration file"
|
"description": "Edit server raw configuration file"
|
||||||
},
|
},
|
||||||
"serverLabel": "Server",
|
"serverLabel": "Server",
|
||||||
|
"frpsUrl": {
|
||||||
|
"title": "FRP server override address - Optional",
|
||||||
|
"description": "Clients can use this address to connect instead of the default IP. This is useful for port forwarding/CDN/reverse proxy. Format: [tcp/kcp/websocket]://127.0.0.1:7000"
|
||||||
|
},
|
||||||
"id": "ID (Click for install command)",
|
"id": "ID (Click for install command)",
|
||||||
"status": "Configuration Status",
|
"status": "Configuration Status",
|
||||||
"info": "Running Info/Version",
|
"info": "Running Info/Version",
|
||||||
@@ -204,7 +208,7 @@
|
|||||||
"vhost_http_port": "HTTP Listen Port",
|
"vhost_http_port": "HTTP Listen Port",
|
||||||
"subdomain_host": "Subdomain Host",
|
"subdomain_host": "Subdomain Host",
|
||||||
"quic_bind_port": "Quic Bind Port",
|
"quic_bind_port": "Quic Bind Port",
|
||||||
"kcp_bind_port":"KCP Bind Port"
|
"kcp_bind_port": "KCP Bind Port"
|
||||||
},
|
},
|
||||||
"editor": {
|
"editor": {
|
||||||
"comment": "Node {{id}} Comment",
|
"comment": "Node {{id}} Comment",
|
||||||
@@ -452,6 +456,10 @@
|
|||||||
"title": "Node {{id}} Comment",
|
"title": "Node {{id}} Comment",
|
||||||
"hint": "You can modify the comment in advanced mode!",
|
"hint": "You can modify the comment in advanced mode!",
|
||||||
"empty": "Nothing here"
|
"empty": "Nothing here"
|
||||||
|
},
|
||||||
|
"frps_url": {
|
||||||
|
"title": "FRP server override address - Optional",
|
||||||
|
"hint": "You can override the server address stored in the master to handle the inconsistency between the domain name/IP/port and the real port during port forwarding or reverse proxy/CDN. Format: [tcp/kcp/websocket]://127.0.0.1:7000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -511,4 +519,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -147,6 +147,10 @@
|
|||||||
"description": "编辑服务器原始配置文件"
|
"description": "编辑服务器原始配置文件"
|
||||||
},
|
},
|
||||||
"serverLabel": "服务器",
|
"serverLabel": "服务器",
|
||||||
|
"frpsUrl": {
|
||||||
|
"title": "FRP 服务器覆盖地址 - 可选",
|
||||||
|
"description": "客户端可以使用该地址连接,而不是使用默认的IP。在端口转发/CDN/反向代理时很有用。格式:[tcp/kcp/websocket]://127.0.0.1:7000"
|
||||||
|
},
|
||||||
"install": {
|
"install": {
|
||||||
"title": "安装命令",
|
"title": "安装命令",
|
||||||
"description": "请选择您的操作系统并复制相应的安装命令",
|
"description": "请选择您的操作系统并复制相应的安装命令",
|
||||||
@@ -211,7 +215,7 @@
|
|||||||
"vhost_http_port": "HTTP 监听端口",
|
"vhost_http_port": "HTTP 监听端口",
|
||||||
"subdomain_host": "域名后缀",
|
"subdomain_host": "域名后缀",
|
||||||
"quic_bind_port": "Quic 监听端口",
|
"quic_bind_port": "Quic 监听端口",
|
||||||
"kcp_bind_port":"KCP 监听端口"
|
"kcp_bind_port": "KCP 监听端口"
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"button": "新建",
|
"button": "新建",
|
||||||
@@ -452,6 +456,10 @@
|
|||||||
"title": "节点 {{id}} 的备注",
|
"title": "节点 {{id}} 的备注",
|
||||||
"hint": "可以到高级模式修改备注哦!",
|
"hint": "可以到高级模式修改备注哦!",
|
||||||
"empty": "空空如也"
|
"empty": "空空如也"
|
||||||
|
},
|
||||||
|
"frps_url": {
|
||||||
|
"title": "FRP 服务端覆盖地址 - 可选",
|
||||||
|
"hint": "可以覆盖 master 存储的服务端地址,用于处理端口转发或反向代理/CDN时,域名/IP/端口和真实端口不一致的问题。格式:[tcp/kcp/websocket]://127.0.0.1:7000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -510,4 +518,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -135,6 +135,10 @@ export interface UpdateFRPCRequest {
|
|||||||
* @generated from protobuf field: optional string comment = 4;
|
* @generated from protobuf field: optional string comment = 4;
|
||||||
*/
|
*/
|
||||||
comment?: string;
|
comment?: string;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: optional string frps_url = 5;
|
||||||
|
*/
|
||||||
|
frpsUrl?: string;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @generated from protobuf message api_client.UpdateFRPCResponse
|
* @generated from protobuf message api_client.UpdateFRPCResponse
|
||||||
@@ -808,7 +812,8 @@ class UpdateFRPCRequest$Type extends MessageType<UpdateFRPCRequest> {
|
|||||||
{ no: 1, name: "client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
{ no: 1, name: "client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 2, name: "server_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
{ no: 2, name: "server_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 3, name: "config", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ },
|
{ no: 3, name: "config", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ },
|
||||||
{ no: 4, name: "comment", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
|
{ no: 4, name: "comment", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 5, name: "frps_url", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
create(value?: PartialMessage<UpdateFRPCRequest>): UpdateFRPCRequest {
|
create(value?: PartialMessage<UpdateFRPCRequest>): UpdateFRPCRequest {
|
||||||
@@ -834,6 +839,9 @@ class UpdateFRPCRequest$Type extends MessageType<UpdateFRPCRequest> {
|
|||||||
case /* optional string comment */ 4:
|
case /* optional string comment */ 4:
|
||||||
message.comment = reader.string();
|
message.comment = reader.string();
|
||||||
break;
|
break;
|
||||||
|
case /* optional string frps_url */ 5:
|
||||||
|
message.frpsUrl = reader.string();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
let u = options.readUnknownField;
|
let u = options.readUnknownField;
|
||||||
if (u === "throw")
|
if (u === "throw")
|
||||||
@@ -858,6 +866,9 @@ class UpdateFRPCRequest$Type extends MessageType<UpdateFRPCRequest> {
|
|||||||
/* optional string comment = 4; */
|
/* optional string comment = 4; */
|
||||||
if (message.comment !== undefined)
|
if (message.comment !== undefined)
|
||||||
writer.tag(4, WireType.LengthDelimited).string(message.comment);
|
writer.tag(4, WireType.LengthDelimited).string(message.comment);
|
||||||
|
/* optional string frps_url = 5; */
|
||||||
|
if (message.frpsUrl !== undefined)
|
||||||
|
writer.tag(5, WireType.LengthDelimited).string(message.frpsUrl);
|
||||||
let u = options.writeUnknownFields;
|
let u = options.writeUnknownFields;
|
||||||
if (u !== false)
|
if (u !== false)
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
@@ -137,6 +137,10 @@ export interface UpdateFRPSRequest {
|
|||||||
* @generated from protobuf field: optional string server_ip = 4;
|
* @generated from protobuf field: optional string server_ip = 4;
|
||||||
*/
|
*/
|
||||||
serverIp?: string;
|
serverIp?: string;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: repeated string frps_urls = 5;
|
||||||
|
*/
|
||||||
|
frpsUrls: string[];
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @generated from protobuf message api_server.UpdateFRPSResponse
|
* @generated from protobuf message api_server.UpdateFRPSResponse
|
||||||
@@ -655,11 +659,13 @@ class UpdateFRPSRequest$Type extends MessageType<UpdateFRPSRequest> {
|
|||||||
{ no: 1, name: "server_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
{ no: 1, name: "server_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 2, name: "config", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ },
|
{ no: 2, name: "config", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ },
|
||||||
{ no: 3, name: "comment", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
{ no: 3, name: "comment", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 4, name: "server_ip", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
|
{ no: 4, name: "server_ip", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 5, name: "frps_urls", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
create(value?: PartialMessage<UpdateFRPSRequest>): UpdateFRPSRequest {
|
create(value?: PartialMessage<UpdateFRPSRequest>): UpdateFRPSRequest {
|
||||||
const message = globalThis.Object.create((this.messagePrototype!));
|
const message = globalThis.Object.create((this.messagePrototype!));
|
||||||
|
message.frpsUrls = [];
|
||||||
if (value !== undefined)
|
if (value !== undefined)
|
||||||
reflectionMergePartial<UpdateFRPSRequest>(this, message, value);
|
reflectionMergePartial<UpdateFRPSRequest>(this, message, value);
|
||||||
return message;
|
return message;
|
||||||
@@ -681,6 +687,9 @@ class UpdateFRPSRequest$Type extends MessageType<UpdateFRPSRequest> {
|
|||||||
case /* optional string server_ip */ 4:
|
case /* optional string server_ip */ 4:
|
||||||
message.serverIp = reader.string();
|
message.serverIp = reader.string();
|
||||||
break;
|
break;
|
||||||
|
case /* repeated string frps_urls */ 5:
|
||||||
|
message.frpsUrls.push(reader.string());
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
let u = options.readUnknownField;
|
let u = options.readUnknownField;
|
||||||
if (u === "throw")
|
if (u === "throw")
|
||||||
@@ -705,6 +714,9 @@ class UpdateFRPSRequest$Type extends MessageType<UpdateFRPSRequest> {
|
|||||||
/* optional string server_ip = 4; */
|
/* optional string server_ip = 4; */
|
||||||
if (message.serverIp !== undefined)
|
if (message.serverIp !== undefined)
|
||||||
writer.tag(4, WireType.LengthDelimited).string(message.serverIp);
|
writer.tag(4, WireType.LengthDelimited).string(message.serverIp);
|
||||||
|
/* repeated string frps_urls = 5; */
|
||||||
|
for (let i = 0; i < message.frpsUrls.length; i++)
|
||||||
|
writer.tag(5, WireType.LengthDelimited).string(message.frpsUrls[i]);
|
||||||
let u = options.writeUnknownFields;
|
let u = options.writeUnknownFields;
|
||||||
if (u !== false)
|
if (u !== false)
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
@@ -81,6 +81,10 @@ export interface Client {
|
|||||||
* @generated from protobuf field: optional string origin_client_id = 9;
|
* @generated from protobuf field: optional string origin_client_id = 9;
|
||||||
*/
|
*/
|
||||||
originClientId?: string;
|
originClientId?: string;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: optional string frps_url = 10;
|
||||||
|
*/
|
||||||
|
frpsUrl?: string; // 客户端用于连接frps的url,解决 frp 在 CDN 后的问题,格式类似 [tcp/ws/wss/quic/kcp]://example.com:7000
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @generated from protobuf message common.Server
|
* @generated from protobuf message common.Server
|
||||||
@@ -106,6 +110,10 @@ export interface Server {
|
|||||||
* @generated from protobuf field: optional string comment = 5;
|
* @generated from protobuf field: optional string comment = 5;
|
||||||
*/
|
*/
|
||||||
comment?: string; // 用户自定义的备注
|
comment?: string; // 用户自定义的备注
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: repeated string frps_urls = 6;
|
||||||
|
*/
|
||||||
|
frpsUrls: string[]; // 客户端用于连接frps的url,解决 frp 在 CDN 后的问题,格式类似 [tcp/ws/wss/quic/kcp]://example.com:7000,可以有多个
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @generated from protobuf message common.User
|
* @generated from protobuf message common.User
|
||||||
@@ -458,7 +466,8 @@ class Client$Type extends MessageType<Client> {
|
|||||||
{ no: 6, name: "server_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
{ no: 6, name: "server_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 7, name: "stopped", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ },
|
{ no: 7, name: "stopped", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ },
|
||||||
{ no: 8, name: "client_ids", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ },
|
{ no: 8, name: "client_ids", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 9, name: "origin_client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
|
{ no: 9, name: "origin_client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 10, name: "frps_url", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
create(value?: PartialMessage<Client>): Client {
|
create(value?: PartialMessage<Client>): Client {
|
||||||
@@ -497,6 +506,9 @@ class Client$Type extends MessageType<Client> {
|
|||||||
case /* optional string origin_client_id */ 9:
|
case /* optional string origin_client_id */ 9:
|
||||||
message.originClientId = reader.string();
|
message.originClientId = reader.string();
|
||||||
break;
|
break;
|
||||||
|
case /* optional string frps_url */ 10:
|
||||||
|
message.frpsUrl = reader.string();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
let u = options.readUnknownField;
|
let u = options.readUnknownField;
|
||||||
if (u === "throw")
|
if (u === "throw")
|
||||||
@@ -533,6 +545,9 @@ class Client$Type extends MessageType<Client> {
|
|||||||
/* optional string origin_client_id = 9; */
|
/* optional string origin_client_id = 9; */
|
||||||
if (message.originClientId !== undefined)
|
if (message.originClientId !== undefined)
|
||||||
writer.tag(9, WireType.LengthDelimited).string(message.originClientId);
|
writer.tag(9, WireType.LengthDelimited).string(message.originClientId);
|
||||||
|
/* optional string frps_url = 10; */
|
||||||
|
if (message.frpsUrl !== undefined)
|
||||||
|
writer.tag(10, WireType.LengthDelimited).string(message.frpsUrl);
|
||||||
let u = options.writeUnknownFields;
|
let u = options.writeUnknownFields;
|
||||||
if (u !== false)
|
if (u !== false)
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
@@ -551,11 +566,13 @@ class Server$Type extends MessageType<Server> {
|
|||||||
{ no: 2, name: "secret", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
{ no: 2, name: "secret", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 3, name: "ip", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
{ no: 3, name: "ip", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 4, name: "config", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
{ no: 4, name: "config", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 5, name: "comment", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
|
{ no: 5, name: "comment", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 6, name: "frps_urls", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
create(value?: PartialMessage<Server>): Server {
|
create(value?: PartialMessage<Server>): Server {
|
||||||
const message = globalThis.Object.create((this.messagePrototype!));
|
const message = globalThis.Object.create((this.messagePrototype!));
|
||||||
|
message.frpsUrls = [];
|
||||||
if (value !== undefined)
|
if (value !== undefined)
|
||||||
reflectionMergePartial<Server>(this, message, value);
|
reflectionMergePartial<Server>(this, message, value);
|
||||||
return message;
|
return message;
|
||||||
@@ -580,6 +597,9 @@ class Server$Type extends MessageType<Server> {
|
|||||||
case /* optional string comment */ 5:
|
case /* optional string comment */ 5:
|
||||||
message.comment = reader.string();
|
message.comment = reader.string();
|
||||||
break;
|
break;
|
||||||
|
case /* repeated string frps_urls */ 6:
|
||||||
|
message.frpsUrls.push(reader.string());
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
let u = options.readUnknownField;
|
let u = options.readUnknownField;
|
||||||
if (u === "throw")
|
if (u === "throw")
|
||||||
@@ -607,6 +627,9 @@ class Server$Type extends MessageType<Server> {
|
|||||||
/* optional string comment = 5; */
|
/* optional string comment = 5; */
|
||||||
if (message.comment !== undefined)
|
if (message.comment !== undefined)
|
||||||
writer.tag(5, WireType.LengthDelimited).string(message.comment);
|
writer.tag(5, WireType.LengthDelimited).string(message.comment);
|
||||||
|
/* repeated string frps_urls = 6; */
|
||||||
|
for (let i = 0; i < message.frpsUrls.length; i++)
|
||||||
|
writer.tag(6, WireType.LengthDelimited).string(message.frpsUrls[i]);
|
||||||
let u = options.writeUnknownFields;
|
let u = options.writeUnknownFields;
|
||||||
if (u !== false)
|
if (u !== false)
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
@@ -1,62 +1,58 @@
|
|||||||
import { FRPCFormCard } from '@/components/frpc/frpc_card'
|
// import { Providers } from '@/components/providers'
|
||||||
import { Providers } from '@/components/providers'
|
// import { RootLayout } from '@/components/layout'
|
||||||
import { APITest } from '@/components/apitest'
|
// import { Header } from '@/components/header'
|
||||||
import { Separator } from '@/components/ui/separator'
|
// import { Button } from '@/components/ui/button'
|
||||||
import { FRPSFormCard } from '@/components/frps/frps_card'
|
// import { ProxyConfigList } from '@/components/proxy/proxy_config_list'
|
||||||
import { RootLayout } from '@/components/layout'
|
// import { Input } from '@/components/ui/input'
|
||||||
import { Header } from '@/components/header'
|
// import { createProxyConfig } from '@/api/proxy'
|
||||||
import { createProxyConfig, listProxyConfig } from '@/api/proxy'
|
// import { TypedProxyConfig } from '@/types/proxy'
|
||||||
import { Button } from '@/components/ui/button'
|
// import { ClientConfig } from '@/types/client'
|
||||||
import { TypedProxyConfig } from '@/types/proxy'
|
// import { useState } from 'react'
|
||||||
import { ClientConfig } from '@/types/client'
|
|
||||||
import { ProxyConfigList } from '@/components/proxy/proxy_config_list'
|
|
||||||
import { Input } from '@/components/ui/input'
|
|
||||||
import { useState } from 'react'
|
|
||||||
|
|
||||||
export default function Test() {
|
export default function Test() {
|
||||||
const [name, setName] = useState<string>('')
|
// const [name, setName] = useState<string>('')
|
||||||
const [triggerRefetch, setTriggerRefetch] = useState<number>(0)
|
// const [triggerRefetch, setTriggerRefetch] = useState<number>(0)
|
||||||
|
|
||||||
function create() {
|
// function create() {
|
||||||
const buffer = Buffer.from(
|
// const buffer = Buffer.from(
|
||||||
JSON.stringify({
|
// JSON.stringify({
|
||||||
proxies: [{
|
// proxies: [{
|
||||||
name: name,
|
// name: name,
|
||||||
type: 'tcp',
|
// type: 'tcp',
|
||||||
localIP: '127.0.0.1',
|
// localIP: '127.0.0.1',
|
||||||
localPort: 1234,
|
// localPort: 1234,
|
||||||
remotePort: 4321,
|
// remotePort: 4321,
|
||||||
} as TypedProxyConfig]
|
// } as TypedProxyConfig]
|
||||||
} as ClientConfig),
|
// } as ClientConfig),
|
||||||
)
|
// )
|
||||||
const uint8Array: Uint8Array = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
// const uint8Array: Uint8Array = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
||||||
createProxyConfig({
|
// createProxyConfig({
|
||||||
clientId: 'admin.c.test',
|
// clientId: 'admin.c.test',
|
||||||
config: uint8Array,
|
// config: uint8Array,
|
||||||
serverId: 'default',
|
// serverId: 'default',
|
||||||
})
|
// })
|
||||||
.then(() => {
|
// .then(() => {
|
||||||
setTriggerRefetch(triggerRefetch + 1)
|
// setTriggerRefetch(triggerRefetch + 1)
|
||||||
})
|
// })
|
||||||
.catch((err) => {
|
// .catch((err) => {
|
||||||
console.log(err)
|
// console.log(err)
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
return (
|
return (
|
||||||
// <>
|
<>
|
||||||
// </>
|
</>
|
||||||
<Providers>
|
// <Providers>
|
||||||
<RootLayout mainHeader={<Header />}>
|
// <RootLayout mainHeader={<Header />}>
|
||||||
<div className="w-full">
|
// <div className="w-full">
|
||||||
<div className="flex flex-1 flex-col">
|
// <div className="flex flex-1 flex-col">
|
||||||
<div className="flex flex-1 flex-row mb-2 gap-2">
|
// <div className="flex flex-1 flex-row mb-2 gap-2">
|
||||||
<Button onClick={create}>新建</Button>
|
// <Button onClick={create}>新建</Button>
|
||||||
<Input value={name} onChange={(e) => setName(e.target.value)} ></Input>
|
// <Input value={name} onChange={(e) => setName(e.target.value)} ></Input>
|
||||||
</div>
|
// </div>
|
||||||
<ProxyConfigList Keyword="" ProxyConfigs={[]} TriggerRefetch={triggerRefetch.toString()} />
|
// <ProxyConfigList Keyword="" ProxyConfigs={[]} TriggerRefetch={triggerRefetch.toString()} />
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</RootLayout>
|
// </RootLayout>
|
||||||
</Providers>
|
// </Providers>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user