diff --git a/biz/client/rpc_handler.go b/biz/client/rpc_handler.go index 4323423..89b04df 100644 --- a/biz/client/rpc_handler.go +++ b/biz/client/rpc_handler.go @@ -55,6 +55,8 @@ func HandleServerMessage(appInstance app.Application, req *pb.ServerMessage) *pb return app.WrapperServerMsg(appInstance, req, GetWireGuardRuntimeInfo) case pb.Event_EVENT_RESTART_WIREGUARD: return app.WrapperServerMsg(appInstance, req, RestartWireGuard) + case pb.Event_EVENT_UPGRADE_FRPP: + return app.WrapperServerMsg(appInstance, req, UpgradeFrpp) case pb.Event_EVENT_PING: rawData, _ := proto.Marshal(conf.GetVersion().ToProto()) return &pb.ClientMessage{ diff --git a/biz/client/upgrade_frpp.go b/biz/client/upgrade_frpp.go new file mode 100644 index 0000000..51438f8 --- /dev/null +++ b/biz/client/upgrade_frpp.go @@ -0,0 +1,62 @@ +package client + +import ( + "context" + + bizupgrade "github.com/VaalaCat/frp-panel/biz/common/upgrade" + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/services/app" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +// UpgradeFrpp 收到 master 下发的升级指令后,异步执行升级并快速 ACK。 +// 说明:必须快速返回 response,避免 master 端 HTTP/RPC 长时间阻塞; +// +// 真正的 stop/restart 将由 upgrader service/worker 执行(会导致连接短暂断开,属于预期)。 +func UpgradeFrpp(ctx *app.Context, req *pb.UpgradeFrppRequest) (*pb.UpgradeFrppResponse, error) { + log := logger.Logger(ctx) + log.Infof("upgrade frpp request received, clientIds=%v version=%s downloadUrl=%s", req.GetClientIds(), req.GetVersion(), req.GetDownloadUrl()) + + // 默认值处理(proto3 optional 未设置时 GetXXX 返回零值) + backup := true + if req.Backup != nil { + backup = req.GetBackup() + } + restartService := true + if req.RestartService != nil { + restartService = req.GetRestartService() + } + useGithubProxy := true + if req.UseGithubProxy != nil { + useGithubProxy = req.GetUseGithubProxy() + } + + opts := bizupgrade.Options{ + Version: req.GetVersion(), + DownloadURL: req.GetDownloadUrl(), + GithubProxy: req.GetGithubProxy(), + UseGithubProxy: useGithubProxy, + HTTPProxy: req.GetHttpProxy(), + TargetPath: req.GetTargetPath(), + Backup: backup, + ServiceName: req.GetServiceName(), + RestartService: restartService, + WorkDir: req.GetWorkdir(), + ServiceArgs: req.GetServiceArgs(), + } + + // 异步执行:确保能快速回 ACK,避免远程触发链路因重启/断连卡死 + go func() { + bg := context.Background() + if _, err := bizupgrade.StartWithResult(bg, opts); err != nil { + logger.Logger(bg).WithError(err).Error("upgrade frpp failed") + } + }() + + return &pb.UpgradeFrppResponse{ + Status: &pb.Status{ + Code: pb.RespCode_RESP_CODE_SUCCESS, + Message: "accepted", + }, + }, nil +} diff --git a/biz/master/client/upgrade_frpp.go b/biz/master/client/upgrade_frpp.go new file mode 100644 index 0000000..12dc469 --- /dev/null +++ b/biz/master/client/upgrade_frpp.go @@ -0,0 +1,100 @@ +package client + +import ( + "sync" + + "github.com/VaalaCat/frp-panel/common" + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/services/app" + "github.com/VaalaCat/frp-panel/services/dao" + "github.com/VaalaCat/frp-panel/services/rpc" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +func UpgradeFrppHandler(ctx *app.Context, req *pb.UpgradeFrppRequest) (*pb.UpgradeFrppResponse, error) { + userInfo := common.GetUserInfo(ctx) + clientIds := req.GetClientIds() + log := logger.Logger(ctx) + log.Infof("upgrade frpp called, user=%v clientIds=%v", userInfo, clientIds) + + if len(clientIds) == 0 { + return &pb.UpgradeFrppResponse{ + Status: &pb.Status{Code: pb.RespCode_RESP_CODE_INVALID, Message: "client_ids is empty"}, + }, nil + } + + // 默认值处理:proto3 optional 未设置时 GetXXX 返回零值 + backup := true + if req.Backup != nil { + backup = req.GetBackup() + } + restartService := true + if req.RestartService != nil { + restartService = req.GetRestartService() + } + useGithubProxy := true + if req.UseGithubProxy != nil { + useGithubProxy = req.GetUseGithubProxy() + } + + // 并发下发,提高多 client 批量升级速度 + var ( + wg sync.WaitGroup + errOnce error + mu sync.Mutex + ) + + for _, cid := range clientIds { + clientId := cid + wg.Add(1) + go func() { + defer wg.Done() + + _, err := dao.NewQuery(ctx).GetClientByClientID(userInfo, clientId) + if err != nil { + mu.Lock() + if errOnce == nil { + errOnce = err + } + mu.Unlock() + return + } + + // 每次只给单个 client 下发(减少 payload 混淆) + reqForClient := &pb.UpgradeFrppRequest{ + ClientIds: []string{clientId}, + Version: req.Version, + DownloadUrl: req.DownloadUrl, + GithubProxy: req.GithubProxy, + UseGithubProxy: &useGithubProxy, + HttpProxy: req.HttpProxy, + TargetPath: req.TargetPath, + Backup: &backup, + ServiceName: req.ServiceName, + RestartService: &restartService, + Workdir: req.Workdir, + ServiceArgs: req.GetServiceArgs(), + } + + resp := &pb.UpgradeFrppResponse{} + if err := rpc.CallClientWrapper(ctx, clientId, pb.Event_EVENT_UPGRADE_FRPP, reqForClient, resp); err != nil { + mu.Lock() + if errOnce == nil { + errOnce = err + } + mu.Unlock() + return + } + }() + } + wg.Wait() + + if errOnce != nil { + log.WithError(errOnce).Error("upgrade frpp dispatch failed") + return nil, errOnce + } + + return &pb.UpgradeFrppResponse{ + Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"}, + }, nil +} diff --git a/biz/master/handler.go b/biz/master/handler.go index 044cb4e..4b6c1c0 100644 --- a/biz/master/handler.go +++ b/biz/master/handler.go @@ -55,6 +55,7 @@ func ConfigureRouter(appInstance app.Application, router *gin.Engine) { clientRouter.POST("/delete", app.Wrapper(appInstance, client.DeleteClientHandler)) clientRouter.POST("/list", app.Wrapper(appInstance, client.ListClientsHandler)) clientRouter.POST("/install_workerd", app.Wrapper(appInstance, worker.InstallWorkerd)) + clientRouter.POST("/upgrade", app.Wrapper(appInstance, client.UpgradeFrppHandler)) } serverRouter := v1.Group("/server") { diff --git a/common/request.go b/common/request.go index c50d8b6..2ccb185 100644 --- a/common/request.go +++ b/common/request.go @@ -28,7 +28,7 @@ type ReqType interface { pb.StartProxyRequest | pb.StopProxyRequest | pb.CreateWorkerRequest | pb.RemoveWorkerRequest | pb.RunWorkerRequest | pb.StopWorkerRequest | pb.UpdateWorkerRequest | pb.GetWorkerRequest | pb.ListWorkersRequest | pb.CreateWorkerIngressRequest | pb.GetWorkerIngressRequest | - pb.GetWorkerStatusRequest | pb.InstallWorkerdRequest | pb.RedeployWorkerRequest | + pb.GetWorkerStatusRequest | pb.InstallWorkerdRequest | pb.RedeployWorkerRequest | pb.UpgradeFrppRequest | pb.StartSteamLogRequest | // wireguard api pb.CreateNetworkRequest | pb.DeleteNetworkRequest | pb.UpdateNetworkRequest | pb.GetNetworkRequest | pb.ListNetworksRequest | pb.RestartWireGuardRequest | diff --git a/common/response.go b/common/response.go index fc7376a..ef42074 100644 --- a/common/response.go +++ b/common/response.go @@ -29,7 +29,7 @@ type RespType interface { pb.StartProxyResponse | pb.StopProxyResponse | pb.CreateWorkerResponse | pb.RemoveWorkerResponse | pb.RunWorkerResponse | pb.StopWorkerResponse | pb.UpdateWorkerResponse | pb.GetWorkerResponse | pb.ListWorkersResponse | pb.CreateWorkerIngressResponse | pb.GetWorkerIngressResponse | - pb.GetWorkerStatusResponse | pb.InstallWorkerdResponse | pb.RedeployWorkerResponse | + pb.GetWorkerStatusResponse | pb.InstallWorkerdResponse | pb.RedeployWorkerResponse | pb.UpgradeFrppResponse | pb.StartSteamLogResponse | // wireguard api pb.CreateNetworkResponse | pb.DeleteNetworkResponse | pb.UpdateNetworkResponse | pb.GetNetworkResponse | pb.ListNetworksResponse | pb.RestartWireGuardResponse | @@ -114,6 +114,8 @@ func getEvent(origin interface{}) (pb.Event, protoreflect.ProtoMessage, error) { return pb.Event_EVENT_GET_WORKER_STATUS, ptr, nil case *pb.InstallWorkerdResponse: return pb.Event_EVENT_INSTALL_WORKERD, ptr, nil + case *pb.UpgradeFrppResponse: + return pb.Event_EVENT_UPGRADE_FRPP, ptr, nil case *pb.CreateWireGuardResponse: return pb.Event_EVENT_CREATE_WIREGUARD, ptr, nil case *pb.DeleteWireGuardResponse: diff --git a/idl/api_client.proto b/idl/api_client.proto index 88ededa..a38cc56 100644 --- a/idl/api_client.proto +++ b/idl/api_client.proto @@ -283,4 +283,23 @@ message RedeployWorkerRequest { message RedeployWorkerResponse { optional common.Status status = 1; +} + +message UpgradeFrppRequest { + repeated string client_ids = 1; + optional string version = 2; // will be used if download_url is not set + optional string download_url = 3; // will be used with highest priority + optional string github_proxy = 4; // when download_url is not set, github_proxy will be used + optional bool use_github_proxy = 5; // only used when download_url is not set + optional string http_proxy = 6; // http/https proxy for download, default HTTP_PROXY + optional string target_path = 7; // binary path to overwrite, default current running binary + optional bool backup = 8; // create .bak backup before overwrite + optional string service_name = 9; // service name to control + optional bool restart_service = 10; // restart service after replace (will interrupt service) + optional string workdir = 11; // upgrade worker plan/lock directory (default system temp) + repeated string service_args = 12; // service args to pass to utils.ControlSystemService +} + +message UpgradeFrppResponse { + optional common.Status status = 1; } \ No newline at end of file diff --git a/idl/rpc_master.proto b/idl/rpc_master.proto index 3793a33..274c062 100644 --- a/idl/rpc_master.proto +++ b/idl/rpc_master.proto @@ -34,6 +34,7 @@ enum Event { EVENT_UPDATE_WIREGUARD = 25; EVENT_GET_WIREGUARD_RUNTIME_INFO = 26; EVENT_RESTART_WIREGUARD = 27; + EVENT_UPGRADE_FRPP = 28; } message ServerBase { diff --git a/pb/api_client.pb.go b/pb/api_client.pb.go index 439f9d5..daa27a7 100644 --- a/pb/api_client.pb.go +++ b/pb/api_client.pb.go @@ -2926,6 +2926,182 @@ func (x *RedeployWorkerResponse) GetStatus() *Status { return nil } +type UpgradeFrppRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ClientIds []string `protobuf:"bytes,1,rep,name=client_ids,json=clientIds,proto3" json:"client_ids,omitempty"` + Version *string `protobuf:"bytes,2,opt,name=version,proto3,oneof" json:"version,omitempty"` // will be used if download_url is not set + DownloadUrl *string `protobuf:"bytes,3,opt,name=download_url,json=downloadUrl,proto3,oneof" json:"download_url,omitempty"` // will be used with highest priority + GithubProxy *string `protobuf:"bytes,4,opt,name=github_proxy,json=githubProxy,proto3,oneof" json:"github_proxy,omitempty"` // when download_url is not set, github_proxy will be used + UseGithubProxy *bool `protobuf:"varint,5,opt,name=use_github_proxy,json=useGithubProxy,proto3,oneof" json:"use_github_proxy,omitempty"` // only used when download_url is not set + HttpProxy *string `protobuf:"bytes,6,opt,name=http_proxy,json=httpProxy,proto3,oneof" json:"http_proxy,omitempty"` // http/https proxy for download, default HTTP_PROXY + TargetPath *string `protobuf:"bytes,7,opt,name=target_path,json=targetPath,proto3,oneof" json:"target_path,omitempty"` // binary path to overwrite, default current running binary + Backup *bool `protobuf:"varint,8,opt,name=backup,proto3,oneof" json:"backup,omitempty"` // create .bak backup before overwrite + ServiceName *string `protobuf:"bytes,9,opt,name=service_name,json=serviceName,proto3,oneof" json:"service_name,omitempty"` // service name to control + RestartService *bool `protobuf:"varint,10,opt,name=restart_service,json=restartService,proto3,oneof" json:"restart_service,omitempty"` // restart service after replace (will interrupt service) + Workdir *string `protobuf:"bytes,11,opt,name=workdir,proto3,oneof" json:"workdir,omitempty"` // upgrade worker plan/lock directory (default system temp) + ServiceArgs []string `protobuf:"bytes,12,rep,name=service_args,json=serviceArgs,proto3" json:"service_args,omitempty"` // service args to pass to utils.ControlSystemService + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpgradeFrppRequest) Reset() { + *x = UpgradeFrppRequest{} + mi := &file_api_client_proto_msgTypes[56] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpgradeFrppRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpgradeFrppRequest) ProtoMessage() {} + +func (x *UpgradeFrppRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[56] + 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 UpgradeFrppRequest.ProtoReflect.Descriptor instead. +func (*UpgradeFrppRequest) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{56} +} + +func (x *UpgradeFrppRequest) GetClientIds() []string { + if x != nil { + return x.ClientIds + } + return nil +} + +func (x *UpgradeFrppRequest) GetVersion() string { + if x != nil && x.Version != nil { + return *x.Version + } + return "" +} + +func (x *UpgradeFrppRequest) GetDownloadUrl() string { + if x != nil && x.DownloadUrl != nil { + return *x.DownloadUrl + } + return "" +} + +func (x *UpgradeFrppRequest) GetGithubProxy() string { + if x != nil && x.GithubProxy != nil { + return *x.GithubProxy + } + return "" +} + +func (x *UpgradeFrppRequest) GetUseGithubProxy() bool { + if x != nil && x.UseGithubProxy != nil { + return *x.UseGithubProxy + } + return false +} + +func (x *UpgradeFrppRequest) GetHttpProxy() string { + if x != nil && x.HttpProxy != nil { + return *x.HttpProxy + } + return "" +} + +func (x *UpgradeFrppRequest) GetTargetPath() string { + if x != nil && x.TargetPath != nil { + return *x.TargetPath + } + return "" +} + +func (x *UpgradeFrppRequest) GetBackup() bool { + if x != nil && x.Backup != nil { + return *x.Backup + } + return false +} + +func (x *UpgradeFrppRequest) GetServiceName() string { + if x != nil && x.ServiceName != nil { + return *x.ServiceName + } + return "" +} + +func (x *UpgradeFrppRequest) GetRestartService() bool { + if x != nil && x.RestartService != nil { + return *x.RestartService + } + return false +} + +func (x *UpgradeFrppRequest) GetWorkdir() string { + if x != nil && x.Workdir != nil { + return *x.Workdir + } + return "" +} + +func (x *UpgradeFrppRequest) GetServiceArgs() []string { + if x != nil { + return x.ServiceArgs + } + return nil +} + +type UpgradeFrppResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status *Status `protobuf:"bytes,1,opt,name=status,proto3,oneof" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpgradeFrppResponse) Reset() { + *x = UpgradeFrppResponse{} + mi := &file_api_client_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpgradeFrppResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpgradeFrppResponse) ProtoMessage() {} + +func (x *UpgradeFrppResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[57] + 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 UpgradeFrppResponse.ProtoReflect.Descriptor instead. +func (*UpgradeFrppResponse) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{57} +} + +func (x *UpgradeFrppResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + var File_api_client_proto protoreflect.FileDescriptor const file_api_client_proto_rawDesc = "" + @@ -3258,6 +3434,38 @@ const file_api_client_proto_rawDesc = "" + "_worker_id\"P\n" + "\x16RedeployWorkerResponse\x12+\n" + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" + + "\a_status\"\xee\x04\n" + + "\x12UpgradeFrppRequest\x12\x1d\n" + + "\n" + + "client_ids\x18\x01 \x03(\tR\tclientIds\x12\x1d\n" + + "\aversion\x18\x02 \x01(\tH\x00R\aversion\x88\x01\x01\x12&\n" + + "\fdownload_url\x18\x03 \x01(\tH\x01R\vdownloadUrl\x88\x01\x01\x12&\n" + + "\fgithub_proxy\x18\x04 \x01(\tH\x02R\vgithubProxy\x88\x01\x01\x12-\n" + + "\x10use_github_proxy\x18\x05 \x01(\bH\x03R\x0euseGithubProxy\x88\x01\x01\x12\"\n" + + "\n" + + "http_proxy\x18\x06 \x01(\tH\x04R\thttpProxy\x88\x01\x01\x12$\n" + + "\vtarget_path\x18\a \x01(\tH\x05R\n" + + "targetPath\x88\x01\x01\x12\x1b\n" + + "\x06backup\x18\b \x01(\bH\x06R\x06backup\x88\x01\x01\x12&\n" + + "\fservice_name\x18\t \x01(\tH\aR\vserviceName\x88\x01\x01\x12,\n" + + "\x0frestart_service\x18\n" + + " \x01(\bH\bR\x0erestartService\x88\x01\x01\x12\x1d\n" + + "\aworkdir\x18\v \x01(\tH\tR\aworkdir\x88\x01\x01\x12!\n" + + "\fservice_args\x18\f \x03(\tR\vserviceArgsB\n" + + "\n" + + "\b_versionB\x0f\n" + + "\r_download_urlB\x0f\n" + + "\r_github_proxyB\x13\n" + + "\x11_use_github_proxyB\r\n" + + "\v_http_proxyB\x0e\n" + + "\f_target_pathB\t\n" + + "\a_backupB\x0f\n" + + "\r_service_nameB\x12\n" + + "\x10_restart_serviceB\n" + + "\n" + + "\b_workdir\"M\n" + + "\x13UpgradeFrppResponse\x12+\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" + "\a_statusB\aZ\x05../pbb\x06proto3" var ( @@ -3272,7 +3480,7 @@ func file_api_client_proto_rawDescGZIP() []byte { return file_api_client_proto_rawDescData } -var file_api_client_proto_msgTypes = make([]protoimpl.MessageInfo, 57) +var file_api_client_proto_msgTypes = make([]protoimpl.MessageInfo, 59) var file_api_client_proto_goTypes = []any{ (*InitClientRequest)(nil), // 0: api_client.InitClientRequest (*InitClientResponse)(nil), // 1: api_client.InitClientResponse @@ -3330,61 +3538,64 @@ var file_api_client_proto_goTypes = []any{ (*InstallWorkerdResponse)(nil), // 53: api_client.InstallWorkerdResponse (*RedeployWorkerRequest)(nil), // 54: api_client.RedeployWorkerRequest (*RedeployWorkerResponse)(nil), // 55: api_client.RedeployWorkerResponse - nil, // 56: api_client.GetWorkerStatusResponse.WorkerStatusEntry - (*Status)(nil), // 57: common.Status - (*Client)(nil), // 58: common.Client - (*ProxyInfo)(nil), // 59: common.ProxyInfo - (*ProxyConfig)(nil), // 60: common.ProxyConfig - (*ProxyWorkingStatus)(nil), // 61: common.ProxyWorkingStatus - (*Worker)(nil), // 62: common.Worker + (*UpgradeFrppRequest)(nil), // 56: api_client.UpgradeFrppRequest + (*UpgradeFrppResponse)(nil), // 57: api_client.UpgradeFrppResponse + nil, // 58: api_client.GetWorkerStatusResponse.WorkerStatusEntry + (*Status)(nil), // 59: common.Status + (*Client)(nil), // 60: common.Client + (*ProxyInfo)(nil), // 61: common.ProxyInfo + (*ProxyConfig)(nil), // 62: common.ProxyConfig + (*ProxyWorkingStatus)(nil), // 63: common.ProxyWorkingStatus + (*Worker)(nil), // 64: common.Worker } var file_api_client_proto_depIdxs = []int32{ - 57, // 0: api_client.InitClientResponse.status:type_name -> common.Status - 57, // 1: api_client.ListClientsResponse.status:type_name -> common.Status - 58, // 2: api_client.ListClientsResponse.clients:type_name -> common.Client - 57, // 3: api_client.GetClientResponse.status:type_name -> common.Status - 58, // 4: api_client.GetClientResponse.client:type_name -> common.Client - 57, // 5: api_client.DeleteClientResponse.status:type_name -> common.Status - 57, // 6: api_client.UpdateFRPCResponse.status:type_name -> common.Status - 57, // 7: api_client.RemoveFRPCResponse.status:type_name -> common.Status - 57, // 8: api_client.StopFRPCResponse.status:type_name -> common.Status - 57, // 9: api_client.StartFRPCResponse.status:type_name -> common.Status - 57, // 10: api_client.GetProxyStatsByClientIDResponse.status:type_name -> common.Status - 59, // 11: api_client.GetProxyStatsByClientIDResponse.proxy_infos:type_name -> common.ProxyInfo - 57, // 12: api_client.ListProxyConfigsResponse.status:type_name -> common.Status - 60, // 13: api_client.ListProxyConfigsResponse.proxy_configs:type_name -> common.ProxyConfig - 57, // 14: api_client.CreateProxyConfigResponse.status:type_name -> common.Status - 57, // 15: api_client.DeleteProxyConfigResponse.status:type_name -> common.Status - 57, // 16: api_client.UpdateProxyConfigResponse.status:type_name -> common.Status - 57, // 17: api_client.GetProxyConfigResponse.status:type_name -> common.Status - 60, // 18: api_client.GetProxyConfigResponse.proxy_config:type_name -> common.ProxyConfig - 61, // 19: api_client.GetProxyConfigResponse.working_status:type_name -> common.ProxyWorkingStatus - 57, // 20: api_client.StopProxyResponse.status:type_name -> common.Status - 57, // 21: api_client.StartProxyResponse.status:type_name -> common.Status - 62, // 22: api_client.CreateWorkerRequest.worker:type_name -> common.Worker - 57, // 23: api_client.CreateWorkerResponse.status:type_name -> common.Status - 57, // 24: api_client.RemoveWorkerResponse.status:type_name -> common.Status - 62, // 25: api_client.UpdateWorkerRequest.worker:type_name -> common.Worker - 57, // 26: api_client.UpdateWorkerResponse.status:type_name -> common.Status - 57, // 27: api_client.RunWorkerResponse.status:type_name -> common.Status - 57, // 28: api_client.StopWorkerResponse.status:type_name -> common.Status - 57, // 29: api_client.ListWorkersResponse.status:type_name -> common.Status - 62, // 30: api_client.ListWorkersResponse.workers:type_name -> common.Worker - 57, // 31: api_client.CreateWorkerIngressResponse.status:type_name -> common.Status - 57, // 32: api_client.GetWorkerIngressResponse.status:type_name -> common.Status - 60, // 33: api_client.GetWorkerIngressResponse.proxy_configs:type_name -> common.ProxyConfig - 57, // 34: api_client.GetWorkerResponse.status:type_name -> common.Status - 62, // 35: api_client.GetWorkerResponse.worker:type_name -> common.Worker - 58, // 36: api_client.GetWorkerResponse.clients:type_name -> common.Client - 57, // 37: api_client.GetWorkerStatusResponse.status:type_name -> common.Status - 56, // 38: api_client.GetWorkerStatusResponse.worker_status:type_name -> api_client.GetWorkerStatusResponse.WorkerStatusEntry - 57, // 39: api_client.InstallWorkerdResponse.status:type_name -> common.Status - 57, // 40: api_client.RedeployWorkerResponse.status:type_name -> common.Status - 41, // [41:41] is the sub-list for method output_type - 41, // [41:41] is the sub-list for method input_type - 41, // [41:41] is the sub-list for extension type_name - 41, // [41:41] is the sub-list for extension extendee - 0, // [0:41] is the sub-list for field type_name + 59, // 0: api_client.InitClientResponse.status:type_name -> common.Status + 59, // 1: api_client.ListClientsResponse.status:type_name -> common.Status + 60, // 2: api_client.ListClientsResponse.clients:type_name -> common.Client + 59, // 3: api_client.GetClientResponse.status:type_name -> common.Status + 60, // 4: api_client.GetClientResponse.client:type_name -> common.Client + 59, // 5: api_client.DeleteClientResponse.status:type_name -> common.Status + 59, // 6: api_client.UpdateFRPCResponse.status:type_name -> common.Status + 59, // 7: api_client.RemoveFRPCResponse.status:type_name -> common.Status + 59, // 8: api_client.StopFRPCResponse.status:type_name -> common.Status + 59, // 9: api_client.StartFRPCResponse.status:type_name -> common.Status + 59, // 10: api_client.GetProxyStatsByClientIDResponse.status:type_name -> common.Status + 61, // 11: api_client.GetProxyStatsByClientIDResponse.proxy_infos:type_name -> common.ProxyInfo + 59, // 12: api_client.ListProxyConfigsResponse.status:type_name -> common.Status + 62, // 13: api_client.ListProxyConfigsResponse.proxy_configs:type_name -> common.ProxyConfig + 59, // 14: api_client.CreateProxyConfigResponse.status:type_name -> common.Status + 59, // 15: api_client.DeleteProxyConfigResponse.status:type_name -> common.Status + 59, // 16: api_client.UpdateProxyConfigResponse.status:type_name -> common.Status + 59, // 17: api_client.GetProxyConfigResponse.status:type_name -> common.Status + 62, // 18: api_client.GetProxyConfigResponse.proxy_config:type_name -> common.ProxyConfig + 63, // 19: api_client.GetProxyConfigResponse.working_status:type_name -> common.ProxyWorkingStatus + 59, // 20: api_client.StopProxyResponse.status:type_name -> common.Status + 59, // 21: api_client.StartProxyResponse.status:type_name -> common.Status + 64, // 22: api_client.CreateWorkerRequest.worker:type_name -> common.Worker + 59, // 23: api_client.CreateWorkerResponse.status:type_name -> common.Status + 59, // 24: api_client.RemoveWorkerResponse.status:type_name -> common.Status + 64, // 25: api_client.UpdateWorkerRequest.worker:type_name -> common.Worker + 59, // 26: api_client.UpdateWorkerResponse.status:type_name -> common.Status + 59, // 27: api_client.RunWorkerResponse.status:type_name -> common.Status + 59, // 28: api_client.StopWorkerResponse.status:type_name -> common.Status + 59, // 29: api_client.ListWorkersResponse.status:type_name -> common.Status + 64, // 30: api_client.ListWorkersResponse.workers:type_name -> common.Worker + 59, // 31: api_client.CreateWorkerIngressResponse.status:type_name -> common.Status + 59, // 32: api_client.GetWorkerIngressResponse.status:type_name -> common.Status + 62, // 33: api_client.GetWorkerIngressResponse.proxy_configs:type_name -> common.ProxyConfig + 59, // 34: api_client.GetWorkerResponse.status:type_name -> common.Status + 64, // 35: api_client.GetWorkerResponse.worker:type_name -> common.Worker + 60, // 36: api_client.GetWorkerResponse.clients:type_name -> common.Client + 59, // 37: api_client.GetWorkerStatusResponse.status:type_name -> common.Status + 58, // 38: api_client.GetWorkerStatusResponse.worker_status:type_name -> api_client.GetWorkerStatusResponse.WorkerStatusEntry + 59, // 39: api_client.InstallWorkerdResponse.status:type_name -> common.Status + 59, // 40: api_client.RedeployWorkerResponse.status:type_name -> common.Status + 59, // 41: api_client.UpgradeFrppResponse.status:type_name -> common.Status + 42, // [42:42] is the sub-list for method output_type + 42, // [42:42] is the sub-list for method input_type + 42, // [42:42] is the sub-list for extension type_name + 42, // [42:42] is the sub-list for extension extendee + 0, // [0:42] is the sub-list for field type_name } func init() { file_api_client_proto_init() } @@ -3449,13 +3660,15 @@ func file_api_client_proto_init() { file_api_client_proto_msgTypes[53].OneofWrappers = []any{} file_api_client_proto_msgTypes[54].OneofWrappers = []any{} file_api_client_proto_msgTypes[55].OneofWrappers = []any{} + file_api_client_proto_msgTypes[56].OneofWrappers = []any{} + file_api_client_proto_msgTypes[57].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_client_proto_rawDesc), len(file_api_client_proto_rawDesc)), NumEnums: 0, - NumMessages: 57, + NumMessages: 59, NumExtensions: 0, NumServices: 0, }, diff --git a/pb/rpc_master.pb.go b/pb/rpc_master.pb.go index c56056e..f9200c8 100644 --- a/pb/rpc_master.pb.go +++ b/pb/rpc_master.pb.go @@ -52,6 +52,7 @@ const ( Event_EVENT_UPDATE_WIREGUARD Event = 25 Event_EVENT_GET_WIREGUARD_RUNTIME_INFO Event = 26 Event_EVENT_RESTART_WIREGUARD Event = 27 + Event_EVENT_UPGRADE_FRPP Event = 28 ) // Enum value maps for Event. @@ -85,6 +86,7 @@ var ( 25: "EVENT_UPDATE_WIREGUARD", 26: "EVENT_GET_WIREGUARD_RUNTIME_INFO", 27: "EVENT_RESTART_WIREGUARD", + 28: "EVENT_UPGRADE_FRPP", } Event_value = map[string]int32{ "EVENT_UNSPECIFIED": 0, @@ -115,6 +117,7 @@ var ( "EVENT_UPDATE_WIREGUARD": 25, "EVENT_GET_WIREGUARD_RUNTIME_INFO": 26, "EVENT_RESTART_WIREGUARD": 27, + "EVENT_UPGRADE_FRPP": 28, } ) @@ -1513,7 +1516,7 @@ const file_rpc_master_proto_rawDesc = "" + "\x0f_interface_nameB\x0f\n" + "\r_runtime_info\"H\n" + "\x1eReportWireGuardRuntimeInfoResp\x12&\n" + - "\x06status\x18\x01 \x01(\v2\x0e.common.StatusR\x06status*\xb6\x05\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusR\x06status*\xce\x05\n" + "\x05Event\x12\x15\n" + "\x11EVENT_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15EVENT_REGISTER_CLIENT\x10\x01\x12\x19\n" + @@ -1546,7 +1549,8 @@ const file_rpc_master_proto_rawDesc = "" + "\x16EVENT_DELETE_WIREGUARD\x10\x18\x12\x1a\n" + "\x16EVENT_UPDATE_WIREGUARD\x10\x19\x12$\n" + " EVENT_GET_WIREGUARD_RUNTIME_INFO\x10\x1a\x12\x1b\n" + - "\x17EVENT_RESTART_WIREGUARD\x10\x1b2\x81\a\n" + + "\x17EVENT_RESTART_WIREGUARD\x10\x1b\x12\x16\n" + + "\x12EVENT_UPGRADE_FRPP\x10\x1c2\x81\a\n" + "\x06Master\x12>\n" + "\n" + "ServerSend\x12\x15.master.ClientMessage\x1a\x15.master.ServerMessage(\x010\x01\x12M\n" + diff --git a/www/api/client.ts b/www/api/client.ts index 626d22a..2066967 100644 --- a/www/api/client.ts +++ b/www/api/client.ts @@ -9,6 +9,8 @@ import { InitClientResponse, ListClientsRequest, ListClientsResponse, + UpgradeFrppRequest, + UpgradeFrppResponse, } from '@/lib/pb/api_client' import { BaseResponse } from '@/types/api' @@ -32,3 +34,8 @@ export const initClient = async (req: InitClientRequest) => { const res = await http.post(API_PATH + '/client/init', InitClientRequest.toJson(req)) return InitClientResponse.fromJson((res.data as BaseResponse).body) } + +export const upgradeFrpp = async (req: UpgradeFrppRequest) => { + const res = await http.post(API_PATH + '/client/upgrade', UpgradeFrppRequest.toJson(req)) + return UpgradeFrppResponse.fromJson((res.data as BaseResponse).body) +} diff --git a/www/components/base/client_upgrade_dialog.tsx b/www/components/base/client_upgrade_dialog.tsx new file mode 100644 index 0000000..ae60e2d --- /dev/null +++ b/www/components/base/client_upgrade_dialog.tsx @@ -0,0 +1,111 @@ +'use client' + +import { useState } from 'react' +import { useMutation } from '@tanstack/react-query' +import { useTranslation } from 'react-i18next' +import { toast } from 'sonner' + +import { UpgradeFrppRequest } from '@/lib/pb/api_client' +import { upgradeFrpp } from '@/api/client' + +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' + +export interface ClientUpgradeDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + clientId: string + defaultVersion?: string + defaultGithubProxy?: string + defaultUseGithubProxy?: boolean + defaultServiceName?: string + onDispatched?: () => void +} + +export function ClientUpgradeDialog({ + open, + onOpenChange, + clientId, + defaultVersion = 'latest', + defaultGithubProxy, + defaultUseGithubProxy = true, + defaultServiceName = 'frpp', + onDispatched, +}: ClientUpgradeDialogProps) { + const { t } = useTranslation() + + const [version, setVersion] = useState(defaultVersion) + const [downloadUrl, setDownloadUrl] = useState('') + const [httpProxy, setHttpProxy] = useState('') + + const upgradeMutation = useMutation({ + mutationFn: upgradeFrpp, + onSuccess: () => { + toast.success(t('client.upgrade.dispatched')) + onOpenChange(false) + onDispatched?.() + }, + onError: (e: any) => { + toast.error(t('client.upgrade.failed'), { description: e.message }) + }, + }) + + const onSubmit = () => { + upgradeMutation.mutate( + UpgradeFrppRequest.create({ + clientIds: [clientId], + version: version || 'latest', + downloadUrl: downloadUrl || undefined, + useGithubProxy: defaultUseGithubProxy, + githubProxy: defaultGithubProxy || undefined, + httpProxy: httpProxy || undefined, + backup: true, + serviceName: defaultServiceName, + restartService: true, + workdir: undefined, + serviceArgs: [], + }), + ) + } + + return ( + + + + {t('client.upgrade.title')} + +

{clientId + ' ' + t('client.upgrade.warning')}

+
+
+ +
+ + setVersion(e.target.value)} placeholder="latest" /> + + + setDownloadUrl(e.target.value)} + placeholder={t('client.upgrade.download_url_placeholder')} + /> + + + setHttpProxy(e.target.value)} placeholder="http://127.0.0.1:7890" /> +
+ + + + + +
+
+ ) +} + + diff --git a/www/components/frpc/client_item.tsx b/www/components/frpc/client_item.tsx index e5e56f0..e5043b6 100644 --- a/www/components/frpc/client_item.tsx +++ b/www/components/frpc/client_item.tsx @@ -41,6 +41,7 @@ import { $clientTableRefetchTrigger } from '@/store/refetch-trigger' import { NeedUpgrade } from '@/config/notify' import { Label } from '../ui/label' import { Checkbox } from '../ui/checkbox' +import { ClientUpgradeDialog } from '../base/client_upgrade_dialog' export type ClientTableSchema = { id: string @@ -346,6 +347,7 @@ export const ClientActions: React.FC = ({ client, table }) => { const router = useRouter() const platformInfo = useStore($platformInfo) const frontendPreference = useStore($frontendPreference) + const [upgradeDialogOpen, setUpgradeDialogOpen] = useState(false) const removeClient = useMutation({ mutationFn: deleteClient, @@ -398,8 +400,26 @@ export const ClientActions: React.FC = ({ client, table }) => { URL.revokeObjectURL(aTag.href) } + const githubProxyUrl = + frontendPreference.useServerGithubProxyUrl && frontendPreference.githubProxyUrl + ? frontendPreference.githubProxyUrl + : platformInfo?.githubProxyUrl || '' + return ( - + <> + { + $clientTableRefetchTrigger.set(Math.random()) + }} + /> + + + + ) } diff --git a/www/i18n/locales/en.json b/www/i18n/locales/en.json index 0b33b29..2c70969 100644 --- a/www/i18n/locales/en.json +++ b/www/i18n/locales/en.json @@ -81,6 +81,17 @@ "address": "Client Address", "connectTime": "Connected Since" }, + "upgrade": { + "dispatched": "Upgrade dispatched. Client may go offline briefly.", + "failed": "Failed to dispatch upgrade", + "title": "Upgrade frp-panel", + "warning": "Upgrade will restart client service. Connection may drop briefly (expected).", + "version": "Version (default latest)", + "download_url": "Download URL (optional, highest priority)", + "download_url_placeholder": "e.g. https://ghfast.top/https://github.com/.../frp-panel-linux-amd64", + "http_proxy": "HTTP proxy (optional)", + "confirm": "Start upgrade" + }, "status_online": "Online", "status_offline": "Offline", "create": { @@ -141,6 +152,7 @@ "download_config": "Download Configuration", "realtime_log": "Real-time Log", "remote_terminal": "Remote Terminal", + "upgrade": "Upgrade frp-panel", "pause": "Pause", "resume": "Resume", "delete": "Delete" @@ -167,6 +179,27 @@ "status_pause": "Paused", "status_unknown": "Unknown" }, + "common": { + "cancel": "Cancel", + "loading": "Loading...", + "stream_log_pkgs_select": "Stream Log From", + "download": "Click here to download", + "copy": "Copy", + "submit": "Submit", + "login": "Login", + "register": "Register", + "userInfo": "User Information", + "logout": "Logout", + "success": "Success", + "add": "Add", + "back": "Back", + "saving": "Saving...", + "save": "Save", + "failed": "Failed", + "clientType": "Client Type", + "disconnect": "Disconnect", + "connect": "Connect" + }, "table": { "pagination": { "rowsPerPage": "rows per page", @@ -517,27 +550,6 @@ "add": "Add" } }, - "common": { - "stream_log_pkgs_select": "Stream Log From", - "download": "Click here to download", - "copy": "Copy", - "submit": "Submit", - "login": "Login", - "register": "Register", - "userInfo": "User Information", - "logout": "Logout", - "success": "Success", - "cancel": "Cancel", - "loading": "Loading...", - "add": "Add", - "back": "Back", - "saving": "Saving...", - "save": "Save", - "failed": "Failed", - "clientType": "Client Type", - "disconnect": "Disconnect", - "connect": "Connect" - }, "traffic": { "chart": { "inbound": "Inbound", diff --git a/www/i18n/locales/fr.json b/www/i18n/locales/fr.json index 5d17a5b..b33ac37 100644 --- a/www/i18n/locales/fr.json +++ b/www/i18n/locales/fr.json @@ -81,6 +81,17 @@ "address": "Adresse du Client", "connectTime": "Connecté Depuis" }, + "upgrade": { + "dispatched": "Mise à Niveau Disparue", + "failed": "Mise à Niveau Échouée", + "title": "Mise à Niveau du Client", + "warning": "Cette action est irréversible. Êtes-vous sûr de vouloir mettre à niveau ce client ?", + "version": "Version", + "download_url": "URL de Téléchargement", + "download_url_placeholder": "URL de Téléchargement", + "http_proxy": "Proxy HTTP", + "confirm": "Confirmer" + }, "status_online": "En Ligne", "status_offline": "Hors Ligne", "create": { @@ -141,6 +152,7 @@ "download_config": "Télécharger la Configuration", "realtime_log": "Journal en Temps Réel", "remote_terminal": "Terminal à Distance", + "upgrade": "Mise à Niveau", "pause": "Pause", "resume": "Reprendre", "delete": "Supprimer" @@ -167,6 +179,27 @@ "status_pause": "En Pause", "status_unknown": "Inconnu" }, + "common": { + "cancel": "Annuler", + "loading": "Chargement...", + "stream_log_pkgs_select": "Journal de Flux Depuis", + "download": "Cliquez ici pour télécharger", + "copy": "Copier", + "submit": "Soumettre", + "login": "Connexion", + "register": "S'inscrire", + "userInfo": "Informations Utilisateur", + "logout": "Déconnexion", + "success": "Succès", + "add": "Ajouter", + "back": "Retour", + "saving": "Enregistrement en cours...", + "save": "Enregistrer", + "failed": "Échec", + "clientType": "Type de Client", + "disconnect": "Déconnecter", + "connect": "Connecter" + }, "table": { "pagination": { "rowsPerPage": "lignes par page", @@ -521,27 +554,6 @@ "add": "Ajouter" } }, - "common": { - "stream_log_pkgs_select": "Journal de Flux Depuis", - "download": "Cliquez ici pour télécharger", - "copy": "Copier", - "submit": "Soumettre", - "login": "Connexion", - "register": "S'inscrire", - "userInfo": "Informations Utilisateur", - "logout": "Déconnexion", - "success": "Succès", - "cancel": "Annuler", - "loading": "Chargement...", - "add": "Ajouter", - "back": "Retour", - "saving": "Enregistrement en cours...", - "save": "Enregistrer", - "failed": "Échec", - "clientType": "Type de Client", - "disconnect": "Déconnecter", - "connect": "Connecter" - }, "traffic": { "chart": { "inbound": "Entrant", @@ -1033,4 +1045,4 @@ "save_success": "Enregistrement réussi", "save_error": "Échec de l'enregistrement" } -} +} \ No newline at end of file diff --git a/www/i18n/locales/zh-tw.json b/www/i18n/locales/zh-tw.json index 104cf14..a36e323 100644 --- a/www/i18n/locales/zh-tw.json +++ b/www/i18n/locales/zh-tw.json @@ -81,6 +81,17 @@ "address": "客戶端地址", "connectTime": "連接時間" }, + "upgrade": { + "dispatched": "升級任務已下發,客戶端可能短時離線", + "failed": "升級任務下發失敗", + "title": "升級客戶端", + "warning": "此操作無法撤消。您確定要升級該客戶端?", + "version": "版本", + "download_url": "下載地址", + "download_url_placeholder": "例如:https://ghfast.top/https://github.com/.../frp-panel-linux-amd64", + "http_proxy": "HTTP代理", + "confirm": "確定升級" + }, "status_online": "在線", "status_offline": "離線", "create": { @@ -141,6 +152,7 @@ "download_config": "下載配置", "realtime_log": "實時日志", "remote_terminal": "遠程終端", + "upgrade": "升級客戶端", "pause": "暫停", "resume": "啟動", "delete": "刪除" @@ -167,6 +179,27 @@ "status_pause": "已暫停", "status_unknown": "未知" }, + "common": { + "cancel": "取消", + "loading": "載入中...", + "stream_log_pkgs_select": "捕獲日志的模塊", + "download": "點擊這里下載", + "copy": "覆制", + "submit": "提交", + "login": "登錄", + "register": "注冊", + "userInfo": "用戶信息", + "logout": "退出登錄", + "success": "成功", + "add": "添加", + "back": "返回", + "saving": "保存中", + "save": "保存", + "failed": "失敗", + "clientType": "客戶端類型", + "disconnect": "斷開連接", + "connect": "連接" + }, "table": { "pagination": { "rowsPerPage": "行 每頁", @@ -513,27 +546,6 @@ "add": "添加" } }, - "common": { - "stream_log_pkgs_select": "捕獲日志的模塊", - "download": "點擊這里下載", - "copy": "覆制", - "submit": "提交", - "login": "登錄", - "register": "注冊", - "userInfo": "用戶信息", - "logout": "退出登錄", - "success": "成功", - "cancel": "取消", - "loading": "載入中...", - "add": "添加", - "back": "返回", - "saving": "保存中", - "save": "保存", - "failed": "失敗", - "clientType": "客戶端類型", - "disconnect": "斷開連接", - "connect": "連接" - }, "traffic": { "chart": { "inbound": "入站", @@ -1023,4 +1035,4 @@ "save_success": "保存成功", "save_error": "保存失敗" } -} +} \ No newline at end of file diff --git a/www/i18n/locales/zh.json b/www/i18n/locales/zh.json index 4e07914..7e8c83c 100644 --- a/www/i18n/locales/zh.json +++ b/www/i18n/locales/zh.json @@ -81,6 +81,17 @@ "address": "客户端地址", "connectTime": "连接时间" }, + "upgrade": { + "dispatched": "升级任务已下发,客户端可能短暂离线", + "failed": "升级下发失败", + "title": "升级 frp-panel", + "warning": "升级会重启客户端服务,可能导致连接短暂断开(属于正常现象)。", + "version": "版本(默认 latest)", + "download_url": "下载地址(可选,优先级最高)", + "download_url_placeholder": "例如:https://ghfast.top/https://github.com/.../frp-panel-linux-amd64", + "http_proxy": "HTTP 代理(可选)", + "confirm": "开始升级" + }, "status_online": "在线", "status_offline": "离线", "create": { @@ -141,6 +152,7 @@ "download_config": "下载配置", "realtime_log": "实时日志", "remote_terminal": "远程终端", + "upgrade": "升级 frp-panel", "pause": "暂停", "resume": "启动", "delete": "删除" @@ -167,6 +179,27 @@ "status_pause": "已暂停", "status_unknown": "未知" }, + "common": { + "cancel": "取消", + "loading": "加载中...", + "stream_log_pkgs_select": "捕获日志的模块", + "download": "点击这里下载", + "copy": "复制", + "submit": "提交", + "login": "登录", + "register": "注册", + "userInfo": "用户信息", + "logout": "退出登录", + "success": "成功", + "add": "添加", + "back": "返回", + "saving": "保存中", + "save": "保存", + "failed": "失败", + "clientType": "客户端类型", + "disconnect": "断开连接", + "connect": "连接" + }, "table": { "pagination": { "rowsPerPage": "行 每页", @@ -513,27 +546,6 @@ "add": "添加" } }, - "common": { - "stream_log_pkgs_select": "捕获日志的模块", - "download": "点击这里下载", - "copy": "复制", - "submit": "提交", - "login": "登录", - "register": "注册", - "userInfo": "用户信息", - "logout": "退出登录", - "success": "成功", - "cancel": "取消", - "loading": "加载中...", - "add": "添加", - "back": "返回", - "saving": "保存中", - "save": "保存", - "failed": "失败", - "clientType": "客户端类型", - "disconnect": "断开连接", - "connect": "连接" - }, "traffic": { "chart": { "inbound": "入站", diff --git a/www/lib/pb/api_client.ts b/www/lib/pb/api_client.ts index 71eba5e..1dd2416 100644 --- a/www/lib/pb/api_client.ts +++ b/www/lib/pb/api_client.ts @@ -744,6 +744,68 @@ export interface RedeployWorkerResponse { */ status?: Status; } +/** + * @generated from protobuf message api_client.UpgradeFrppRequest + */ +export interface UpgradeFrppRequest { + /** + * @generated from protobuf field: repeated string client_ids = 1; + */ + clientIds: string[]; + /** + * @generated from protobuf field: optional string version = 2; + */ + version?: string; // will be used if download_url is not set + /** + * @generated from protobuf field: optional string download_url = 3; + */ + downloadUrl?: string; // will be used with highest priority + /** + * @generated from protobuf field: optional string github_proxy = 4; + */ + githubProxy?: string; // when download_url is not set, github_proxy will be used + /** + * @generated from protobuf field: optional bool use_github_proxy = 5; + */ + useGithubProxy?: boolean; // only used when download_url is not set + /** + * @generated from protobuf field: optional string http_proxy = 6; + */ + httpProxy?: string; // http/https proxy for download, default HTTP_PROXY + /** + * @generated from protobuf field: optional string target_path = 7; + */ + targetPath?: string; // binary path to overwrite, default current running binary + /** + * @generated from protobuf field: optional bool backup = 8; + */ + backup?: boolean; // create .bak backup before overwrite + /** + * @generated from protobuf field: optional string service_name = 9; + */ + serviceName?: string; // service name to control + /** + * @generated from protobuf field: optional bool restart_service = 10; + */ + restartService?: boolean; // restart service after replace (will interrupt service) + /** + * @generated from protobuf field: optional string workdir = 11; + */ + workdir?: string; // upgrade worker plan/lock directory (default system temp) + /** + * @generated from protobuf field: repeated string service_args = 12; + */ + serviceArgs: string[]; // service args to pass to utils.ControlSystemService +} +/** + * @generated from protobuf message api_client.UpgradeFrppResponse + */ +export interface UpgradeFrppResponse { + /** + * @generated from protobuf field: optional common.Status status = 1; + */ + status?: Status; +} // @generated message type with reflection information, may provide speed optimized methods class InitClientRequest$Type extends MessageType { constructor() { @@ -3730,3 +3792,174 @@ class RedeployWorkerResponse$Type extends MessageType { * @generated MessageType for protobuf message api_client.RedeployWorkerResponse */ export const RedeployWorkerResponse = new RedeployWorkerResponse$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class UpgradeFrppRequest$Type extends MessageType { + constructor() { + super("api_client.UpgradeFrppRequest", [ + { no: 1, name: "client_ids", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "version", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "download_url", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 4, name: "github_proxy", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 5, name: "use_github_proxy", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 6, name: "http_proxy", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 7, name: "target_path", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 8, name: "backup", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 9, name: "service_name", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 10, name: "restart_service", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 11, name: "workdir", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 12, name: "service_args", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): UpgradeFrppRequest { + const message = globalThis.Object.create((this.messagePrototype!)); + message.clientIds = []; + message.serviceArgs = []; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UpgradeFrppRequest): UpgradeFrppRequest { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated string client_ids */ 1: + message.clientIds.push(reader.string()); + break; + case /* optional string version */ 2: + message.version = reader.string(); + break; + case /* optional string download_url */ 3: + message.downloadUrl = reader.string(); + break; + case /* optional string github_proxy */ 4: + message.githubProxy = reader.string(); + break; + case /* optional bool use_github_proxy */ 5: + message.useGithubProxy = reader.bool(); + break; + case /* optional string http_proxy */ 6: + message.httpProxy = reader.string(); + break; + case /* optional string target_path */ 7: + message.targetPath = reader.string(); + break; + case /* optional bool backup */ 8: + message.backup = reader.bool(); + break; + case /* optional string service_name */ 9: + message.serviceName = reader.string(); + break; + case /* optional bool restart_service */ 10: + message.restartService = reader.bool(); + break; + case /* optional string workdir */ 11: + message.workdir = reader.string(); + break; + case /* repeated string service_args */ 12: + message.serviceArgs.push(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: UpgradeFrppRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* repeated string client_ids = 1; */ + for (let i = 0; i < message.clientIds.length; i++) + writer.tag(1, WireType.LengthDelimited).string(message.clientIds[i]); + /* optional string version = 2; */ + if (message.version !== undefined) + writer.tag(2, WireType.LengthDelimited).string(message.version); + /* optional string download_url = 3; */ + if (message.downloadUrl !== undefined) + writer.tag(3, WireType.LengthDelimited).string(message.downloadUrl); + /* optional string github_proxy = 4; */ + if (message.githubProxy !== undefined) + writer.tag(4, WireType.LengthDelimited).string(message.githubProxy); + /* optional bool use_github_proxy = 5; */ + if (message.useGithubProxy !== undefined) + writer.tag(5, WireType.Varint).bool(message.useGithubProxy); + /* optional string http_proxy = 6; */ + if (message.httpProxy !== undefined) + writer.tag(6, WireType.LengthDelimited).string(message.httpProxy); + /* optional string target_path = 7; */ + if (message.targetPath !== undefined) + writer.tag(7, WireType.LengthDelimited).string(message.targetPath); + /* optional bool backup = 8; */ + if (message.backup !== undefined) + writer.tag(8, WireType.Varint).bool(message.backup); + /* optional string service_name = 9; */ + if (message.serviceName !== undefined) + writer.tag(9, WireType.LengthDelimited).string(message.serviceName); + /* optional bool restart_service = 10; */ + if (message.restartService !== undefined) + writer.tag(10, WireType.Varint).bool(message.restartService); + /* optional string workdir = 11; */ + if (message.workdir !== undefined) + writer.tag(11, WireType.LengthDelimited).string(message.workdir); + /* repeated string service_args = 12; */ + for (let i = 0; i < message.serviceArgs.length; i++) + writer.tag(12, WireType.LengthDelimited).string(message.serviceArgs[i]); + 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.UpgradeFrppRequest + */ +export const UpgradeFrppRequest = new UpgradeFrppRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class UpgradeFrppResponse$Type extends MessageType { + constructor() { + super("api_client.UpgradeFrppResponse", [ + { no: 1, name: "status", kind: "message", T: () => Status } + ]); + } + create(value?: PartialMessage): UpgradeFrppResponse { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UpgradeFrppResponse): UpgradeFrppResponse { + 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: UpgradeFrppResponse, 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.UpgradeFrppResponse + */ +export const UpgradeFrppResponse = new UpgradeFrppResponse$Type();