feat: proxy list [新增代理列表实体]

refactor: change client manager structure [重构:更改客户端管理器结构适配影子客户端]

feat: add proxy config table and dao [添加代理配置独立数据表和DAO层]

feat: new proxy config entity [新建的代理配置实体]

feat: update and delete proxy config [更新和删除代理配置接口]

feat: get config api and beautify proxy item [更新获取配置API并美化代理项]

feat: change proxy form style [美化修改代理的表单样式]

fix: client edit [修复:编辑客户端的问题]

fix: shadow copy status error [修复:影子客户端复制状态错误]

fix: http proxy bug [修复:HTTP代理类型的错误]

fix: cannot update client [修复:无法更新客户端]

feat: record trigger refetch [自动重新获取表格内的数据]

fix: filter string length [修复:过滤字符串长度]

fix: add client error [修复:添加客户端错误]

fix: do not notify client when stopped [修复:停止时不通知客户端]

fix: delete when proxy duplicate [修复:代理重复时删除]

feat: add http proxy location [添加HTTP代理路由路径]

chore: edit style [编辑样式美化]

fix: remove expired client [修复:自动移除过期客户端]

feat: proxy status [新增代理状态提示]

fix: build [修复:构建]

fix: refetch trigger [修复:重新获取数据的问题]

fix: remove all expired client [修复:移除所有过期客户端]

feat: i18n for proxy [代理页面的国际化翻译]
This commit is contained in:
VaalaCat
2024-12-16 08:15:35 +00:00
parent 74908d03a1
commit 373276633f
99 changed files with 5490 additions and 928 deletions

View File

@@ -0,0 +1,42 @@
package client
import (
"context"
"fmt"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/pb"
"github.com/VaalaCat/frp-panel/tunnel"
"github.com/samber/lo"
)
func GetProxyConfig(c context.Context, req *pb.GetProxyConfigRequest) (*pb.GetProxyConfigResponse, error) {
var (
clientID = req.GetClientId()
serverID = req.GetServerId()
proxyName = req.GetName()
)
ctrl := tunnel.GetClientController()
cli := ctrl.Get(clientID, serverID)
if cli == nil {
logger.Logger(c).Errorf("cannot get client, clientID: [%s], serverID: [%s]", clientID, serverID)
return nil, fmt.Errorf("cannot get client")
}
workingStatus, ok := cli.GetProxyStatus(proxyName)
if !ok {
logger.Logger(c).Errorf("cannot get proxy status, client: [%s], server: [%s], proxy name: [%s]", clientID, serverID, proxyName)
return nil, fmt.Errorf("cannot get proxy status")
}
return &pb.GetProxyConfigResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "success"},
WorkingStatus: &pb.ProxyWorkingStatus{
Name: lo.ToPtr(workingStatus.Name),
Type: lo.ToPtr(workingStatus.Type),
Status: lo.ToPtr(workingStatus.Phase),
Err: lo.ToPtr(workingStatus.Err),
RemoteAddr: lo.ToPtr(workingStatus.RemoteAddr),
},
}, nil
}

View File

@@ -2,23 +2,20 @@ package client
import (
"context"
"os"
"time"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/pb"
"github.com/VaalaCat/frp-panel/tunnel"
)
func RemoveFrpcHandler(ctx context.Context, req *pb.RemoveFRPCRequest) (*pb.RemoveFRPCResponse, error) {
logger.Logger(ctx).Infof("remove frpc, req: [%+v]", req)
cli := tunnel.GetClientController().Get(req.GetClientId())
if cli == nil {
logger.Logger(ctx).Infof("client not found, no need to remove")
return &pb.RemoveFRPCResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_INVALID, Message: "client not found"},
}, nil
}
cli.Stop()
tunnel.GetClientController().Delete(req.GetClientId())
logger.Logger(ctx).Infof("remove frpc, req: [%+v], will exit in 10s", req)
go func() {
time.Sleep(10 * time.Second)
os.Exit(0)
}()
return &pb.RemoveFRPCResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"},

View File

@@ -36,6 +36,8 @@ func HandleServerMessage(req *pb.ServerMessage) *pb.ClientMessage {
return common.WrapperServerMsg(req, StopSteamLogHandler)
case pb.Event_EVENT_START_PTY_CONNECT:
return common.WrapperServerMsg(req, StartPTYConnect)
case pb.Event_EVENT_GET_PROXY_INFO:
return common.WrapperServerMsg(req, GetProxyConfig)
case pb.Event_EVENT_PING:
rawData, _ := proto.Marshal(conf.GetVersion().ToProto())
return &pb.ClientMessage{

View File

@@ -10,10 +10,12 @@ import (
"github.com/VaalaCat/frp-panel/services/client"
"github.com/VaalaCat/frp-panel/tunnel"
"github.com/VaalaCat/frp-panel/utils"
"github.com/samber/lo"
)
func PullConfig(clientID, clientSecret string) error {
ctx := context.Background()
ctrl := tunnel.GetClientController()
logger.Logger(ctx).Infof("start to pull client config, clientID: [%s]", clientID)
cli, err := rpc.MasterCli(ctx)
@@ -32,6 +34,38 @@ func PullConfig(clientID, clientSecret string) error {
return err
}
if resp.GetClient().GetStopped() {
logger.Logger(ctx).Infof("client [%s] is stopped, stop client", clientID)
ctrl.StopByClient(clientID)
return nil
}
if len(resp.GetClient().GetOriginClientId()) == 0 {
currentClientIDs := ctrl.List()
if idsToRemove, _ := lo.Difference(resp.GetClient().GetClientIds(), currentClientIDs); len(idsToRemove) > 0 {
logger.Logger(ctx).Infof("client [%s] has %d expired child clients, remove clientIDs: [%+v]", clientID, len(idsToRemove), idsToRemove)
for _, id := range idsToRemove {
ctrl.StopByClient(id)
ctrl.DeleteByClient(id)
}
}
}
// this client is shadow client, has no config
// pull child client config
if len(resp.GetClient().GetClientIds()) > 0 {
for _, id := range resp.GetClient().GetClientIds() {
if id == clientID {
logger.Logger(ctx).Infof("client [%s] is shadow client, skip", clientID)
continue
}
if err := PullConfig(id, clientSecret); err != nil {
logger.Logger(context.Background()).WithError(err).Errorf("cannot pull child client config, id: [%s]", id)
}
}
return nil
}
if len(resp.GetClient().GetConfig()) == 0 {
logger.Logger(ctx).Infof("client [%s] config is empty, wait for server init", clientID)
return nil
@@ -43,37 +77,38 @@ func PullConfig(clientID, clientSecret string) error {
return err
}
ctrl := tunnel.GetClientController()
serverID := resp.GetClient().GetServerId()
if t := ctrl.Get(clientID); t == nil {
ctrl.Add(clientID, client.NewClientHandler(c, p, v))
ctrl.Run(clientID)
if t := ctrl.Get(clientID, serverID); t == nil {
logger.Logger(ctx).Infof("client [%s] for server [%s] not exists, create it", clientID, serverID)
ctrl.Add(clientID, serverID, client.NewClientHandler(c, p, v))
ctrl.Run(clientID, serverID)
} else {
if !reflect.DeepEqual(t.GetCommonCfg(), c) {
logger.Logger(ctx).Infof("client %s config changed, will recreate it", clientID)
tcli := ctrl.Get(clientID)
logger.Logger(ctx).Infof("client [%s] for server [%s] config changed, will recreate it", clientID, serverID)
tcli := ctrl.Get(clientID, serverID)
if tcli != nil {
tcli.Stop()
ctrl.Delete(clientID)
ctrl.Delete(clientID, serverID)
}
ctrl.Add(clientID, client.NewClientHandler(c, p, v))
ctrl.Run(clientID)
ctrl.Add(clientID, serverID, client.NewClientHandler(c, p, v))
ctrl.Run(clientID, serverID)
} else {
logger.Logger(ctx).Infof("client %s already exists, update if need", clientID)
tcli := ctrl.Get(clientID)
logger.Logger(ctx).Infof("client [%s] for server [%s] already exists, update if need", clientID, serverID)
tcli := ctrl.Get(clientID, serverID)
if tcli == nil || !tcli.Running() {
if tcli != nil {
tcli.Stop()
ctrl.Delete(clientID)
ctrl.Delete(clientID, serverID)
}
ctrl.Add(clientID, client.NewClientHandler(c, p, v))
ctrl.Run(clientID)
ctrl.Add(clientID, serverID, client.NewClientHandler(c, p, v))
ctrl.Run(clientID, serverID)
} else {
tcli.Update(p, v)
}
}
}
logger.Logger(ctx).Infof("pull client config success, clientID: [%s]", clientID)
logger.Logger(ctx).Infof("pull client config success, clientID: [%s], serverID: [%s]", clientID, serverID)
return nil
}

View File

@@ -3,15 +3,18 @@ package client
import (
"context"
"github.com/VaalaCat/frp-panel/conf"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/pb"
"github.com/VaalaCat/frp-panel/tunnel"
)
func StartFRPCHandler(ctx context.Context, req *pb.StartFRPCRequest) (*pb.StartFRPCResponse, error) {
logger.Logger(ctx).Infof("client get a start client request, origin is: [%+v]", req)
tunnel.GetClientController().Run(req.GetClientId())
if err := PullConfig(req.GetClientId(), conf.Get().Client.Secret); err != nil {
logger.Logger(ctx).WithError(err).Error("cannot pull client config")
return nil, err
}
return &pb.StartFRPCResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"},

View File

@@ -11,7 +11,8 @@ import (
func StopFRPCHandler(ctx context.Context, req *pb.StopFRPCRequest) (*pb.StopFRPCResponse, error) {
logger.Logger(ctx).Infof("client get a stop client request, origin is: [%+v]", req)
tunnel.GetClientController().Stop(req.GetClientId())
tunnel.GetClientController().StopAll()
tunnel.GetClientController().DeleteAll()
return &pb.StopFRPCResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"},

View File

@@ -16,27 +16,27 @@ func UpdateFrpcHander(ctx context.Context, req *pb.UpdateFRPCRequest) (*pb.Updat
content := req.GetConfig()
c, p, v, err := utils.LoadClientConfig(content, false)
if err != nil {
logger.Logger(context.Background()).WithError(err).Errorf("cannot load config")
logger.Logger(ctx).WithError(err).Errorf("cannot load config")
return &pb.UpdateFRPCResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_INVALID, Message: err.Error()},
}, err
}
cli := tunnel.GetClientController().Get(req.GetClientId())
cli := tunnel.GetClientController().Get(req.GetClientId(), req.GetServerId())
if cli != nil {
if reflect.DeepEqual(c, cli.GetCommonCfg()) {
logger.Logger(ctx).Warnf("client common config not changed")
cli.Update(p, v)
} else {
cli.Stop()
tunnel.GetClientController().Delete(req.GetClientId())
tunnel.GetClientController().Add(req.GetClientId(), client.NewClientHandler(c, p, v))
tunnel.GetClientController().Run(req.GetClientId())
tunnel.GetClientController().Delete(req.GetClientId(), req.GetServerId())
tunnel.GetClientController().Add(req.GetClientId(), req.GetServerId(), client.NewClientHandler(c, p, v))
tunnel.GetClientController().Run(req.GetClientId(), req.GetServerId())
}
logger.Logger(ctx).Infof("update client, id: [%s] success, running", req.GetClientId())
} else {
tunnel.GetClientController().Add(req.GetClientId(), client.NewClientHandler(c, p, v))
tunnel.GetClientController().Run(req.GetClientId())
tunnel.GetClientController().Add(req.GetClientId(), req.GetServerId(), client.NewClientHandler(c, p, v))
tunnel.GetClientController().Run(req.GetClientId(), req.GetServerId())
logger.Logger(ctx).Infof("add new client, id: [%s], running", req.GetClientId())
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/VaalaCat/frp-panel/dao"
"github.com/VaalaCat/frp-panel/models"
"github.com/VaalaCat/frp-panel/pb"
"github.com/VaalaCat/frp-panel/utils"
"github.com/google/uuid"
)
@@ -20,7 +21,7 @@ func InitClientHandler(c context.Context, req *pb.InitClientRequest) (*pb.InitCl
}, nil
}
if len(userClientID) == 0 {
if len(userClientID) == 0 || !utils.IsClientIDPermited(userClientID) {
return &pb.InitClientResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_INVALID, Message: "invalid client id"},
}, nil
@@ -34,6 +35,7 @@ func InitClientHandler(c context.Context, req *pb.InitClientRequest) (*pb.InitCl
TenantID: userInfo.GetTenantID(),
UserID: userInfo.GetUserID(),
ConnectSecret: uuid.New().String(),
IsShadow: true,
}); err != nil {
return nil, err
}

View File

@@ -32,6 +32,10 @@ func DeleteClientHandler(ctx context.Context, req *pb.DeleteClientRequest) (*pb.
return nil, err
}
if err := dao.DeleteProxyConfigsByClientIDOrOriginClientID(userInfo, clientID); err != nil {
return nil, err
}
go func() {
resp, err := rpc.CallClient(context.Background(), req.GetClientId(), pb.Event_EVENT_REMOVE_FRPC, req)
if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"github.com/VaalaCat/frp-panel/common"
"github.com/VaalaCat/frp-panel/dao"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/models"
"github.com/VaalaCat/frp-panel/pb"
"github.com/samber/lo"
)
@@ -16,6 +17,7 @@ func GetClientHandler(ctx context.Context, req *pb.GetClientRequest) (*pb.GetCli
var (
userInfo = common.GetUserInfo(ctx)
clientID = req.GetClientId()
serverID = req.GetServerId()
)
if !userInfo.Valid() {
@@ -30,20 +32,54 @@ func GetClientHandler(ctx context.Context, req *pb.GetClientRequest) (*pb.GetCli
}, nil
}
client, err := dao.GetClientByClientID(userInfo, clientID)
if err != nil {
return nil, err
respCli := &pb.Client{}
if len(serverID) == 0 {
client, err := dao.GetClientByClientID(userInfo, clientID)
if err != nil {
return nil, err
}
clientIDs, err := dao.GetClientIDsInShadowByClientID(userInfo, clientID)
if err != nil {
logger.Logger(ctx).WithError(err).Errorf("cannot get client ids in shadow, id: [%s]", clientID)
}
respCli = &pb.Client{
Id: lo.ToPtr(client.ClientID),
Secret: lo.ToPtr(client.ConnectSecret),
Config: lo.ToPtr(string(client.ConfigContent)),
ServerId: lo.ToPtr(client.ServerID),
Stopped: lo.ToPtr(client.Stopped),
Comment: lo.ToPtr(client.Comment),
ClientIds: clientIDs,
}
} else {
client, err := dao.GetClientByFilter(userInfo, &models.ClientEntity{
OriginClientID: clientID,
ServerID: serverID,
}, lo.ToPtr(false))
if err != nil {
client, err = dao.GetClientByFilter(userInfo, &models.ClientEntity{
ClientID: clientID,
ServerID: serverID,
}, nil)
if err != nil {
return nil, err
}
}
respCli = &pb.Client{
Id: lo.ToPtr(client.ClientID),
Secret: lo.ToPtr(client.ConnectSecret),
Config: lo.ToPtr(string(client.ConfigContent)),
ServerId: lo.ToPtr(client.ServerID),
Stopped: lo.ToPtr(client.Stopped),
Comment: lo.ToPtr(client.Comment),
ClientIds: nil,
}
}
return &pb.GetClientResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"},
Client: &pb.Client{
Id: lo.ToPtr(client.ClientID),
Secret: lo.ToPtr(client.ConnectSecret),
Config: lo.ToPtr(string(client.ConfigContent)),
ServerId: lo.ToPtr(client.ServerID),
Stopped: lo.ToPtr(client.Stopped),
Comment: lo.ToPtr(client.Comment),
},
Client: respCli,
}, nil
}

View File

@@ -1,10 +1,15 @@
package client
import (
"context"
"fmt"
"github.com/VaalaCat/frp-panel/common"
"github.com/VaalaCat/frp-panel/dao"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/models"
"github.com/samber/lo"
"github.com/tiendc/go-deepcopy"
)
type ValidateableClientRequest interface {
@@ -32,3 +37,79 @@ func ValidateClientRequest(req ValidateableClientRequest) (*models.ClientEntity,
return cli, nil
}
func MakeClientShadowed(c context.Context, serverID string, clientEntity *models.ClientEntity) (*models.ClientEntity, error) {
userInfo := common.GetUserInfo(c)
var clientID = clientEntity.ClientID
var childClient *models.ClientEntity
var err error
if len(clientEntity.ConfigContent) != 0 {
childClient, err = ChildClientForServer(c, serverID, clientEntity)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot create child client, id: [%s]", clientID)
return nil, err
}
if err := dao.RebuildProxyConfigFromClient(userInfo, &models.Client{ClientEntity: childClient}); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot rebuild proxy config from client, id: [%s]", childClient.ClientID)
return nil, err
}
}
clientEntity.IsShadow = true
clientEntity.ConfigContent = nil
clientEntity.ServerID = ""
if err := dao.UpdateClient(userInfo, clientEntity); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot update client, id: [%s]", clientID)
return nil, err
}
if err := dao.DeleteProxyConfigsByClientID(userInfo, clientID); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot delete proxy configs, id: [%s]", clientID)
return nil, err
}
return childClient, nil
}
// ChildClientForServer 支持传入serverID和任意类型client返回serverID对应的client in shadow如果不存在则新建
func ChildClientForServer(c context.Context, serverID string, clientEntity *models.ClientEntity) (*models.ClientEntity, error) {
userInfo := common.GetUserInfo(c)
originClientID := clientEntity.ClientID
if len(clientEntity.OriginClientID) != 0 {
originClientID = clientEntity.OriginClientID
}
existClient, err := dao.GetClientByFilter(userInfo, &models.ClientEntity{
ServerID: serverID,
OriginClientID: originClientID,
}, lo.ToPtr(false))
if err == nil {
return existClient, nil
}
shadowCount, err := dao.CountClientsInShadow(userInfo, originClientID)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot count shadow clients, id: [%s]", originClientID)
return nil, err
}
copiedClient := &models.ClientEntity{}
if err := deepcopy.Copy(copiedClient, clientEntity); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot copy client, id: [%s]", originClientID)
return nil, err
}
copiedClient.ServerID = serverID
copiedClient.ClientID = common.ShadowedClientID(originClientID, shadowCount+1)
copiedClient.OriginClientID = originClientID
copiedClient.IsShadow = false
copiedClient.Stopped = false
if err := dao.CreateClient(userInfo, copiedClient); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot create child client, id: [%s]", copiedClient.ClientID)
return nil, err
}
return copiedClient, nil
}

View File

@@ -55,13 +55,19 @@ func ListClientsHandler(ctx context.Context, req *pb.ListClientsRequest) (*pb.Li
}
respClients := lo.Map(clients, func(c *models.ClientEntity, _ int) *pb.Client {
clientIDs, err := dao.GetClientIDsInShadowByClientID(userInfo, c.ClientID)
if err != nil {
logger.Logger(ctx).Errorf("get client ids in shadow by client id error: %v", err)
}
return &pb.Client{
Id: lo.ToPtr(c.ClientID),
Secret: lo.ToPtr(c.ConnectSecret),
Config: lo.ToPtr(string(c.ConfigContent)),
ServerId: lo.ToPtr(c.ServerID),
Stopped: lo.ToPtr(c.Stopped),
Comment: lo.ToPtr(c.Comment),
Id: lo.ToPtr(c.ClientID),
Secret: lo.ToPtr(c.ConnectSecret),
Config: lo.ToPtr(string(c.ConfigContent)),
ServerId: lo.ToPtr(c.ServerID),
Stopped: lo.ToPtr(c.Stopped),
Comment: lo.ToPtr(c.Comment),
ClientIds: clientIDs,
}
})

View File

@@ -4,6 +4,8 @@ import (
"context"
"fmt"
"github.com/VaalaCat/frp-panel/dao"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/models"
"github.com/VaalaCat/frp-panel/pb"
"github.com/samber/lo"
@@ -11,22 +13,37 @@ import (
func RPCPullConfig(ctx context.Context, req *pb.PullClientConfigReq) (*pb.PullClientConfigResp, error) {
var (
err error
cli *models.ClientEntity
err error
cli *models.ClientEntity
clientIDs []string
)
if cli, err = ValidateClientRequest(req.GetBase()); err != nil {
logger.Logger(ctx).WithError(err).Errorf("cannot validate client request")
return nil, err
}
if cli.Stopped {
if cli.IsShadow {
proxies, err := dao.AdminListProxyConfigsWithFilters(&models.ProxyConfigEntity{
OriginClientID: cli.ClientID,
})
if err != nil {
logger.Logger(ctx).Infof("cannot get client ids in shadow, maybe not a shadow client, id: [%s]", cli.ClientID)
}
clientIDs = lo.Map(proxies, func(p *models.ProxyConfig, _ int) string { return p.ClientID })
}
if cli.Stopped && cli.IsShadow {
return nil, fmt.Errorf("client is stopped")
}
return &pb.PullClientConfigResp{
Client: &pb.Client{
Id: lo.ToPtr(cli.ClientID),
Config: lo.ToPtr(string(cli.ConfigContent)),
Id: lo.ToPtr(cli.ClientID),
ServerId: lo.ToPtr(cli.ServerID),
Config: lo.ToPtr(string(cli.ConfigContent)),
OriginClientId: lo.ToPtr(cli.OriginClientID),
ClientIds: clientIDs,
},
}, nil
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/VaalaCat/frp-panel/common"
"github.com/VaalaCat/frp-panel/dao"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/models"
"github.com/VaalaCat/frp-panel/pb"
"github.com/VaalaCat/frp-panel/rpc"
"github.com/VaalaCat/frp-panel/utils"
@@ -18,31 +19,57 @@ import (
func UpdateFrpcHander(c context.Context, req *pb.UpdateFRPCRequest) (*pb.UpdateFRPCResponse, error) {
logger.Logger(c).Infof("update frpc, req: [%+v]", req)
var (
content = req.GetConfig()
serverID = req.GetServerId()
clientID = req.GetClientId()
userInfo = common.GetUserInfo(c)
content = req.GetConfig()
serverID = req.GetServerId()
reqClientID = req.GetClientId() // may be shadow or child
userInfo = common.GetUserInfo(c)
)
cliCfg, err := utils.LoadClientConfigNormal(content, true)
if err != nil {
logger.Logger(context.Background()).WithError(err).Errorf("cannot load config")
logger.Logger(c).WithError(err).Errorf("cannot load config")
return &pb.UpdateFRPCResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_INVALID, Message: err.Error()},
}, err
}
cli, err := dao.GetClientByClientID(userInfo, req.GetClientId())
cli, err := dao.GetClientByClientID(userInfo, reqClientID)
if err != nil {
logger.Logger(context.Background()).WithError(err).Errorf("cannot get client, id: [%s]", req.GetClientId())
logger.Logger(c).WithError(err).Errorf("cannot get client, id: [%s]", reqClientID)
return &pb.UpdateFRPCResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_INVALID, Message: "cannot get client"},
}, fmt.Errorf("cannot get client")
}
if cli.IsShadow {
cli, err = ChildClientForServer(c, serverID, cli)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot get child client, id: [%s]", reqClientID)
return &pb.UpdateFRPCResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_INVALID, Message: "cannot get child client"},
}, fmt.Errorf("cannot get child client")
}
}
if cli.IsShadow && len(cli.ConfigContent) == 0 {
logger.Logger(c).Warnf("client is shadowed, cannot update, id: [%s]", reqClientID)
return nil, fmt.Errorf("client is shadowed, cannot update")
}
if !cli.IsShadow && len(cli.OriginClientID) == 0 {
logger.Logger(c).Warnf("client is not shadowed, make it shadow, id: [%s]", reqClientID)
cli, err = MakeClientShadowed(c, serverID, cli)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot make client shadowed, id: [%s]", reqClientID)
return &pb.UpdateFRPCResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_INVALID, Message: "cannot make client shadowed"},
}, fmt.Errorf("cannot make client shadowed")
}
}
srv, err := dao.GetServerByServerID(userInfo, req.GetServerId())
if err != nil || srv == nil || len(srv.ServerIP) == 0 || len(srv.ConfigContent) == 0 {
logger.Logger(context.Background()).WithError(err).Errorf("cannot get server, server is not prepared, id: [%s]", req.GetServerId())
logger.Logger(c).WithError(err).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: "cannot get server"},
}, fmt.Errorf("cannot get server")
@@ -50,7 +77,7 @@ func UpdateFrpcHander(c context.Context, req *pb.UpdateFRPCRequest) (*pb.UpdateF
srvConf, err := srv.GetConfigContent()
if srvConf == nil || err != nil {
logger.Logger(context.Background()).WithError(err).Errorf("cannot get server, id: [%s]", serverID)
logger.Logger(c).WithError(err).Errorf("cannot get server, id: [%s]", serverID)
return nil, err
}
@@ -60,26 +87,22 @@ func UpdateFrpcHander(c context.Context, req *pb.UpdateFRPCRequest) (*pb.UpdateF
cliCfg.Auth = v1.AuthClientConfig{}
cliCfg.Metadatas = map[string]string{
common.FRPAuthTokenKey: userInfo.GetToken(),
common.FRPClientIDKey: clientID,
common.FRPClientIDKey: reqClientID,
}
newCfg := struct {
v1.ClientCommonConfig
Proxies []v1.ProxyConfigurer `json:"proxies,omitempty"`
Visitors []v1.VisitorBaseConfig `json:"visitors,omitempty"`
Visitors []v1.VisitorConfigurer `json:"visitors,omitempty"`
}{
ClientCommonConfig: cliCfg.ClientCommonConfig,
Proxies: lo.Map(cliCfg.Proxies, func(item v1.TypedProxyConfig, _ int) v1.ProxyConfigurer {
return item.ProxyConfigurer
}),
Visitors: lo.Map(cliCfg.Visitors, func(item v1.TypedVisitorConfig, _ int) v1.VisitorBaseConfig {
return *item.GetBaseConfig()
}),
Proxies: lo.Map(cliCfg.Proxies, func(item v1.TypedProxyConfig, _ int) v1.ProxyConfigurer { return item.ProxyConfigurer }),
Visitors: lo.Map(cliCfg.Visitors, func(item v1.TypedVisitorConfig, _ int) v1.VisitorConfigurer { return item.VisitorConfigurer }),
}
rawCliConf, err := json.Marshal(newCfg)
if err != nil {
logger.Logger(context.Background()).WithError(err).Error("cannot marshal config")
logger.Logger(c).WithError(err).Error("cannot marshal config")
return nil, err
}
@@ -90,28 +113,45 @@ func UpdateFrpcHander(c context.Context, req *pb.UpdateFRPCRequest) (*pb.UpdateF
}
if err := dao.UpdateClient(userInfo, cli); err != nil {
logger.Logger(context.Background()).WithError(err).Errorf("cannot update client, id: [%s]", clientID)
logger.Logger(c).WithError(err).Errorf("cannot update client, id: [%s]", cli.ClientID)
return nil, err
}
if err := dao.RebuildProxyConfigFromClient(userInfo, &models.Client{ClientEntity: cli}); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot rebuild proxy config from client, id: [%s]", cli.ClientID)
return nil, err
}
cliReq := &pb.UpdateFRPCRequest{
ClientId: lo.ToPtr(clientID),
ClientId: lo.ToPtr(cli.ClientID),
ServerId: lo.ToPtr(serverID),
Config: rawCliConf,
}
go func() {
resp, err := rpc.CallClient(context.Background(), req.GetClientId(), pb.Event_EVENT_UPDATE_FRPC, cliReq)
childCtx := context.Background()
cliToUpdate, err := dao.GetClientByFilter(userInfo, &models.ClientEntity{ClientID: cli.OriginClientID}, nil)
if err != nil {
logger.Logger(context.Background()).WithError(err).Errorf("update event send to client error, server: [%s], client: [%s]", serverID, req.GetClientId())
logger.Logger(childCtx).WithError(err).Errorf("cannot get origin client, id: [%s]", cliToUpdate.OriginClientID)
return
}
if cliToUpdate.Stopped {
logger.Logger(childCtx).Infof("client [%s] is stopped, do not send update event", cliToUpdate.OriginClientID)
return
}
resp, err := rpc.CallClient(childCtx, cliToUpdate.ClientID, pb.Event_EVENT_UPDATE_FRPC, cliReq)
if err != nil {
logger.Logger(childCtx).WithError(err).Errorf("update event send to client error, server: [%s], client: [%+v], updated client: [%+v]", serverID, cliToUpdate, cli)
}
if resp == nil {
logger.Logger(c).Errorf("cannot get response, server: [%s], client: [%s]", serverID, req.GetClientId())
logger.Logger(childCtx).Errorf("cannot get response, server: [%s], client: [%s]", serverID, cliToUpdate.OriginClientID)
}
}()
logger.Logger(c).Infof("update frpc success, client id: [%s]", req.GetClientId())
logger.Logger(c).Infof("update frpc success, client id: [%s]", reqClientID)
return &pb.UpdateFRPCResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"},
}, nil

View File

@@ -74,8 +74,13 @@ func ConfigureRouter(router *gin.Engine) {
}
proxyRouter := v1.Group("/proxy", middleware.JWTAuth, middleware.AuthCtx)
{
proxyRouter.POST("/get_by_cid", common.Wrapper(proxy.GetProxyByCID))
proxyRouter.POST("/get_by_sid", common.Wrapper(proxy.GetProxyBySID))
proxyRouter.POST("/get_by_cid", common.Wrapper(proxy.GetProxyStatsByClientID))
proxyRouter.POST("/get_by_sid", common.Wrapper(proxy.GetProxyStatsByServerID))
proxyRouter.POST("/list_configs", common.Wrapper(proxy.ListProxyConfigs))
proxyRouter.POST("/create_config", common.Wrapper(proxy.CreateProxyConfig))
proxyRouter.POST("/update_config", common.Wrapper(proxy.UpdateProxyConfig))
proxyRouter.POST("/delete_config", common.Wrapper(proxy.DeleteProxyConfig))
proxyRouter.POST("/get_config", common.Wrapper(proxy.GetProxyConfig))
}
v1.GET("/pty/:clientID", middleware.JWTAuth, middleware.AuthCtx, shell.PTYHandler)
v1.GET("/log", middleware.JWTAuth, middleware.AuthCtx, streamlog.GetLogHander)

View File

@@ -66,7 +66,7 @@ func GetClientsStatus(c context.Context, req *pb.GetClientsStatusRequest) (*pb.G
Ping: int32(pingTime),
Version: clientVersion,
Addr: lo.ToPtr(mgr.ClientAddr(clientID)),
ConnectTime: lo.ToPtr(int32(connectTime.UnixMilli())),
ConnectTime: lo.ToPtr(connectTime.UnixMilli()),
}
}

View File

