mirror of
				https://github.com/VaalaCat/frp-panel.git
				synced 2025-10-31 10:46:36 +08:00 
			
		
		
		
	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:
		
							
								
								
									
										42
									
								
								biz/client/get_proxy_info.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								biz/client/get_proxy_info.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -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"}, | ||||
|   | ||||
| @@ -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{ | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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"}, | ||||
|   | ||||
| @@ -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"}, | ||||
|   | ||||
| @@ -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()) | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| 	} | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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()), | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										151
									
								
								biz/master/proxy/create_proxy_config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								biz/master/proxy/create_proxy_config.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										82
									
								
								biz/master/proxy/delete_proxy_config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								biz/master/proxy/delete_proxy_config.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										65
									
								
								biz/master/proxy/get_proxy_config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								biz/master/proxy/get_proxy_config.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
| @@ -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), | ||||
|   | ||||
							
								
								
									
										86
									
								
								biz/master/proxy/list_config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								biz/master/proxy/list_config.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -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, | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
|   | ||||
							
								
								
									
										127
									
								
								biz/master/proxy/update_proxy_config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								biz/master/proxy/update_proxy_config.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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{ | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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 ( | ||||
|   | ||||
							
								
								
									
										143
									
								
								dao/client.go
									
									
									
									
									
								
							
							
						
						
									
										143
									
								
								dao/client.go
									
									
									
									
									
								
							| @@ -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) | ||||
| } | ||||
|   | ||||
							
								
								
									
										392
									
								
								dao/proxy.go
									
									
									
									
									
								
							
							
						
						
									
										392
									
								
								dao/proxy.go
									
									
									
									
									
								
							| @@ -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
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @@ -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
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							| @@ -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= | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
| @@ -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 { | ||||
| @@ -71,4 +73,22 @@ message ProxyInfo { | ||||
| 	optional int64 history_traffic_in = 7; | ||||
| 	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; | ||||
| } | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
							
								
								
									
										19
									
								
								models/db.go
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								models/db.go
									
									
									
									
									
								
							| @@ -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
									
								
							
							
						
						
									
										74
									
								
								models/helper.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										48
									
								
								models/proxy_config.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -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
											
										
									
								
							| @@ -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, | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										442
									
								
								pb/common.pb.go
									
									
									
									
									
								
							
							
						
						
									
										442
									
								
								pb/common.pb.go
									
									
									
									
									
								
							| @@ -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, | ||||
| 		}, | ||||
|   | ||||
| @@ -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 ( | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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() | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										144
									
								
								tunnel/client.go
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								tunnel/client.go
									
									
									
									
									
								
							| @@ -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() | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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
									
								
							
							
						
						
									
										23
									
								
								utils/load_test.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										21
									
								
								utils/validate.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										40
									
								
								www/api/proxy.ts
									
									
									
									
									
										Normal 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) | ||||
| } | ||||
| @@ -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) | ||||
| } | ||||
| @@ -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> { | ||||
|   | ||||
| @@ -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  | ||||
|     }) : '-' | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
							
								
								
									
										46
									
								
								www/components/base/drop-down-menu.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								www/components/base/drop-down-menu.tsx
									
									
									
									
									
										Normal 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> | ||||
|   ) | ||||
| } | ||||
| @@ -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"> | ||||
|       <Input  | ||||
|         className="max-w-40 h-auto"  | ||||
|     <div className="flex flex-row gap-2 items-center"> | ||||
|       <Input | ||||
|         className="text-sm"  | ||||
|         defaultValue={keyword}  | ||||
|         placeholder={t('input.id.placeholder')} | ||||
|         placeholder={t('input.keyword.placeholder')} | ||||
|         onChange={(e) => setInput(e.target.value)} | ||||
|       /> | ||||
|       <Button  | ||||
|       <Button | ||||
|         variant="outline"  | ||||
|         size="sm"  | ||||
|         onClick={() => { | ||||
|           setKeyword(input) | ||||
|           refetchTrigger && refetchTrigger(JSON.stringify(Math.random())) | ||||
|   | ||||
							
								
								
									
										70
									
								
								www/components/base/list-input.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								www/components/base/list-input.tsx
									
									
									
									
									
										Normal 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; | ||||
| @@ -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  | ||||
| export const ServerSelector: React.FC<ServerSelectorProps> = ({ | ||||
|   serverID, | ||||
|   setServerID, | ||||
|   onOpenChange, | ||||
|   setServer, | ||||
| }) => { | ||||
|   const { t } = useTranslation() | ||||
|   const handleServerChange = (value: string) => { setServerID(value) } | ||||
|   const [keyword, setKeyword] = React.useState('') | ||||
|  | ||||
|   const { data: serverList, refetch: refetchServers } = useQuery({ | ||||
| @@ -29,14 +31,24 @@ 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')} | ||||
|       value={serverID} | ||||
|       setValue={handleServerChange} | ||||
|       dataList={serverList?.servers.map((server) => ({  | ||||
|         value: server.id || '',  | ||||
|         label: server.id || ''  | ||||
|       dataList={serverList?.servers.map((server) => ({ | ||||
|         value: server.id || '', | ||||
|         label: server.id || '' | ||||
|       })) || []} | ||||
|       onKeyWordChange={setKeyword} | ||||
|       onOpenChange={() => { | ||||
|   | ||||
							
								
								
									
										57
									
								
								www/components/base/visit-preview.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								www/components/base/visit-preview.tsx
									
									
									
									
									
										Normal 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; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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) | ||||
|                 }) | ||||
|               } | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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,19 +167,103 @@ 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> | ||||
|       )}     | ||||
|       )} | ||||
|       defaultValue={defaultValue} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| 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> | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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()) | ||||
|     }, | ||||
|   }) | ||||
|  | ||||
|   | ||||
| @@ -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({ | ||||
|   | ||||
							
								
								
									
										146
									
								
								www/components/proxy/mutate_proxy_config.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								www/components/proxy/mutate_proxy_config.tsx
									
									
									
									
									
										Normal 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> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
							
								
								
									
										102
									
								
								www/components/proxy/proxy_config_actions.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								www/components/proxy/proxy_config_actions.tsx
									
									
									
									
									
										Normal 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>} /> | ||||
|   </>) | ||||
| } | ||||
							
								
								
									
										152
									
								
								www/components/proxy/proxy_config_item.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								www/components/proxy/proxy_config_item.tsx
									
									
									
									
									
										Normal 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> | ||||
| } | ||||
							
								
								
									
										125
									
								
								www/components/proxy/proxy_config_list.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								www/components/proxy/proxy_config_list.tsx
									
									
									
									
									
										Normal 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) | ||||
| } | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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> | ||||
| @@ -129,7 +129,7 @@ export const ClientStatsCard: React.FC<ClientStatsCardProps> = ({ clientID: defa | ||||
|  | ||||
| const ProxyStatusCard: React.FC<{ proxyInfo: ProxyInfo | undefined }> = ({ proxyInfo }) => { | ||||
|   const { t } = useTranslation(); | ||||
|    | ||||
|  | ||||
|   if (!proxyInfo) { | ||||
|     return null; | ||||
|   } | ||||
|   | ||||
| @@ -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
									
								
							
							
						
						
									
										9
									
								
								www/config/notify.ts
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -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": { | ||||
|   | ||||
| @@ -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": { | ||||
|   | ||||
| @@ -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>( | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -11,4 +11,9 @@ export function formatBytes(bytes: number): string { | ||||
|   const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] | ||||
|   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) | ||||
| } | ||||
| @@ -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
									
								
							
							
						
						
									
										40
									
								
								www/pages/proxies.tsx
									
									
									
									
									
										Normal 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> | ||||
|   ) | ||||
| } | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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> | ||||
|   ) | ||||
| } | ||||
|   | ||||
							
								
								
									
										5
									
								
								www/store/refetch-trigger.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								www/store/refetch-trigger.ts
									
									
									
									
									
										Normal 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) | ||||
		Reference in New Issue
	
	Block a user
	 VaalaCat
					VaalaCat