@@ -0,0 +1,151 @@
package proxy
import (
"context"
"errors"
"fmt"
"github.com/VaalaCat/frp-panel/biz/master/client"
"github.com/VaalaCat/frp-panel/common"
"github.com/VaalaCat/frp-panel/dao"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/models"
"github.com/VaalaCat/frp-panel/pb"
"github.com/VaalaCat/frp-panel/utils"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/samber/lo"
"gorm.io/gorm"
)
func CreateProxyConfig(c context.Context, req *pb.CreateProxyConfigRequest) (*pb.CreateProxyConfigResponse, error) {
if len(req.GetClientId()) == 0 || len(req.GetServerId()) == 0 || len(req.GetConfig()) == 0 {
return nil, fmt.Errorf("request invalid")
}
var (
userInfo = common.GetUserInfo(c)
clientID = req.GetClientId()
serverID = req.GetServerId()
)
// 1. 检查是否有已连接该服务端的客户端
// 2. 检查是否有Shadow客户端
// 3. 如果没有则新建Shadow客户端和子客户端
clientEntity, err := dao.GetClientByFilter(userInfo, &models.ClientEntity{OriginClientID: clientID, ServerID: serverID}, lo.ToPtr(false))
if errors.Is(err, gorm.ErrRecordNotFound) {
clientEntity, err = dao.GetClientByFilter(userInfo, &models.ClientEntity{ClientID: clientID}, nil)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot get client, id: [%s]", clientID)
return nil, err
}
if (!clientEntity.IsShadow || len(clientEntity.ConfigContent) != 0) && len(clientEntity.OriginClientID) == 0 {
// 没shadow过需要shadow
_, err = client.MakeClientShadowed(c, serverID, clientEntity)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot make client shadow, id: [%s]", clientID)
return nil, err
}
}
// shadow过但没找到子客户端需要新建
clientEntity, err = client.ChildClientForServer(c, serverID, clientEntity)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot create child client, id: [%s]", clientID)
return nil, err
}
}
// 有任何失败,返回
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot get client, id: [%s]", clientID)
return nil, err
}
_, err = dao.GetServerByServerID(userInfo, serverID)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot get server, id: [%s]", serverID)
return nil, err
}
proxyCfg := &models.ProxyConfigEntity{}
if err := proxyCfg.FillClientConfig(clientEntity); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot fill client config, id: [%s]", clientID)
return nil, err
}
typedProxyCfgs, err := utils.LoadProxiesFromContent(req.GetConfig())
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot load proxies from content")
return nil, err
}
if len(typedProxyCfgs) == 0 || len(typedProxyCfgs) > 1 {
logger.Logger(c).Errorf("invalid config, cfg len: [%d]", len(typedProxyCfgs))
return nil, fmt.Errorf("invalid config")
}
if err := proxyCfg.FillTypedProxyConfig(typedProxyCfgs[0]); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot fill typed proxy config")
return nil, err
}
var existedProxyCfg *models.ProxyConfig
existedProxyCfg, err = dao.GetProxyConfigByOriginClientIDAndName(userInfo, clientID, proxyCfg.Name)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
logger.Logger(c).WithError(err).Errorf("cannot get proxy config, id: [%s]", clientID)
return nil, err
}
if !req.GetOverwrite() && err == nil {
logger.Logger(c).Errorf("proxy config already exist, cfg: [%+v]", proxyCfg)
return nil, fmt.Errorf("proxy config already exist")
}
// update client config
if oldCfg, err := clientEntity.GetConfigContent(); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot get client config, id: [%s]", clientID)
return nil, err
} else {
oldCfg.Proxies = lo.Filter(oldCfg.Proxies, func(proxy v1.TypedProxyConfig, _ int) bool {
return proxy.GetBaseConfig().Name != typedProxyCfgs[0].GetBaseConfig().Name
})
oldCfg.Proxies = append(oldCfg.Proxies, typedProxyCfgs...)
if err := clientEntity.SetConfigContent(*oldCfg); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot set client config, id: [%s]", clientID)
return nil, err
}
}
rawCfg, err := clientEntity.MarshalJSONConfig()
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot marshal client config, id: [%s]", clientID)
return nil, err
}
_, err = client.UpdateFrpcHander(c, &pb.UpdateFRPCRequest{
ClientId: &clientEntity.ClientID,
ServerId: &serverID,
Config: rawCfg,
Comment: &clientEntity.Comment,
})
if err != nil {
logger.Logger(c).WithError(err).Warnf("cannot update frpc failed, id: [%s]", clientID)
}
if existedProxyCfg != nil && existedProxyCfg.ServerID != serverID {
logger.Logger(c).Warnf("client and server not match, delete old proxy, client: [%s], server: [%s], proxy: [%s]", clientID, serverID, proxyCfg.Name)
if _, err := DeleteProxyConfig(c, &pb.DeleteProxyConfigRequest{
ClientId: lo.ToPtr(existedProxyCfg.ClientID),
ServerId: lo.ToPtr(existedProxyCfg.ServerID),
Name: &proxyCfg.Name,
}); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot delete old proxy, client: [%s], server: [%s], proxy: [%s]", clientID, clientEntity.ServerID, proxyCfg.Name)
return nil, err
}
}
return &pb.CreateProxyConfigResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"},
}, nil
}

View File

@@ -0,0 +1,82 @@
package proxy
import (
"context"
"fmt"
"github.com/VaalaCat/frp-panel/biz/master/client"
"github.com/VaalaCat/frp-panel/common"
"github.com/VaalaCat/frp-panel/dao"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/pb"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/samber/lo"
)
func DeleteProxyConfig(c context.Context, req *pb.DeleteProxyConfigRequest) (*pb.DeleteProxyConfigResponse, error) {
var (
userInfo = common.GetUserInfo(c)
clientID = req.GetClientId()
serverID = req.GetServerId()
proxyName = req.GetName()
)
if len(clientID) == 0 || len(serverID) == 0 || len(proxyName) == 0 {
return nil, fmt.Errorf("request invalid")
}
cli, err := dao.GetClientByClientID(userInfo, clientID)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot get client, id: [%s]", clientID)
return nil, err
}
if cli.ServerID != serverID {
return nil, fmt.Errorf("client and server not match")
}
oldCfg, err := cli.GetConfigContent()
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot get client config content, id: [%s]", clientID)
return nil, err
}
oldCfg.Proxies = lo.Filter(oldCfg.Proxies, func(p v1.TypedProxyConfig, _ int) bool {
return p.GetBaseConfig().Name != proxyName
})
if err := cli.SetConfigContent(*oldCfg); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot set client config, id: [%s]", clientID)
return nil, err
}
if err := dao.UpdateClient(userInfo, cli); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot update client, id: [%s]", clientID)
return nil, err
}
rawCfg, err := cli.MarshalJSONConfig()
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot marshal client config, id: [%s]", clientID)
return nil, err
}
_, err = client.UpdateFrpcHander(c, &pb.UpdateFRPCRequest{
ClientId: &cli.ClientID,
ServerId: &serverID,
Config: rawCfg,
Comment: &cli.Comment,
})
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot update frpc, id: [%s]", clientID)
return nil, err
}
if err := dao.DeleteProxyConfig(userInfo, clientID, proxyName); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot delete proxy config, id: [%s]", clientID)
return nil, err
}
logger.Logger(c).Infof("delete proxy config, id: [%s], name: [%s]", clientID, proxyName)
return &pb.DeleteProxyConfigResponse{}, nil
}

View File

@@ -0,0 +1,65 @@
package proxy
import (
"context"
"fmt"
"github.com/VaalaCat/frp-panel/common"
"github.com/VaalaCat/frp-panel/dao"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/models"
"github.com/VaalaCat/frp-panel/pb"
"github.com/VaalaCat/frp-panel/rpc"
"github.com/samber/lo"
)
func GetProxyConfig(c context.Context, req *pb.GetProxyConfigRequest) (*pb.GetProxyConfigResponse, error) {
var (
userInfo = common.GetUserInfo(c)
clientID = req.GetClientId()
serverID = req.GetServerId()
proxyName = req.GetName()
)
proxyConfig, err := dao.GetProxyConfigByFilter(userInfo, &models.ProxyConfigEntity{
ClientID: clientID,
ServerID: serverID,
Name: proxyName,
})
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot get proxy config, client: [%s], server: [%s], proxy name: [%s]", clientID, serverID, proxyName)
return nil, err
}
resp := &pb.GetProxyConfigResponse{}
if err := rpc.CallClientWrapper(c, proxyConfig.OriginClientID, pb.Event_EVENT_GET_PROXY_INFO, &pb.GetProxyConfigRequest{
ClientId: lo.ToPtr(proxyConfig.ClientID),
ServerId: lo.ToPtr(proxyConfig.ServerID),
Name: lo.ToPtr(fmt.Sprintf("%s.%s", userInfo.GetUserName(), proxyName)),
}, resp); err != nil {
resp.WorkingStatus = &pb.ProxyWorkingStatus{
Status: lo.ToPtr("error"),
}
logger.Logger(c).WithError(err).Errorf("cannot get proxy config, client: [%s], server: [%s], proxy name: [%s]", proxyConfig.OriginClientID, proxyConfig.ServerID, proxyConfig.Name)
}
if len(resp.GetWorkingStatus().GetStatus()) == 0 {
resp.WorkingStatus = &pb.ProxyWorkingStatus{
Status: lo.ToPtr("unknown"),
}
}
return &pb.GetProxyConfigResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "success"},
ProxyConfig: &pb.ProxyConfig{
Id: lo.ToPtr(uint32(proxyConfig.ID)),
Name: lo.ToPtr(proxyConfig.Name),
Type: lo.ToPtr(proxyConfig.Type),
ClientId: lo.ToPtr(proxyConfig.ClientID),
ServerId: lo.ToPtr(proxyConfig.ServerID),
Config: lo.ToPtr(string(proxyConfig.Content)),
OriginClientId: lo.ToPtr(proxyConfig.OriginClientID),
},
WorkingStatus: resp.GetWorkingStatus(),
}, nil
}

View File

@@ -10,8 +10,8 @@ import (
"github.com/VaalaCat/frp-panel/pb"
)
// GetProxyByCID get proxy info by client id
func GetProxyByCID(c context.Context, req *pb.GetProxyByCIDRequest) (*pb.GetProxyByCIDResponse, error) {
// GetProxyStatsByClientID get proxy info by client id
func GetProxyStatsByClientID(c context.Context, req *pb.GetProxyStatsByClientIDRequest) (*pb.GetProxyStatsByClientIDResponse, error) {
logger.Logger(c).Infof("get proxy by client id, req: [%+v]", req)
var (
clientID = req.GetClientId()
@@ -22,13 +22,13 @@ func GetProxyByCID(c context.Context, req *pb.GetProxyByCIDRequest) (*pb.GetProx
return nil, fmt.Errorf("request invalid")
}
proxyList, err := dao.GetProxyByClientID(userInfo, clientID)
if proxyList == nil || err != nil {
proxyStatsList, err := dao.GetProxyStatsByClientID(userInfo, clientID)
if proxyStatsList == nil || err != nil {
logger.Logger(context.Background()).WithError(err).Errorf("cannot get proxy, client id: [%s]", clientID)
return nil, err
}
return &pb.GetProxyByCIDResponse{
return &pb.GetProxyStatsByClientIDResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"},
ProxyInfos: convertProxyList(proxyList),
ProxyInfos: convertProxyStatsList(proxyStatsList),
}, nil
}

View File

@@ -10,8 +10,8 @@ import (
"github.com/VaalaCat/frp-panel/pb"
)
// GetProxyBySID get proxy info by server id
func GetProxyBySID(c context.Context, req *pb.GetProxyBySIDRequest) (*pb.GetProxyBySIDResponse, error) {
// GetProxyStatsByServerID get proxy info by server id
func GetProxyStatsByServerID(c context.Context, req *pb.GetProxyStatsByServerIDRequest) (*pb.GetProxyStatsByServerIDResponse, error) {
logger.Logger(c).Infof("get proxy by server id, req: [%+v]", req)
var (
serverID = req.GetServerId()
@@ -22,13 +22,13 @@ func GetProxyBySID(c context.Context, req *pb.GetProxyBySIDRequest) (*pb.GetProx
return nil, fmt.Errorf("request invalid")
}
proxyList, err := dao.GetProxyByServerID(userInfo, serverID)
proxyList, err := dao.GetProxyStatsByServerID(userInfo, serverID)
if proxyList == nil || err != nil {
logger.Logger(context.Background()).WithError(err).Errorf("cannot get proxy, server id: [%s]", serverID)
return nil, err
}
return &pb.GetProxyBySIDResponse{
return &pb.GetProxyStatsByServerIDResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"},
ProxyInfos: convertProxyList(proxyList),
ProxyInfos: convertProxyStatsList(proxyList),
}, nil
}

View File

@@ -6,8 +6,8 @@ import (
"github.com/samber/lo"
)
func convertProxyList(proxyList []*models.ProxyEntity) []*pb.ProxyInfo {
return lo.Map(proxyList, func(item *models.ProxyEntity, index int) *pb.ProxyInfo {
func convertProxyStatsList(proxyList []*models.ProxyStatsEntity) []*pb.ProxyInfo {
return lo.Map(proxyList, func(item *models.ProxyStatsEntity, index int) *pb.ProxyInfo {
return &pb.ProxyInfo{
Name: lo.ToPtr(item.Name),
Type: lo.ToPtr(item.Type),

View File

@@ -0,0 +1,86 @@
package proxy
import (
"context"
"github.com/VaalaCat/frp-panel/common"
"github.com/VaalaCat/frp-panel/dao"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/models"
"github.com/VaalaCat/frp-panel/pb"
"github.com/samber/lo"
)
func ListProxyConfigs(ctx context.Context, req *pb.ListProxyConfigsRequest) (*pb.ListProxyConfigsResponse, error) {
logger.Logger(ctx).Infof("list proxy configs, req: [%+v]", req)
var (
userInfo = common.GetUserInfo(ctx)
)
if !userInfo.Valid() {
return &pb.ListProxyConfigsResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_INVALID, Message: "invalid user"},
}, nil
}
var (
page = int(req.GetPage())
pageSize = int(req.GetPageSize())
keyword = req.GetKeyword()
clientID = req.GetClientId()
serverID = req.GetServerId()
hasKeyword = len(keyword) > 0
hasClientID = len(clientID) > 0
hasServerID = len(serverID) > 0
proxyConfigs []*models.ProxyConfig
err error
proxyCounts int64
filter = &models.ProxyConfigEntity{}
)
if hasClientID {
filter.OriginClientID = clientID
}
if hasServerID {
filter.ServerID = serverID
}
if hasKeyword {
proxyConfigs, err = dao.ListProxyConfigsWithFiltersAndKeyword(userInfo, page, pageSize, filter, keyword)
} else {
proxyConfigs, err = dao.ListProxyConfigsWithFilters(userInfo, page, pageSize, filter)
}
if err != nil {
return nil, err
}
if hasKeyword {
proxyCounts, err = dao.CountProxyConfigsWithFiltersAndKeyword(userInfo, filter, keyword)
} else {
proxyCounts, err = dao.CountProxyConfigsWithFilters(userInfo, filter)
}
if err != nil {
return nil, err
}
respProxyConfigs := lo.Map(proxyConfigs, func(item *models.ProxyConfig, _ int) *pb.ProxyConfig {
return &pb.ProxyConfig{
Id: lo.ToPtr(uint32(item.ID)),
Name: lo.ToPtr(item.Name),
Type: lo.ToPtr(item.Type),
ClientId: lo.ToPtr(item.ClientID),
ServerId: lo.ToPtr(item.ServerID),
Config: lo.ToPtr(string(item.Content)),
OriginClientId: lo.ToPtr(item.OriginClientID),
}
})
return &pb.ListProxyConfigsResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "success"},
ProxyConfigs: respProxyConfigs,
Total: lo.ToPtr(int32(proxyCounts)),
}, nil
}

View File

@@ -19,23 +19,24 @@ func CollectDailyStats() error {
}
}()
proxies, err := dao.AdminGetAllProxies(tx)
proxies, err := dao.AdminGetAllProxyStats(tx)
if err != nil {
logger.Logger(context.Background()).WithError(err).Error("CollectDailyStats cannot get proxies")
return err
}
proxyDailyStats := lo.Map(proxies, func(item *models.ProxyEntity, _ int) *models.HistoryProxyStats {
proxyDailyStats := lo.Map(proxies, func(item *models.ProxyStatsEntity, _ int) *models.HistoryProxyStats {
return &models.HistoryProxyStats{
ProxyID: item.ProxyID,
ServerID: item.ServerID,
ClientID: item.ClientID,
Name: item.Name,
Type: item.Type,
UserID: item.UserID,
TenantID: item.TenantID,
TrafficIn: item.HistoryTrafficIn,
TrafficOut: item.HistoryTrafficOut,
ProxyID: item.ProxyID,
ServerID: item.ServerID,
ClientID: item.ClientID,
OriginClientID: item.OriginClientID,
Name: item.Name,
Type: item.Type,
UserID: item.UserID,
TenantID: item.TenantID,
TrafficIn: item.HistoryTrafficIn,
TrafficOut: item.HistoryTrafficOut,
}
})

View File

@@ -0,0 +1,127 @@
package proxy
import (
"context"
"fmt"
"github.com/VaalaCat/frp-panel/biz/master/client"
"github.com/VaalaCat/frp-panel/common"
"github.com/VaalaCat/frp-panel/dao"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/models"
"github.com/VaalaCat/frp-panel/pb"
"github.com/VaalaCat/frp-panel/utils"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/samber/lo"
)
func UpdateProxyConfig(c context.Context, req *pb.UpdateProxyConfigRequest) (*pb.UpdateProxyConfigResponse, error) {
if len(req.GetClientId()) == 0 || len(req.GetServerId()) == 0 || len(req.GetConfig()) == 0 {
return nil, fmt.Errorf("request invalid")
}
var (
userInfo = common.GetUserInfo(c)
clientID = req.GetClientId()
serverID = req.GetServerId()
)
clientEntity, err := dao.GetClientByClientID(userInfo, clientID)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot get client, id: [%s]", clientID)
return nil, err
}
if clientEntity.ServerID != serverID {
logger.Logger(c).Errorf("client and server not match, find or create client, client: [%s], server: [%s]", clientID, serverID)
originClient, err := dao.GetClientByClientID(userInfo, clientEntity.OriginClientID)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot get origin client, id: [%s]", clientEntity.OriginClientID)
return nil, err
}
clientEntity, err = client.ChildClientForServer(c, serverID, originClient)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot create child client, id: [%s]", clientID)
return nil, err
}
}
_, err = dao.GetServerByServerID(userInfo, serverID)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot get server, id: [%s]", serverID)
return nil, err
}
proxyCfg := &models.ProxyConfigEntity{}
if err := proxyCfg.FillClientConfig(clientEntity); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot fill client config, id: [%s]", clientID)
return nil, err
}
typedProxyCfgs, err := utils.LoadProxiesFromContent(req.GetConfig())
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot load proxies from content")
return nil, err
}
if len(typedProxyCfgs) == 0 || len(typedProxyCfgs) > 1 {
logger.Logger(c).Errorf("invalid config, cfg len: [%d]", len(typedProxyCfgs))
return nil, fmt.Errorf("invalid config")
}
if err := proxyCfg.FillTypedProxyConfig(typedProxyCfgs[0]); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot fill typed proxy config")
return nil, err
}
oldProxyCfg, err := dao.GetProxyConfigByOriginClientIDAndName(userInfo, clientID, proxyCfg.Name)
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot get proxy config, id: [%s]", clientID)
return nil, err
}
if dao.UpdateProxyConfig(userInfo, &models.ProxyConfig{
Model: oldProxyCfg.Model,
ProxyConfigEntity: proxyCfg,
}) != nil {
logger.Logger(c).Errorf("update proxy config failed, cfg: [%+v]", proxyCfg)
return nil, fmt.Errorf("update proxy config failed")
}
// update client config
if oldCfg, err := clientEntity.GetConfigContent(); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot get client config, id: [%s]", clientID)
return nil, err
} else {
oldCfg.Proxies = lo.Filter(oldCfg.Proxies, func(proxy v1.TypedProxyConfig, _ int) bool {
return proxy.GetBaseConfig().Name != typedProxyCfgs[0].GetBaseConfig().Name
})
oldCfg.Proxies = append(oldCfg.Proxies, typedProxyCfgs...)
if err := clientEntity.SetConfigContent(*oldCfg); err != nil {
logger.Logger(c).WithError(err).Errorf("cannot set client config, id: [%s]", clientID)
return nil, err
}
}
rawCfg, err := clientEntity.MarshalJSONConfig()
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot marshal client config, id: [%s]", clientID)
return nil, err
}
_, err = client.UpdateFrpcHander(c, &pb.UpdateFRPCRequest{
ClientId: &clientEntity.ClientID,
ServerId: &serverID,
Config: rawCfg,
Comment: &clientEntity.Comment,
})
if err != nil {
logger.Logger(c).WithError(err).Errorf("cannot update frpc, id: [%s]", clientID)
return nil, err
}
return &pb.UpdateProxyConfigResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"},
}, nil
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/VaalaCat/frp-panel/dao"
"github.com/VaalaCat/frp-panel/models"
"github.com/VaalaCat/frp-panel/pb"
"github.com/VaalaCat/frp-panel/utils"
"github.com/google/uuid"
)
@@ -23,7 +24,7 @@ func InitServerHandler(c context.Context, req *pb.InitServerRequest) (*pb.InitSe
}, nil
}
if len(userServerID) == 0 || len(serverIP) == 0 {
if len(userServerID) == 0 || len(serverIP) == 0 || !utils.IsClientIDPermited(userServerID) {
return &pb.InitServerResponse{
Status: &pb.Status{Code: pb.RespCode_RESP_CODE_INVALID, Message: "request invalid"},
}, nil

View File

@@ -16,7 +16,7 @@ func PushProxyInfo(ctx context.Context, req *pb.PushProxyInfoReq) (*pb.PushProxy
return nil, err
}
if err = dao.AdminUpdateProxy(srv, req.GetProxyInfos()); err != nil {
if err = dao.AdminUpdateProxyStats(srv, req.GetProxyInfos()); err != nil {
return nil, err
}
return &pb.PushProxyInfoResp{

View File

@@ -28,7 +28,7 @@ func UpdateFrpsHander(ctx context.Context, req *pb.UpdateFRPSRequest) (*pb.Updat
if cli := tunnel.GetServerController().Get(serverID); cli != nil {
if !reflect.DeepEqual(cli.GetCommonCfg(), s) {
cli.Stop()
tunnel.GetClientController().Delete(serverID)
tunnel.GetServerController().Delete(serverID)
logger.Logger(ctx).Infof("server %s config changed, will recreate it", serverID)
} else {
logger.Logger(ctx).Infof("server %s config not changed", serverID)

View File

@@ -74,6 +74,10 @@ func initDatabase(c context.Context) {
logger.Logger(c).Infof("start to init database, type: %s", conf.Get().DB.Type)
models.MustInitDBManager(nil, conf.Get().DB.Type)
if conf.Get().IsDebug {
models.GetDBManager().SetDebug(true)
}
switch conf.Get().DB.Type {
case "sqlite3":
if sqlitedb, err := gorm.Open(sqlite.Open(conf.Get().DB.DSN), &gorm.Config{}); err != nil {

View File

@@ -14,6 +14,10 @@ func GlobalClientID(username, clientType, clientID string) string {
return fmt.Sprintf("%s.%s.%s", username, clientType, clientID)
}
func ShadowedClientID(clientID string, shadowCount int64) string {
return fmt.Sprintf("%s@%d", clientID, shadowCount)
}
func Wrapper[T ReqType, U RespType](handler func(context.Context, *T) (*U, error)) func(c *gin.Context) {
return func(c *gin.Context) {
req, err := GetProtoRequest[T](c)

View File

@@ -22,7 +22,9 @@ type ReqType interface {
pb.GetPlatformInfoRequest | pb.GetClientsStatusRequest |
pb.GetClientCertRequest |
pb.StartFRPCRequest | pb.StopFRPCRequest | pb.StartFRPSRequest | pb.StopFRPSRequest |
pb.GetProxyByCIDRequest | pb.GetProxyBySIDRequest
pb.GetProxyStatsByClientIDRequest | pb.GetProxyStatsByServerIDRequest |
pb.CreateProxyConfigRequest | pb.ListProxyConfigsRequest | pb.UpdateProxyConfigRequest |
pb.DeleteProxyConfigRequest | pb.GetProxyConfigRequest
}
func GetProtoRequest[T ReqType](c *gin.Context) (r *T, err error) {
@@ -47,60 +49,14 @@ func GetProtoRequest[T ReqType](c *gin.Context) (r *T, err error) {
}
func GetServerMessageRequest[T ReqType](b []byte, r *T, trans func(b []byte, m protoreflect.ProtoMessage) error) (err error) {
switch ptr := any(r).(type) {
case *pb.CommonRequest:
return trans(b, ptr)
case *pb.UpdateFRPCRequest:
return trans(b, ptr)
case *pb.RemoveFRPCRequest:
return trans(b, ptr)
case *pb.UpdateFRPSRequest:
return trans(b, ptr)
case *pb.RemoveFRPSRequest:
return trans(b, ptr)
case *pb.RegisterRequest:
return trans(b, ptr)
case *pb.LoginRequest:
return trans(b, ptr)
case *pb.InitClientRequest:
return trans(b, ptr)
case *pb.ListClientsRequest:
return trans(b, ptr)
case *pb.GetClientRequest:
return trans(b, ptr)
case *pb.DeleteClientRequest:
return trans(b, ptr)
case *pb.InitServerRequest:
return trans(b, ptr)
case *pb.ListServersRequest:
return trans(b, ptr)
case *pb.GetServerRequest:
return trans(b, ptr)
case *pb.DeleteServerRequest:
return trans(b, ptr)
case *pb.GetUserInfoRequest:
return trans(b, ptr)
case *pb.UpdateUserInfoRequest:
return trans(b, ptr)
case *pb.GetPlatformInfoRequest:
return trans(b, ptr)
case *pb.GetClientsStatusRequest:
return trans(b, ptr)
case *pb.GetClientCertRequest:
return trans(b, ptr)
case *pb.StartFRPCRequest:
return trans(b, ptr)
case *pb.StopFRPCRequest:
return trans(b, ptr)
case *pb.StartFRPSRequest:
return trans(b, ptr)
case *pb.StopFRPSRequest:
return trans(b, ptr)
case *pb.GetProxyByCIDRequest:
return trans(b, ptr)
case *pb.GetProxyBySIDRequest:
return trans(b, ptr)
default:
msg, ok := any(r).(protoreflect.ProtoMessage)
if !ok {
return fmt.Errorf("type does not implement protoreflect.ProtoMessage")
}
return fmt.Errorf("cannot unmarshal unknown type: %T", r)
err = trans(b, msg)
if err != nil {
return err
}
return nil
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/VaalaCat/frp-panel/pb"
"github.com/gin-gonic/gin"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
type RespType interface {
@@ -21,7 +22,9 @@ type RespType interface {
pb.GetPlatformInfoResponse | pb.GetClientsStatusResponse |
pb.GetClientCertResponse |
pb.StartFRPCResponse | pb.StopFRPCResponse | pb.StartFRPSResponse | pb.StopFRPSResponse |
pb.GetProxyByCIDResponse | pb.GetProxyBySIDResponse
pb.GetProxyStatsByClientIDResponse | pb.GetProxyStatsByServerIDResponse |
pb.CreateProxyConfigResponse | pb.ListProxyConfigsResponse | pb.UpdateProxyConfigResponse |
pb.DeleteProxyConfigResponse | pb.GetProxyConfigResponse
}
func OKResp[T RespType](c *gin.Context, origin *T) {
@@ -53,89 +56,45 @@ func ErrUnAuthorized(c *gin.Context, err string) {
}
func ProtoResp[T RespType](origin *T) (*pb.ClientMessage, error) {
event, msg, err := getEvent(origin)
if err != nil {
return nil, err
}
rawData, err := proto.Marshal(msg)
if err != nil {
return nil, err
}
return &pb.ClientMessage{
Event: event,
Data: rawData,
}, nil
}
func getEvent(origin interface{}) (pb.Event, protoreflect.ProtoMessage, error) {
switch ptr := any(origin).(type) {
case *pb.CommonResponse:
rawData, err := proto.Marshal(ptr)
if err != nil {
return nil, err
}
return &pb.ClientMessage{
Event: pb.Event_EVENT_DATA,
Data: rawData,
}, nil
return pb.Event_EVENT_DATA, ptr, nil
case *pb.UpdateFRPCResponse:
rawData, err := proto.Marshal(ptr)
if err != nil {
return nil, err
}
return &pb.ClientMessage{
Event: pb.Event_EVENT_UPDATE_FRPC,
Data: rawData,
}, nil
return pb.Event_EVENT_UPDATE_FRPC, ptr, nil
case *pb.RemoveFRPCResponse:
rawData, err := proto.Marshal(ptr)
if err != nil {
return nil, err
}
return &pb.ClientMessage{
Event: pb.Event_EVENT_REMOVE_FRPC,
Data: rawData,
}, nil
return pb.Event_EVENT_REMOVE_FRPC, ptr, nil
case *pb.UpdateFRPSResponse:
rawData, err := proto.Marshal(ptr)
if err != nil {
return nil, err
}
return &pb.ClientMessage{
Event: pb.Event_EVENT_UPDATE_FRPC,
Data: rawData,
}, nil
return pb.Event_EVENT_UPDATE_FRPC, ptr, nil
case *pb.RemoveFRPSResponse:
rawData, err := proto.Marshal(ptr)
if err != nil {
return nil, err
}
return &pb.ClientMessage{
Event: pb.Event_EVENT_REMOVE_FRPC,
Data: rawData,
}, nil
return pb.Event_EVENT_REMOVE_FRPC, ptr, nil
case *pb.StartFRPCResponse:
rawData, err := proto.Marshal(ptr)
if err != nil {
return nil, err
}
return &pb.ClientMessage{
Event: pb.Event_EVENT_START_FRPC,
Data: rawData,
}, nil
return pb.Event_EVENT_START_FRPC, ptr, nil
case *pb.StopFRPCResponse:
rawData, err := proto.Marshal(ptr)
if err != nil {
return nil, err
}
return &pb.ClientMessage{
Event: pb.Event_EVENT_STOP_FRPC,
Data: rawData,
}, nil
return pb.Event_EVENT_STOP_FRPC, ptr, nil
case *pb.StartFRPSResponse:
rawData, err := proto.Marshal(ptr)
if err != nil {
return nil, err
}
return &pb.ClientMessage{
Event: pb.Event_EVENT_START_FRPS,
Data: rawData,
}, nil
return pb.Event_EVENT_START_FRPS, ptr, nil
case *pb.StopFRPSResponse:
rawData, err := proto.Marshal(ptr)
if err != nil {
return nil, err
}
return &pb.ClientMessage{
Event: pb.Event_EVENT_STOP_FRPS,
Data: rawData,
}, nil
return pb.Event_EVENT_STOP_FRPS, ptr, nil
case *pb.GetProxyConfigResponse:
return pb.Event_EVENT_GET_PROXY_INFO, ptr, nil
default:
return 0, nil, fmt.Errorf("cannot unmarshal unknown type: %T", origin)
}
return nil, fmt.Errorf("cannot unmarshal unknown type: %T", origin)
}

View File

@@ -49,6 +49,7 @@ type Config struct {
ID string `env:"ID" env-description:"client id"`
Secret string `env:"SECRET" env-description:"client secret"`
} `env-prefix:"CLIENT_"`
IsDebug bool `env:"IS_DEBUG" env-default:"false" env-description:"is debug mode"`
}
var (

View File

@@ -5,6 +5,7 @@ import (
"github.com/VaalaCat/frp-panel/models"
"github.com/samber/lo"
"gorm.io/gorm"
)
func ValidateClientSecret(clientID, clientSecret string) (*models.ClientEntity, error) {
@@ -63,6 +64,56 @@ func GetClientByClientID(userInfo models.UserInfo, clientID string) (*models.Cli
return c.ClientEntity, nil
}
func GetClientByFilter(userInfo models.UserInfo, client *models.ClientEntity, shadow *bool) (*models.ClientEntity, error) {
db := models.GetDBManager().GetDefaultDB()
filter := &models.ClientEntity{}
if len(client.ClientID) != 0 {
filter.ClientID = client.ClientID
}
if len(client.OriginClientID) != 0 {
filter.OriginClientID = client.OriginClientID
}
if len(client.ConnectSecret) != 0 {
filter.ConnectSecret = client.ConnectSecret
}
if len(client.ServerID) != 0 {
filter.ServerID = client.ServerID
}
if shadow != nil {
filter.IsShadow = *shadow
}
c := &models.Client{}
err := db.
Where(&models.Client{ClientEntity: &models.ClientEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
}}).
Where(filter).
First(c).Error
if err != nil {
return nil, err
}
return c.ClientEntity, nil
}
func GetClientByOriginClientID(originClientID string) (*models.ClientEntity, error) {
if originClientID == "" {
return nil, fmt.Errorf("invalid origin client id")
}
db := models.GetDBManager().GetDefaultDB()
c := &models.Client{}
err := db.
Where(&models.Client{ClientEntity: &models.ClientEntity{
OriginClientID: originClientID,
}}).
First(c).Error
if err != nil {
return nil, err
}
return c.ClientEntity, nil
}
func CreateClient(userInfo models.UserInfo, client *models.ClientEntity) error {
client.UserID = userInfo.GetUserID()
client.TenantID = userInfo.GetTenantID()
@@ -84,11 +135,13 @@ func DeleteClient(userInfo models.UserInfo, clientID string) error {
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
},
}).Delete(&models.Client{
}).Or(&models.Client{
ClientEntity: &models.ClientEntity{
ClientID: clientID,
OriginClientID: clientID,
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
},
}).Error
}).Delete(&models.Client{}).Error
}
func UpdateClient(userInfo models.UserInfo, client *models.ClientEntity) error {
@@ -118,7 +171,14 @@ func ListClients(userInfo models.UserInfo, page, pageSize int) ([]*models.Client
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
},
}).Offset(offset).Limit(pageSize).Find(&clients).Error
}).
Where(
db.Where(
normalClientFilter(db),
).Or(
"is_shadow = ?", true,
),
).Offset(offset).Limit(pageSize).Find(&clients).Error
if err != nil {
return nil, err
}
@@ -129,6 +189,8 @@ func ListClients(userInfo models.UserInfo, page, pageSize int) ([]*models.Client
}
func ListClientsWithKeyword(userInfo models.UserInfo, page, pageSize int, keyword string) ([]*models.ClientEntity, error) {
// 只获取没shadow且config有东西
// 或isShadow的client
if page < 1 || pageSize < 1 || len(keyword) == 0 {
return nil, fmt.Errorf("invalid page or page size or keyword")
}
@@ -137,12 +199,13 @@ func ListClientsWithKeyword(userInfo models.UserInfo, page, pageSize int, keywor
offset := (page - 1) * pageSize
var clients []*models.Client
err := db.Where(&models.Client{
ClientEntity: &models.ClientEntity{
err := db.Where("client_id like ?", "%"+keyword+"%").
Where(&models.Client{ClientEntity: &models.ClientEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
},
}).Where("client_id like ?", "%"+keyword+"%").Offset(offset).Limit(pageSize).Find(&clients).Error
}}).
Where(normalClientFilter(db)).
Offset(offset).Limit(pageSize).Find(&clients).Error
if err != nil {
return nil, err
}
@@ -178,7 +241,8 @@ func CountClients(userInfo models.UserInfo) (int64, error) {
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
},
}).Count(&count).Error
}).
Where(normalClientFilter(db)).Count(&count).Error
if err != nil {
return 0, err
}
@@ -193,7 +257,8 @@ func CountClientsWithKeyword(userInfo models.UserInfo, keyword string) (int64, e
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
},
}).Where("client_id like ?", "%"+keyword+"%").Count(&count).Error
}).
Where(normalClientFilter(db)).Where("client_id like ?", "%"+keyword+"%").Count(&count).Error
if err != nil {
return 0, err
}
@@ -209,9 +274,23 @@ func CountConfiguredClients(userInfo models.UserInfo) (int64, error) {
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
}}).
Not(&models.Client{
Where(normalClientFilter(db)).
Count(&count).Error
if err != nil {
return 0, err
}
return count, nil
}
func CountClientsInShadow(userInfo models.UserInfo, clientID string) (int64, error) {
db := models.GetDBManager().GetDefaultDB()
var count int64
err := db.Model(&models.Client{}).
Where(&models.Client{
ClientEntity: &models.ClientEntity{
ConfigContent: []byte{},
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
OriginClientID: clientID,
}}).
Count(&count).Error
if err != nil {
@@ -219,3 +298,43 @@ func CountConfiguredClients(userInfo models.UserInfo) (int64, error) {
}
return count, nil
}
func GetClientIDsInShadowByClientID(userInfo models.UserInfo, clientID string) ([]string, error) {
db := models.GetDBManager().GetDefaultDB()
var clients []*models.Client
err := db.Where(&models.Client{
ClientEntity: &models.ClientEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
OriginClientID: clientID,
}}).Find(&clients).Error
if err != nil {
return nil, err
}
return lo.Map(clients, func(c *models.Client, _ int) string {
return c.ClientID
}), nil
}
func AdminGetClientIDsInShadowByClientID(clientID string) ([]string, error) {
db := models.GetDBManager().GetDefaultDB()
var clients []*models.Client
err := db.Where(&models.Client{
ClientEntity: &models.ClientEntity{
OriginClientID: clientID,
}}).Find(&clients).Error
if err != nil {
return nil, err
}
return lo.Map(clients, func(c *models.Client, _ int) string {
return c.ClientID
}), nil
}
func normalClientFilter(db *gorm.DB) *gorm.DB {
// 1. 没shadow过的老client
// 2. shadow过的shadow client
return db.Where("origin_client_id is NULL").
Or("is_shadow = ?", true).
Or("LENGTH(origin_client_id) = ?", 0)
}

View File

@@ -1,10 +1,12 @@
package dao
import (
"context"
"fmt"
"strings"
"time"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/models"
"github.com/VaalaCat/frp-panel/pb"
"github.com/VaalaCat/frp-panel/utils"
@@ -14,44 +16,54 @@ import (
"gorm.io/gorm/clause"
)
func GetProxyByClientID(userInfo models.UserInfo, clientID string) ([]*models.ProxyEntity, error) {
func GetProxyStatsByClientID(userInfo models.UserInfo, clientID string) ([]*models.ProxyStatsEntity, error) {
if clientID == "" {
return nil, fmt.Errorf("invalid client id")
}
db := models.GetDBManager().GetDefaultDB()
list := []*models.Proxy{}
list := []*models.ProxyStats{}
err := db.
Where(&models.Proxy{ProxyEntity: &models.ProxyEntity{
Where(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ClientID: clientID,
}}).
Or(&models.Proxy{ProxyEntity: &models.ProxyEntity{
Or(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: 0,
TenantID: userInfo.GetTenantID(),
ClientID: clientID,
}}).
Or(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
OriginClientID: clientID,
}}).
Or(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: 0,
TenantID: userInfo.GetTenantID(),
OriginClientID: clientID,
}}).
Find(&list).Error
if err != nil {
return nil, err
}
return lo.Map(list, func(item *models.Proxy, _ int) *models.ProxyEntity {
return item.ProxyEntity
return lo.Map(list, func(item *models.ProxyStats, _ int) *models.ProxyStatsEntity {
return item.ProxyStatsEntity
}), nil
}
func GetProxyByServerID(userInfo models.UserInfo, serverID string) ([]*models.ProxyEntity, error) {
func GetProxyStatsByServerID(userInfo models.UserInfo, serverID string) ([]*models.ProxyStatsEntity, error) {
if serverID == "" {
return nil, fmt.Errorf("invalid server id")
}
db := models.GetDBManager().GetDefaultDB()
list := []*models.Proxy{}
list := []*models.ProxyStats{}
err := db.
Where(&models.Proxy{ProxyEntity: &models.ProxyEntity{
Where(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ServerID: serverID,
}}).Or(&models.Proxy{ProxyEntity: &models.ProxyEntity{
}}).Or(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: 0,
TenantID: userInfo.GetTenantID(),
ServerID: serverID,
@@ -60,12 +72,12 @@ func GetProxyByServerID(userInfo models.UserInfo, serverID string) ([]*models.Pr
if err != nil {
return nil, err
}
return lo.Map(list, func(item *models.Proxy, _ int) *models.ProxyEntity {
return item.ProxyEntity
return lo.Map(list, func(item *models.ProxyStats, _ int) *models.ProxyStatsEntity {
return item.ProxyStatsEntity
}), nil
}
func AdminUpdateProxy(srv *models.ServerEntity, inputs []*pb.ProxyInfo) error {
func AdminUpdateProxyStats(srv *models.ServerEntity, inputs []*pb.ProxyInfo) error {
if srv.ServerID == "" {
return fmt.Errorf("invalid server id")
}
@@ -105,15 +117,15 @@ func AdminUpdateProxy(srv *models.ServerEntity, inputs []*pb.ProxyInfo) error {
)
p.Go(
func() error {
oldProxy := []*models.Proxy{}
oldProxy := []*models.ProxyStats{}
if err := tx.
Where(&models.Proxy{ProxyEntity: &models.ProxyEntity{
Where(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: srv.UserID,
ServerID: srv.ServerID,
}}).Find(&oldProxy).Error; err != nil {
return err
}
oldProxyMap := lo.SliceToMap(oldProxy, func(p *models.Proxy) (string, *models.Proxy) {
oldProxyMap := lo.SliceToMap(oldProxy, func(p *models.ProxyStats) (string, *models.ProxyStats) {
return p.Name, p
})
queryResults[2] = oldProxyMap
@@ -126,16 +138,16 @@ func AdminUpdateProxy(srv *models.ServerEntity, inputs []*pb.ProxyInfo) error {
user := queryResults[0].(models.User)
clients := queryResults[1].([]*models.Client)
oldProxyMap := queryResults[2].(map[string]*models.Proxy)
oldProxyMap := queryResults[2].(map[string]*models.ProxyStats)
inputMap := map[string]*pb.ProxyInfo{}
proxyMap := map[string]*models.ProxyEntity{}
proxyMap := map[string]*models.ProxyStatsEntity{}
for _, proxyInfo := range inputs {
if proxyInfo == nil {
continue
}
proxyName := strings.TrimPrefix(proxyInfo.GetName(), user.UserName+".")
proxyMap[proxyName] = &models.ProxyEntity{
proxyMap[proxyName] = &models.ProxyStatsEntity{
ServerID: srv.ServerID,
Name: proxyName,
Type: proxyInfo.GetType(),
@@ -147,7 +159,7 @@ func AdminUpdateProxy(srv *models.ServerEntity, inputs []*pb.ProxyInfo) error {
inputMap[proxyName] = proxyInfo
}
proxyEntityMap := map[string]*models.ProxyEntity{}
proxyEntityMap := map[string]*models.ProxyStatsEntity{}
for _, client := range clients {
cliCfg, err := client.GetConfigContent()
if err != nil || cliCfg == nil {
@@ -156,15 +168,16 @@ func AdminUpdateProxy(srv *models.ServerEntity, inputs []*pb.ProxyInfo) error {
for _, cfg := range cliCfg.Proxies {
if proxy, ok := proxyMap[cfg.GetBaseConfig().Name]; ok {
proxy.ClientID = client.ClientID
proxy.OriginClientID = client.OriginClientID
proxyEntityMap[proxy.Name] = proxy
}
}
}
nowTime := time.Now()
results := lo.Values(lo.MapValues(proxyEntityMap, func(p *models.ProxyEntity, name string) *models.Proxy {
item := &models.Proxy{
ProxyEntity: p,
results := lo.Values(lo.MapValues(proxyEntityMap, func(p *models.ProxyStatsEntity, name string) *models.ProxyStats {
item := &models.ProxyStats{
ProxyStatsEntity: p,
}
if oldProxy, ok := oldProxyMap[name]; ok {
item.ProxyID = oldProxy.ProxyID
@@ -188,31 +201,346 @@ func AdminUpdateProxy(srv *models.ServerEntity, inputs []*pb.ProxyInfo) error {
})
}
func AdminGetTenantProxies(tenantID int) ([]*models.ProxyEntity, error) {
func AdminGetTenantProxyStats(tenantID int) ([]*models.ProxyStatsEntity, error) {
db := models.GetDBManager().GetDefaultDB()
list := []*models.Proxy{}
list := []*models.ProxyStats{}
err := db.
Where(&models.Proxy{ProxyEntity: &models.ProxyEntity{
Where(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
TenantID: tenantID,
}}).
Find(&list).Error
if err != nil {
return nil, err
}
return lo.Map(list, func(item *models.Proxy, _ int) *models.ProxyEntity {
return item.ProxyEntity
return lo.Map(list, func(item *models.ProxyStats, _ int) *models.ProxyStatsEntity {
return item.ProxyStatsEntity
}), nil
}
func AdminGetAllProxies(tx *gorm.DB) ([]*models.ProxyEntity, error) {
func AdminGetAllProxyStats(tx *gorm.DB) ([]*models.ProxyStatsEntity, error) {
db := tx
list := []*models.Proxy{}
list := []*models.ProxyStats{}
err := db.Clauses(clause.Locking{Strength: "UPDATE"}).
Find(&list).Error
if err != nil {
return nil, err
}
return lo.Map(list, func(item *models.Proxy, _ int) *models.ProxyEntity {
return item.ProxyEntity
return lo.Map(list, func(item *models.ProxyStats, _ int) *models.ProxyStatsEntity {
return item.ProxyStatsEntity
}), nil
}
func AdminCreateProxyConfig(proxyCfg *models.ProxyConfig) error {
db := models.GetDBManager().GetDefaultDB()
return db.Create(proxyCfg).Error
}
// RebuildProxyConfigFromClient rebuild proxy from client
func RebuildProxyConfigFromClient(userInfo models.UserInfo, client *models.Client) error {
db := models.GetDBManager().GetDefaultDB()
pxyCfgs, err := utils.LoadProxiesFromContent(client.ConfigContent)
if err != nil {
return err
}
proxyConfigEntities := []*models.ProxyConfig{}
for _, pxyCfg := range pxyCfgs {
proxyCfg := &models.ProxyConfig{
ProxyConfigEntity: &models.ProxyConfigEntity{},
}
if oldProxyCfg, err := GetProxyConfigByOriginClientIDAndName(userInfo, client.ClientID, pxyCfg.GetBaseConfig().Name); err == nil {
logger.Logger(context.Background()).WithError(err).Warnf("proxy config already exist, will be override, clientID: [%s], name: [%s]",
client.ClientID, pxyCfg.GetBaseConfig().Name)
proxyCfg.Model = oldProxyCfg.Model
}
if err := proxyCfg.FillClientConfig(client.ClientEntity); err != nil {
return err
}
if err := proxyCfg.FillTypedProxyConfig(pxyCfg); err != nil {
return err
}
proxyConfigEntities = append(proxyConfigEntities, proxyCfg)
}
if err := DeleteProxyConfigsByClientIDOrOriginClientID(userInfo, client.ClientID); err != nil {
return err
}
if len(proxyConfigEntities) == 0 {
return nil
}
return db.Save(proxyConfigEntities).Error
}
func AdminGetProxyConfigByClientIDAndName(clientID string, name string) (*models.ProxyConfig, error) {
db := models.GetDBManager().GetDefaultDB()
proxyCfg := &models.ProxyConfig{}
err := db.
Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
ClientID: clientID,
Name: name,
}}).
First(proxyCfg).Error
if err != nil {
return nil, err
}
return proxyCfg, nil
}
func GetProxyConfigsByClientID(userInfo models.UserInfo, clientID string) ([]*models.ProxyConfigEntity, error) {
if clientID == "" {
return nil, fmt.Errorf("invalid client id")
}
db := models.GetDBManager().GetDefaultDB()
list := []*models.ProxyConfig{}
err := db.
Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ClientID: clientID,
}}).
Find(&list).Error
if err != nil {
return nil, err
}
return lo.Map(list, func(item *models.ProxyConfig, _ int) *models.ProxyConfigEntity {
return item.ProxyConfigEntity
}), nil
}
func GetProxyConfigByFilter(userInfo models.UserInfo, proxyConfig *models.ProxyConfigEntity) (*models.ProxyConfig, error) {
db := models.GetDBManager().GetDefaultDB()
filter := &models.ProxyConfigEntity{}
if len(proxyConfig.ClientID) != 0 {
filter.ClientID = proxyConfig.ClientID
}
if len(proxyConfig.OriginClientID) != 0 {
filter.OriginClientID = proxyConfig.OriginClientID
}
if len(proxyConfig.Name) != 0 {
filter.Name = proxyConfig.Name
}
if len(proxyConfig.Type) != 0 {
filter.Type = proxyConfig.Type
}
if len(proxyConfig.ServerID) != 0 {
filter.ServerID = proxyConfig.ServerID
}
filter.UserID = userInfo.GetUserID()
filter.TenantID = userInfo.GetTenantID()
respProxyCfg := &models.ProxyConfig{}
err := db.
Where(&models.ProxyConfig{ProxyConfigEntity: filter}).
First(respProxyCfg).Error
if err != nil {
return nil, err
}
return respProxyCfg, nil
}
func ListProxyConfigsWithFilters(userInfo models.UserInfo, page, pageSize int, filters *models.ProxyConfigEntity) ([]*models.ProxyConfig, error) {
if page < 1 || pageSize < 1 {
return nil, fmt.Errorf("invalid page or page size")
}
db := models.GetDBManager().GetDefaultDB()
offset := (page - 1) * pageSize
filters.UserID = userInfo.GetUserID()
filters.TenantID = userInfo.GetTenantID()
var proxyConfigs []*models.ProxyConfig
err := db.Where(&models.ProxyConfig{
ProxyConfigEntity: filters,
}).Where(filters).Offset(offset).Limit(pageSize).Find(&proxyConfigs).Error
if err != nil {
return nil, err
}
return proxyConfigs, nil
}
func AdminListProxyConfigsWithFilters(filters *models.ProxyConfigEntity) ([]*models.ProxyConfig, error) {
db := models.GetDBManager().GetDefaultDB()
var proxyConfigs []*models.ProxyConfig
err := db.Where(&models.ProxyConfig{
ProxyConfigEntity: filters,
}).Where(filters).Find(&proxyConfigs).Error
if err != nil {
return nil, err
}
return proxyConfigs, nil
}
func ListProxyConfigsWithFiltersAndKeyword(userInfo models.UserInfo, page, pageSize int, filters *models.ProxyConfigEntity, keyword string) ([]*models.ProxyConfig, error) {
if page < 1 || pageSize < 1 || len(keyword) == 0 {
return nil, fmt.Errorf("invalid page or page size or keyword")
}
db := models.GetDBManager().GetDefaultDB()
offset := (page - 1) * pageSize
filters.UserID = userInfo.GetUserID()
filters.TenantID = userInfo.GetTenantID()
var proxyConfigs []*models.ProxyConfig
err := db.Where(&models.ProxyConfig{
ProxyConfigEntity: filters,
}).Where(filters).Where("name like ?", "%"+keyword+"%").Offset(offset).Limit(pageSize).Find(&proxyConfigs).Error
if err != nil {
return nil, err
}
return proxyConfigs, nil
}
func ListProxyConfigsWithKeyword(userInfo models.UserInfo, page, pageSize int, keyword string) ([]*models.ProxyConfig, error) {
return ListProxyConfigsWithFiltersAndKeyword(userInfo, page, pageSize, &models.ProxyConfigEntity{}, keyword)
}
func ListProxyConfigs(userInfo models.UserInfo, page, pageSize int) ([]*models.ProxyConfig, error) {
return ListProxyConfigsWithFilters(userInfo, page, pageSize, &models.ProxyConfigEntity{})
}
func CreateProxyConfig(userInfo models.UserInfo, proxyCfg *models.ProxyConfigEntity) error {
db := models.GetDBManager().GetDefaultDB()
proxyCfg.UserID = userInfo.GetUserID()
proxyCfg.TenantID = userInfo.GetTenantID()
return db.Create(&models.ProxyConfig{ProxyConfigEntity: proxyCfg}).Error
}
func UpdateProxyConfig(userInfo models.UserInfo, proxyCfg *models.ProxyConfig) error {
if proxyCfg.ID == 0 {
return fmt.Errorf("invalid proxy config id")
}
db := models.GetDBManager().GetDefaultDB()
proxyCfg.UserID = userInfo.GetUserID()
proxyCfg.TenantID = userInfo.GetTenantID()
return db.Where(&models.ProxyConfig{
ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ClientID: proxyCfg.ClientID,
},
Model: &gorm.Model{
ID: proxyCfg.ID,
},
}).Save(proxyCfg).Error
}
func DeleteProxyConfig(userInfo models.UserInfo, clientID, name string) error {
if clientID == "" || name == "" {
return fmt.Errorf("invalid client id or name")
}
db := models.GetDBManager().GetDefaultDB()
return db.Unscoped().
Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ClientID: clientID,
Name: name,
}}).
Delete(&models.ProxyConfig{}).Error
}
func DeleteProxyConfigsByClientIDOrOriginClientID(userInfo models.UserInfo, clientID string) error {
if clientID == "" {
return fmt.Errorf("invalid client id")
}
db := models.GetDBManager().GetDefaultDB()
return db.Unscoped().
Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ClientID: clientID,
}}).
Or(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
OriginClientID: clientID,
}}).
Delete(&models.ProxyConfig{}).Error
}
func DeleteProxyConfigsByClientID(userInfo models.UserInfo, clientID string) error {
if clientID == "" {
return fmt.Errorf("invalid client id")
}
db := models.GetDBManager().GetDefaultDB()
return db.Unscoped().
Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ClientID: clientID,
}}).
Delete(&models.ProxyConfig{}).Error
}
func GetProxyConfigByOriginClientIDAndName(userInfo models.UserInfo, clientID string, name string) (*models.ProxyConfig, error) {
if clientID == "" || name == "" {
return nil, fmt.Errorf("invalid client id or name")
}
db := models.GetDBManager().GetDefaultDB()
item := &models.ProxyConfig{}
err := db.
Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
OriginClientID: clientID,
Name: name,
}}).
First(&item).Error
if err != nil {
return nil, err
}
return item, nil
}
func CountProxyConfigs(userInfo models.UserInfo) (int64, error) {
return CountProxyConfigsWithFilters(userInfo, &models.ProxyConfigEntity{})
}
func CountProxyConfigsWithFilters(userInfo models.UserInfo, filters *models.ProxyConfigEntity) (int64, error) {
db := models.GetDBManager().GetDefaultDB()
filters.UserID = userInfo.GetUserID()
filters.TenantID = userInfo.GetTenantID()
var count int64
err := db.Model(&models.ProxyConfig{}).Where(&models.ProxyConfig{
ProxyConfigEntity: filters,
}).Count(&count).Error
if err != nil {
return 0, err
}
return count, nil
}
func CountProxyConfigsWithFiltersAndKeyword(userInfo models.UserInfo, filters *models.ProxyConfigEntity, keyword string) (int64, error) {
if len(keyword) == 0 {
return CountProxyConfigsWithFilters(userInfo, filters)
}
db := models.GetDBManager().GetDefaultDB()
filters.UserID = userInfo.GetUserID()
filters.TenantID = userInfo.GetTenantID()
var count int64
err := db.Model(&models.ProxyConfig{}).Where(&models.ProxyConfig{
ProxyConfigEntity: filters,
}).Where("name like ?", "%"+keyword+"%").Count(&count).Error
if err != nil {
return 0, err
}
return count, nil
}

1
go.mod
View File

@@ -25,6 +25,7 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/sourcegraph/conc v0.3.0
github.com/spf13/cobra v1.8.0
github.com/tiendc/go-deepcopy v1.2.0
golang.org/x/crypto v0.25.0
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.35.2

2
go.sum
View File

@@ -258,6 +258,8 @@ github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
github.com/templexxx/xorsimd v0.4.3/go.mod h1:oZQcD6RFDisW2Am58dSAGwwL6rHjbzrlu25VDqfWkQg=
github.com/tiendc/go-deepcopy v1.2.0 h1:6vCCs+qdLQHzFqY1fcPirsAWOmrLbuccilfp8UzD1Qo=
github.com/tiendc/go-deepcopy v1.2.0/go.mod h1:toXoeQoUqXOOS/X4sKuiAoSk6elIdqc0pN7MTgOOo2I=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=

View File

@@ -28,6 +28,7 @@ message ListClientsResponse {
message GetClientRequest {
optional string client_id = 1;
optional string server_id = 2;
}
message GetClientResponse {
@@ -78,11 +79,69 @@ message StartFRPCResponse {
optional common.Status status = 1;
}
message GetProxyByCIDRequest {
message GetProxyStatsByClientIDRequest {
optional string client_id = 1;
}
message GetProxyByCIDResponse {
message GetProxyStatsByClientIDResponse {
optional common.Status status = 1;
repeated common.ProxyInfo proxy_infos = 2;
}
message ListProxyConfigsRequest {
optional int32 page = 1;
optional int32 page_size = 2;
optional string keyword = 3;
optional string client_id = 4;
optional string server_id = 5;
}
message ListProxyConfigsResponse {
optional common.Status status = 1;
optional int32 total = 2;
repeated common.ProxyConfig proxy_configs = 3;
}
message CreateProxyConfigRequest {
optional string client_id = 1;
optional string server_id = 2;
optional bytes config = 3;
optional bool overwrite = 4;
}
message CreateProxyConfigResponse {
optional common.Status status = 1;
}
message DeleteProxyConfigRequest {
optional string client_id = 1;
optional string server_id = 2;
optional string name = 3;
}
message DeleteProxyConfigResponse {
optional common.Status status = 1;
}
message UpdateProxyConfigRequest {
optional string client_id = 1;
optional string server_id = 2;
optional string name = 3;
optional bytes config = 4;
}
message UpdateProxyConfigResponse {
optional common.Status status = 1;
}
message GetProxyConfigRequest {
optional string client_id = 1;
optional string server_id = 2;
optional string name = 3;
}
message GetProxyConfigResponse {
optional common.Status status = 1;
optional common.ProxyConfig proxy_config = 2;
optional common.ProxyWorkingStatus working_status = 3;
}

View File

@@ -17,7 +17,7 @@ message ClientStatus {
int32 ping = 4; // 单位为毫秒
optional ClientVersion version = 5;
optional string addr = 6;
optional int32 connect_time = 7; // 连接建立的时间
optional int64 connect_time = 7; // 连接建立的时间
}
message ClientVersion {

View File

@@ -79,11 +79,11 @@ message StartFRPSResponse {
optional common.Status status = 1;
}
message GetProxyBySIDRequest {
message GetProxyStatsByServerIDRequest {
optional string server_id = 1;
}
message GetProxyBySIDResponse {
message GetProxyStatsByServerIDResponse {
optional common.Status status = 1;
repeated common.ProxyInfo proxy_infos = 2;
}

View File

@@ -40,6 +40,8 @@ message Client {
optional string comment = 5; // 用户自定义的备注
optional string server_id = 6;
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
optional string origin_client_id = 9;
}
message Server {
@@ -72,3 +74,21 @@ message ProxyInfo {
optional int64 history_traffic_out = 8;
optional bool first_sync = 9;
}
message ProxyConfig {
optional uint32 id = 1;
optional string name = 2;
optional string type = 3;
optional string client_id = 4;
optional string server_id = 5;
optional string config = 6;
optional string origin_client_id = 7;
}
message ProxyWorkingStatus {
optional string name = 1;
optional string type = 2;
optional string status = 3;
optional string err = 4;
optional string remote_addr = 5;
}

View File

@@ -23,6 +23,7 @@ enum Event {
EVENT_START_STREAM_LOG = 15;
EVENT_STOP_STREAM_LOG = 16;
EVENT_START_PTY_CONNECT = 17;
EVENT_GET_PROXY_INFO = 18;
}
message ServerBase {

View File

@@ -15,17 +15,19 @@ type Client struct {
}
type ClientEntity struct {
ClientID string `json:"client_id" gorm:"uniqueIndex;not null;primaryKey"`
ServerID string `json:"server_id"`
TenantID int `json:"tenant_id" gorm:"not null"`
UserID int `json:"user_id" gorm:"not null"`
ConfigContent []byte `json:"config_content"`
ConnectSecret string `json:"connect_secret" gorm:"not null"`
Stopped bool `json:"stopped"`
Comment string `json:"comment"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
ClientID string `json:"client_id" gorm:"uniqueIndex;not null;primaryKey"`
ServerID string `json:"server_id"`
TenantID int `json:"tenant_id" gorm:"not null"`
UserID int `json:"user_id" gorm:"not null"`
ConfigContent []byte `json:"config_content"`
ConnectSecret string `json:"connect_secret" gorm:"not null"`
Stopped bool `json:"stopped"`
Comment string `json:"comment"`
IsShadow bool `json:"is_shadow" gorm:"index"`
OriginClientID string `json:"origin_client_id" gorm:"index"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
func (*Client) TableName() string {
@@ -61,3 +63,11 @@ func (c *ClientEntity) GetConfigContent() (*v1.ClientConfig, error) {
}
return cliCfg, err
}
func (c *ClientEntity) MarshalJSONConfig() ([]byte, error) {
cliCfg, err := c.GetConfigContent()
if err != nil {
return nil, err
}
return json.Marshal(cliCfg)
}

View File

@@ -12,12 +12,14 @@ type DBManager interface {
GetDefaultDB() *gorm.DB
SetDB(dbType string, db *gorm.DB)
RemoveDB(dbType string)
SetDebug(bool)
Init()
}
type dbManagerImpl struct {
DBs map[string]*gorm.DB // key: db type
defaultDBType string
debug bool
}
func (dbm *dbManagerImpl) Init() {
@@ -34,12 +36,15 @@ func (dbm *dbManagerImpl) Init() {
if err := db.AutoMigrate(&Cert{}); err != nil {
logger.Logger(context.Background()).WithError(err).Fatalf("cannot init db table [%s]", (&Cert{}).TableName())
}
if err := db.AutoMigrate(&Proxy{}); err != nil {
logger.Logger(context.Background()).WithError(err).Fatalf("cannot init db table [%s]", (&Proxy{}).TableName())
if err := db.AutoMigrate(&ProxyStats{}); err != nil {
logger.Logger(context.Background()).WithError(err).Fatalf("cannot init db table [%s]", (&ProxyStats{}).TableName())
}
if err := db.AutoMigrate(&HistoryProxyStats{}); err != nil {
logger.Logger(context.Background()).WithError(err).Fatalf("cannot init db table [%s]", (&HistoryProxyStats{}).TableName())
}
if err := db.AutoMigrate(&ProxyConfig{}); err != nil {
logger.Logger(context.Background()).WithError(err).Fatalf("cannot init db table [%s]", (&ProxyConfig{}).TableName())
}
}
}
@@ -83,5 +88,13 @@ func (dbm *dbManagerImpl) RemoveDB(dbType string) {
}
func (dbm *dbManagerImpl) GetDefaultDB() *gorm.DB {
return dbm.DBs[dbm.defaultDBType]
db := dbm.DBs[dbm.defaultDBType]
if dbm.debug {
return db.Debug()
}
return db
}
func (dbm *dbManagerImpl) SetDebug(debug bool) {
dbm.debug = debug
}

74
models/helper.go Normal file
View File

@@ -0,0 +1,74 @@
package models
import (
"context"
"encoding/json"
"errors"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/utils"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/tiendc/go-deepcopy"
)
func ParseProxyConfigFromClient(client *Client) ([]*ProxyConfigEntity, error) {
proxyCfg, err := utils.LoadProxiesFromContent(client.ConfigContent)
if err != nil {
return nil, err
}
resp := []*ProxyConfigEntity{}
for _, cfg := range proxyCfg {
tmpProxyEntity := &ProxyConfigEntity{}
if err := tmpProxyEntity.FillClientConfig(client.ClientEntity); err != nil {
return nil, err
}
if err := tmpProxyEntity.FillTypedProxyConfig(cfg); err != nil {
return nil, err
}
resp = append(resp, tmpProxyEntity)
}
return resp, nil
}
func BuildClientConfigFromProxyConfig(client *Client, proxyCfgs []*ProxyConfig) (*Client, error) {
if client == nil || len(proxyCfgs) == 0 {
return nil, errors.New("client or proxy config is nil")
}
resp := &Client{}
if err := deepcopy.Copy(resp, client); err != nil {
return nil, err
}
cliCfg, err := utils.LoadClientConfigNormal(client.ConfigContent, true)
if err != nil {
return nil, err
}
pxyCfgs := []v1.TypedProxyConfig{}
for _, proxyCfg := range proxyCfgs {
pxy, err := utils.LoadProxiesFromContent(proxyCfg.Content)
if err != nil {
logger.Logger(context.Background()).WithError(err).Errorf("cannot load proxy config, name: [%s]", proxyCfg.Name)
continue
}
pxyCfgs = append(pxyCfgs, pxy...)
}
cliCfg.Proxies = pxyCfgs
cliCfgBytes, err := json.Marshal(cliCfg)
if err != nil {
return nil, err
}
client.ConfigContent = cliCfgBytes
return client, nil
}

48
models/proxy_config.go Normal file
View File

@@ -0,0 +1,48 @@
package models
import (
"fmt"
v1 "github.com/fatedier/frp/pkg/config/v1"
"gorm.io/gorm"
)
type ProxyConfig struct {
*gorm.Model
*ProxyConfigEntity
}
type ProxyConfigEntity struct {
ServerID string `json:"server_id" gorm:"index"`
ClientID string `json:"client_id" gorm:"index"`
Name string `json:"name" gorm:"index"`
Type string `json:"type" gorm:"index"`
UserID int `json:"user_id" gorm:"index"`
TenantID int `json:"tenant_id" gorm:"index"`
OriginClientID string `json:"origin_client_id" gorm:"index"`
Content []byte `json:"content"`
}
func (*ProxyConfig) TableName() string {
return "proxy_config"
}
func (p *ProxyConfigEntity) FillTypedProxyConfig(cfg v1.TypedProxyConfig) error {
var err error
p.Name = cfg.GetBaseConfig().Name
p.Type = cfg.GetBaseConfig().Type
p.Content, err = cfg.MarshalJSON()
return err
}
func (p *ProxyConfigEntity) FillClientConfig(cli *ClientEntity) error {
if cli == nil {
return fmt.Errorf("invalid client, client is nil")
}
p.ServerID = cli.ServerID
p.ClientID = cli.ClientID
p.UserID = cli.UserID
p.TenantID = cli.TenantID
p.OriginClientID = cli.OriginClientID
return nil
}

View File

@@ -6,14 +6,15 @@ import (
"gorm.io/gorm"
)
type Proxy struct {
*ProxyEntity
type ProxyStats struct {
*ProxyStatsEntity
}
type ProxyEntity struct {
type ProxyStatsEntity struct {
ProxyID int `json:"proxy_id" gorm:"primary_key;auto_increment"`
ServerID string `json:"server_id" gorm:"index"`
ClientID string `json:"client_id" gorm:"index"`
OriginClientID string `json:"origin_client_id" gorm:"index"`
Name string `json:"name"`
Type string `json:"type"`
UserID int `json:"user_id" gorm:"index"`
@@ -27,23 +28,24 @@ type ProxyEntity struct {
DeletedAt gorm.DeletedAt `gorm:"index"`
}
func (*Proxy) TableName() string {
return "proxies"
func (*ProxyStats) TableName() string {
return "proxy_stats"
}
// HistoryProxyStats 历史流量统计,不保证精准,只是为了展示。精准请使用 proxies 表中的 history_traffic_in/history_traffic_out
// 后续看看是否要改成时序类数据 https://github.com/nakabonne/tstorage
type HistoryProxyStats struct {
gorm.Model
ProxyID int `json:"proxy_id" gorm:"index"`
ServerID string `json:"server_id" gorm:"index"`
ClientID string `json:"client_id" gorm:"index"`
Name string `json:"name"`
Type string `json:"type"`
UserID int `json:"user_id" gorm:"index"`
TenantID int `json:"tenant_id" gorm:"index"`
TrafficIn int64 `json:"traffic_in"`
TrafficOut int64 `json:"traffic_out"`
ProxyID int `json:"proxy_id" gorm:"index"`
ServerID string `json:"server_id" gorm:"index"`
ClientID string `json:"client_id" gorm:"index"`
OriginClientID string `json:"origin_client_id" gorm:"index"`
Name string `json:"name"`
Type string `json:"type"`
UserID int `json:"user_id" gorm:"index"`
TenantID int `json:"tenant_id" gorm:"index"`
TrafficIn int64 `json:"traffic_in"`
TrafficOut int64 `json:"traffic_out"`
}
func (*HistoryProxyStats) TableName() string {

File diff suppressed because it is too large Load Diff

View File

@@ -83,7 +83,7 @@ type ClientStatus struct {
Ping int32 `protobuf:"varint,4,opt,name=ping,proto3" json:"ping,omitempty"` // 单位为毫秒
Version *ClientVersion `protobuf:"bytes,5,opt,name=version,proto3,oneof" json:"version,omitempty"`
Addr *string `protobuf:"bytes,6,opt,name=addr,proto3,oneof" json:"addr,omitempty"`
ConnectTime *int32 `protobuf:"varint,7,opt,name=connect_time,json=connectTime,proto3,oneof" json:"connect_time,omitempty"` // 连接建立的时间
ConnectTime *int64 `protobuf:"varint,7,opt,name=connect_time,json=connectTime,proto3,oneof" json:"connect_time,omitempty"` // 连接建立的时间
}
func (x *ClientStatus) Reset() {
@@ -158,7 +158,7 @@ func (x *ClientStatus) GetAddr() string {
return ""
}
func (x *ClientStatus) GetConnectTime() int32 {
func (x *ClientStatus) GetConnectTime() int64 {
if x != nil && x.ConnectTime != nil {
return *x.ConnectTime
}
@@ -493,7 +493,7 @@ var file_api_master_proto_rawDesc = []byte{
0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x06,
0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x88, 0x01, 0x01, 0x12,
0x26, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
0x07, 0x20, 0x01, 0x28, 0x05, 0x48, 0x02, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
0x07, 0x20, 0x01, 0x28, 0x03, 0x48, 0x02, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
0x54, 0x69, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x22, 0x59, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50,
0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41,

View File

@@ -828,7 +828,7 @@ func (x *StartFRPSResponse) GetStatus() *Status {
return nil
}
type GetProxyBySIDRequest struct {
type GetProxyStatsByServerIDRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
@@ -836,20 +836,20 @@ type GetProxyBySIDRequest struct {
ServerId *string `protobuf:"bytes,1,opt,name=server_id,json=serverId,proto3,oneof" json:"server_id,omitempty"`
}
func (x *GetProxyBySIDRequest) Reset() {
*x = GetProxyBySIDRequest{}
func (x *GetProxyStatsByServerIDRequest) Reset() {
*x = GetProxyStatsByServerIDRequest{}
mi := &file_api_server_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetProxyBySIDRequest) String() string {
func (x *GetProxyStatsByServerIDRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetProxyBySIDRequest) ProtoMessage() {}
func (*GetProxyStatsByServerIDRequest) ProtoMessage() {}
func (x *GetProxyBySIDRequest) ProtoReflect() protoreflect.Message {
func (x *GetProxyStatsByServerIDRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_server_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -861,19 +861,19 @@ func (x *GetProxyBySIDRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use GetProxyBySIDRequest.ProtoReflect.Descriptor instead.
func (*GetProxyBySIDRequest) Descriptor() ([]byte, []int) {
// Deprecated: Use GetProxyStatsByServerIDRequest.ProtoReflect.Descriptor instead.
func (*GetProxyStatsByServerIDRequest) Descriptor() ([]byte, []int) {
return file_api_server_proto_rawDescGZIP(), []int{16}
}
func (x *GetProxyBySIDRequest) GetServerId() string {
func (x *GetProxyStatsByServerIDRequest) GetServerId() string {
if x != nil && x.ServerId != nil {
return *x.ServerId
}
return ""
}
type GetProxyBySIDResponse struct {
type GetProxyStatsByServerIDResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
@@ -882,20 +882,20 @@ type GetProxyBySIDResponse struct {
ProxyInfos []*ProxyInfo `protobuf:"bytes,2,rep,name=proxy_infos,json=proxyInfos,proto3" json:"proxy_infos,omitempty"`
}
func (x *GetProxyBySIDResponse) Reset() {
*x = GetProxyBySIDResponse{}
func (x *GetProxyStatsByServerIDResponse) Reset() {
*x = GetProxyStatsByServerIDResponse{}
mi := &file_api_server_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetProxyBySIDResponse) String() string {
func (x *GetProxyStatsByServerIDResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetProxyBySIDResponse) ProtoMessage() {}
func (*GetProxyStatsByServerIDResponse) ProtoMessage() {}
func (x *GetProxyBySIDResponse) ProtoReflect() protoreflect.Message {
func (x *GetProxyStatsByServerIDResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_server_proto_msgTypes[17]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -907,19 +907,19 @@ func (x *GetProxyBySIDResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use GetProxyBySIDResponse.ProtoReflect.Descriptor instead.
func (*GetProxyBySIDResponse) Descriptor() ([]byte, []int) {
// Deprecated: Use GetProxyStatsByServerIDResponse.ProtoReflect.Descriptor instead.
func (*GetProxyStatsByServerIDResponse) Descriptor() ([]byte, []int) {
return file_api_server_proto_rawDescGZIP(), []int{17}
}
func (x *GetProxyBySIDResponse) GetStatus() *Status {
func (x *GetProxyStatsByServerIDResponse) GetStatus() *Status {
if x != nil {
return x.Status
}
return nil
}
func (x *GetProxyBySIDResponse) GetProxyInfos() []*ProxyInfo {
func (x *GetProxyStatsByServerIDResponse) GetProxyInfos() []*ProxyInfo {
if x != nil {
return x.ProxyInfos
}
@@ -1035,21 +1035,22 @@ var file_api_server_proto_rawDesc = []byte{
0x65, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x42, 0x09,
0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x46, 0x0a, 0x14, 0x47, 0x65, 0x74,
0x50, 0x72, 0x6f, 0x78, 0x79, 0x42, 0x79, 0x53, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x20, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x64,
0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69,
0x64, 0x22, 0x83, 0x01, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x42, 0x79,
0x53, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x00, 0x52, 0x06, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x32, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x78,
0x79, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x49, 0x6e, 0x66, 0x6f,
0x52, 0x0a, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x73, 0x42, 0x09, 0x0a, 0x07,
0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x07, 0x5a, 0x05, 0x2e, 0x2e, 0x2f, 0x70, 0x62,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x50, 0x0a, 0x1e, 0x47, 0x65, 0x74,
0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76,
0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x09, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00,
0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a,
0x0a, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x22, 0x8d, 0x01, 0x0a, 0x1f,
0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x42, 0x79, 0x53,
0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48,
0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x32, 0x0a, 0x0b,
0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79,
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x73,
0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x07, 0x5a, 0x05, 0x2e,
0x2e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -1066,27 +1067,27 @@ func file_api_server_proto_rawDescGZIP() []byte {
var file_api_server_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
var file_api_server_proto_goTypes = []any{
(*InitServerRequest)(nil), // 0: api_server.InitServerRequest
(*InitServerResponse)(nil), // 1: api_server.InitServerResponse
(*ListServersRequest)(nil), // 2: api_server.ListServersRequest
(*ListServersResponse)(nil), // 3: api_server.ListServersResponse
(*GetServerRequest)(nil), // 4: api_server.GetServerRequest
(*GetServerResponse)(nil), // 5: api_server.GetServerResponse
(*DeleteServerRequest)(nil), // 6: api_server.DeleteServerRequest
(*DeleteServerResponse)(nil), // 7: api_server.DeleteServerResponse
(*UpdateFRPSRequest)(nil), // 8: api_server.UpdateFRPSRequest
(*UpdateFRPSResponse)(nil), // 9: api_server.UpdateFRPSResponse
(*RemoveFRPSRequest)(nil), // 10: api_server.RemoveFRPSRequest
(*RemoveFRPSResponse)(nil), // 11: api_server.RemoveFRPSResponse
(*StopFRPSRequest)(nil), // 12: api_server.StopFRPSRequest
(*StopFRPSResponse)(nil), // 13: api_server.StopFRPSResponse
(*StartFRPSRequest)(nil), // 14: api_server.StartFRPSRequest
(*StartFRPSResponse)(nil), // 15: api_server.StartFRPSResponse
(*GetProxyBySIDRequest)(nil), // 16: api_server.GetProxyBySIDRequest
(*GetProxyBySIDResponse)(nil), // 17: api_server.GetProxyBySIDResponse
(*Status)(nil), // 18: common.Status
(*Server)(nil), // 19: common.Server
(*ProxyInfo)(nil), // 20: common.ProxyInfo
(*InitServerRequest)(nil), // 0: api_server.InitServerRequest
(*InitServerResponse)(nil), // 1: api_server.InitServerResponse
(*ListServersRequest)(nil), // 2: api_server.ListServersRequest
(*ListServersResponse)(nil), // 3: api_server.ListServersResponse
(*GetServerRequest)(nil), // 4: api_server.GetServerRequest
(*GetServerResponse)(nil), // 5: api_server.GetServerResponse
(*DeleteServerRequest)(nil), // 6: api_server.DeleteServerRequest
(*DeleteServerResponse)(nil), // 7: api_server.DeleteServerResponse
(*UpdateFRPSRequest)(nil), // 8: api_server.UpdateFRPSRequest
(*UpdateFRPSResponse)(nil), // 9: api_server.UpdateFRPSResponse
(*RemoveFRPSRequest)(nil), // 10: api_server.RemoveFRPSRequest
(*RemoveFRPSResponse)(nil), // 11: api_server.RemoveFRPSResponse
(*StopFRPSRequest)(nil), // 12: api_server.StopFRPSRequest
(*StopFRPSResponse)(nil), // 13: api_server.StopFRPSResponse
(*StartFRPSRequest)(nil), // 14: api_server.StartFRPSRequest
(*StartFRPSResponse)(nil), // 15: api_server.StartFRPSResponse
(*GetProxyStatsByServerIDRequest)(nil), // 16: api_server.GetProxyStatsByServerIDRequest
(*GetProxyStatsByServerIDResponse)(nil), // 17: api_server.GetProxyStatsByServerIDResponse
(*Status)(nil), // 18: common.Status
(*Server)(nil), // 19: common.Server
(*ProxyInfo)(nil), // 20: common.ProxyInfo
}
var file_api_server_proto_depIdxs = []int32{
18, // 0: api_server.InitServerResponse.status:type_name -> common.Status
@@ -1099,8 +1100,8 @@ var file_api_server_proto_depIdxs = []int32{
18, // 7: api_server.RemoveFRPSResponse.status:type_name -> common.Status
18, // 8: api_server.StopFRPSResponse.status:type_name -> common.Status
18, // 9: api_server.StartFRPSResponse.status:type_name -> common.Status
18, // 10: api_server.GetProxyBySIDResponse.status:type_name -> common.Status
20, // 11: api_server.GetProxyBySIDResponse.proxy_infos:type_name -> common.ProxyInfo
18, // 10: api_server.GetProxyStatsByServerIDResponse.status:type_name -> common.Status
20, // 11: api_server.GetProxyStatsByServerIDResponse.proxy_infos:type_name -> common.ProxyInfo
12, // [12:12] is the sub-list for method output_type
12, // [12:12] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension type_name

View File

@@ -286,12 +286,14 @@ type Client struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
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"`
Config *string `protobuf:"bytes,3,opt,name=config,proto3,oneof" json:"config,omitempty"`
Comment *string `protobuf:"bytes,5,opt,name=comment,proto3,oneof" json:"comment,omitempty"` // 用户自定义的备注
ServerId *string `protobuf:"bytes,6,opt,name=server_id,json=serverId,proto3,oneof" json:"server_id,omitempty"`
Stopped *bool `protobuf:"varint,7,opt,name=stopped,proto3,oneof" json:"stopped,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"`
Config *string `protobuf:"bytes,3,opt,name=config,proto3,oneof" json:"config,omitempty"`
Comment *string `protobuf:"bytes,5,opt,name=comment,proto3,oneof" json:"comment,omitempty"` // 用户自定义的备注
ServerId *string `protobuf:"bytes,6,opt,name=server_id,json=serverId,proto3,oneof" json:"server_id,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
OriginClientId *string `protobuf:"bytes,9,opt,name=origin_client_id,json=originClientId,proto3,oneof" json:"origin_client_id,omitempty"`
}
func (x *Client) Reset() {
@@ -366,6 +368,20 @@ func (x *Client) GetStopped() bool {
return false
}
func (x *Client) GetClientIds() []string {
if x != nil {
return x.ClientIds
}
return nil
}
func (x *Client) GetOriginClientId() string {
if x != nil && x.OriginClientId != nil {
return *x.OriginClientId
}
return ""
}
type Server struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -653,6 +669,176 @@ func (x *ProxyInfo) GetFirstSync() bool {
return false
}
type ProxyConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id *uint32 `protobuf:"varint,1,opt,name=id,proto3,oneof" json:"id,omitempty"`
Name *string `protobuf:"bytes,2,opt,name=name,proto3,oneof" json:"name,omitempty"`
Type *string `protobuf:"bytes,3,opt,name=type,proto3,oneof" json:"type,omitempty"`
ClientId *string `protobuf:"bytes,4,opt,name=client_id,json=clientId,proto3,oneof" json:"client_id,omitempty"`
ServerId *string `protobuf:"bytes,5,opt,name=server_id,json=serverId,proto3,oneof" json:"server_id,omitempty"`
Config *string `protobuf:"bytes,6,opt,name=config,proto3,oneof" json:"config,omitempty"`
OriginClientId *string `protobuf:"bytes,7,opt,name=origin_client_id,json=originClientId,proto3,oneof" json:"origin_client_id,omitempty"`
}
func (x *ProxyConfig) Reset() {
*x = ProxyConfig{}
mi := &file_common_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProxyConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProxyConfig) ProtoMessage() {}
func (x *ProxyConfig) ProtoReflect() protoreflect.Message {
mi := &file_common_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProxyConfig.ProtoReflect.Descriptor instead.
func (*ProxyConfig) Descriptor() ([]byte, []int) {
return file_common_proto_rawDescGZIP(), []int{7}
}
func (x *ProxyConfig) GetId() uint32 {
if x != nil && x.Id != nil {
return *x.Id
}
return 0
}
func (x *ProxyConfig) GetName() string {
if x != nil && x.Name != nil {
return *x.Name
}
return ""
}
func (x *ProxyConfig) GetType() string {
if x != nil && x.Type != nil {
return *x.Type
}
return ""
}
func (x *ProxyConfig) GetClientId() string {
if x != nil && x.ClientId != nil {
return *x.ClientId
}
return ""
}
func (x *ProxyConfig) GetServerId() string {
if x != nil && x.ServerId != nil {
return *x.ServerId
}
return ""
}
func (x *ProxyConfig) GetConfig() string {
if x != nil && x.Config != nil {
return *x.Config
}
return ""
}
func (x *ProxyConfig) GetOriginClientId() string {
if x != nil && x.OriginClientId != nil {
return *x.OriginClientId
}
return ""
}
type ProxyWorkingStatus struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name *string `protobuf:"bytes,1,opt,name=name,proto3,oneof" json:"name,omitempty"`
Type *string `protobuf:"bytes,2,opt,name=type,proto3,oneof" json:"type,omitempty"`
Status *string `protobuf:"bytes,3,opt,name=status,proto3,oneof" json:"status,omitempty"`
Err *string `protobuf:"bytes,4,opt,name=err,proto3,oneof" json:"err,omitempty"`
RemoteAddr *string `protobuf:"bytes,5,opt,name=remote_addr,json=remoteAddr,proto3,oneof" json:"remote_addr,omitempty"`
}
func (x *ProxyWorkingStatus) Reset() {
*x = ProxyWorkingStatus{}
mi := &file_common_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProxyWorkingStatus) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProxyWorkingStatus) ProtoMessage() {}
func (x *ProxyWorkingStatus) ProtoReflect() protoreflect.Message {
mi := &file_common_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProxyWorkingStatus.ProtoReflect.Descriptor instead.
func (*ProxyWorkingStatus) Descriptor() ([]byte, []int) {
return file_common_proto_rawDescGZIP(), []int{8}
}
func (x *ProxyWorkingStatus) GetName() string {
if x != nil && x.Name != nil {
return *x.Name
}
return ""
}
func (x *ProxyWorkingStatus) GetType() string {
if x != nil && x.Type != nil {
return *x.Type
}
return ""
}
func (x *ProxyWorkingStatus) GetStatus() string {
if x != nil && x.Status != nil {
return *x.Status
}
return ""
}
func (x *ProxyWorkingStatus) GetErr() string {
if x != nil && x.Err != nil {
return *x.Err
}
return ""
}
func (x *ProxyWorkingStatus) GetRemoteAddr() string {
if x != nil && x.RemoteAddr != nil {
return *x.RemoteAddr
}
return ""
}
var File_common_proto protoreflect.FileDescriptor
var file_common_proto_rawDesc = []byte{
@@ -672,7 +858,7 @@ var file_common_proto_rawDesc = []byte{
0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x48, 0x01, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f,
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x22,
0xfa, 0x01, 0x0a, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64,
0xdd, 0x02, 0x0a, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12,
0x1b, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48,
0x01, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06,
@@ -683,95 +869,135 @@ var file_common_proto_rawDesc = []byte{
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52, 0x08, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x73, 0x74,
0x6f, 0x70, 0x70, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x48, 0x05, 0x52, 0x07, 0x73,
0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64,
0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x5f,
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65,
0x6e, 0x74, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64,
0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x22, 0xbb, 0x01, 0x0a,
0x06, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06,
0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x06,
0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x88, 0x01, 0x01, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x70, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x02, 0x69, 0x70, 0x88, 0x01, 0x01, 0x12, 0x1b,
0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03,
0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x63,
0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52, 0x07,
0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69,
0x64, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x42, 0x05, 0x0a, 0x03,
0x5f, 0x69, 0x70, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x0a,
0x0a, 0x08, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0xd5, 0x02, 0x0a, 0x04, 0x55,
0x73, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x06, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20,
0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x06, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x88, 0x01, 0x01,
0x12, 0x1f, 0x0a, 0x08, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01,
0x28, 0x03, 0x48, 0x01, 0x52, 0x08, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x44, 0x88, 0x01,
0x01, 0x12, 0x1f, 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x08, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x88,
0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28,
0x09, 0x48, 0x03, 0x52, 0x05, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a,
0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52,
0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x52, 0x6f,
0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x05, 0x52, 0x04, 0x52, 0x6f, 0x6c, 0x65,
0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x07, 0x20, 0x01,
0x28, 0x09, 0x48, 0x06, 0x52, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x25,
0x0a, 0x0b, 0x52, 0x61, 0x77, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x08, 0x20,
0x01, 0x28, 0x09, 0x48, 0x07, 0x52, 0x0b, 0x52, 0x61, 0x77, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,
0x72, 0x64, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44,
0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x44, 0x42, 0x0b, 0x0a,
0x09, 0x5f, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x45,
0x6d, 0x61, 0x69, 0x6c, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42,
0x07, 0x0a, 0x05, 0x5f, 0x52, 0x6f, 0x6c, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x54, 0x6f, 0x6b,
0x65, 0x6e, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x52, 0x61, 0x77, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,
0x72, 0x64, 0x22, 0x84, 0x04, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x49, 0x6e, 0x66, 0x6f,
0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00,
0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x74, 0x79, 0x70,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x88,
0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,
0x64, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69,
0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x65,
0x72, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x10, 0x74, 0x6f, 0x64, 0x61, 0x79, 0x5f,
0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03,
0x48, 0x04, 0x52, 0x0e, 0x74, 0x6f, 0x64, 0x61, 0x79, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63,
0x49, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x11, 0x74, 0x6f, 0x64, 0x61, 0x79, 0x5f, 0x74,
0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03,
0x48, 0x05, 0x52, 0x0f, 0x74, 0x6f, 0x64, 0x61, 0x79, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63,
0x4f, 0x75, 0x74, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x12, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72,
0x79, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x69, 0x6e, 0x18, 0x07, 0x20, 0x01,
0x28, 0x03, 0x48, 0x06, 0x52, 0x10, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x54, 0x72, 0x61,
0x66, 0x66, 0x69, 0x63, 0x49, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x33, 0x0a, 0x13, 0x68, 0x69, 0x73,
0x74, 0x6f, 0x72, 0x79, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x6f, 0x75, 0x74,
0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x48, 0x07, 0x52, 0x11, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72,
0x79, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x4f, 0x75, 0x74, 0x88, 0x01, 0x01, 0x12, 0x22,
0x0a, 0x0a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x09, 0x20, 0x01,
0x28, 0x08, 0x48, 0x08, 0x52, 0x09, 0x66, 0x69, 0x72, 0x73, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x88,
0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x5f,
0x74, 0x79, 0x70, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f,
0x69, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64,
0x42, 0x13, 0x0a, 0x11, 0x5f, 0x74, 0x6f, 0x64, 0x61, 0x79, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66,
0x69, 0x63, 0x5f, 0x69, 0x6e, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x74, 0x6f, 0x64, 0x61, 0x79, 0x5f,
0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x6f, 0x75, 0x74, 0x42, 0x15, 0x0a, 0x13, 0x5f,
0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x69,
0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x73, 0x12, 0x2d, 0x0a, 0x10, 0x6f, 0x72, 0x69, 0x67,
0x69, 0x6e, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01,
0x28, 0x09, 0x48, 0x06, 0x52, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x43, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x42, 0x09,
0x0a, 0x07, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74,
0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x42, 0x0a,
0x0a, 0x08, 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x6f,
0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x22,
0xbb, 0x01, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12,
0x1b, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48,
0x01, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x88, 0x01, 0x01, 0x12, 0x13, 0x0a, 0x02,
0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x02, 0x69, 0x70, 0x88, 0x01,
0x01, 0x12, 0x1b, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28,
0x09, 0x48, 0x03, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x88, 0x01, 0x01, 0x12, 0x1d,
0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48,
0x04, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x42, 0x05, 0x0a,
0x03, 0x5f, 0x69, 0x64, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x42,
0x05, 0x0a, 0x03, 0x5f, 0x69, 0x70, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0xd5, 0x02,
0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x06, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44,
0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x06, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44,
0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x44, 0x18,
0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x01, 0x52, 0x08, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49,
0x44, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x08, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61,
0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x04,
0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x05, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x88, 0x01, 0x01,
0x12, 0x1b, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
0x48, 0x04, 0x52, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a,
0x04, 0x52, 0x6f, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x05, 0x52, 0x04, 0x52,
0x6f, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x06, 0x52, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x88, 0x01,
0x01, 0x12, 0x25, 0x0a, 0x0b, 0x52, 0x61, 0x77, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x07, 0x52, 0x0b, 0x52, 0x61, 0x77, 0x50, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x55, 0x73, 0x65,
0x72, 0x49, 0x44, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x44,
0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x08, 0x0a,
0x06, 0x5f, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x53, 0x74, 0x61, 0x74,
0x75, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x52, 0x6f, 0x6c, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x52, 0x61, 0x77, 0x50, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x84, 0x04, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x49,
0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x04, 0x74, 0x79,
0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f,
0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65,
0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x08, 0x73, 0x65,
0x72, 0x76, 0x65, 0x72, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x10, 0x74, 0x6f, 0x64,
0x61, 0x79, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x69, 0x6e, 0x18, 0x05, 0x20,
0x01, 0x28, 0x03, 0x48, 0x04, 0x52, 0x0e, 0x74, 0x6f, 0x64, 0x61, 0x79, 0x54, 0x72, 0x61, 0x66,
0x66, 0x69, 0x63, 0x49, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x11, 0x74, 0x6f, 0x64, 0x61,
0x79, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20,
0x01, 0x28, 0x03, 0x48, 0x05, 0x52, 0x0f, 0x74, 0x6f, 0x64, 0x61, 0x79, 0x54, 0x72, 0x61, 0x66,
0x66, 0x69, 0x63, 0x4f, 0x75, 0x74, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x12, 0x68, 0x69, 0x73,
0x74, 0x6f, 0x72, 0x79, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x69, 0x6e, 0x18,
0x07, 0x20, 0x01, 0x28, 0x03, 0x48, 0x06, 0x52, 0x10, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79,
0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x49, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x33, 0x0a, 0x13,
0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f,
0x69, 0x6e, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x74,
0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x6f, 0x75, 0x74, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x66,
0x69, 0x72, 0x73, 0x74, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x2a, 0xbc, 0x01, 0x0a, 0x08, 0x52, 0x65,
0x73, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x45, 0x53, 0x50, 0x5f, 0x43,
0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10,
0x00, 0x12, 0x15, 0x0a, 0x11, 0x52, 0x45, 0x53, 0x50, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x53,
0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x53, 0x50,
0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10,
0x02, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x53, 0x50, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x41,
0x4c, 0x52, 0x45, 0x41, 0x44, 0x59, 0x5f, 0x45, 0x58, 0x49, 0x53, 0x54, 0x53, 0x10, 0x03, 0x12,
0x15, 0x0a, 0x11, 0x52, 0x45, 0x53, 0x50, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x49, 0x4e, 0x56,
0x41, 0x4c, 0x49, 0x44, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x53, 0x50, 0x5f, 0x43,
0x4f, 0x44, 0x45, 0x5f, 0x46, 0x49, 0x4e, 0x49, 0x53, 0x48, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16,
0x52, 0x45, 0x53, 0x50, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x41, 0x55, 0x54, 0x48,
0x4f, 0x52, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x06, 0x2a, 0x55, 0x0a, 0x0a, 0x43, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54,
0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59,
0x50, 0x45, 0x5f, 0x46, 0x52, 0x50, 0x43, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4c, 0x49,
0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x52, 0x50, 0x53, 0x10, 0x02, 0x42,
0x07, 0x5a, 0x05, 0x2e, 0x2e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6f, 0x75, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x48, 0x07, 0x52, 0x11, 0x68, 0x69, 0x73,
0x74, 0x6f, 0x72, 0x79, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x4f, 0x75, 0x74, 0x88, 0x01,
0x01, 0x12, 0x22, 0x0a, 0x0a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x18,
0x09, 0x20, 0x01, 0x28, 0x08, 0x48, 0x08, 0x52, 0x09, 0x66, 0x69, 0x72, 0x73, 0x74, 0x53, 0x79,
0x6e, 0x63, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x07,
0x0a, 0x05, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x63, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x5f, 0x69, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x5f, 0x69, 0x64, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x74, 0x6f, 0x64, 0x61, 0x79, 0x5f, 0x74, 0x72,
0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x69, 0x6e, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x74, 0x6f, 0x64,
0x61, 0x79, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x6f, 0x75, 0x74, 0x42, 0x15,
0x0a, 0x13, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66,
0x69, 0x63, 0x5f, 0x69, 0x6e, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72,
0x79, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x6f, 0x75, 0x74, 0x42, 0x0d, 0x0a,
0x0b, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x22, 0xb9, 0x02, 0x0a,
0x0b, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x13, 0x0a, 0x02,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x88, 0x01,
0x01, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48,
0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x74, 0x79,
0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f,
0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76,
0x65, 0x72, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x05, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x10, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x06,
0x52, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64,
0x88, 0x01, 0x01, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e,
0x61, 0x6d, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x0c, 0x0a, 0x0a,
0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x22, 0xd5, 0x01, 0x0a, 0x12, 0x50, 0x72, 0x6f,
0x78, 0x79, 0x57, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x88, 0x01,
0x01, 0x12, 0x1b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x48, 0x02, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x15,
0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x03, 0x65,
0x72, 0x72, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52, 0x0a, 0x72, 0x65,
0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f,
0x6e, 0x61, 0x6d, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x09, 0x0a,
0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x65, 0x72, 0x72,
0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x2a, 0xbc, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a,
0x15, 0x52, 0x45, 0x53, 0x50, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45,
0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x52, 0x45, 0x53, 0x50,
0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12,
0x17, 0x0a, 0x13, 0x52, 0x45, 0x53, 0x50, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x4e, 0x4f, 0x54,
0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x53, 0x50,
0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x41, 0x4c, 0x52, 0x45, 0x41, 0x44, 0x59, 0x5f, 0x45, 0x58,
0x49, 0x53, 0x54, 0x53, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x52, 0x45, 0x53, 0x50, 0x5f, 0x43,
0x4f, 0x44, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x04, 0x12, 0x14, 0x0a,
0x10, 0x52, 0x45, 0x53, 0x50, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x49, 0x4e, 0x49, 0x53,
0x48, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, 0x52, 0x45, 0x53, 0x50, 0x5f, 0x43, 0x4f, 0x44, 0x45,
0x5f, 0x55, 0x4e, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x06, 0x2a,
0x55, 0x0a, 0x0a, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a,
0x17, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53,
0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4c,
0x49, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x52, 0x50, 0x43, 0x10, 0x01,
0x12, 0x14, 0x0a, 0x10, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
0x46, 0x52, 0x50, 0x53, 0x10, 0x02, 0x42, 0x07, 0x5a, 0x05, 0x2e, 0x2e, 0x2f, 0x70, 0x62, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -787,17 +1013,19 @@ func file_common_proto_rawDescGZIP() []byte {
}
var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_common_proto_goTypes = []any{
(RespCode)(0), // 0: common.RespCode
(ClientType)(0), // 1: common.ClientType
(*Status)(nil), // 2: common.Status
(*CommonRequest)(nil), // 3: common.CommonRequest
(*CommonResponse)(nil), // 4: common.CommonResponse
(*Client)(nil), // 5: common.Client
(*Server)(nil), // 6: common.Server
(*User)(nil), // 7: common.User
(*ProxyInfo)(nil), // 8: common.ProxyInfo
(RespCode)(0), // 0: common.RespCode
(ClientType)(0), // 1: common.ClientType
(*Status)(nil), // 2: common.Status
(*CommonRequest)(nil), // 3: common.CommonRequest
(*CommonResponse)(nil), // 4: common.CommonResponse
(*Client)(nil), // 5: common.Client
(*Server)(nil), // 6: common.Server
(*User)(nil), // 7: common.User
(*ProxyInfo)(nil), // 8: common.ProxyInfo
(*ProxyConfig)(nil), // 9: common.ProxyConfig
(*ProxyWorkingStatus)(nil), // 10: common.ProxyWorkingStatus
}
var file_common_proto_depIdxs = []int32{
0, // 0: common.Status.code:type_name -> common.RespCode
@@ -820,13 +1048,15 @@ func file_common_proto_init() {
file_common_proto_msgTypes[4].OneofWrappers = []any{}
file_common_proto_msgTypes[5].OneofWrappers = []any{}
file_common_proto_msgTypes[6].OneofWrappers = []any{}
file_common_proto_msgTypes[7].OneofWrappers = []any{}
file_common_proto_msgTypes[8].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_common_proto_rawDesc,
NumEnums: 2,
NumMessages: 7,
NumMessages: 9,
NumExtensions: 0,
NumServices: 0,
},

View File

@@ -41,6 +41,7 @@ const (
Event_EVENT_START_STREAM_LOG Event = 15
Event_EVENT_STOP_STREAM_LOG Event = 16
Event_EVENT_START_PTY_CONNECT Event = 17
Event_EVENT_GET_PROXY_INFO Event = 18
)
// Enum value maps for Event.
@@ -64,6 +65,7 @@ var (
15: "EVENT_START_STREAM_LOG",
16: "EVENT_STOP_STREAM_LOG",
17: "EVENT_START_PTY_CONNECT",
18: "EVENT_GET_PROXY_INFO",
}
Event_value = map[string]int32{
"EVENT_UNSPECIFIED": 0,
@@ -84,6 +86,7 @@ var (
"EVENT_START_STREAM_LOG": 15,
"EVENT_STOP_STREAM_LOG": 16,
"EVENT_START_PTY_CONNECT": 17,
"EVENT_GET_PROXY_INFO": 18,
}
)
@@ -1220,7 +1223,7 @@ var file_rpc_master_proto_rawDesc = []byte{
0x02, 0x52, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x88, 0x01, 0x01, 0x12, 0x12, 0x0a, 0x04, 0x64,
0x6f, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x42,
0x07, 0x0a, 0x05, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68, 0x65, 0x69,
0x67, 0x68, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x77, 0x69, 0x64, 0x74, 0x68, 0x2a, 0x9b, 0x03,
0x67, 0x68, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x77, 0x69, 0x64, 0x74, 0x68, 0x2a, 0xb5, 0x03,
0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x15, 0x0a, 0x11, 0x45, 0x56, 0x45, 0x4e, 0x54,
0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19,
0x0a, 0x15, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52,
@@ -1246,46 +1249,47 @@ var file_rpc_master_proto_rawDesc = []byte{
0x47, 0x10, 0x0f, 0x12, 0x19, 0x0a, 0x15, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x53, 0x54, 0x4f,
0x50, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x4c, 0x4f, 0x47, 0x10, 0x10, 0x12, 0x1b,
0x0a, 0x17, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x50, 0x54,
0x59, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x11, 0x32, 0xd7, 0x04, 0x0a, 0x06,
0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x0a, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
0x53, 0x65, 0x6e, 0x64, 0x12, 0x15, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6c,
0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x15, 0x2e, 0x6d, 0x61,
0x73, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x10, 0x50, 0x75, 0x6c, 0x6c, 0x43, 0x6c,
0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x73,
0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x52, 0x65, 0x73, 0x70, 0x12, 0x4d, 0x0a, 0x10, 0x50, 0x75, 0x6c, 0x6c, 0x53, 0x65, 0x72,
0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x73, 0x74,
0x65, 0x72, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e,
0x50, 0x75, 0x6c, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x52, 0x65, 0x73, 0x70, 0x12, 0x3b, 0x0a, 0x08, 0x46, 0x52, 0x50, 0x43, 0x41, 0x75, 0x74, 0x68,
0x12, 0x16, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x46, 0x52, 0x50, 0x41, 0x75, 0x74,
0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65,
0x72, 0x2e, 0x46, 0x52, 0x50, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x44, 0x0a, 0x0d, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x49, 0x6e,
0x66, 0x6f, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x73, 0x68,
0x50, 0x72, 0x6f, 0x78, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x6d,
0x59, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x11, 0x12, 0x18, 0x0a, 0x14, 0x45,
0x56, 0x45, 0x4e, 0x54, 0x5f, 0x47, 0x45, 0x54, 0x5f, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x5f, 0x49,
0x4e, 0x46, 0x4f, 0x10, 0x12, 0x32, 0xd7, 0x04, 0x0a, 0x06, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72,
0x12, 0x3e, 0x0a, 0x0a, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x15,
0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x15, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x53,
0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x28, 0x01, 0x30, 0x01,
0x12, 0x4d, 0x0a, 0x10, 0x50, 0x75, 0x6c, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75,
0x6c, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65,
0x71, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x43,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x12,
0x4d, 0x0a, 0x10, 0x50, 0x75, 0x6c, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x6c,
0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71,
0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x53, 0x65,
0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x12, 0x3b,
0x0a, 0x08, 0x46, 0x52, 0x50, 0x43, 0x41, 0x75, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x6d, 0x61, 0x73,
0x74, 0x65, 0x72, 0x2e, 0x46, 0x52, 0x50, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x46, 0x52, 0x50, 0x41,
0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0d, 0x50,
0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x6d,
0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x49,
0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x12, 0x52, 0x0a, 0x13, 0x50, 0x75, 0x73, 0x68, 0x43,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x12, 0x1e,
0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x19,
0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x72, 0x65,
0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x28, 0x01, 0x12, 0x52, 0x0a, 0x13, 0x50,
0x75, 0x73, 0x68, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c,
0x6f, 0x67, 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x73, 0x68,
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x52,
0x65, 0x71, 0x1a, 0x19, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x73, 0x68,
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x28, 0x01, 0x12,
0x44, 0x0a, 0x0a, 0x50, 0x54, 0x59, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x12, 0x18, 0x2e,
0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x54, 0x59, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
0x2e, 0x50, 0x54, 0x59, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x07, 0x5a, 0x05, 0x2e, 0x2e, 0x2f, 0x70, 0x62, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e,
0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73,
0x70, 0x12, 0x52, 0x0a, 0x13, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53,
0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65,
0x72, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65,
0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65,
0x72, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x52,
0x65, 0x73, 0x70, 0x28, 0x01, 0x12, 0x52, 0x0a, 0x13, 0x50, 0x75, 0x73, 0x68, 0x53, 0x65, 0x72,
0x76, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x12, 0x1e, 0x2e, 0x6d,
0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x6d,
0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x28, 0x01, 0x12, 0x44, 0x0a, 0x0a, 0x50, 0x54, 0x59,
0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
0x2e, 0x50, 0x54, 0x59, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x54, 0x59, 0x53, 0x65,
0x72, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42,
0x07, 0x5a, 0x05, 0x2e, 0x2e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@@ -6,13 +6,29 @@ import (
"io"
"sync"
"github.com/VaalaCat/frp-panel/common"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/pb"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
func CallClientWrapper[R common.RespType](c context.Context, clientID string, event pb.Event, req proto.Message, resp *R) error {
cresp, err := CallClient(c, clientID, event, req)
if err != nil {
return err
}
protoMsgRef, ok := any(resp).(protoreflect.ProtoMessage)
if !ok {
return fmt.Errorf("type does not implement protoreflect.ProtoMessage")
}
return proto.Unmarshal(cresp.GetData(), protoMsgRef)
}
func CallClient(c context.Context, clientID string, event pb.Event, msg proto.Message) (*pb.ClientMessage, error) {
sender := GetClientsManager().Get(clientID)
if sender == nil {

View File

@@ -69,13 +69,14 @@ func GetGlobalClientSerivce() ClientHandler {
func NewClientHandler(commonCfg *v1.ClientCommonConfig,
proxyCfgs []v1.ProxyConfigurer,
visitorCfgs []v1.VisitorConfigurer) *Client {
ctx := context.Background()
warning, err := validation.ValidateAllClientConfig(commonCfg, proxyCfgs, visitorCfgs)
if warning != nil {
logger.Logger(context.Background()).WithError(err).Warnf("validate client config warning: %+v", warning)
logger.Logger(ctx).WithError(err).Warnf("validate client config warning: %+v", warning)
}
if err != nil {
logrus.Panic(err)
logger.Logger(ctx).Panic(err)
}
log.InitLogger(commonCfg.Log.To, commonCfg.Log.Level, int(commonCfg.Log.MaxDays), commonCfg.Log.DisablePrintColor)
@@ -85,7 +86,7 @@ func NewClientHandler(commonCfg *v1.ClientCommonConfig,
VisitorCfgs: visitorCfgs,
})
if err != nil {
logrus.Panic(err)
logger.Logger(ctx).Panic(err)
}
return &Client{
@@ -98,12 +99,14 @@ func NewClientHandler(commonCfg *v1.ClientCommonConfig,
func (c *Client) Run() {
if c.running {
logger.Logger(context.Background()).Warn("client is running, skip run")
return
}
shouldGracefulClose := c.Common.Transport.Protocol == "kcp" || c.Common.Transport.Protocol == "quic"
if shouldGracefulClose {
go handleTermSignal(c.cli)
var wg conc.WaitGroup
wg.Go(func() { handleTermSignal(c.cli) })
}
c.running = true
c.done = make(chan bool)
@@ -113,15 +116,14 @@ func (c *Client) Run() {
close(c.done)
}()
wg := conc.NewWaitGroup()
wg.Go(
func() {
ctx := context.Background()
if err := c.cli.Run(ctx); err != nil {
logger.Logger(ctx).Errorf("run client error: %v", err)
}
},
)
var wg conc.WaitGroup
wg.Go(func() {
ctx := context.Background()
logger.Logger(ctx).Infof("start to run client")
if err := c.cli.Run(ctx); err != nil {
logger.Logger(ctx).Errorf("run client error: %v", err)
}
})
wg.Wait()
}

View File

@@ -1,23 +1,32 @@
package tunnel
import (
"sync"
"context"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/services/client"
"github.com/VaalaCat/frp-panel/utils"
)
type ClientController interface {
Add(clientID string, clientHandler client.ClientHandler)
Get(clientID string) client.ClientHandler
Delete(clientID string)
Set(clientID string, clientHandler client.ClientHandler)
Run(clientID string) // 不阻塞
Stop(clientID string)
Add(clientID string, serverID string, clientHandler client.ClientHandler)
Get(clientID string, serverID string) client.ClientHandler
Delete(clientID string, serverID string)
Set(clientID string, serverID string, clientHandler client.ClientHandler)
Run(clientID string, serverID string) // 不阻塞
Stop(clientID string, serverID string)
GetByClient(clientID string) *utils.SyncMap[string, client.ClientHandler]
DeleteByClient(clientID string)
RunByClient(clientID string) // 不阻塞
StopByClient(clientID string)
StopAll()
DeleteAll()
RunAll()
List() []string
}
type clientController struct {
clients *sync.Map
clients *utils.SyncMap[string, *utils.SyncMap[string, client.ClientHandler]]
}
var (
@@ -26,7 +35,7 @@ var (
func NewClientController() ClientController {
return &clientController{
clients: &sync.Map{},
clients: &utils.SyncMap[string, *utils.SyncMap[string, client.ClientHandler]]{},
}
}
@@ -37,48 +46,129 @@ func GetClientController() ClientController {
return clientControllerInstance
}
func (c *clientController) Add(clientID string, clientHandler client.ClientHandler) {
c.clients.Store(clientID, clientHandler)
func (c *clientController) Add(clientID string, serverID string, clientHandler client.ClientHandler) {
m, _ := c.clients.LoadOrStore(clientID, &utils.SyncMap[string, client.ClientHandler]{})
oldClientHandler, loaded := m.LoadAndDelete(serverID)
if loaded {
oldClientHandler.Stop()
}
m.Store(serverID, clientHandler)
}
func (c *clientController) Get(clientID string) client.ClientHandler {
func (c *clientController) Get(clientID string, serverID string) client.ClientHandler {
v, ok := c.clients.Load(clientID)
if !ok {
return nil
}
return v.(client.ClientHandler)
vv, ok := v.Load(serverID)
if !ok {
return nil
}
return vv
}
func (c *clientController) Delete(clientID string) {
c.Stop(clientID)
func (c *clientController) GetByClient(clientID string) *utils.SyncMap[string, client.ClientHandler] {
v, ok := c.clients.Load(clientID)
if !ok {
return nil
}
return v
}
func (c *clientController) Delete(clientID string, serverID string) {
c.Stop(clientID, serverID)
v, ok := c.clients.Load(clientID)
if !ok {
return
}
v.Delete(serverID)
}
func (c *clientController) DeleteByClient(clientID string) {
c.clients.Delete(clientID)
}
func (c *clientController) Set(clientID string, clientHandler client.ClientHandler) {
c.clients.Store(clientID, clientHandler)
func (c *clientController) Set(clientID string, serverID string, clientHandler client.ClientHandler) {
v, _ := c.clients.LoadOrStore(clientID, &utils.SyncMap[string, client.ClientHandler]{})
v.Store(serverID, clientHandler)
}
func (c *clientController) Run(clientID string) {
func (c *clientController) Run(clientID string, serverID string) {
ctx := context.Background()
v, ok := c.clients.Load(clientID)
if !ok {
logger.Logger(ctx).Errorf("cannot get client by clientID, clientID: [%s] serverID: [%s]", clientID, serverID)
return
}
vv, ok := v.Load(serverID)
if !ok {
logger.Logger(ctx).Errorf("cannot load client connected server, clientID: [%s] serverID: [%s]", clientID, serverID)
return
}
go vv.Run()
}
func (c *clientController) RunByClient(clientID string) {
v, ok := c.clients.Load(clientID)
if !ok {
return
}
go v.(client.ClientHandler).Run()
v.Range(func(k string, v client.ClientHandler) bool {
v.Run()
return true
})
}
func (c *clientController) Stop(clientID string) {
func (c *clientController) Stop(clientID string, serverID string) {
v, ok := c.clients.Load(clientID)
if !ok {
return
}
v.(client.ClientHandler).Stop()
vv, ok := v.Load(serverID)
if !ok {
return
}
vv.Stop()
}
func (c *clientController) StopByClient(clientID string) {
v, ok := c.clients.Load(clientID)
if !ok {
return
}
v.Range(func(k string, v client.ClientHandler) bool {
v.Stop()
return true
})
}
func (c *clientController) StopAll() {
c.clients.Range(func(k string, v *utils.SyncMap[string, client.ClientHandler]) bool {
v.Range(func(k string, v client.ClientHandler) bool {
v.Stop()
return true
})
return true
})
}
func (c *clientController) DeleteAll() {
c.clients.Range(func(k string, v *utils.SyncMap[string, client.ClientHandler]) bool {
c.DeleteByClient(k)
return true
})
c.clients = &utils.SyncMap[string, *utils.SyncMap[string, client.ClientHandler]]{}
}
func (c *clientController) RunAll() {
c.clients.Range(func(k string, v *utils.SyncMap[string, client.ClientHandler]) bool {
c.RunByClient(k)
return true
})
}
func (c *clientController) List() []string {
keys := make([]string, 0)
c.clients.Range(func(key, value interface{}) bool {
keys = append(keys, key.(string))
return true
})
return keys
return c.clients.Keys()
}

View File

@@ -1,6 +1,8 @@
package utils
import (
"fmt"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
@@ -62,3 +64,7 @@ func TransformVisitorConfigurerToMap(origin v1.VisitorConfigurer) (key string, r
r = origin
return
}
func NewProxyKey(clientID, serverID, proxyName string) string {
return fmt.Sprintf("%s/%s/%s", clientID, serverID, proxyName)
}

View File

@@ -19,6 +19,26 @@ func LoadConfigureFromContent(content []byte, c any, strict bool) error {
return config.LoadConfigure(ans, c, strict)
}
func LoadProxiesFromContent(content []byte) ([]v1.TypedProxyConfig, error) {
allCfg := &v1.ClientConfig{}
if err := LoadConfigureFromContent(content, allCfg, true); err != nil {
return nil, err
}
return allCfg.Proxies, nil
}
func LoadVisitorsFromContent(content []byte) ([]v1.TypedVisitorConfig, error) {
allCfg := &v1.ClientConfig{}
if err := LoadConfigureFromContent(content, allCfg, true); err != nil {
return nil, err
}
return allCfg.Visitors, nil
}
func LoadClientConfigNormal(content []byte, strict bool) (*v1.ClientConfig, error) {
var (
cliCfg *v1.ClientCommonConfig

23
utils/load_test.go Normal file
View File

@@ -0,0 +1,23 @@
package utils
import (
"testing"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func TestLoadConfigureFromContent(t *testing.T) {
content := []byte(`[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000`)
allCfg := &v1.ClientConfig{}
if err := LoadConfigureFromContent(content, allCfg, true); err != nil {
t.Error(err)
}
t.Errorf("%+v", allCfg)
}

21
utils/validate.go Normal file
View File

@@ -0,0 +1,21 @@
package utils
func IsClientIDPermited(clientID string) bool {
whiteListChar := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
if len(clientID) == 0 {
return false
}
chrMap := make(map[rune]bool)
for _, chr := range whiteListChar {
chrMap[chr] = true
}
for _, chr := range clientID {
if !chrMap[chr] {
return false
}
}
return true
}

40
www/api/proxy.ts Normal file
View File

@@ -0,0 +1,40 @@
import http from '@/api/http'
import { API_PATH } from '@/lib/consts'
import {
CreateProxyConfigRequest,
CreateProxyConfigResponse,
DeleteProxyConfigRequest,
DeleteProxyConfigResponse,
GetProxyConfigRequest,
GetProxyConfigResponse,
ListProxyConfigsRequest,
ListProxyConfigsResponse,
UpdateProxyConfigRequest,
UpdateProxyConfigResponse
} from '@/lib/pb/api_client'
import { BaseResponse } from '@/types/api'
export const createProxyConfig = async (req: CreateProxyConfigRequest) => {
const res = await http.post(API_PATH + '/proxy/create_config', CreateProxyConfigRequest.toJson(req))
return CreateProxyConfigResponse.fromJson((res.data as BaseResponse).body)
}
export const listProxyConfig = async (req: ListProxyConfigsRequest) => {
const res = await http.post(API_PATH + '/proxy/list_configs', ListProxyConfigsRequest.toJson(req))
return ListProxyConfigsResponse.fromJson((res.data as BaseResponse).body)
}
export const updateProxyConfig = async (req: UpdateProxyConfigRequest) => {
const res = await http.post(API_PATH + '/proxy/update_config', UpdateProxyConfigRequest.toJson(req))
return UpdateProxyConfigResponse.fromJson((res.data as BaseResponse).body)
}
export const deleteProxyConfig = async (req: DeleteProxyConfigRequest) => {
const res = await http.post(API_PATH + '/proxy/delete_config', DeleteProxyConfigRequest.toJson(req))
return DeleteProxyConfigResponse.fromJson((res.data as BaseResponse).body)
}
export const getProxyConfig = async (req: GetProxyConfigRequest) => {
const res = await http.post(API_PATH + '/proxy/get_config', GetProxyConfigRequest.toJson(req))
return GetProxyConfigResponse.fromJson((res.data as BaseResponse).body)
}

View File

@@ -1,10 +1,10 @@
import http from '@/api/http'
import { API_PATH } from '@/lib/consts'
import { GetProxyByCIDRequest, GetProxyByCIDResponse } from '@/lib/pb/api_client'
import { GetProxyBySIDRequest, GetProxyBySIDResponse } from '@/lib/pb/api_server'
import { GetProxyStatsByClientIDRequest, GetProxyStatsByClientIDResponse } from '@/lib/pb/api_client'
import { GetProxyStatsByServerIDRequest, GetProxyStatsByServerIDResponse } from '@/lib/pb/api_server'
import { BaseResponse } from '@/types/api'
export const getProxyStatsByClientID = async (req: GetProxyByCIDRequest) => {
export const getProxyStatsByClientID = async (req: GetProxyStatsByClientIDRequest) => {
// return {
// proxyInfos: [
// {
@@ -16,11 +16,11 @@ export const getProxyStatsByClientID = async (req: GetProxyByCIDRequest) => {
// },
// ],
// } as GetProxyByCIDResponse
const res = await http.post(API_PATH + '/proxy/get_by_cid', GetProxyByCIDRequest.toJson(req))
return GetProxyByCIDResponse.fromJson((res.data as BaseResponse).body)
const res = await http.post(API_PATH + '/proxy/get_by_cid', GetProxyStatsByClientIDRequest.toJson(req))
return GetProxyStatsByClientIDResponse.fromJson((res.data as BaseResponse).body)
}
export const getProxyStatsByServerID = async (req: GetProxyBySIDRequest) => {
const res = await http.post(API_PATH + '/proxy/get_by_sid', GetProxyBySIDRequest.toJson(req))
return GetProxyBySIDResponse.fromJson((res.data as BaseResponse).body)
export const getProxyStatsByServerID = async (req: GetProxyStatsByServerIDRequest) => {
const res = await http.post(API_PATH + '/proxy/get_by_sid', GetProxyStatsByServerIDRequest.toJson(req))
return GetProxyStatsByServerIDResponse.fromJson((res.data as BaseResponse).body)
}

View File

@@ -1,17 +1,6 @@
import * as React from "react"
import {
SquareTerminal,
ServerCogIcon,
ServerIcon,
MonitorSmartphoneIcon,
MonitorCogIcon,
ChartNetworkIcon,
Scroll,
} from "lucide-react"
import { NavMain } from "@/components/nav-main"
import { NavUser } from "@/components/nav-user"
import { TeamSwitcher } from "@/components/team-switcher"
import {
Sidebar,
SidebarContent,
@@ -27,7 +16,7 @@ import { RegisterAndLogin } from "./header"
import { useRouter } from "next/navigation"
import { useQuery } from "@tanstack/react-query"
import { getPlatformInfo } from "@/api/platform"
import { teams, getNavItems } from '@/config/nav'
import { getNavItems } from '@/config/nav'
import { useTranslation } from 'react-i18next'
export interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {

View File

@@ -14,7 +14,7 @@ export const ClientDetail = ({ clientStatus }: { clientStatus: ClientStatus }) =
const locale = i18n.language === 'zh' ? zhCN : enUS
const connectTime = clientStatus.connectTime ?
formatDistanceToNow(new Date(clientStatus.connectTime), {
formatDistanceToNow(new Date(parseInt(clientStatus.connectTime.toString())), {
addSuffix: true,
locale
}) : '-'

View File

@@ -67,7 +67,9 @@ export function Combobox({
variant="outline"
role="combobox"
aria-expanded={open}
className={cn("w-full justify-between font-normal", className)}
className={cn("w-full justify-between font-normal", className,
!value && "text-muted-foreground"
)}
>
{value
? (dataList.find((item) => item.value === value)?.label || value)

View File

@@ -0,0 +1,46 @@
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { LucideIcon } from "lucide-react"
export interface DropdownMenuProps {
menuGroup: {
name: string,
onClick: () => void
icon?: LucideIcon
className?: string
}[][]
title: string
trigger: React.ReactNode
}
export function BaseDropdownMenu({ menuGroup, title, trigger }: DropdownMenuProps) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
{trigger || <Button variant="outline">Open</Button>}
</DropdownMenuTrigger>
<DropdownMenuContent className="w-fit">
<DropdownMenuLabel>{title}</DropdownMenuLabel>
<DropdownMenuSeparator />
{menuGroup.map((items, id1) => (
<DropdownMenuGroup key={id1}>
{items.map((item, id2) => (
<DropdownMenuItem onClick={item.onClick} key={id2} className={item.className}>
{/* {<>{item.icon}</>} */}
{item.name}
</DropdownMenuItem>
))}
</DropdownMenuGroup>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}

View File

@@ -16,16 +16,15 @@ export const IdInput: React.FC<IdInputProps> = ({ setKeyword, keyword, refetchTr
const [input, setInput] = useState(keyword)
return (
<div className="flex flex-1 flex-row gap-2">
<div className="flex flex-row gap-2 items-center">
<Input
className="max-w-40 h-auto"
className="text-sm"
defaultValue={keyword}
placeholder={t('input.id.placeholder')}
placeholder={t('input.keyword.placeholder')}
onChange={(e) => setInput(e.target.value)}
/>
<Button
variant="outline"
size="sm"
onClick={() => {
setKeyword(input)
refetchTrigger && refetchTrigger(JSON.stringify(Math.random()))

View File

@@ -0,0 +1,70 @@
import React, { useState } from 'react';
import { Badge } from '../ui/badge';
import { Input } from '../ui/input';
import { Button } from '../ui/button';
import { useTranslation } from 'react-i18next';
interface StringListInputProps {
value: string[];
onChange: React.Dispatch<React.SetStateAction<string[]>>;
placeholder?: string;
}
const StringListInput: React.FC<StringListInputProps> = ({ value, onChange, placeholder }) => {
const { t } = useTranslation();
const [inputValue, setInputValue] = useState('');
const handleAdd = () => {
if (inputValue.trim()) {
if (value && value.includes(inputValue)) {
return;
}
if (value) {
onChange([...value, inputValue]);
} else {
onChange([inputValue]);
}
setInputValue('');
}
};
const handleRemove = (itemToRemove: string) => {
onChange(value.filter(item => item !== itemToRemove));
};
return (
<div className="mx-auto">
<div className="flex items-center mb-4">
<Input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
className="flex-1 px-4 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
placeholder={placeholder || t('input.list.placeholder')}
/>
<Button
disabled={!inputValue || value && value.includes(inputValue)}
onClick={handleAdd}
className="ml-2 px-4 py-2"
>
{t('input.list.add')}
</Button>
</div>
<div className="flex flex-wrap gap-2">
{value && value.map((item, index) => (
<Badge key={index} className='flex flex-row items-center justify-start'>{item}
<div
onClick={() => handleRemove(item)}
className="ml-1 h-4 w-4 text-center rounded-full hover:text-red-500 cursor-pointer"
>
×
</div>
</Badge>
))}
</div>
</div>
);
};
export default StringListInput;

View File

@@ -1,24 +1,26 @@
"use client"
import React from 'react'
import React, { useEffect } from 'react'
import { keepPreviousData, useQuery } from '@tanstack/react-query'
import { listServer } from '@/api/server'
import { Combobox } from './combobox'
import { useTranslation } from 'react-i18next'
import { Server } from '@/lib/pb/common'
export interface ServerSelectorProps {
serverID?: string
setServerID: (serverID: string) => void
onOpenChange?: () => void
setServer?: (server: Server) => void
}
export const ServerSelector: React.FC<ServerSelectorProps> = ({
serverID,
setServerID,
onOpenChange
onOpenChange,
setServer,
}) => {
const { t } = useTranslation()
const handleServerChange = (value: string) => { setServerID(value) }
const [keyword, setKeyword] = React.useState('')
const { data: serverList, refetch: refetchServers } = useQuery({
@@ -29,6 +31,16 @@ export const ServerSelector: React.FC<ServerSelectorProps> = ({
placeholderData: keepPreviousData,
})
const handleServerChange = (value: string) => {
setServerID(value)
}
useEffect(() => {
if (serverID) {
setServer && setServer(serverList?.servers.find((server) => server.id == serverID) || {})
}
}, [serverID])
return (
<Combobox
placeholder={t('selector.server.placeholder')}

View File

@@ -0,0 +1,57 @@
import { Server } from "@/lib/pb/common";
import { HTTPProxyConfig, TypedProxyConfig } from "@/types/proxy";
import { ServerConfig } from "@/types/server";
import { ArrowRight, Globe, Monitor } from 'lucide-react';
export function VisitPreview({ server, typedProxyConfig }: { server: Server; typedProxyConfig: TypedProxyConfig }) {
const serverCfg = JSON.parse(server?.config || '{}') as ServerConfig;
const serverAddress = server.ip || serverCfg.bindAddr || 'Unknown';
const serverPort = getServerPort(typedProxyConfig, serverCfg);
const localAddress = typedProxyConfig.localIP || '127.0.0.1';
const localPort = typedProxyConfig.localPort;
function getServerPath(httpProxyConfig: HTTPProxyConfig) {
if (!httpProxyConfig.locations) {
return "";
}
if (httpProxyConfig.locations.length == 0) {
return "";
}
if (httpProxyConfig.locations.length == 1) {
return httpProxyConfig.locations[0];
}
return `[${httpProxyConfig.locations.join(",")}]`;
}
return (
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-start p-2 text-sm font-mono text-nowrap">
<div className="flex items-center mb-2 sm:mb-0">
<Globe className="w-4 h-4 text-blue-500 mr-2 flex-shrink-0" />
<span className="text-nowrap">{typedProxyConfig.type == "http" ? "http://" : ""}{
typedProxyConfig.type == "http" ? `${(typedProxyConfig as HTTPProxyConfig).subdomain}.${serverCfg.subDomainHost}` : serverAddress}:{
serverPort}{typedProxyConfig.type == "http" ? getServerPath(typedProxyConfig as HTTPProxyConfig) : ""}</span>
</div>
<ArrowRight className="hidden sm:block w-4 h-4 text-gray-400 mx-2 flex-shrink-0" />
<div className="flex items-center mb-2 sm:mb-0">
<Monitor className="w-4 h-4 text-green-500 mr-2 flex-shrink-0" />
<span className="text-nowrap">{localAddress}:{localPort}</span>
</div>
</div>
);
}
function getServerPort(proxyConfig: TypedProxyConfig, serverConfig: ServerConfig): number | undefined {
switch (proxyConfig.type) {
case 'tcp':
return (proxyConfig as any).remotePort;
case 'udp':
return (proxyConfig as any).remotePort;
case 'http':
return serverConfig.vhostHTTPPort;
case 'https':
return serverConfig.vhostHTTPSPort;
default:
return undefined;
}
}

View File

@@ -49,7 +49,7 @@ export const CreateClientDialog = ({refetchTrigger}: {refetchTrigger?: (randStr:
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" size={'sm'}>
<Button variant="outline">
{t('client.create.button')}
</Button>
</DialogTrigger>

View File

@@ -29,15 +29,16 @@ import { useStore } from '@nanostores/react'
import { $platformInfo } from '@/store/user'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { getClientsStatus } from '@/api/platform'
import { ClientType } from '@/lib/pb/common'
import { Client, ClientType } from '@/lib/pb/common'
import { ClientStatus, ClientStatus_Status } from '@/lib/pb/api_master'
import { startFrpc, stopFrpc } from '@/api/frp'
import { Badge } from '../ui/badge'
import { Label } from '@/components/ui/label'
import { ClientDetail } from '../base/client_detail'
import { Input } from '../ui/input'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { $clientTableRefetchTrigger } from '@/store/refetch-trigger'
import { NeedUpgrade } from '@/config/notify'
export type ClientTableSchema = {
id: string
@@ -46,6 +47,7 @@ export type ClientTableSchema = {
stopped: boolean
info?: string
config?: string
originClient: Client
}
export interface TableMetaType extends TableMeta<ClientTableSchema> {
@@ -213,12 +215,17 @@ export const ClientInfo = ({ client }: { client: ClientTableSchema }) => {
return (
<div className="flex items-center gap-2 flex-row">
<Badge variant={"secondary"} className={`p-2 border rounded font-mono w-fit ${infoColor} text-nowrap rounded-full h-6`}>
<Badge variant={"secondary"} className={`p-2 border font-mono w-fit ${infoColor} text-nowrap rounded-full h-6`}>
{`${clientsStatus?.clients[client.id].ping}ms,${t(trans(clientsStatus?.clients[client.id]))}`}
</Badge>
{clientsStatus?.clients[client.id].version &&
<ClientDetail clientStatus={clientsStatus?.clients[client.id]} />
}
{NeedUpgrade(clientsStatus?.clients[client.id].version) &&
<Badge variant={"destructive"} className={`p-2 border font-mono w-fit text-nowrap rounded-full h-6`}>
{`需要升级!`}
</Badge>
}
</div>
)
}
@@ -252,7 +259,7 @@ export const ClientSecret = ({ client }: { client: ClientTableSchema }) => {
<div className="space-y-2">
<h4 className="font-medium leading-none">{t('client.start.title')}</h4>
<p className="text-sm text-muted-foreground">
{t('client.install.description')} (<a className='text-blue-500' href='https://github.com/VaalaCat/frp-panel/releases' target="_blank" rel="noopener noreferrer">{t('common.download')}</a>)
{t('client.start.description')} (<a className='text-blue-500' href='https://github.com/VaalaCat/frp-panel/releases' target="_blank" rel="noopener noreferrer">{t('common.download')}</a>)
</p>
</div>
<div className="grid gap-2">
@@ -285,19 +292,17 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
const router = useRouter()
const platformInfo = useStore($platformInfo)
// placeholder for refetch
const refetchList = () => {}
const removeClient = useMutation({
mutationFn: deleteClient,
onSuccess: () => {
toast(t('client.delete.success'))
refetchList()
$clientTableRefetchTrigger.set(Math.random())
},
onError: (e) => {
toast(t('client.delete.failed'), {
description: e.message,
})
$clientTableRefetchTrigger.set(Math.random())
},
})
@@ -305,12 +310,13 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
mutationFn: stopFrpc,
onSuccess: () => {
toast(t('client.operation.stop_success'))
refetchList()
$clientTableRefetchTrigger.set(Math.random())
},
onError: (e) => {
toast(t('client.operation.stop_failed'), {
description: e.message,
})
$clientTableRefetchTrigger.set(Math.random())
},
})
@@ -318,12 +324,13 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
mutationFn: startFrpc,
onSuccess: () => {
toast(t('client.operation.start_success'))
refetchList()
$clientTableRefetchTrigger.set(Math.random())
},
onError: (e) => {
toast(t('client.operation.start_failed'), {
description: e.message,
})
$clientTableRefetchTrigger.set(Math.random())
},
})
@@ -357,7 +364,7 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
toast(t('client.actions_menu.copy_failed'))
}
} catch (error) {
toast(t('client.actions_menu.copy_failed'),{
toast(t('client.actions_menu.copy_failed'), {
description: JSON.stringify(error)
})
}

View File

@@ -14,8 +14,11 @@ import {
} from '@tanstack/react-table'
import React from 'react'
import { useQuery } from '@tanstack/react-query'
import { keepPreviousData, useQuery } from '@tanstack/react-query'
import { listClient } from '@/api/client'
import { ClientConfigured } from '@/lib/consts'
import { useStore } from '@nanostores/react'
import { $clientTableRefetchTrigger } from '@/store/refetch-trigger'
export interface ClientListProps {
Clients: Client[]
@@ -26,13 +29,16 @@ export interface ClientListProps {
export const ClientList: React.FC<ClientListProps> = ({ Clients, Keyword, TriggerRefetch }) => {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
const globalRefetchTrigger = useStore($clientTableRefetchTrigger)
const data = Clients.map(
(client) =>
({
id: client.id == undefined ? '' : client.id,
status: client.config == undefined || client.config == '' ? 'invalid' : 'valid',
status: ClientConfigured(client) ? 'valid' : 'invalid',
secret: client.secret == undefined ? '' : client.secret,
config: client.config,
originClient: client,
}) as ClientTableSchema,
)
@@ -46,6 +52,7 @@ export const ClientList: React.FC<ClientListProps> = ({ Clients, Keyword, Trigge
pageSize,
Keyword,
TriggerRefetch,
globalRefetchTrigger,
}
const pagination = React.useMemo(
() => ({
@@ -60,6 +67,7 @@ export const ClientList: React.FC<ClientListProps> = ({ Clients, Keyword, Trigge
queryFn: async () => {
return await listClient({ page: fetchDataOptions.pageIndex + 1, pageSize: fetchDataOptions.pageSize, keyword: fetchDataOptions.Keyword })
},
placeholderData: keepPreviousData,
})
const table = useReactTable({
@@ -67,10 +75,11 @@ export const ClientList: React.FC<ClientListProps> = ({ Clients, Keyword, Trigge
dataQuery.data?.clients.map((client) => {
return {
id: client.id == undefined ? '' : client.id,
status: client.config == undefined || client.config == '' ? 'invalid' : 'valid',
status: ClientConfigured(client) ? 'valid' : 'invalid',
secret: client.secret == undefined ? '' : client.secret,
config: client.config,
stopped: client.stopped,
originClient: client,
} as ClientTableSchema
}) ?? data,
pageCount: Math.ceil(

View File

@@ -15,6 +15,7 @@ import { TypedProxyConfig } from '@/types/proxy'
import { ClientSelector } from '../base/client-selector'
import { ServerSelector } from '../base/server-selector'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
export interface FRPCFormCardProps {
clientID?: string
@@ -42,13 +43,20 @@ export const FRPCFormCard: React.FC<FRPCFormCardProps> = ({
}
}, [defaultClientID, defaultServerID])
const { data: client, refetch: refetchClient } = useQuery({
queryKey: ['getClient', clientID],
const { data: client, refetch: refetchClient, error } = useQuery({
queryKey: ['getClient', clientID, serverID],
queryFn: () => {
return getClient({ clientId: clientID })
return getClient({ clientId: clientID, serverId: serverID })
},
retry: false,
})
useEffect(() => {
if (error) {
setClientProxyConfigs([])
}
}, [error])
useEffect(() => {
if (!client || !client?.client) return
if (client?.client?.config == undefined) return
@@ -56,7 +64,6 @@ export const FRPCFormCard: React.FC<FRPCFormCardProps> = ({
const clientConf = JSON.parse(client?.client?.config || '{}') as ClientConfig
const proxyConfs = clientConf.proxies
console.log('proxyConfs', proxyConfs)
if (proxyConfs) {
setClientProxyConfigs(proxyConfs)
}

View File

@@ -3,7 +3,7 @@ import React, { useEffect } from 'react'
import { useState } from 'react'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Label } from '@radix-ui/react-label'
import { HTTPProxyForm, STCPProxyForm, TCPProxyForm, UDPProxyForm } from './proxy_form'
import { HTTPProxyForm, STCPProxyForm, TCPProxyForm, TypedProxyForm, UDPProxyForm } from './proxy_form'
import { Button } from '@/components/ui/button'
import { Client, RespCode } from '@/lib/pb/common'
import { ClientConfig } from '@/types/client'
@@ -123,73 +123,35 @@ export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID, client,
<p>{t('proxy.form.expand', { count: clientProxyConfigs.length })}</p>
</AccordionHeader>
</AccordionTrigger>
<AccordionContent className="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-4">
{clientProxyConfigs.map((item) => {
<AccordionContent className="grid gap-2 grid-cols-1">
{clientProxyConfigs.map((item, index) => {
return (
<Card key={item.name}>
<CardContent>
<div className="flex flex-col w-full pt-2">
<Accordion type="single" collapsible>
<AccordionItem value={item.name}>
<AccordionHeader className="flex flex-row justify-between">
<div>{t('proxy.form.tunnel_name')}: {item.name}</div>
<Button
variant={'outline'}
onClick={() => {
handleDeleteProxy(item.name)
}}
>
{t('proxy.form.delete')}
</Button>
</AccordionHeader>
<AccordionTrigger>{t('proxy.form.type_label', { type: item.type })}</AccordionTrigger>
<AccordionContent>
{item.type === 'tcp' && serverID && clientID && (
<TCPProxyForm
defaultProxyConfig={item}
proxyName={item.name}
serverID={serverID}
clientID={clientID}
clientProxyConfigs={clientProxyConfigs}
setClientProxyConfigs={setClientProxyConfigs}
/>
)}
{item.type === 'udp' && serverID && clientID && (
<UDPProxyForm
defaultProxyConfig={item}
proxyName={item.name}
serverID={serverID}
clientID={clientID}
clientProxyConfigs={clientProxyConfigs}
setClientProxyConfigs={setClientProxyConfigs}
/>
)}
{item.type === 'http' && serverID && clientID && (
<HTTPProxyForm
defaultProxyConfig={item}
proxyName={item.name}
serverID={serverID}
clientID={clientID}
clientProxyConfigs={clientProxyConfigs}
setClientProxyConfigs={setClientProxyConfigs}
/>
)}
{item.type === 'stcp' && serverID && clientID && (
<STCPProxyForm
defaultProxyConfig={item}
proxyName={item.name}
serverID={serverID}
clientID={clientID}
clientProxyConfigs={clientProxyConfigs}
setClientProxyConfigs={setClientProxyConfigs}
/>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</CardContent>
</Card>
<Accordion type="single" collapsible key={index}>
<AccordionItem value={item.name}>
<AccordionTrigger>
<div className='flex flex-row justify-start items-center w-full gap-4'>
<Button variant={'outline'} onClick={() => { handleDeleteProxy(item.name) }}>
{t('proxy.form.delete')}
</Button>
<div>{t('proxy.form.tunnel_name')}: {item.name}</div>
<div>{t('proxy.form.type_label', { type: item.type })}</div>
</div>
</AccordionTrigger>
<AccordionContent className='border rounded-xl p-4'>
{serverID && clientID && (
<TypedProxyForm
enablePreview
defaultProxyConfig={item}
proxyName={item.name}
serverID={serverID}
clientID={clientID}
clientProxyConfigs={clientProxyConfigs}
setClientProxyConfigs={setClientProxyConfigs}
/>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
)
})}
</AccordionContent>

View File

@@ -4,7 +4,7 @@ import React from 'react'
import { ZodPortSchema, ZodStringSchema } from '@/lib/consts'
import { useEffect, useState } from 'react'
import { zodResolver } from '@hookform/resolvers/zod'
import { Control, FieldValues, useForm } from 'react-hook-form'
import { Control, useForm } from 'react-hook-form'
import { Button } from '@/components/ui/button'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
@@ -13,8 +13,8 @@ import { YesIcon } from '@/components/ui/icon'
import { useTranslation } from 'react-i18next'
import { useQuery } from '@tanstack/react-query'
import { getServer } from '@/api/server'
import { ServerConfig } from '@/types/server'
import { ArrowRightIcon } from 'lucide-react'
import { VisitPreview } from '../base/visit-preview'
import StringListInput from '../base/list-input'
export const TCPConfigSchema = z.object({
remotePort: ZodPortSchema,
@@ -31,7 +31,8 @@ export const UDPConfigSchema = z.object({
export const HTTPConfigSchema = z.object({
localPort: ZodPortSchema,
localIP: ZodStringSchema.default('127.0.0.1'),
subDomain: ZodStringSchema,
subdomain: ZodStringSchema,
locations: z.array(ZodStringSchema).optional(),
})
export const STCPConfigSchema = z.object({
@@ -44,6 +45,7 @@ export interface ProxyFormProps {
clientID: string
serverID: string
proxyName: string
enablePreview?: boolean
defaultProxyConfig?: TypedProxyConfig
clientProxyConfigs: TypedProxyConfig[]
setClientProxyConfigs: React.Dispatch<React.SetStateAction<TypedProxyConfig[]>>
@@ -71,7 +73,7 @@ const HostField = ({
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<Input placeholder={placeholder || '127.0.0.1'} {...field} />
<Input className='text-sm' placeholder={placeholder || '127.0.0.1'} {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -102,7 +104,7 @@ const PortField = ({
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<Input placeholder={placeholder || '1234'} {...field} />
<Input className='text-sm' placeholder={placeholder || '1234'} {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -133,7 +135,7 @@ const SecretStringField = ({
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<Input placeholder={placeholder || "secret"} {...field} />
<Input className='text-sm' placeholder={placeholder || "secret"} {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -165,7 +167,7 @@ const StringField = ({
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<Input placeholder={placeholder || '127.0.0.1'} {...field} />
<Input className='text-sm' placeholder={placeholder || '127.0.0.1'} {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -175,9 +177,93 @@ const StringField = ({
)
}
export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs }) => {
const StringArrayField = ({
control,
name,
label,
placeholder,
defaultValue,
}: {
control: Control<any>
name: string
label: string
placeholder?: string
defaultValue?: string[]
}) => {
const { t } = useTranslation()
return (
<FormField
name={name}
control={control}
render={({ field }) => (
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<StringListInput placeholder={placeholder || '/path'} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue={defaultValue}
/>
)
}
export const TypedProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs, enablePreview }) => {
if (!defaultProxyConfig) {
return <></>
}
return (<> {defaultProxyConfig.type === 'tcp' && serverID && clientID && (
<TCPProxyForm
defaultProxyConfig={defaultProxyConfig}
proxyName={proxyName}
serverID={serverID}
clientID={clientID}
clientProxyConfigs={clientProxyConfigs}
setClientProxyConfigs={setClientProxyConfigs}
enablePreview={enablePreview}
/>
)}
{defaultProxyConfig.type === 'udp' && serverID && clientID && (
<UDPProxyForm
defaultProxyConfig={defaultProxyConfig}
proxyName={proxyName}
serverID={serverID}
clientID={clientID}
clientProxyConfigs={clientProxyConfigs}
setClientProxyConfigs={setClientProxyConfigs}
enablePreview={enablePreview}
/>
)}
{defaultProxyConfig.type === 'http' && serverID && clientID && (
<HTTPProxyForm
defaultProxyConfig={defaultProxyConfig}
proxyName={proxyName}
serverID={serverID}
clientID={clientID}
clientProxyConfigs={clientProxyConfigs}
setClientProxyConfigs={setClientProxyConfigs}
enablePreview={enablePreview}
/>
)}
{defaultProxyConfig.type === 'stcp' && serverID && clientID && (
<STCPProxyForm
defaultProxyConfig={defaultProxyConfig}
proxyName={proxyName}
serverID={serverID}
clientID={clientID}
clientProxyConfigs={clientProxyConfigs}
setClientProxyConfigs={setClientProxyConfigs}
enablePreview={enablePreview}
/>
)}</>)
}
export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs, enablePreview }) => {
const defaultConfig = defaultProxyConfig as TCPProxyConfig
const [_, setTCPConfig] = useState<TCPProxyConfig | undefined>()
const [timeoutID, setTimeoutID] = useState<NodeJS.Timeout | undefined>()
const form = useForm<z.infer<typeof TCPConfigSchema>>({
resolver: zodResolver(TCPConfigSchema),
defaultValues: {
@@ -201,6 +287,7 @@ export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
}
return proxyCfg
})
console.log('newProxiyConfigs', newProxiyConfigs)
setClientProxyConfigs(newProxiyConfigs)
}
@@ -208,9 +295,12 @@ export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
const handleSave = () => {
setSaveDisabled(true)
setTimeout(() => {
if (timeoutID) {
clearTimeout(timeoutID)
}
setTimeoutID(setTimeout(() => {
setSaveDisabled(false)
}, 3000)
}, 3000))
}
const { t } = useTranslation()
@@ -225,26 +315,18 @@ export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 px-0.5">
<Label className="text-sm font-medium">{t('proxy.form.access_method')}</Label>
{server?.server?.ip && defaultConfig.remotePort && defaultConfig.localIP && defaultConfig.localPort && (
<div className="flex items-center space-x-2">
<Input
value={`${server?.server?.ip}:${defaultConfig?.remotePort}`}
className="text-sm font-mono"
disabled
/>{' '}
<ArrowRightIcon className="h-4 w-4" />{' '}
<Input
value={`${defaultConfig?.localIP}:${defaultConfig?.localPort}`}
className="text-sm font-mono"
disabled
/>
{server?.server?.ip && defaultConfig.remotePort && defaultConfig.localIP && defaultConfig.localPort && enablePreview && (
<div className="flex items-center space-x-2 flex-col justify-start w-full">
<Label className="text-sm font-medium text-start w-full">{t('proxy.form.access_method')}</Label>
<div className='w-full justify-start overflow-x-scroll'>
<VisitPreview server={server?.server} typedProxyConfig={defaultConfig} />
</div>
</div>
)}
<PortField name="localPort" control={form.control} label={t('proxy.form.local_port')} />
<HostField name="localIP" control={form.control} label={t('proxy.form.local_ip')} />
<PortField name="remotePort" control={form.control} label={t('proxy.form.remote_port')} placeholder='4321'/>
<Button type="submit" disabled={isSaveDisabled} variant={'outline'}>
<PortField name="remotePort" control={form.control} label={t('proxy.form.remote_port')} placeholder='4321' />
<Button type="submit" disabled={isSaveDisabled} variant={'outline'} className='w-full'>
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
{t('proxy.form.save_changes')}
</Button>
@@ -253,9 +335,10 @@ export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
)
}
export const STCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs }) => {
export const STCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs, enablePreview }) => {
const defaultConfig = defaultProxyConfig as STCPProxyConfig
const [_, setSTCPConfig] = useState<STCPProxyConfig | undefined>()
const [timeoutID, setTimeoutID] = useState<NodeJS.Timeout | undefined>()
const form = useForm<z.infer<typeof STCPConfigSchema>>({
resolver: zodResolver(STCPConfigSchema),
defaultValues: {
@@ -286,9 +369,12 @@ export const STCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
const handleSave = () => {
setSaveDisabled(true)
setTimeout(() => {
if (timeoutID) {
clearTimeout(timeoutID)
}
setTimeoutID(setTimeout(() => {
setSaveDisabled(false)
}, 3000)
}, 3000))
}
const { t } = useTranslation()
@@ -299,7 +385,7 @@ export const STCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
<PortField name="localPort" control={form.control} label={t('proxy.form.local_port')} />
<HostField name="localIP" control={form.control} label={t('proxy.form.local_ip')} />
<SecretStringField name="secretKey" control={form.control} label={t('proxy.form.secret_key')} />
<Button type="submit" disabled={isSaveDisabled} variant={'outline'}>
<Button type="submit" disabled={isSaveDisabled} variant={'outline'} className='w-full'>
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
{t('proxy.form.save_changes')}
</Button>
@@ -308,9 +394,10 @@ export const STCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
)
}
export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs }) => {
export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs, enablePreview }) => {
const defaultConfig = defaultProxyConfig as UDPProxyConfig
const [_, setUDPConfig] = useState<UDPProxyConfig | undefined>()
const [timeoutID, setTimeoutID] = useState<NodeJS.Timeout | undefined>()
const form = useForm<z.infer<typeof UDPConfigSchema>>({
resolver: zodResolver(UDPConfigSchema),
defaultValues: {
@@ -341,9 +428,12 @@ export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
const handleSave = () => {
setSaveDisabled(true)
setTimeout(() => {
if (timeoutID) {
clearTimeout(timeoutID)
}
setTimeoutID(setTimeout(() => {
setSaveDisabled(false)
}, 3000)
}, 3000))
}
const { t } = useTranslation()
@@ -358,25 +448,18 @@ export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 px-0.5">
{server?.server?.ip && defaultConfig.remotePort && defaultConfig.localIP && defaultConfig.localPort && (
<div className="flex items-center space-x-2">
<Input
value={`${server?.server?.ip}:${defaultConfig?.remotePort}`}
className="text-sm font-mono"
disabled
/>{' '}
<ArrowRightIcon className="h-4 w-4" />{' '}
<Input
value={`${defaultConfig?.localIP}:${defaultConfig?.localPort}`}
className="text-sm font-mono"
disabled
/>
{server?.server?.ip && defaultConfig.remotePort && defaultConfig.localIP && defaultConfig.localPort && enablePreview && (
<div className="flex items-center space-x-2 flex-col justify-start w-full">
<Label className="text-sm font-medium text-start w-full">{t('proxy.form.access_method')}</Label>
<div className='w-full justify-start overflow-x-scroll'>
<VisitPreview server={server?.server} typedProxyConfig={defaultConfig} />
</div>
</div>
)}
<PortField name="localPort" control={form.control} label={t('proxy.form.local_port')} />
<HostField name="localIP" control={form.control} label={t('proxy.form.local_ip')} />
<PortField name="remotePort" control={form.control} label={t('proxy.form.remote_port')} />
<Button type="submit" disabled={isSaveDisabled} variant={'outline'}>
<Button type="submit" disabled={isSaveDisabled} variant={'outline'} className='w-full'>
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
{t('proxy.form.save_changes')}
</Button>
@@ -385,11 +468,18 @@ export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
)
}
export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs }) => {
export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs, enablePreview }) => {
const defaultConfig = defaultProxyConfig as HTTPProxyConfig
const [_, setHTTPConfig] = useState<HTTPProxyConfig | undefined>()
const [serverConfig, setServerConfig] = useState<ServerConfig | undefined>()
const [timeoutID, setTimeoutID] = useState<NodeJS.Timeout | undefined>()
const form = useForm<z.infer<typeof HTTPConfigSchema>>({
resolver: zodResolver(HTTPConfigSchema),
defaultValues: {
localIP: defaultConfig?.localIP,
localPort: defaultConfig?.localPort,
subdomain: defaultConfig?.subdomain,
locations: defaultConfig?.locations,
}
})
useEffect(() => {
@@ -413,9 +503,12 @@ export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
const handleSave = () => {
setSaveDisabled(true)
setTimeout(() => {
if (timeoutID) {
clearTimeout(timeoutID)
}
setTimeoutID(setTimeout(() => {
setSaveDisabled(false)
}, 3000)
}, 3000))
}
const { t } = useTranslation()
@@ -427,24 +520,23 @@ export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
},
})
useEffect(() => {
if (server && server.server?.config) {
setServerConfig(JSON.parse(server.server?.config) as ServerConfig)
}
}, [server])
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 px-0.5">
<Label className="text-sm font-medium">{t('proxy.form.access_method')}</Label>
<p className="text-sm border rounded p-2 my-2 font-mono overflow-auto">
{`http://${(defaultProxyConfig as HTTPProxyConfig).subdomain}.${serverConfig?.subDomainHost}:${serverConfig?.vhostHTTPPort
} -> ${defaultProxyConfig?.localIP}:${defaultProxyConfig?.localPort}`}
</p>
{server && server.server && server.server.ip && defaultConfig &&
defaultConfig.localIP && defaultConfig.localPort &&
defaultConfig.subdomain
&& enablePreview && <div className="flex items-center space-x-2 flex-col justify-start w-full">
<Label className="text-sm font-medium text-start w-full">{t('proxy.form.access_method')}</Label>
<div className='w-full justify-start overflow-x-scroll'>
<VisitPreview server={server?.server} typedProxyConfig={defaultConfig} />
</div>
</div>}
<PortField name="localPort" control={form.control} label={t('proxy.form.local_port')} />
<HostField name="localIP" control={form.control} label={t('proxy.form.local_ip')} />
<StringField name="subDomain" control={form.control} label={t('proxy.form.subdomain')} placeholder={"your_sub_domain"} />
<Button type="submit" disabled={isSaveDisabled} variant={'outline'}>
<StringField name="subdomain" control={form.control} label={t('proxy.form.subdomain')} placeholder={"your_sub_domain"} />
<StringArrayField name="locations" control={form.control} label={t('proxy.form.route')} placeholder={"/path"} />
<Button type="submit" disabled={isSaveDisabled} variant={'outline'} className='w-full'>
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
{t('proxy.form.save_changes')}
</Button>

View File

@@ -49,7 +49,7 @@ export const CreateServerDialog = ({refetchTrigger}: {refetchTrigger?: (randStr:
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" size={'sm'}>
<Button variant="outline">
{t('server.create.button')}
</Button>
</DialogTrigger>

View File

@@ -36,6 +36,7 @@ import { ClientDetail } from '../base/client_detail'
import { useTranslation } from 'react-i18next'
import { Input } from '@/components/ui/input'
import { toast } from 'sonner'
import { $serverTableRefetchTrigger } from '@/store/refetch-trigger'
export type ServerTableSchema = {
id: string
@@ -257,7 +258,7 @@ export const ServerSecret = ({ server }: { server: ServerTableSchema }) => {
<div className="space-y-2">
<h4 className="font-medium leading-none">{t('server.start.title')}</h4>
<p className="text-sm text-muted-foreground">
{t('server.install.description')} (<a className='text-blue-500' href='https://github.com/VaalaCat/frp-panel/releases' target="_blank" rel="noopener noreferrer">{t('common.download')}</a>)
{t('server.start.description')} (<a className='text-blue-500' href='https://github.com/VaalaCat/frp-panel/releases' target="_blank" rel="noopener noreferrer">{t('common.download')}</a>)
</p>
</div>
<div className="grid gap-2">
@@ -290,18 +291,17 @@ export const ServerActions: React.FC<ServerItemProps> = ({ server, table }) => {
const router = useRouter()
const platformInfo = useStore($platformInfo)
const refetchList = () => { }
const removeServer = useMutation({
mutationFn: deleteServer,
onSuccess: () => {
toast(t('server.delete.success'))
refetchList()
$serverTableRefetchTrigger.set(Math.random())
},
onError: (e) => {
toast(t('server.delete.failed'), {
description: e.message,
})
$serverTableRefetchTrigger.set(Math.random())
},
})

View File

@@ -14,8 +14,10 @@ import {
} from '@tanstack/react-table'
import React from 'react'
import { useQuery } from '@tanstack/react-query'
import { keepPreviousData, useQuery } from '@tanstack/react-query'
import { listServer } from '@/api/server'
import { $serverTableRefetchTrigger } from '@/store/refetch-trigger'
import { useStore } from '@nanostores/react'
export interface ServerListProps {
Servers: Server[]
@@ -26,6 +28,8 @@ export interface ServerListProps {
export const ServerList: React.FC<ServerListProps> = ({ Servers, Keyword, TriggerRefetch }) => {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
const globalRefetchTrigger = useStore($serverTableRefetchTrigger)
const data = Servers.map(
(server) =>
({
@@ -46,6 +50,7 @@ export const ServerList: React.FC<ServerListProps> = ({ Servers, Keyword, Trigge
pageSize,
Keyword,
TriggerRefetch,
globalRefetchTrigger,
}
const pagination = React.useMemo(
() => ({
@@ -60,6 +65,7 @@ export const ServerList: React.FC<ServerListProps> = ({ Servers, Keyword, Trigge
queryFn: async () => {
return await listServer({ page: fetchDataOptions.pageIndex + 1, pageSize: fetchDataOptions.pageSize, keyword: fetchDataOptions.Keyword })
},
placeholderData: keepPreviousData,
})
const table = useReactTable({

View File

@@ -0,0 +1,146 @@
"use client"
import { useEffect, useState } from 'react'
import { useMutation } from '@tanstack/react-query'
import { Label } from '@/components/ui/label'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { useTranslation } from 'react-i18next'
import { ServerSelector } from '../base/server-selector'
import { ClientSelector } from '../base/client-selector'
import { TypedProxyForm } from '../frpc/proxy_form'
import { ProxyType, TypedProxyConfig } from '@/types/proxy'
import { BaseSelector } from '../base/selector'
import { createProxyConfig } from '@/api/proxy'
import { ClientConfig } from '@/types/client'
import { ObjToUint8Array } from '@/lib/utils'
import { VisitPreview } from '../base/visit-preview'
import { ProxyConfig, Server } from '@/lib/pb/common'
import { TypedProxyConfigValid } from '@/lib/consts'
import { toast } from 'sonner'
import { $proxyTableRefetchTrigger } from '@/store/refetch-trigger'
export type ProxyConfigMutateDialogProps = {
overwrite?: boolean
defaultProxyConfig?: TypedProxyConfig
defaultOriginalProxyConfig?: ProxyConfig
disableChangeProxyName?: boolean
}
export const ProxyConfigMutateDialog = ({ ...props }: ProxyConfigMutateDialogProps) => {
const { t } = useTranslation()
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" className='w-fit'>
{t('proxy.config.create')}
</Button>
</DialogTrigger>
<DialogContent className='max-h-screen overflow-auto'>
<ProxyConfigMutateForm {...props} />
</DialogContent>
</Dialog>
)
}
export const ProxyConfigMutateForm = ({ overwrite, defaultProxyConfig, defaultOriginalProxyConfig, disableChangeProxyName }: ProxyConfigMutateDialogProps) => {
const { t } = useTranslation()
const [newClientID, setNewClientID] = useState<string | undefined>()
const [newServerID, setNewServerID] = useState<string | undefined>()
const [proxyConfigs, setProxyConfigs] = useState<TypedProxyConfig[]>([])
const [proxyName, setProxyName] = useState<string | undefined>('')
const [proxyType, setProxyType] = useState<ProxyType>('http')
const [selectedServer, setSelectedServer] = useState<Server | undefined>()
const supportedProxyTypes: ProxyType[] = ["http", "tcp", "udp"]
const createProxyConfigMutation = useMutation({
mutationKey: ['createProxyConfig', newClientID, newServerID],
mutationFn: () => createProxyConfig({
clientId: newClientID!,
serverId: newServerID!,
config: ObjToUint8Array({
proxies: proxyConfigs
} as ClientConfig),
overwrite,
}),
onSuccess: () => {
toast(t('proxy.config.create_success'))
$proxyTableRefetchTrigger.set(Math.random())
},
onError: (e) => {
toast(t('proxy.config.create_failed'), {
description: JSON.stringify(e),
})
$proxyTableRefetchTrigger.set(Math.random())
}
})
useEffect(() => {
if (proxyName && proxyType) {
setProxyConfigs([{ name: proxyName, type: proxyType }])
}
}, [proxyName, proxyType])
useEffect(() => {
if (defaultProxyConfig && defaultOriginalProxyConfig) {
setProxyConfigs([defaultProxyConfig])
setProxyType(defaultProxyConfig.type)
setProxyName(defaultProxyConfig.name)
setNewClientID(defaultOriginalProxyConfig.originClientId)
setNewServerID(defaultOriginalProxyConfig.serverId)
}
}, [defaultProxyConfig, defaultOriginalProxyConfig])
return (
<>
<Label>{t('proxy.config.select_server')} </Label>
<ServerSelector setServerID={setNewServerID} serverID={newServerID} setServer={setSelectedServer} />
<Label>{t('proxy.config.select_client')} </Label>
<ClientSelector setClientID={setNewClientID} clientID={newClientID} />
<Label>{t('proxy.config.select_proxy_type')} </Label>
<BaseSelector
dataList={supportedProxyTypes.map((type) => ({ value: type, label: type }))}
value={proxyType}
setValue={(value) => { setProxyType(value as ProxyType) }}
/>
<div className='flex flex-row w-full overflow-auto'>
{proxyConfigs && selectedServer && proxyConfigs.length > 0 &&
proxyConfigs[0] && TypedProxyConfigValid(proxyConfigs[0]) &&
<div className='flex flex-col'>
<VisitPreview server={selectedServer} typedProxyConfig={proxyConfigs[0]} />
</div>
}
</div>
<Label>{t('proxy.config.proxy_name')} </Label>
<Input className='text-sm' defaultValue={proxyName} onChange={(e) => setProxyName(e.target.value)} disabled={disableChangeProxyName} />
{proxyName && newClientID && newServerID && <TypedProxyForm
serverID={newServerID}
clientID={newClientID}
proxyName={proxyName}
defaultProxyConfig={proxyConfigs && proxyConfigs.length > 0 ? proxyConfigs[0] : undefined}
clientProxyConfigs={proxyConfigs}
setClientProxyConfigs={setProxyConfigs}
enablePreview={false}
/>}
<Button
disabled={!TypedProxyConfigValid(proxyConfigs[0])}
onClick={() => {
if (!TypedProxyConfigValid(proxyConfigs[0])) {
toast(t('proxy.config.invalid_config'))
return
}
createProxyConfigMutation.mutate()
}} >{t('proxy.config.submit')}</Button>
</>
)
}

View File

@@ -0,0 +1,102 @@
import { ProxyType, TypedProxyConfig } from "@/types/proxy"
import { useTranslation } from "react-i18next"
import { Button } from "../ui/button"
import { BaseDropdownMenu } from "../base/drop-down-menu"
import { deleteProxyConfig } from "@/api/proxy"
import { useMutation } from "@tanstack/react-query"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { ProxyConfigMutateForm } from "./mutate_proxy_config"
import { useEffect, useState } from "react"
import { Row } from "@tanstack/react-table"
import { ProxyConfigTableSchema } from "./proxy_config_item"
import { MoreHorizontal } from "lucide-react"
import { toast } from "sonner"
import { $proxyTableRefetchTrigger } from "@/store/refetch-trigger"
export interface ProxyConfigActionsProps {
serverID: string
clientID: string
name: string
row: Row<ProxyConfigTableSchema>
}
export function ProxyConfigActions({ serverID, clientID, name, row }: ProxyConfigActionsProps) {
const { t } = useTranslation()
const [proxyMutateFormOpen, setProxyMutateFormOpen] = useState(false)
const [deleteWarnDialogOpen, setDeleteWarnDialogOpen] = useState(false)
const deleteProxyConfigMutation = useMutation({
mutationKey: ['deleteProxyConfig', serverID, clientID, name],
mutationFn: () => deleteProxyConfig({
serverId: serverID,
clientId: clientID,
name,
}),
onSuccess: () => {
toast(t('proxy.action.delete_success'))
$proxyTableRefetchTrigger.set(Math.random())
},
onError: (e) => {
toast(t('proxy.action.delete_failed'), {
description: JSON.stringify(e),
})
$proxyTableRefetchTrigger.set(Math.random())
},
})
const menuActions = [[
{
name: t('proxy.action.edit'),
onClick: () => { setProxyMutateFormOpen(true) },
},
{
name: t('proxy.action.delete'),
onClick: () => { setDeleteWarnDialogOpen(true) },
className: 'text-destructive',
},
]]
return (<>
<Dialog open={proxyMutateFormOpen} onOpenChange={setProxyMutateFormOpen}>
<DialogContent className="max-h-screen overflow-auto">
<ProxyConfigMutateForm
disableChangeProxyName
defaultProxyConfig={JSON.parse(row.original.config || '{}') as TypedProxyConfig}
overwrite={true}
defaultOriginalProxyConfig={row.original.originalProxyConfig}
/>
</DialogContent>
</Dialog>
<Dialog open={deleteWarnDialogOpen} onOpenChange={setDeleteWarnDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('proxy.action.delete_tunnel')}</DialogTitle>
<DialogDescription>
<p className="text-destructive">{t('proxy.action.delete_attention_title')}</p>
<p className="text-gray-500 border-l-4 border-gray-500 pl-4 py-2">
{t('proxy.action.delete_attention_description')}
</p>
</DialogDescription>
</DialogHeader>
<DialogFooter><DialogClose asChild><Button type="submit" onClick={() => {
deleteProxyConfigMutation.mutate()
}}>
{t('proxy.action.delete_attention_confirm')}
</Button></DialogClose></DialogFooter>
</DialogContent>
</Dialog>
<BaseDropdownMenu
menuGroup={menuActions}
title={t('proxy.action.title')}
trigger={<Button variant="ghost" className="h-8 w-8 p-0">
<MoreHorizontal className="h-4 w-4" />
</Button>} />
</>)
}

View File

@@ -0,0 +1,152 @@
import { ColumnDef, Row, Table } from '@tanstack/react-table'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { VisitPreview } from '../base/visit-preview'
import { useQuery } from '@tanstack/react-query'
import { getServer } from '@/api/server'
import { ProxyType, TypedProxyConfig } from '@/types/proxy'
import { ProxyConfigActions } from './proxy_config_actions'
import { ProxyConfig } from '@/lib/pb/common'
import { getProxyConfig } from '@/api/proxy'
import { Badge } from '../ui/badge'
import { useStore } from '@nanostores/react'
import { $proxyTableRefetchTrigger } from '@/store/refetch-trigger'
export type ProxyConfigTableSchema = {
serverID: string
clientID: string
name: string
type: ProxyType
localIP?: string
localPort?: number
visitPreview: string
config?: string
originalProxyConfig: ProxyConfig
}
export const columns: ColumnDef<ProxyConfigTableSchema>[] = [
{
accessorKey: 'clientID',
header: function Header() {
const { t } = useTranslation()
return t('proxy.item.client_id')
},
cell: ({ row }) => {
return <div className='font-mono text-nowrap'>{row.original.originalProxyConfig.originClientId}</div>
},
},
{
accessorKey: 'name',
header: function Header() {
const { t } = useTranslation()
return t('proxy.item.proxy_name')
},
cell: ({ row }) => {
return <div className='font-mono text-nowrap'>{row.original.name}</div>
},
},
{
accessorKey: 'type',
header: function Header() {
const { t } = useTranslation()
return t('proxy.item.proxy_type')
},
cell: ({ row }) => {
return <div className='font-mono text-nowrap'>{row.original.type}</div>
},
},
{
accessorKey: 'serverID',
header: function Header() {
const { t } = useTranslation()
return t('proxy.item.server_id')
},
cell: ({ row }) => {
return <div className='font-mono text-nowrap'>{row.original.serverID}</div>
},
},
{
id: 'status',
header: function Header() {
const { t } = useTranslation()
return t('proxy.item.status')
},
cell: ProxyStatus,
},
{
accessorKey: 'visitPreview',
header: function Header() {
const { t } = useTranslation()
return t('proxy.item.visit_preview')
},
cell: ({ row }) => {
return <VisitPreviewField row={row} />
},
},
{
id: 'action',
cell: ({ row }) => {
return <ProxyConfigActions
row={row}
serverID={row.original.serverID}
clientID={row.original.clientID}
name={row.original.name}
/>
},
}
]
function VisitPreviewField({ row }: { row: Row<ProxyConfigTableSchema> }) {
const { data: server } = useQuery({
queryKey: ['getServer', row.original.serverID],
queryFn: () => {
return getServer({ serverId: row.original.serverID })
},
})
const typedProxyConfig = JSON.parse(row.original.config || '{}') as TypedProxyConfig
return <VisitPreview
server={server?.server || {}}
typedProxyConfig={typedProxyConfig} />
}
function ProxyStatus({ row }: { row: Row<ProxyConfigTableSchema> }) {
const refetchTrigger = useStore($proxyTableRefetchTrigger)
const { data } = useQuery({
queryKey: ['getProxyConfig', row.original.clientID, row.original.serverID, row.original.name, refetchTrigger],
queryFn: () => {
return getProxyConfig({
clientId: row.original.clientID,
serverId: row.original.serverID,
name: row.original.name
})
},
refetchInterval: 10000
})
function getStatusColor(status: string): string {
switch (status) {
case 'new':
return 'text-blue-500'
case 'wait start':
return 'text-yellow-400';
case 'start error':
return 'text-red-500';
case 'running':
return 'text-green-500';
case 'check failed':
return 'text-orange-500';
case 'error':
return 'text-red-600';
default:
return 'text-gray-500';
}
}
return <div className="flex items-center gap-2 flex-row text-nowrap">
<Badge variant={"secondary"} className={`p-2 border rounded font-mono w-fit ${getStatusColor(data?.workingStatus?.status || 'unknown')} text-nowrap rounded-full h-6`}>
{data?.workingStatus?.status || "loading"}
</Badge>
</div>
}

View File

@@ -0,0 +1,125 @@
import { ProxyConfig } from '@/lib/pb/common'
import { ProxyConfigTableSchema, columns as proxyConfigColumnsDef } from './proxy_config_item'
import { DataTable } from '../base/data_table'
import {
getSortedRowModel,
getCoreRowModel,
ColumnFiltersState,
useReactTable,
getFilteredRowModel,
getPaginationRowModel,
SortingState,
PaginationState,
} from '@tanstack/react-table'
import React from 'react'
import { keepPreviousData, useQuery } from '@tanstack/react-query'
import { listProxyConfig } from '@/api/proxy'
import { TypedProxyConfig } from '@/types/proxy'
import { $proxyTableRefetchTrigger } from '@/store/refetch-trigger'
import { useStore } from '@nanostores/react'
export interface ProxyConfigListProps {
ProxyConfigs: ProxyConfig[]
Keyword?: string
ClientID?: string
ServerID?: string
TriggerRefetch?: string
}
export const ProxyConfigList: React.FC<ProxyConfigListProps> = ({ ProxyConfigs, Keyword, TriggerRefetch, ClientID, ServerID }) => {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
const globalRefetchTrigger = useStore($proxyTableRefetchTrigger)
const data = ProxyConfigs.map(
(proxy_config) =>
({
id: proxy_config.id || '',
clientID: proxy_config.clientId || '',
serverID: proxy_config.serverId || '',
name: proxy_config.name || '',
type: proxy_config.type || '',
visitPreview: "for test",
originalProxyConfig: proxy_config,
}) as ProxyConfigTableSchema,
)
const [{ pageIndex, pageSize }, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
})
const fetchDataOptions = {
pageIndex,
pageSize,
Keyword,
TriggerRefetch,
ClientID,
ServerID,
globalRefetchTrigger,
}
const pagination = React.useMemo(
() => ({
pageIndex,
pageSize,
}),
[pageIndex, pageSize],
)
const dataQuery = useQuery({
queryKey: ['listProxyConfigs', fetchDataOptions],
queryFn: async () => {
return await listProxyConfig({
page: fetchDataOptions.pageIndex + 1,
pageSize: fetchDataOptions.pageSize,
keyword: fetchDataOptions.Keyword,
clientId: fetchDataOptions.ClientID,
serverId: fetchDataOptions.ServerID,
})
},
placeholderData: keepPreviousData,
})
const table = useReactTable({
data:
dataQuery.data?.proxyConfigs.map((proxy_config) => {
return {
id: proxy_config.id || '',
name: proxy_config.name || '',
clientID: proxy_config.clientId || '',
serverID: proxy_config.serverId || '',
type: proxy_config.type || '',
config: proxy_config.config || '',
localIP: proxy_config.config && ParseProxyConfig(proxy_config.config).localIP,
localPort: proxy_config.config && ParseProxyConfig(proxy_config.config).localPort,
visitPreview: "",
originalProxyConfig: proxy_config,
} as ProxyConfigTableSchema
}) ?? data,
pageCount: Math.ceil(
//@ts-ignore
(dataQuery.data?.total == undefined ? 0 : dataQuery.data?.total) / fetchDataOptions.pageSize ?? 0,
),
columns: proxyConfigColumnsDef,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
onPaginationChange: setPagination,
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
manualPagination: true,
state: {
sorting,
pagination,
columnFilters,
},
})
return <DataTable table={table} columns={proxyConfigColumnsDef} />
}
function ParseProxyConfig(cfg: string): TypedProxyConfig {
return JSON.parse(cfg)
}

View File

@@ -35,7 +35,7 @@ export function RegisterComponent() {
}
const onSubmit = async (values: z.infer<typeof RegisterSchema>) => {
toast('auth.registering')
toast(t('auth.registering'))
try {
const res = await register({ ...values })
if (res.status?.code === RespCode.SUCCESS) {

View File

@@ -28,8 +28,8 @@ export const ClientStatsCard: React.FC<ClientStatsCardProps> = ({ clientID: defa
const { data: clientStatsList, refetch: refetchClientStats } = useQuery({
queryKey: ['clientStats', clientID],
queryFn: async () => {
return await getProxyStatsByClientID({ clientId: clientID! })
queryFn: () => {
return getProxyStatsByClientID({ clientId: clientID! })
},
})
@@ -98,8 +98,8 @@ export const ClientStatsCard: React.FC<ClientStatsCardProps> = ({ clientID: defa
setProxyname={setProxyName} />
<div className="w-full grid gap-4 grid-cols-1">
{clientStatsList && clientStatsList.proxyInfos.length > 0 &&
<ProxyStatusCard
proxyInfo={mergeProxyInfos(clientStatsList.proxyInfos).find((proxyInfo) => proxyInfo.name === proxyName)} />}
<ProxyStatusCard
proxyInfo={mergeProxyInfos(clientStatsList.proxyInfos).find((proxyInfo) => proxyInfo.name === proxyName)} />}
</div>
</CardContent>
<CardFooter>

View File

@@ -6,6 +6,7 @@ import {
MonitorCogIcon,
ChartNetworkIcon,
Scroll,
Cable,
} from "lucide-react"
import { TbBuildingTunnel } from "react-icons/tb"
@@ -30,6 +31,11 @@ export const getNavItems = (t: any) => [
url: "/servers",
icon: ServerIcon,
},
{
title: t('nav.editTunnel'),
url: "/proxies",
icon: Cable,
},
{
title: t('nav.editClient'),
url: "/clientedit",

9
www/config/notify.ts Normal file
View File

@@ -0,0 +1,9 @@
import { ClientVersion } from "@/lib/pb/api_master"
export function NeedUpgrade(version: ClientVersion | undefined) {
if (!(version)) return false
if (!version.gitVersion) return false
const versionString = version?.gitVersion
const [a, b, c] = versionString.split('.')
return Number(b) < 1
}

View File

@@ -11,11 +11,12 @@
"nav": {
"clients": "Clients",
"servers": "Servers",
"editClient": "Edit Tunnel",
"editClient": "Edit Client",
"editServer": "Edit Server",
"trafficStats": "Traffic Stats",
"realTimeLog": "Real-time Log",
"console": "Console",
"editTunnel": "Edit Tunnel",
"user": {
"profile": "Profile",
"settings": "Settings"
@@ -65,7 +66,6 @@
"info": "Information",
"download": "Click here to download",
"copy": "Copy",
"download": "Download",
"submit": "Submit",
"cancel": "Cancel",
"save": "Save",
@@ -114,7 +114,10 @@
"totalServers": "Total Servers",
"totalClients": "Total Clients",
"unit": "",
"menuHint": "Please modify in the left menu"
"menuHint": "Please modify in the left menu",
"refresh": {
"data": "Refresh Data"
}
},
"server": {
"configuration": "Server Configuration",
@@ -151,7 +154,7 @@
},
"start": {
"title": "Start Command",
"description": "Copy and run the following command to start frps",
"description": "Copy and run the following command to start frp-panel server",
"copy": "Copy Command"
},
"actions_menu": {
@@ -245,7 +248,7 @@
},
"start": {
"title": "Start Command",
"description": "Copy and run the following command to start frpc",
"description": "Copy and run the following command to start frp-panel client",
"copy": "Copy Command"
},
"actions_menu": {
@@ -340,7 +343,43 @@
"save_success": "Save successful",
"save_error": "Save failed",
"save_changes": "Save Changes",
"submit": "Submit Changes"
"submit": "Submit Changes",
"default_port": "Default Port",
"port_placeholder": "Port (1-65535)",
"ip_placeholder": "IP Address (eg. 127.0.0.1)",
"subdomain_placeholder": "Input Subdomain",
"secret_placeholder": "Input Secret Key",
"route": "Route"
},
"config": {
"create": "Create",
"create_success": "Create successful",
"create_failed": "Create failed",
"select_server": "Select Server",
"select_client": "Select Client",
"select_proxy_type": "Select Tunnel Type",
"proxy_name": "Tunnel Name",
"invalid_config": "Invalid configuration",
"submit": "Submit"
},
"action": {
"delete_success": "Delete Successful",
"delete_failed": "Delete Failed",
"edit": "Edit",
"delete": "Delete",
"delete_tunnel": "Delete Tunnel",
"delete_attention_title": "Tunnel will be permanently deleted, please confirm",
"delete_attention_description": "After deletion, Tunnel will not be recovered, please be careful",
"delete_attention_confirm": "Confirm",
"title": "Tunnel Operation"
},
"item": {
"client_id": "Client ID",
"proxy_name": "Tunnel Name",
"proxy_type": "Tunnel Type",
"server_id": "Server ID",
"status": "Status",
"visit_preview": "Visit Preview"
},
"type": {
"http": "HTTP",
@@ -379,10 +418,10 @@
},
"frpc": {
"form": {
"title": "Edit Tunnel",
"title": "Edit Client",
"description": {
"warning": "Warning⚠: The selected 'Server' must be configured in advance!",
"instruction": "Select client and server to edit tunnel"
"instruction": "Select client and server to edit configuration"
},
"advanced": {
"title": "Advanced Mode",
@@ -428,9 +467,13 @@
}
},
"input": {
"list": {
"placeholder": "Please enter ...",
"add": "Add"
},
"search": "Search",
"id": {
"placeholder": "Enter ID"
"keyword": {
"placeholder": "Enter keyword"
}
},
"table": {

View File

@@ -11,11 +11,12 @@
"nav": {
"clients": "客户端",
"servers": "服务端",
"editClient": "编辑隧道",
"editClient": "编辑客户端",
"editServer": "编辑服务端",
"trafficStats": "流量统计",
"realTimeLog": "实时日志",
"console": "控制台",
"editTunnel": "编辑隧道",
"user": {
"profile": "个人资料",
"settings": "设置"
@@ -153,7 +154,7 @@
},
"start": {
"title": "启动命令",
"description": "复制并运行以下命令来启动 frps",
"description": "复制并运行以下命令来启动 frp-panel 服务端",
"copy": "复制命令"
},
"actions_menu": {
@@ -247,7 +248,7 @@
},
"start": {
"title": "启动命令",
"description": "复制并运行以下命令来启动 frpc",
"description": "复制并运行以下命令来启动 frp-panel 客户端",
"copy": "复制命令"
},
"actions_menu": {
@@ -347,7 +348,38 @@
"port_placeholder": "请输入端口号 (1-65535)",
"ip_placeholder": "请输入IP地址 (例如: 127.0.0.1)",
"subdomain_placeholder": "请输入子域名",
"secret_placeholder": "请输入密钥"
"secret_placeholder": "请输入密钥",
"route": "路由"
},
"config": {
"create": "创建",
"create_success": "创建成功",
"create_failed": "创建失败",
"select_server": "选择服务器",
"select_client": "选择客户端",
"select_proxy_type": "选择隧道类型",
"proxy_name": "隧道名称",
"invalid_config": "配置不正确",
"submit": "提交"
},
"action": {
"delete_success": "删除成功",
"delete_failed": "删除失败",
"edit": "编辑",
"delete": "删除",
"delete_tunnel": "删除隧道",
"delete_attention_title": "隧道将被永久删除,请确认",
"delete_attention_description": "删除后将无法恢复,请谨慎操作",
"delete_attention_confirm": "确认删除",
"title": "隧道操作"
},
"item": {
"client_id": "客户端ID",
"proxy_name": "隧道名称",
"proxy_type": "隧道类型",
"server_id": "服务端ID",
"status": "状态",
"visit_preview": "访问预览"
},
"type": {
"http": "HTTP",
@@ -386,10 +418,10 @@
},
"frpc": {
"form": {
"title": "编辑隧道",
"title": "编辑客户端",
"description": {
"warning": "注意⚠️:选择的「服务端」必须提前配置!",
"instruction": "选择客户端和服务端以编辑隧道"
"instruction": "选择客户端和服务端以配置"
},
"advanced": {
"title": "高级模式",
@@ -434,9 +466,13 @@
}
},
"input": {
"list": {
"placeholder": "请输入...",
"add": "添加"
},
"search": "搜索",
"id": {
"placeholder": "请输入ID"
"keyword": {
"placeholder": "请输入关键词"
}
},
"table": {

View File

@@ -1,24 +1,37 @@
import * as z from 'zod'
import { Client, Server } from './pb/common'
import { GetPlatformInfoResponse } from './pb/api_user'
import { TypedProxyConfig } from '@/types/proxy'
export const API_PATH = '/api/v1'
export const SET_TOKEN_HEADER = 'x-set-authorization'
export const X_CLIENT_REQUEST_ID = 'x-client-request-id'
export const LOCAL_STORAGE_TOKEN_KEY = 'token'
export const ZodPortSchema = z.coerce
.number({required_error: 'validation.required'})
.number({ required_error: 'validation.required' })
.min(1, { message: 'validation.portRange.min' })
.max(65535, { message: 'validation.portRange.max' })
export const ZodIPSchema = z.string({required_error: 'validation.required'})
export const ZodIPSchema = z.string({ required_error: 'validation.required' })
.regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, { message: 'validation.ipAddress' })
export const ZodStringSchema = z.string({required_error: 'validation.required'})
export const ZodStringSchema = z.string({ required_error: 'validation.required' })
.min(1, { message: 'validation.required' })
export const ZodEmailSchema = z.string({required_error: 'validation.required'})
export const ZodEmailSchema = z.string({ required_error: 'validation.required' })
.min(1, { message: 'validation.required' })
.email({ message: 'auth.email.invalid' })
export const TypedProxyConfigValid = (typedProxyCfg: TypedProxyConfig | undefined): boolean => {
return (typedProxyCfg?.localPort && typedProxyCfg.localIP && typedProxyCfg.name && typedProxyCfg.type) ? true : false
}
export const ClientConfigured = (client: Client | undefined): boolean => {
if (client == undefined) {
return false
}
return !((client.config == undefined || client.config == '') &&
(client.clientIds == undefined || client.clientIds.length == 0))
}
// .refine((e) => e === "abcd@fg.com", "This email is not in our database")
export const ExecCommandStr = <T extends Client | Server>(

View File

@@ -10,6 +10,8 @@ import { UnknownFieldHandler } from "@protobuf-ts/runtime";
import type { PartialMessage } from "@protobuf-ts/runtime";
import { reflectionMergePartial } from "@protobuf-ts/runtime";
import { MessageType } from "@protobuf-ts/runtime";
import { ProxyWorkingStatus } from "./common";
import { ProxyConfig } from "./common";
import { ProxyInfo } from "./common";
import { Client } from "./common";
import { Status } from "./common";
@@ -77,6 +79,10 @@ export interface GetClientRequest {
* @generated from protobuf field: optional string client_id = 1;
*/
clientId?: string;
/**
* @generated from protobuf field: optional string server_id = 2;
*/
serverId?: string;
}
/**
* @generated from protobuf message api_client.GetClientResponse
@@ -194,18 +200,18 @@ export interface StartFRPCResponse {
status?: Status;
}
/**
* @generated from protobuf message api_client.GetProxyByCIDRequest
* @generated from protobuf message api_client.GetProxyStatsByClientIDRequest
*/
export interface GetProxyByCIDRequest {
export interface GetProxyStatsByClientIDRequest {
/**
* @generated from protobuf field: optional string client_id = 1;
*/
clientId?: string;
}
/**
* @generated from protobuf message api_client.GetProxyByCIDResponse
* @generated from protobuf message api_client.GetProxyStatsByClientIDResponse
*/
export interface GetProxyByCIDResponse {
export interface GetProxyStatsByClientIDResponse {
/**
* @generated from protobuf field: optional common.Status status = 1;
*/
@@ -215,6 +221,168 @@ export interface GetProxyByCIDResponse {
*/
proxyInfos: ProxyInfo[];
}
/**
* @generated from protobuf message api_client.ListProxyConfigsRequest
*/
export interface ListProxyConfigsRequest {
/**
* @generated from protobuf field: optional int32 page = 1;
*/
page?: number;
/**
* @generated from protobuf field: optional int32 page_size = 2;
*/
pageSize?: number;
/**
* @generated from protobuf field: optional string keyword = 3;
*/
keyword?: string;
/**
* @generated from protobuf field: optional string client_id = 4;
*/
clientId?: string;
/**
* @generated from protobuf field: optional string server_id = 5;
*/
serverId?: string;
}
/**
* @generated from protobuf message api_client.ListProxyConfigsResponse
*/
export interface ListProxyConfigsResponse {
/**
* @generated from protobuf field: optional common.Status status = 1;
*/
status?: Status;
/**
* @generated from protobuf field: optional int32 total = 2;
*/
total?: number;
/**
* @generated from protobuf field: repeated common.ProxyConfig proxy_configs = 3;
*/
proxyConfigs: ProxyConfig[];
}
/**
* @generated from protobuf message api_client.CreateProxyConfigRequest
*/
export interface CreateProxyConfigRequest {
/**
* @generated from protobuf field: optional string client_id = 1;
*/
clientId?: string;
/**
* @generated from protobuf field: optional string server_id = 2;
*/
serverId?: string;
/**
* @generated from protobuf field: optional bytes config = 3;
*/
config?: Uint8Array;
/**
* @generated from protobuf field: optional bool overwrite = 4;
*/
overwrite?: boolean;
}
/**
* @generated from protobuf message api_client.CreateProxyConfigResponse
*/
export interface CreateProxyConfigResponse {
/**
* @generated from protobuf field: optional common.Status status = 1;
*/
status?: Status;
}
/**
* @generated from protobuf message api_client.DeleteProxyConfigRequest
*/
export interface DeleteProxyConfigRequest {
/**
* @generated from protobuf field: optional string client_id = 1;
*/
clientId?: string;
/**
* @generated from protobuf field: optional string server_id = 2;
*/
serverId?: string;
/**
* @generated from protobuf field: optional string name = 3;
*/
name?: string;
}
/**
* @generated from protobuf message api_client.DeleteProxyConfigResponse
*/
export interface DeleteProxyConfigResponse {
/**
* @generated from protobuf field: optional common.Status status = 1;
*/
status?: Status;
}
/**
* @generated from protobuf message api_client.UpdateProxyConfigRequest
*/
export interface UpdateProxyConfigRequest {
/**
* @generated from protobuf field: optional string client_id = 1;
*/
clientId?: string;
/**
* @generated from protobuf field: optional string server_id = 2;
*/
serverId?: string;
/**
* @generated from protobuf field: optional string name = 3;
*/
name?: string;
/**
* @generated from protobuf field: optional bytes config = 4;
*/
config?: Uint8Array;
}
/**
* @generated from protobuf message api_client.UpdateProxyConfigResponse
*/
export interface UpdateProxyConfigResponse {
/**
* @generated from protobuf field: optional common.Status status = 1;
*/
status?: Status;
}
/**
* @generated from protobuf message api_client.GetProxyConfigRequest
*/
export interface GetProxyConfigRequest {
/**
* @generated from protobuf field: optional string client_id = 1;
*/
clientId?: string;
/**
* @generated from protobuf field: optional string server_id = 2;
*/
serverId?: string;
/**
* @generated from protobuf field: optional string name = 3;
*/
name?: string;
}
/**
* @generated from protobuf message api_client.GetProxyConfigResponse
*/
export interface GetProxyConfigResponse {
/**
* @generated from protobuf field: optional common.Status status = 1;
*/
status?: Status;
/**
* @generated from protobuf field: optional common.ProxyConfig proxy_config = 2;
*/
proxyConfig?: ProxyConfig;
/**
* @generated from protobuf field: optional common.ProxyWorkingStatus working_status = 3;
*/
workingStatus?: ProxyWorkingStatus;
}
// @generated message type with reflection information, may provide speed optimized methods
class InitClientRequest$Type extends MessageType<InitClientRequest> {
constructor() {
@@ -439,7 +607,8 @@ export const ListClientsResponse = new ListClientsResponse$Type();
class GetClientRequest$Type extends MessageType<GetClientRequest> {
constructor() {
super("api_client.GetClientRequest", [
{ 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*/ }
]);
}
create(value?: PartialMessage<GetClientRequest>): GetClientRequest {
@@ -456,6 +625,9 @@ class GetClientRequest$Type extends MessageType<GetClientRequest> {
case /* optional string client_id */ 1:
message.clientId = reader.string();
break;
case /* optional string server_id */ 2:
message.serverId = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
@@ -471,6 +643,9 @@ class GetClientRequest$Type extends MessageType<GetClientRequest> {
/* optional string client_id = 1; */
if (message.clientId !== undefined)
writer.tag(1, WireType.LengthDelimited).string(message.clientId);
/* optional string server_id = 2; */
if (message.serverId !== undefined)
writer.tag(2, WireType.LengthDelimited).string(message.serverId);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
@@ -1016,19 +1191,19 @@ class StartFRPCResponse$Type extends MessageType<StartFRPCResponse> {
*/
export const StartFRPCResponse = new StartFRPCResponse$Type();
// @generated message type with reflection information, may provide speed optimized methods
class GetProxyByCIDRequest$Type extends MessageType<GetProxyByCIDRequest> {
class GetProxyStatsByClientIDRequest$Type extends MessageType<GetProxyStatsByClientIDRequest> {
constructor() {
super("api_client.GetProxyByCIDRequest", [
super("api_client.GetProxyStatsByClientIDRequest", [
{ no: 1, name: "client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<GetProxyByCIDRequest>): GetProxyByCIDRequest {
create(value?: PartialMessage<GetProxyStatsByClientIDRequest>): GetProxyStatsByClientIDRequest {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<GetProxyByCIDRequest>(this, message, value);
reflectionMergePartial<GetProxyStatsByClientIDRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetProxyByCIDRequest): GetProxyByCIDRequest {
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetProxyStatsByClientIDRequest): GetProxyStatsByClientIDRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
@@ -1047,7 +1222,7 @@ class GetProxyByCIDRequest$Type extends MessageType<GetProxyByCIDRequest> {
}
return message;
}
internalBinaryWrite(message: GetProxyByCIDRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
internalBinaryWrite(message: GetProxyStatsByClientIDRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional string client_id = 1; */
if (message.clientId !== undefined)
writer.tag(1, WireType.LengthDelimited).string(message.clientId);
@@ -1058,25 +1233,25 @@ class GetProxyByCIDRequest$Type extends MessageType<GetProxyByCIDRequest> {
}
}
/**
* @generated MessageType for protobuf message api_client.GetProxyByCIDRequest
* @generated MessageType for protobuf message api_client.GetProxyStatsByClientIDRequest
*/
export const GetProxyByCIDRequest = new GetProxyByCIDRequest$Type();
export const GetProxyStatsByClientIDRequest = new GetProxyStatsByClientIDRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class GetProxyByCIDResponse$Type extends MessageType<GetProxyByCIDResponse> {
class GetProxyStatsByClientIDResponse$Type extends MessageType<GetProxyStatsByClientIDResponse> {
constructor() {
super("api_client.GetProxyByCIDResponse", [
super("api_client.GetProxyStatsByClientIDResponse", [
{ no: 1, name: "status", kind: "message", T: () => Status },
{ no: 2, name: "proxy_infos", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => ProxyInfo }
]);
}
create(value?: PartialMessage<GetProxyByCIDResponse>): GetProxyByCIDResponse {
create(value?: PartialMessage<GetProxyStatsByClientIDResponse>): GetProxyStatsByClientIDResponse {
const message = globalThis.Object.create((this.messagePrototype!));
message.proxyInfos = [];
if (value !== undefined)
reflectionMergePartial<GetProxyByCIDResponse>(this, message, value);
reflectionMergePartial<GetProxyStatsByClientIDResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetProxyByCIDResponse): GetProxyByCIDResponse {
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetProxyStatsByClientIDResponse): GetProxyStatsByClientIDResponse {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
@@ -1098,7 +1273,7 @@ class GetProxyByCIDResponse$Type extends MessageType<GetProxyByCIDResponse> {
}
return message;
}
internalBinaryWrite(message: GetProxyByCIDResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
internalBinaryWrite(message: GetProxyStatsByClientIDResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional common.Status status = 1; */
if (message.status)
Status.internalBinaryWrite(message.status, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
@@ -1112,6 +1287,593 @@ class GetProxyByCIDResponse$Type extends MessageType<GetProxyByCIDResponse> {
}
}
/**
* @generated MessageType for protobuf message api_client.GetProxyByCIDResponse
* @generated MessageType for protobuf message api_client.GetProxyStatsByClientIDResponse
*/
export const GetProxyByCIDResponse = new GetProxyByCIDResponse$Type();
export const GetProxyStatsByClientIDResponse = new GetProxyStatsByClientIDResponse$Type();
// @generated message type with reflection information, may provide speed optimized methods
class ListProxyConfigsRequest$Type extends MessageType<ListProxyConfigsRequest> {
constructor() {
super("api_client.ListProxyConfigsRequest", [
{ no: 1, name: "page", kind: "scalar", opt: true, T: 5 /*ScalarType.INT32*/ },
{ no: 2, name: "page_size", kind: "scalar", opt: true, T: 5 /*ScalarType.INT32*/ },
{ no: 3, name: "keyword", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 4, name: "client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 5, name: "server_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<ListProxyConfigsRequest>): ListProxyConfigsRequest {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<ListProxyConfigsRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ListProxyConfigsRequest): ListProxyConfigsRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* optional int32 page */ 1:
message.page = reader.int32();
break;
case /* optional int32 page_size */ 2:
message.pageSize = reader.int32();
break;
case /* optional string keyword */ 3:
message.keyword = reader.string();
break;
case /* optional string client_id */ 4:
message.clientId = reader.string();
break;
case /* optional string server_id */ 5:
message.serverId = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: ListProxyConfigsRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional int32 page = 1; */
if (message.page !== undefined)
writer.tag(1, WireType.Varint).int32(message.page);
/* optional int32 page_size = 2; */
if (message.pageSize !== undefined)
writer.tag(2, WireType.Varint).int32(message.pageSize);
/* optional string keyword = 3; */
if (message.keyword !== undefined)
writer.tag(3, WireType.LengthDelimited).string(message.keyword);
/* optional string client_id = 4; */
if (message.clientId !== undefined)
writer.tag(4, WireType.LengthDelimited).string(message.clientId);
/* optional string server_id = 5; */
if (message.serverId !== undefined)
writer.tag(5, WireType.LengthDelimited).string(message.serverId);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message api_client.ListProxyConfigsRequest
*/
export const ListProxyConfigsRequest = new ListProxyConfigsRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class ListProxyConfigsResponse$Type extends MessageType<ListProxyConfigsResponse> {
constructor() {
super("api_client.ListProxyConfigsResponse", [
{ no: 1, name: "status", kind: "message", T: () => Status },
{ no: 2, name: "total", kind: "scalar", opt: true, T: 5 /*ScalarType.INT32*/ },
{ no: 3, name: "proxy_configs", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => ProxyConfig }
]);
}
create(value?: PartialMessage<ListProxyConfigsResponse>): ListProxyConfigsResponse {
const message = globalThis.Object.create((this.messagePrototype!));
message.proxyConfigs = [];
if (value !== undefined)
reflectionMergePartial<ListProxyConfigsResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ListProxyConfigsResponse): ListProxyConfigsResponse {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* optional common.Status status */ 1:
message.status = Status.internalBinaryRead(reader, reader.uint32(), options, message.status);
break;
case /* optional int32 total */ 2:
message.total = reader.int32();
break;
case /* repeated common.ProxyConfig proxy_configs */ 3:
message.proxyConfigs.push(ProxyConfig.internalBinaryRead(reader, reader.uint32(), options));
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: ListProxyConfigsResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional common.Status status = 1; */
if (message.status)
Status.internalBinaryWrite(message.status, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
/* optional int32 total = 2; */
if (message.total !== undefined)
writer.tag(2, WireType.Varint).int32(message.total);
/* repeated common.ProxyConfig proxy_configs = 3; */
for (let i = 0; i < message.proxyConfigs.length; i++)
ProxyConfig.internalBinaryWrite(message.proxyConfigs[i], writer.tag(3, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message api_client.ListProxyConfigsResponse
*/
export const ListProxyConfigsResponse = new ListProxyConfigsResponse$Type();
// @generated message type with reflection information, may provide speed optimized methods
class CreateProxyConfigRequest$Type extends MessageType<CreateProxyConfigRequest> {
constructor() {
super("api_client.CreateProxyConfigRequest", [
{ 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: 3, name: "config", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ },
{ no: 4, name: "overwrite", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }
]);
}
create(value?: PartialMessage<CreateProxyConfigRequest>): CreateProxyConfigRequest {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<CreateProxyConfigRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: CreateProxyConfigRequest): CreateProxyConfigRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* optional string client_id */ 1:
message.clientId = reader.string();
break;
case /* optional string server_id */ 2:
message.serverId = reader.string();
break;
case /* optional bytes config */ 3:
message.config = reader.bytes();
break;
case /* optional bool overwrite */ 4:
message.overwrite = reader.bool();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: CreateProxyConfigRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional string client_id = 1; */
if (message.clientId !== undefined)
writer.tag(1, WireType.LengthDelimited).string(message.clientId);
/* optional string server_id = 2; */
if (message.serverId !== undefined)
writer.tag(2, WireType.LengthDelimited).string(message.serverId);
/* optional bytes config = 3; */
if (message.config !== undefined)
writer.tag(3, WireType.LengthDelimited).bytes(message.config);
/* optional bool overwrite = 4; */
if (message.overwrite !== undefined)
writer.tag(4, WireType.Varint).bool(message.overwrite);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message api_client.CreateProxyConfigRequest
*/
export const CreateProxyConfigRequest = new CreateProxyConfigRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class CreateProxyConfigResponse$Type extends MessageType<CreateProxyConfigResponse> {
constructor() {
super("api_client.CreateProxyConfigResponse", [
{ no: 1, name: "status", kind: "message", T: () => Status }
]);
}
create(value?: PartialMessage<CreateProxyConfigResponse>): CreateProxyConfigResponse {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<CreateProxyConfigResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: CreateProxyConfigResponse): CreateProxyConfigResponse {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* optional common.Status status */ 1:
message.status = Status.internalBinaryRead(reader, reader.uint32(), options, message.status);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: CreateProxyConfigResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional common.Status status = 1; */
if (message.status)
Status.internalBinaryWrite(message.status, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message api_client.CreateProxyConfigResponse
*/
export const CreateProxyConfigResponse = new CreateProxyConfigResponse$Type();
// @generated message type with reflection information, may provide speed optimized methods
class DeleteProxyConfigRequest$Type extends MessageType<DeleteProxyConfigRequest> {
constructor() {
super("api_client.DeleteProxyConfigRequest", [
{ 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: 3, name: "name", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<DeleteProxyConfigRequest>): DeleteProxyConfigRequest {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<DeleteProxyConfigRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: DeleteProxyConfigRequest): DeleteProxyConfigRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* optional string client_id */ 1:
message.clientId = reader.string();
break;
case /* optional string server_id */ 2:
message.serverId = reader.string();
break;
case /* optional string name */ 3:
message.name = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: DeleteProxyConfigRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional string client_id = 1; */
if (message.clientId !== undefined)
writer.tag(1, WireType.LengthDelimited).string(message.clientId);
/* optional string server_id = 2; */
if (message.serverId !== undefined)
writer.tag(2, WireType.LengthDelimited).string(message.serverId);
/* optional string name = 3; */
if (message.name !== undefined)
writer.tag(3, WireType.LengthDelimited).string(message.name);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message api_client.DeleteProxyConfigRequest
*/
export const DeleteProxyConfigRequest = new DeleteProxyConfigRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class DeleteProxyConfigResponse$Type extends MessageType<DeleteProxyConfigResponse> {
constructor() {
super("api_client.DeleteProxyConfigResponse", [
{ no: 1, name: "status", kind: "message", T: () => Status }
]);
}
create(value?: PartialMessage<DeleteProxyConfigResponse>): DeleteProxyConfigResponse {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<DeleteProxyConfigResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: DeleteProxyConfigResponse): DeleteProxyConfigResponse {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* optional common.Status status */ 1:
message.status = Status.internalBinaryRead(reader, reader.uint32(), options, message.status);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: DeleteProxyConfigResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional common.Status status = 1; */
if (message.status)
Status.internalBinaryWrite(message.status, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message api_client.DeleteProxyConfigResponse
*/
export const DeleteProxyConfigResponse = new DeleteProxyConfigResponse$Type();
// @generated message type with reflection information, may provide speed optimized methods
class UpdateProxyConfigRequest$Type extends MessageType<UpdateProxyConfigRequest> {
constructor() {
super("api_client.UpdateProxyConfigRequest", [
{ 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: 3, name: "name", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 4, name: "config", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }
]);
}
create(value?: PartialMessage<UpdateProxyConfigRequest>): UpdateProxyConfigRequest {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<UpdateProxyConfigRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UpdateProxyConfigRequest): UpdateProxyConfigRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* optional string client_id */ 1:
message.clientId = reader.string();
break;
case /* optional string server_id */ 2:
message.serverId = reader.string();
break;
case /* optional string name */ 3:
message.name = reader.string();
break;
case /* optional bytes config */ 4:
message.config = reader.bytes();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: UpdateProxyConfigRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional string client_id = 1; */
if (message.clientId !== undefined)
writer.tag(1, WireType.LengthDelimited).string(message.clientId);
/* optional string server_id = 2; */
if (message.serverId !== undefined)
writer.tag(2, WireType.LengthDelimited).string(message.serverId);
/* optional string name = 3; */
if (message.name !== undefined)
writer.tag(3, WireType.LengthDelimited).string(message.name);
/* optional bytes config = 4; */
if (message.config !== undefined)
writer.tag(4, WireType.LengthDelimited).bytes(message.config);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message api_client.UpdateProxyConfigRequest
*/
export const UpdateProxyConfigRequest = new UpdateProxyConfigRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class UpdateProxyConfigResponse$Type extends MessageType<UpdateProxyConfigResponse> {
constructor() {
super("api_client.UpdateProxyConfigResponse", [
{ no: 1, name: "status", kind: "message", T: () => Status }
]);
}
create(value?: PartialMessage<UpdateProxyConfigResponse>): UpdateProxyConfigResponse {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<UpdateProxyConfigResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UpdateProxyConfigResponse): UpdateProxyConfigResponse {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* optional common.Status status */ 1:
message.status = Status.internalBinaryRead(reader, reader.uint32(), options, message.status);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: UpdateProxyConfigResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional common.Status status = 1; */
if (message.status)
Status.internalBinaryWrite(message.status, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message api_client.UpdateProxyConfigResponse
*/
export const UpdateProxyConfigResponse = new UpdateProxyConfigResponse$Type();
// @generated message type with reflection information, may provide speed optimized methods
class GetProxyConfigRequest$Type extends MessageType<GetProxyConfigRequest> {
constructor() {
super("api_client.GetProxyConfigRequest", [
{ 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: 3, name: "name", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<GetProxyConfigRequest>): GetProxyConfigRequest {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<GetProxyConfigRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetProxyConfigRequest): GetProxyConfigRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* optional string client_id */ 1:
message.clientId = reader.string();
break;
case /* optional string server_id */ 2:
message.serverId = reader.string();
break;
case /* optional string name */ 3:
message.name = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: GetProxyConfigRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional string client_id = 1; */
if (message.clientId !== undefined)
writer.tag(1, WireType.LengthDelimited).string(message.clientId);
/* optional string server_id = 2; */
if (message.serverId !== undefined)
writer.tag(2, WireType.LengthDelimited).string(message.serverId);
/* optional string name = 3; */
if (message.name !== undefined)
writer.tag(3, WireType.LengthDelimited).string(message.name);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message api_client.GetProxyConfigRequest
*/
export const GetProxyConfigRequest = new GetProxyConfigRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class GetProxyConfigResponse$Type extends MessageType<GetProxyConfigResponse> {
constructor() {
super("api_client.GetProxyConfigResponse", [
{ no: 1, name: "status", kind: "message", T: () => Status },
{ no: 2, name: "proxy_config", kind: "message", T: () => ProxyConfig },
{ no: 3, name: "working_status", kind: "message", T: () => ProxyWorkingStatus }
]);
}
create(value?: PartialMessage<GetProxyConfigResponse>): GetProxyConfigResponse {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<GetProxyConfigResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetProxyConfigResponse): GetProxyConfigResponse {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* optional common.Status status */ 1:
message.status = Status.internalBinaryRead(reader, reader.uint32(), options, message.status);
break;
case /* optional common.ProxyConfig proxy_config */ 2:
message.proxyConfig = ProxyConfig.internalBinaryRead(reader, reader.uint32(), options, message.proxyConfig);
break;
case /* optional common.ProxyWorkingStatus working_status */ 3:
message.workingStatus = ProxyWorkingStatus.internalBinaryRead(reader, reader.uint32(), options, message.workingStatus);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: GetProxyConfigResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional common.Status status = 1; */
if (message.status)
Status.internalBinaryWrite(message.status, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
/* optional common.ProxyConfig proxy_config = 2; */
if (message.proxyConfig)
ProxyConfig.internalBinaryWrite(message.proxyConfig, writer.tag(2, WireType.LengthDelimited).fork(), options).join();
/* optional common.ProxyWorkingStatus working_status = 3; */
if (message.workingStatus)
ProxyWorkingStatus.internalBinaryWrite(message.workingStatus, writer.tag(3, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message api_client.GetProxyConfigResponse
*/
export const GetProxyConfigResponse = new GetProxyConfigResponse$Type();

View File

@@ -41,9 +41,9 @@ export interface ClientStatus {
*/
addr?: string;
/**
* @generated from protobuf field: optional int32 connect_time = 7;
* @generated from protobuf field: optional int64 connect_time = 7;
*/
connectTime?: number; // 连接建立的时间
connectTime?: bigint; // 连接建立的时间
}
/**
* @generated from protobuf enum api_master.ClientStatus.Status
@@ -163,7 +163,7 @@ class ClientStatus$Type extends MessageType<ClientStatus> {
{ no: 4, name: "ping", kind: "scalar", T: 5 /*ScalarType.INT32*/ },
{ no: 5, name: "version", kind: "message", T: () => ClientVersion },
{ no: 6, name: "addr", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 7, name: "connect_time", kind: "scalar", opt: true, T: 5 /*ScalarType.INT32*/ }
{ no: 7, name: "connect_time", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }
]);
}
create(value?: PartialMessage<ClientStatus>): ClientStatus {
@@ -199,8 +199,8 @@ class ClientStatus$Type extends MessageType<ClientStatus> {
case /* optional string addr */ 6:
message.addr = reader.string();
break;
case /* optional int32 connect_time */ 7:
message.connectTime = reader.int32();
case /* optional int64 connect_time */ 7:
message.connectTime = reader.int64().toBigInt();
break;
default:
let u = options.readUnknownField;
@@ -232,9 +232,9 @@ class ClientStatus$Type extends MessageType<ClientStatus> {
/* optional string addr = 6; */
if (message.addr !== undefined)
writer.tag(6, WireType.LengthDelimited).string(message.addr);
/* optional int32 connect_time = 7; */
/* optional int64 connect_time = 7; */
if (message.connectTime !== undefined)
writer.tag(7, WireType.Varint).int32(message.connectTime);
writer.tag(7, WireType.Varint).int64(message.connectTime);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);

View File

@@ -202,18 +202,18 @@ export interface StartFRPSResponse {
status?: Status;
}
/**
* @generated from protobuf message api_server.GetProxyBySIDRequest
* @generated from protobuf message api_server.GetProxyStatsByServerIDRequest
*/
export interface GetProxyBySIDRequest {
export interface GetProxyStatsByServerIDRequest {
/**
* @generated from protobuf field: optional string server_id = 1;
*/
serverId?: string;
}
/**
* @generated from protobuf message api_server.GetProxyBySIDResponse
* @generated from protobuf message api_server.GetProxyStatsByServerIDResponse
*/
export interface GetProxyBySIDResponse {
export interface GetProxyStatsByServerIDResponse {
/**
* @generated from protobuf field: optional common.Status status = 1;
*/
@@ -1038,19 +1038,19 @@ class StartFRPSResponse$Type extends MessageType<StartFRPSResponse> {
*/
export const StartFRPSResponse = new StartFRPSResponse$Type();
// @generated message type with reflection information, may provide speed optimized methods
class GetProxyBySIDRequest$Type extends MessageType<GetProxyBySIDRequest> {
class GetProxyStatsByServerIDRequest$Type extends MessageType<GetProxyStatsByServerIDRequest> {
constructor() {
super("api_server.GetProxyBySIDRequest", [
super("api_server.GetProxyStatsByServerIDRequest", [
{ no: 1, name: "server_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<GetProxyBySIDRequest>): GetProxyBySIDRequest {
create(value?: PartialMessage<GetProxyStatsByServerIDRequest>): GetProxyStatsByServerIDRequest {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<GetProxyBySIDRequest>(this, message, value);
reflectionMergePartial<GetProxyStatsByServerIDRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetProxyBySIDRequest): GetProxyBySIDRequest {
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetProxyStatsByServerIDRequest): GetProxyStatsByServerIDRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
@@ -1069,7 +1069,7 @@ class GetProxyBySIDRequest$Type extends MessageType<GetProxyBySIDRequest> {
}
return message;
}
internalBinaryWrite(message: GetProxyBySIDRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
internalBinaryWrite(message: GetProxyStatsByServerIDRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional string server_id = 1; */
if (message.serverId !== undefined)
writer.tag(1, WireType.LengthDelimited).string(message.serverId);
@@ -1080,25 +1080,25 @@ class GetProxyBySIDRequest$Type extends MessageType<GetProxyBySIDRequest> {
}
}
/**
* @generated MessageType for protobuf message api_server.GetProxyBySIDRequest
* @generated MessageType for protobuf message api_server.GetProxyStatsByServerIDRequest
*/
export const GetProxyBySIDRequest = new GetProxyBySIDRequest$Type();
export const GetProxyStatsByServerIDRequest = new GetProxyStatsByServerIDRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class GetProxyBySIDResponse$Type extends MessageType<GetProxyBySIDResponse> {
class GetProxyStatsByServerIDResponse$Type extends MessageType<GetProxyStatsByServerIDResponse> {
constructor() {
super("api_server.GetProxyBySIDResponse", [
super("api_server.GetProxyStatsByServerIDResponse", [
{ no: 1, name: "status", kind: "message", T: () => Status },
{ no: 2, name: "proxy_infos", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => ProxyInfo }
]);
}
create(value?: PartialMessage<GetProxyBySIDResponse>): GetProxyBySIDResponse {
create(value?: PartialMessage<GetProxyStatsByServerIDResponse>): GetProxyStatsByServerIDResponse {
const message = globalThis.Object.create((this.messagePrototype!));
message.proxyInfos = [];
if (value !== undefined)
reflectionMergePartial<GetProxyBySIDResponse>(this, message, value);
reflectionMergePartial<GetProxyStatsByServerIDResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetProxyBySIDResponse): GetProxyBySIDResponse {
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetProxyStatsByServerIDResponse): GetProxyStatsByServerIDResponse {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
@@ -1120,7 +1120,7 @@ class GetProxyBySIDResponse$Type extends MessageType<GetProxyBySIDResponse> {
}
return message;
}
internalBinaryWrite(message: GetProxyBySIDResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
internalBinaryWrite(message: GetProxyStatsByServerIDResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional common.Status status = 1; */
if (message.status)
Status.internalBinaryWrite(message.status, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
@@ -1134,6 +1134,6 @@ class GetProxyBySIDResponse$Type extends MessageType<GetProxyBySIDResponse> {
}
}
/**
* @generated MessageType for protobuf message api_server.GetProxyBySIDResponse
* @generated MessageType for protobuf message api_server.GetProxyStatsByServerIDResponse
*/
export const GetProxyBySIDResponse = new GetProxyBySIDResponse$Type();
export const GetProxyStatsByServerIDResponse = new GetProxyStatsByServerIDResponse$Type();

View File

@@ -73,6 +73,14 @@ export interface Client {
* @generated from protobuf field: optional bool stopped = 7;
*/
stopped?: boolean;
/**
* @generated from protobuf field: repeated string client_ids = 8;
*/
clientIds: string[]; // some client can connected to more than one server, make a shadow client to handle this
/**
* @generated from protobuf field: optional string origin_client_id = 9;
*/
originClientId?: string;
}
/**
* @generated from protobuf message common.Server
@@ -177,6 +185,64 @@ export interface ProxyInfo {
*/
firstSync?: boolean;
}
/**
* @generated from protobuf message common.ProxyConfig
*/
export interface ProxyConfig {
/**
* @generated from protobuf field: optional uint32 id = 1;
*/
id?: number;
/**
* @generated from protobuf field: optional string name = 2;
*/
name?: string;
/**
* @generated from protobuf field: optional string type = 3;
*/
type?: string;
/**
* @generated from protobuf field: optional string client_id = 4;
*/
clientId?: string;
/**
* @generated from protobuf field: optional string server_id = 5;
*/
serverId?: string;
/**
* @generated from protobuf field: optional string config = 6;
*/
config?: string;
/**
* @generated from protobuf field: optional string origin_client_id = 7;
*/
originClientId?: string;
}
/**
* @generated from protobuf message common.ProxyWorkingStatus
*/
export interface ProxyWorkingStatus {
/**
* @generated from protobuf field: optional string name = 1;
*/
name?: string;
/**
* @generated from protobuf field: optional string type = 2;
*/
type?: string;
/**
* @generated from protobuf field: optional string status = 3;
*/
status?: string;
/**
* @generated from protobuf field: optional string err = 4;
*/
err?: string;
/**
* @generated from protobuf field: optional string remote_addr = 5;
*/
remoteAddr?: string;
}
/**
* @generated from protobuf enum common.RespCode
*/
@@ -390,11 +456,14 @@ class Client$Type extends MessageType<Client> {
{ no: 3, name: "config", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 5, name: "comment", 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: 9, name: "origin_client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<Client>): Client {
const message = globalThis.Object.create((this.messagePrototype!));
message.clientIds = [];
if (value !== undefined)
reflectionMergePartial<Client>(this, message, value);
return message;
@@ -422,6 +491,12 @@ class Client$Type extends MessageType<Client> {
case /* optional bool stopped */ 7:
message.stopped = reader.bool();
break;
case /* repeated string client_ids */ 8:
message.clientIds.push(reader.string());
break;
case /* optional string origin_client_id */ 9:
message.originClientId = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
@@ -452,6 +527,12 @@ class Client$Type extends MessageType<Client> {
/* optional bool stopped = 7; */
if (message.stopped !== undefined)
writer.tag(7, WireType.Varint).bool(message.stopped);
/* repeated string client_ids = 8; */
for (let i = 0; i < message.clientIds.length; i++)
writer.tag(8, WireType.LengthDelimited).string(message.clientIds[i]);
/* optional string origin_client_id = 9; */
if (message.originClientId !== undefined)
writer.tag(9, WireType.LengthDelimited).string(message.originClientId);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
@@ -733,3 +814,165 @@ class ProxyInfo$Type extends MessageType<ProxyInfo> {
* @generated MessageType for protobuf message common.ProxyInfo
*/
export const ProxyInfo = new ProxyInfo$Type();
// @generated message type with reflection information, may provide speed optimized methods
class ProxyConfig$Type extends MessageType<ProxyConfig> {
constructor() {
super("common.ProxyConfig", [
{ no: 1, name: "id", kind: "scalar", opt: true, T: 13 /*ScalarType.UINT32*/ },
{ no: 2, name: "name", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 3, name: "type", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 4, name: "client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 5, name: "server_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 6, name: "config", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 7, name: "origin_client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<ProxyConfig>): ProxyConfig {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<ProxyConfig>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ProxyConfig): ProxyConfig {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* optional uint32 id */ 1:
message.id = reader.uint32();
break;
case /* optional string name */ 2:
message.name = reader.string();
break;
case /* optional string type */ 3:
message.type = reader.string();
break;
case /* optional string client_id */ 4:
message.clientId = reader.string();
break;
case /* optional string server_id */ 5:
message.serverId = reader.string();
break;
case /* optional string config */ 6:
message.config = reader.string();
break;
case /* optional string origin_client_id */ 7:
message.originClientId = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: ProxyConfig, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional uint32 id = 1; */
if (message.id !== undefined)
writer.tag(1, WireType.Varint).uint32(message.id);
/* optional string name = 2; */
if (message.name !== undefined)
writer.tag(2, WireType.LengthDelimited).string(message.name);
/* optional string type = 3; */
if (message.type !== undefined)
writer.tag(3, WireType.LengthDelimited).string(message.type);
/* optional string client_id = 4; */
if (message.clientId !== undefined)
writer.tag(4, WireType.LengthDelimited).string(message.clientId);
/* optional string server_id = 5; */
if (message.serverId !== undefined)
writer.tag(5, WireType.LengthDelimited).string(message.serverId);
/* optional string config = 6; */
if (message.config !== undefined)
writer.tag(6, WireType.LengthDelimited).string(message.config);
/* optional string origin_client_id = 7; */
if (message.originClientId !== undefined)
writer.tag(7, WireType.LengthDelimited).string(message.originClientId);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message common.ProxyConfig
*/
export const ProxyConfig = new ProxyConfig$Type();
// @generated message type with reflection information, may provide speed optimized methods
class ProxyWorkingStatus$Type extends MessageType<ProxyWorkingStatus> {
constructor() {
super("common.ProxyWorkingStatus", [
{ no: 1, name: "name", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 2, name: "type", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 3, name: "status", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 4, name: "err", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 5, name: "remote_addr", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<ProxyWorkingStatus>): ProxyWorkingStatus {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<ProxyWorkingStatus>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ProxyWorkingStatus): ProxyWorkingStatus {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* optional string name */ 1:
message.name = reader.string();
break;
case /* optional string type */ 2:
message.type = reader.string();
break;
case /* optional string status */ 3:
message.status = reader.string();
break;
case /* optional string err */ 4:
message.err = reader.string();
break;
case /* optional string remote_addr */ 5:
message.remoteAddr = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: ProxyWorkingStatus, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* optional string name = 1; */
if (message.name !== undefined)
writer.tag(1, WireType.LengthDelimited).string(message.name);
/* optional string type = 2; */
if (message.type !== undefined)
writer.tag(2, WireType.LengthDelimited).string(message.type);
/* optional string status = 3; */
if (message.status !== undefined)
writer.tag(3, WireType.LengthDelimited).string(message.status);
/* optional string err = 4; */
if (message.err !== undefined)
writer.tag(4, WireType.LengthDelimited).string(message.err);
/* optional string remote_addr = 5; */
if (message.remoteAddr !== undefined)
writer.tag(5, WireType.LengthDelimited).string(message.remoteAddr);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message common.ProxyWorkingStatus
*/
export const ProxyWorkingStatus = new ProxyWorkingStatus$Type();

View File

@@ -12,3 +12,8 @@ export function formatBytes(bytes: number): string {
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
export function ObjToUint8Array(obj: any): Uint8Array {
const buffer = Buffer.from(JSON.stringify(obj))
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

View File

@@ -7,8 +7,8 @@ export default function ClientEditPage() {
return (
<Providers>
<RootLayout mainHeader={<Header />}>
<div className="w-full">
<div className="flex-1 flex-col">
<div className="w-full flex items-center justify-center">
<div className="flex-1 flex-col max-w-2xl">
<FRPCFormCard />
</div>
</div>

40
www/pages/proxies.tsx Normal file
View File

@@ -0,0 +1,40 @@
import { Providers } from '@/components/providers'
import { RootLayout } from '@/components/layout'
import { Header } from '@/components/header'
import { ProxyConfigList } from '@/components/proxy/proxy_config_list'
import { useState } from 'react'
import { ProxyConfigMutateDialog } from '@/components/proxy/mutate_proxy_config'
import { IdInput } from '@/components/base/id_input'
import { ClientSelector } from '@/components/base/client-selector'
import { ServerSelector } from '@/components/base/server-selector'
import { $proxyTableRefetchTrigger } from '@/store/refetch-trigger'
export default function Proxies() {
const [keyword, setKeyword] = useState('')
const [clientID, setClientID] = useState<string | undefined>(undefined)
const [serverID, setServerID] = useState<string | undefined>(undefined)
const triggerRefetch = (n:string) => {
$proxyTableRefetchTrigger.set(Math.random())
}
return (
<Providers>
<RootLayout mainHeader={<Header />}>
<div className="w-full">
<div className="flex flex-1 flex-col">
<div className="flex flex-1 flex-row mb-2 gap-2">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-2">
<ClientSelector clientID={clientID} setClientID={setClientID} />
<ServerSelector serverID={serverID} setServerID={setServerID} />
<IdInput setKeyword={setKeyword} keyword={keyword} refetchTrigger={triggerRefetch} />
<ProxyConfigMutateDialog />
</div>
</div>
<ProxyConfigList Keyword={keyword} ProxyConfigs={[]} ClientID={clientID} ServerID={serverID} />
</div>
</div>
</RootLayout>
</Providers>
)
}

View File

@@ -7,8 +7,8 @@ export default function ServerListPage() {
return (
<Providers>
<RootLayout mainHeader={<Header />}>
<div className="w-full">
<div className="flex-1 flex-col">
<div className="w-full flex items-center justify-center">
<div className="flex-1 flex-col max-w-2xl">
<FRPSFormCard />
</div>
</div>

View File

@@ -5,19 +5,58 @@ import { Separator } from '@/components/ui/separator'
import { FRPSFormCard } from '@/components/frps/frps_card'
import { RootLayout } from '@/components/layout'
import { Header } from '@/components/header'
import { createProxyConfig, listProxyConfig } from '@/api/proxy'
import { Button } from '@/components/ui/button'
import { TypedProxyConfig } from '@/types/proxy'
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() {
const [name, setName] = useState<string>('')
const [triggerRefetch, setTriggerRefetch] = useState<number>(0)
function create() {
const buffer = Buffer.from(
JSON.stringify({
proxies: [{
name: name,
type: 'tcp',
localIP: '127.0.0.1',
localPort: 1234,
remotePort: 4321,
} as TypedProxyConfig]
} as ClientConfig),
)
const uint8Array: Uint8Array = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
createProxyConfig({
clientId: 'admin.c.test',
config: uint8Array,
serverId: 'default',
})
.then(() => {
setTriggerRefetch(triggerRefetch + 1)
})
.catch((err) => {
console.log(err)
})
}
return (
<></>
// <RootLayout header={<Header />} sidebar={<SideBar />}>
// <Providers>
// <div className='grid grid-cols-1 md:grid-cols-2 gap-8'>
// <FRPCFormCard></FRPCFormCard>
// <FRPSFormCard></FRPSFormCard>
// </div>
// <Separator className='my-2' />
// <APITest />
// </Providers>
// </RootLayout>
// <>
// </>
<Providers>
<RootLayout mainHeader={<Header />}>
<div className="w-full">
<div className="flex flex-1 flex-col">
<div className="flex flex-1 flex-row mb-2 gap-2">
<Button onClick={create}></Button>
<Input value={name} onChange={(e) => setName(e.target.value)} ></Input>
</div>
<ProxyConfigList Keyword="" ProxyConfigs={[]} TriggerRefetch={triggerRefetch.toString()} />
</div>
</div>
</RootLayout>
</Providers>
)
}

View File

@@ -0,0 +1,5 @@
import { atom } from 'nanostores'
export const $clientTableRefetchTrigger = atom<number>(0)
export const $serverTableRefetchTrigger = atom<number>(0)
export const $proxyTableRefetchTrigger = atom<number>(0)