feat: ota upgrade ui

This commit is contained in:
VaalaCat
2026-01-02 13:00:48 +00:00
parent e84e6b7732
commit c09ff8cc00
18 changed files with 981 additions and 147 deletions
+2
View File
@@ -55,6 +55,8 @@ func HandleServerMessage(appInstance app.Application, req *pb.ServerMessage) *pb
return app.WrapperServerMsg(appInstance, req, GetWireGuardRuntimeInfo) return app.WrapperServerMsg(appInstance, req, GetWireGuardRuntimeInfo)
case pb.Event_EVENT_RESTART_WIREGUARD: case pb.Event_EVENT_RESTART_WIREGUARD:
return app.WrapperServerMsg(appInstance, req, RestartWireGuard) return app.WrapperServerMsg(appInstance, req, RestartWireGuard)
case pb.Event_EVENT_UPGRADE_FRPP:
return app.WrapperServerMsg(appInstance, req, UpgradeFrpp)
case pb.Event_EVENT_PING: case pb.Event_EVENT_PING:
rawData, _ := proto.Marshal(conf.GetVersion().ToProto()) rawData, _ := proto.Marshal(conf.GetVersion().ToProto())
return &pb.ClientMessage{ return &pb.ClientMessage{
+62
View File
@@ -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
}
+100
View File
@@ -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
}
+1
View File
@@ -55,6 +55,7 @@ func ConfigureRouter(appInstance app.Application, router *gin.Engine) {
clientRouter.POST("/delete", app.Wrapper(appInstance, client.DeleteClientHandler)) clientRouter.POST("/delete", app.Wrapper(appInstance, client.DeleteClientHandler))
clientRouter.POST("/list", app.Wrapper(appInstance, client.ListClientsHandler)) clientRouter.POST("/list", app.Wrapper(appInstance, client.ListClientsHandler))
clientRouter.POST("/install_workerd", app.Wrapper(appInstance, worker.InstallWorkerd)) clientRouter.POST("/install_workerd", app.Wrapper(appInstance, worker.InstallWorkerd))
clientRouter.POST("/upgrade", app.Wrapper(appInstance, client.UpgradeFrppHandler))
} }
serverRouter := v1.Group("/server") serverRouter := v1.Group("/server")
{ {
+1 -1
View File
@@ -28,7 +28,7 @@ type ReqType interface {
pb.StartProxyRequest | pb.StopProxyRequest | pb.StartProxyRequest | pb.StopProxyRequest |
pb.CreateWorkerRequest | pb.RemoveWorkerRequest | pb.RunWorkerRequest | pb.StopWorkerRequest | pb.UpdateWorkerRequest | pb.GetWorkerRequest | pb.CreateWorkerRequest | pb.RemoveWorkerRequest | pb.RunWorkerRequest | pb.StopWorkerRequest | pb.UpdateWorkerRequest | pb.GetWorkerRequest |
pb.ListWorkersRequest | pb.CreateWorkerIngressRequest | pb.GetWorkerIngressRequest | pb.ListWorkersRequest | pb.CreateWorkerIngressRequest | pb.GetWorkerIngressRequest |
pb.GetWorkerStatusRequest | pb.InstallWorkerdRequest | pb.RedeployWorkerRequest | pb.GetWorkerStatusRequest | pb.InstallWorkerdRequest | pb.RedeployWorkerRequest | pb.UpgradeFrppRequest |
pb.StartSteamLogRequest | pb.StartSteamLogRequest |
// wireguard api // wireguard api
pb.CreateNetworkRequest | pb.DeleteNetworkRequest | pb.UpdateNetworkRequest | pb.GetNetworkRequest | pb.ListNetworksRequest | pb.RestartWireGuardRequest | pb.CreateNetworkRequest | pb.DeleteNetworkRequest | pb.UpdateNetworkRequest | pb.GetNetworkRequest | pb.ListNetworksRequest | pb.RestartWireGuardRequest |
+3 -1
View File
@@ -29,7 +29,7 @@ type RespType interface {
pb.StartProxyResponse | pb.StopProxyResponse | pb.StartProxyResponse | pb.StopProxyResponse |
pb.CreateWorkerResponse | pb.RemoveWorkerResponse | pb.RunWorkerResponse | pb.StopWorkerResponse | pb.UpdateWorkerResponse | pb.GetWorkerResponse | pb.CreateWorkerResponse | pb.RemoveWorkerResponse | pb.RunWorkerResponse | pb.StopWorkerResponse | pb.UpdateWorkerResponse | pb.GetWorkerResponse |
pb.ListWorkersResponse | pb.CreateWorkerIngressResponse | pb.GetWorkerIngressResponse | pb.ListWorkersResponse | pb.CreateWorkerIngressResponse | pb.GetWorkerIngressResponse |
pb.GetWorkerStatusResponse | pb.InstallWorkerdResponse | pb.RedeployWorkerResponse | pb.GetWorkerStatusResponse | pb.InstallWorkerdResponse | pb.RedeployWorkerResponse | pb.UpgradeFrppResponse |
pb.StartSteamLogResponse | pb.StartSteamLogResponse |
// wireguard api // wireguard api
pb.CreateNetworkResponse | pb.DeleteNetworkResponse | pb.UpdateNetworkResponse | pb.GetNetworkResponse | pb.ListNetworksResponse | pb.RestartWireGuardResponse | 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 return pb.Event_EVENT_GET_WORKER_STATUS, ptr, nil
case *pb.InstallWorkerdResponse: case *pb.InstallWorkerdResponse:
return pb.Event_EVENT_INSTALL_WORKERD, ptr, nil return pb.Event_EVENT_INSTALL_WORKERD, ptr, nil
case *pb.UpgradeFrppResponse:
return pb.Event_EVENT_UPGRADE_FRPP, ptr, nil
case *pb.CreateWireGuardResponse: case *pb.CreateWireGuardResponse:
return pb.Event_EVENT_CREATE_WIREGUARD, ptr, nil return pb.Event_EVENT_CREATE_WIREGUARD, ptr, nil
case *pb.DeleteWireGuardResponse: case *pb.DeleteWireGuardResponse:
+19
View File
@@ -283,4 +283,23 @@ message RedeployWorkerRequest {
message RedeployWorkerResponse { message RedeployWorkerResponse {
optional common.Status status = 1; 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;
} }
+1
View File
@@ -34,6 +34,7 @@ enum Event {
EVENT_UPDATE_WIREGUARD = 25; EVENT_UPDATE_WIREGUARD = 25;
EVENT_GET_WIREGUARD_RUNTIME_INFO = 26; EVENT_GET_WIREGUARD_RUNTIME_INFO = 26;
EVENT_RESTART_WIREGUARD = 27; EVENT_RESTART_WIREGUARD = 27;
EVENT_UPGRADE_FRPP = 28;
} }
message ServerBase { message ServerBase {
+268 -55
View File
@@ -2926,6 +2926,182 @@ func (x *RedeployWorkerResponse) GetStatus() *Status {
return nil 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 var File_api_client_proto protoreflect.FileDescriptor
const file_api_client_proto_rawDesc = "" + const file_api_client_proto_rawDesc = "" +
@@ -3258,6 +3434,38 @@ const file_api_client_proto_rawDesc = "" +
"_worker_id\"P\n" + "_worker_id\"P\n" +
"\x16RedeployWorkerResponse\x12+\n" + "\x16RedeployWorkerResponse\x12+\n" +
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" +
"\a_status\"\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" "\a_statusB\aZ\x05../pbb\x06proto3"
var ( var (
@@ -3272,7 +3480,7 @@ func file_api_client_proto_rawDescGZIP() []byte {
return file_api_client_proto_rawDescData 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{ var file_api_client_proto_goTypes = []any{
(*InitClientRequest)(nil), // 0: api_client.InitClientRequest (*InitClientRequest)(nil), // 0: api_client.InitClientRequest
(*InitClientResponse)(nil), // 1: api_client.InitClientResponse (*InitClientResponse)(nil), // 1: api_client.InitClientResponse
@@ -3330,61 +3538,64 @@ var file_api_client_proto_goTypes = []any{
(*InstallWorkerdResponse)(nil), // 53: api_client.InstallWorkerdResponse (*InstallWorkerdResponse)(nil), // 53: api_client.InstallWorkerdResponse
(*RedeployWorkerRequest)(nil), // 54: api_client.RedeployWorkerRequest (*RedeployWorkerRequest)(nil), // 54: api_client.RedeployWorkerRequest
(*RedeployWorkerResponse)(nil), // 55: api_client.RedeployWorkerResponse (*RedeployWorkerResponse)(nil), // 55: api_client.RedeployWorkerResponse
nil, // 56: api_client.GetWorkerStatusResponse.WorkerStatusEntry (*UpgradeFrppRequest)(nil), // 56: api_client.UpgradeFrppRequest
(*Status)(nil), // 57: common.Status (*UpgradeFrppResponse)(nil), // 57: api_client.UpgradeFrppResponse
(*Client)(nil), // 58: common.Client nil, // 58: api_client.GetWorkerStatusResponse.WorkerStatusEntry
(*ProxyInfo)(nil), // 59: common.ProxyInfo (*Status)(nil), // 59: common.Status
(*ProxyConfig)(nil), // 60: common.ProxyConfig (*Client)(nil), // 60: common.Client
(*ProxyWorkingStatus)(nil), // 61: common.ProxyWorkingStatus (*ProxyInfo)(nil), // 61: common.ProxyInfo
(*Worker)(nil), // 62: common.Worker (*ProxyConfig)(nil), // 62: common.ProxyConfig
(*ProxyWorkingStatus)(nil), // 63: common.ProxyWorkingStatus
(*Worker)(nil), // 64: common.Worker
} }
var file_api_client_proto_depIdxs = []int32{ var file_api_client_proto_depIdxs = []int32{
57, // 0: api_client.InitClientResponse.status:type_name -> common.Status 59, // 0: api_client.InitClientResponse.status:type_name -> common.Status
57, // 1: api_client.ListClientsResponse.status:type_name -> common.Status 59, // 1: api_client.ListClientsResponse.status:type_name -> common.Status
58, // 2: api_client.ListClientsResponse.clients:type_name -> common.Client 60, // 2: api_client.ListClientsResponse.clients:type_name -> common.Client
57, // 3: api_client.GetClientResponse.status:type_name -> common.Status 59, // 3: api_client.GetClientResponse.status:type_name -> common.Status
58, // 4: api_client.GetClientResponse.client:type_name -> common.Client 60, // 4: api_client.GetClientResponse.client:type_name -> common.Client
57, // 5: api_client.DeleteClientResponse.status:type_name -> common.Status 59, // 5: api_client.DeleteClientResponse.status:type_name -> common.Status
57, // 6: api_client.UpdateFRPCResponse.status:type_name -> common.Status 59, // 6: api_client.UpdateFRPCResponse.status:type_name -> common.Status
57, // 7: api_client.RemoveFRPCResponse.status:type_name -> common.Status 59, // 7: api_client.RemoveFRPCResponse.status:type_name -> common.Status
57, // 8: api_client.StopFRPCResponse.status:type_name -> common.Status 59, // 8: api_client.StopFRPCResponse.status:type_name -> common.Status
57, // 9: api_client.StartFRPCResponse.status:type_name -> common.Status 59, // 9: api_client.StartFRPCResponse.status:type_name -> common.Status
57, // 10: api_client.GetProxyStatsByClientIDResponse.status:type_name -> common.Status 59, // 10: api_client.GetProxyStatsByClientIDResponse.status:type_name -> common.Status
59, // 11: api_client.GetProxyStatsByClientIDResponse.proxy_infos:type_name -> common.ProxyInfo 61, // 11: api_client.GetProxyStatsByClientIDResponse.proxy_infos:type_name -> common.ProxyInfo
57, // 12: api_client.ListProxyConfigsResponse.status:type_name -> common.Status 59, // 12: api_client.ListProxyConfigsResponse.status:type_name -> common.Status
60, // 13: api_client.ListProxyConfigsResponse.proxy_configs:type_name -> common.ProxyConfig 62, // 13: api_client.ListProxyConfigsResponse.proxy_configs:type_name -> common.ProxyConfig
57, // 14: api_client.CreateProxyConfigResponse.status:type_name -> common.Status 59, // 14: api_client.CreateProxyConfigResponse.status:type_name -> common.Status
57, // 15: api_client.DeleteProxyConfigResponse.status:type_name -> common.Status 59, // 15: api_client.DeleteProxyConfigResponse.status:type_name -> common.Status
57, // 16: api_client.UpdateProxyConfigResponse.status:type_name -> common.Status 59, // 16: api_client.UpdateProxyConfigResponse.status:type_name -> common.Status
57, // 17: api_client.GetProxyConfigResponse.status:type_name -> common.Status 59, // 17: api_client.GetProxyConfigResponse.status:type_name -> common.Status
60, // 18: api_client.GetProxyConfigResponse.proxy_config:type_name -> common.ProxyConfig 62, // 18: api_client.GetProxyConfigResponse.proxy_config:type_name -> common.ProxyConfig
61, // 19: api_client.GetProxyConfigResponse.working_status:type_name -> common.ProxyWorkingStatus 63, // 19: api_client.GetProxyConfigResponse.working_status:type_name -> common.ProxyWorkingStatus
57, // 20: api_client.StopProxyResponse.status:type_name -> common.Status 59, // 20: api_client.StopProxyResponse.status:type_name -> common.Status
57, // 21: api_client.StartProxyResponse.status:type_name -> common.Status 59, // 21: api_client.StartProxyResponse.status:type_name -> common.Status
62, // 22: api_client.CreateWorkerRequest.worker:type_name -> common.Worker 64, // 22: api_client.CreateWorkerRequest.worker:type_name -> common.Worker
57, // 23: api_client.CreateWorkerResponse.status:type_name -> common.Status 59, // 23: api_client.CreateWorkerResponse.status:type_name -> common.Status
57, // 24: api_client.RemoveWorkerResponse.status:type_name -> common.Status 59, // 24: api_client.RemoveWorkerResponse.status:type_name -> common.Status
62, // 25: api_client.UpdateWorkerRequest.worker:type_name -> common.Worker 64, // 25: api_client.UpdateWorkerRequest.worker:type_name -> common.Worker
57, // 26: api_client.UpdateWorkerResponse.status:type_name -> common.Status 59, // 26: api_client.UpdateWorkerResponse.status:type_name -> common.Status
57, // 27: api_client.RunWorkerResponse.status:type_name -> common.Status 59, // 27: api_client.RunWorkerResponse.status:type_name -> common.Status
57, // 28: api_client.StopWorkerResponse.status:type_name -> common.Status 59, // 28: api_client.StopWorkerResponse.status:type_name -> common.Status
57, // 29: api_client.ListWorkersResponse.status:type_name -> common.Status 59, // 29: api_client.ListWorkersResponse.status:type_name -> common.Status
62, // 30: api_client.ListWorkersResponse.workers:type_name -> common.Worker 64, // 30: api_client.ListWorkersResponse.workers:type_name -> common.Worker
57, // 31: api_client.CreateWorkerIngressResponse.status:type_name -> common.Status 59, // 31: api_client.CreateWorkerIngressResponse.status:type_name -> common.Status
57, // 32: api_client.GetWorkerIngressResponse.status:type_name -> common.Status 59, // 32: api_client.GetWorkerIngressResponse.status:type_name -> common.Status
60, // 33: api_client.GetWorkerIngressResponse.proxy_configs:type_name -> common.ProxyConfig 62, // 33: api_client.GetWorkerIngressResponse.proxy_configs:type_name -> common.ProxyConfig
57, // 34: api_client.GetWorkerResponse.status:type_name -> common.Status 59, // 34: api_client.GetWorkerResponse.status:type_name -> common.Status
62, // 35: api_client.GetWorkerResponse.worker:type_name -> common.Worker 64, // 35: api_client.GetWorkerResponse.worker:type_name -> common.Worker
58, // 36: api_client.GetWorkerResponse.clients:type_name -> common.Client 60, // 36: api_client.GetWorkerResponse.clients:type_name -> common.Client
57, // 37: api_client.GetWorkerStatusResponse.status:type_name -> common.Status 59, // 37: api_client.GetWorkerStatusResponse.status:type_name -> common.Status
56, // 38: api_client.GetWorkerStatusResponse.worker_status:type_name -> api_client.GetWorkerStatusResponse.WorkerStatusEntry 58, // 38: api_client.GetWorkerStatusResponse.worker_status:type_name -> api_client.GetWorkerStatusResponse.WorkerStatusEntry
57, // 39: api_client.InstallWorkerdResponse.status:type_name -> common.Status 59, // 39: api_client.InstallWorkerdResponse.status:type_name -> common.Status
57, // 40: api_client.RedeployWorkerResponse.status:type_name -> common.Status 59, // 40: api_client.RedeployWorkerResponse.status:type_name -> common.Status
41, // [41:41] is the sub-list for method output_type 59, // 41: api_client.UpgradeFrppResponse.status:type_name -> common.Status
41, // [41:41] is the sub-list for method input_type 42, // [42:42] is the sub-list for method output_type
41, // [41:41] is the sub-list for extension type_name 42, // [42:42] is the sub-list for method input_type
41, // [41:41] is the sub-list for extension extendee 42, // [42:42] is the sub-list for extension type_name
0, // [0:41] is the sub-list for field 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() } 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[53].OneofWrappers = []any{}
file_api_client_proto_msgTypes[54].OneofWrappers = []any{} file_api_client_proto_msgTypes[54].OneofWrappers = []any{}
file_api_client_proto_msgTypes[55].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{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_client_proto_rawDesc), len(file_api_client_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_client_proto_rawDesc), len(file_api_client_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 57, NumMessages: 59,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },
+6 -2
View File
@@ -52,6 +52,7 @@ const (
Event_EVENT_UPDATE_WIREGUARD Event = 25 Event_EVENT_UPDATE_WIREGUARD Event = 25
Event_EVENT_GET_WIREGUARD_RUNTIME_INFO Event = 26 Event_EVENT_GET_WIREGUARD_RUNTIME_INFO Event = 26
Event_EVENT_RESTART_WIREGUARD Event = 27 Event_EVENT_RESTART_WIREGUARD Event = 27
Event_EVENT_UPGRADE_FRPP Event = 28
) )
// Enum value maps for Event. // Enum value maps for Event.
@@ -85,6 +86,7 @@ var (
25: "EVENT_UPDATE_WIREGUARD", 25: "EVENT_UPDATE_WIREGUARD",
26: "EVENT_GET_WIREGUARD_RUNTIME_INFO", 26: "EVENT_GET_WIREGUARD_RUNTIME_INFO",
27: "EVENT_RESTART_WIREGUARD", 27: "EVENT_RESTART_WIREGUARD",
28: "EVENT_UPGRADE_FRPP",
} }
Event_value = map[string]int32{ Event_value = map[string]int32{
"EVENT_UNSPECIFIED": 0, "EVENT_UNSPECIFIED": 0,
@@ -115,6 +117,7 @@ var (
"EVENT_UPDATE_WIREGUARD": 25, "EVENT_UPDATE_WIREGUARD": 25,
"EVENT_GET_WIREGUARD_RUNTIME_INFO": 26, "EVENT_GET_WIREGUARD_RUNTIME_INFO": 26,
"EVENT_RESTART_WIREGUARD": 27, "EVENT_RESTART_WIREGUARD": 27,
"EVENT_UPGRADE_FRPP": 28,
} }
) )
@@ -1513,7 +1516,7 @@ const file_rpc_master_proto_rawDesc = "" +
"\x0f_interface_nameB\x0f\n" + "\x0f_interface_nameB\x0f\n" +
"\r_runtime_info\"H\n" + "\r_runtime_info\"H\n" +
"\x1eReportWireGuardRuntimeInfoResp\x12&\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" + "\x05Event\x12\x15\n" +
"\x11EVENT_UNSPECIFIED\x10\x00\x12\x19\n" + "\x11EVENT_UNSPECIFIED\x10\x00\x12\x19\n" +
"\x15EVENT_REGISTER_CLIENT\x10\x01\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_DELETE_WIREGUARD\x10\x18\x12\x1a\n" +
"\x16EVENT_UPDATE_WIREGUARD\x10\x19\x12$\n" + "\x16EVENT_UPDATE_WIREGUARD\x10\x19\x12$\n" +
" EVENT_GET_WIREGUARD_RUNTIME_INFO\x10\x1a\x12\x1b\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" + "\x06Master\x12>\n" +
"\n" + "\n" +
"ServerSend\x12\x15.master.ClientMessage\x1a\x15.master.ServerMessage(\x010\x01\x12M\n" + "ServerSend\x12\x15.master.ClientMessage\x1a\x15.master.ServerMessage(\x010\x01\x12M\n" +
+7
View File
@@ -9,6 +9,8 @@ import {
InitClientResponse, InitClientResponse,
ListClientsRequest, ListClientsRequest,
ListClientsResponse, ListClientsResponse,
UpgradeFrppRequest,
UpgradeFrppResponse,
} from '@/lib/pb/api_client' } from '@/lib/pb/api_client'
import { BaseResponse } from '@/types/api' 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)) const res = await http.post(API_PATH + '/client/init', InitClientRequest.toJson(req))
return InitClientResponse.fromJson((res.data as BaseResponse).body) 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)
}
@@ -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 (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('client.upgrade.title')}</DialogTitle>
<DialogDescription>
<p className="text-destructive">{clientId + ' ' + t('client.upgrade.warning')}</p>
</DialogDescription>
</DialogHeader>
<div className="grid gap-3 py-2">
<Label>{t('client.upgrade.version')}</Label>
<Input value={version} onChange={(e) => setVersion(e.target.value)} placeholder="latest" />
<Label>{t('client.upgrade.download_url')}</Label>
<Input
value={downloadUrl}
onChange={(e) => setDownloadUrl(e.target.value)}
placeholder={t('client.upgrade.download_url_placeholder')}
/>
<Label>{t('client.upgrade.http_proxy')}</Label>
<Input value={httpProxy} onChange={(e) => setHttpProxy(e.target.value)} placeholder="http://127.0.0.1:7890" />
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
{t('common.cancel')}
</Button>
<Button onClick={onSubmit} disabled={upgradeMutation.isPending}>
{upgradeMutation.isPending ? t('common.loading') : t('client.upgrade.confirm')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
+33 -2
View File
@@ -41,6 +41,7 @@ import { $clientTableRefetchTrigger } from '@/store/refetch-trigger'
import { NeedUpgrade } from '@/config/notify' import { NeedUpgrade } from '@/config/notify'
import { Label } from '../ui/label' import { Label } from '../ui/label'
import { Checkbox } from '../ui/checkbox' import { Checkbox } from '../ui/checkbox'
import { ClientUpgradeDialog } from '../base/client_upgrade_dialog'
export type ClientTableSchema = { export type ClientTableSchema = {
id: string id: string
@@ -346,6 +347,7 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
const router = useRouter() const router = useRouter()
const platformInfo = useStore($platformInfo) const platformInfo = useStore($platformInfo)
const frontendPreference = useStore($frontendPreference) const frontendPreference = useStore($frontendPreference)
const [upgradeDialogOpen, setUpgradeDialogOpen] = useState(false)
const removeClient = useMutation({ const removeClient = useMutation({
mutationFn: deleteClient, mutationFn: deleteClient,
@@ -398,8 +400,26 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
URL.revokeObjectURL(aTag.href) URL.revokeObjectURL(aTag.href)
} }
const githubProxyUrl =
frontendPreference.useServerGithubProxyUrl && frontendPreference.githubProxyUrl
? frontendPreference.githubProxyUrl
: platformInfo?.githubProxyUrl || ''
return ( return (
<Dialog> <>
<ClientUpgradeDialog
open={upgradeDialogOpen}
onOpenChange={setUpgradeDialogOpen}
clientId={client.id}
defaultGithubProxy={githubProxyUrl || undefined}
defaultUseGithubProxy={true}
defaultServiceName="frpp"
onDispatched={() => {
$clientTableRefetchTrigger.set(Math.random())
}}
/>
<Dialog>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0"> <Button variant="ghost" className="h-8 w-8 p-0">
@@ -491,6 +511,16 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
> >
{t('client.actions_menu.remote_terminal')} {t('client.actions_menu.remote_terminal')}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setUpgradeDialogOpen(true)
}}
>
{t('client.actions_menu.upgrade')}
</DropdownMenuItem>
{!client.stopped && ( {!client.stopped && (
<DropdownMenuItem className="text-destructive" onClick={() => stopClient.mutate({ clientId: client.id })}> <DropdownMenuItem className="text-destructive" onClick={() => stopClient.mutate({ clientId: client.id })}>
{t('client.actions_menu.pause')} {t('client.actions_menu.pause')}
@@ -522,6 +552,7 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
</DialogClose> </DialogClose>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</>
) )
} }
+33 -21
View File
@@ -81,6 +81,17 @@
"address": "Client Address", "address": "Client Address",
"connectTime": "Connected Since" "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_online": "Online",
"status_offline": "Offline", "status_offline": "Offline",
"create": { "create": {
@@ -141,6 +152,7 @@
"download_config": "Download Configuration", "download_config": "Download Configuration",
"realtime_log": "Real-time Log", "realtime_log": "Real-time Log",
"remote_terminal": "Remote Terminal", "remote_terminal": "Remote Terminal",
"upgrade": "Upgrade frp-panel",
"pause": "Pause", "pause": "Pause",
"resume": "Resume", "resume": "Resume",
"delete": "Delete" "delete": "Delete"
@@ -167,6 +179,27 @@
"status_pause": "Paused", "status_pause": "Paused",
"status_unknown": "Unknown" "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": { "table": {
"pagination": { "pagination": {
"rowsPerPage": "rows per page", "rowsPerPage": "rows per page",
@@ -517,27 +550,6 @@
"add": "Add" "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": { "traffic": {
"chart": { "chart": {
"inbound": "Inbound", "inbound": "Inbound",
+34 -22
View File
@@ -81,6 +81,17 @@
"address": "Adresse du Client", "address": "Adresse du Client",
"connectTime": "Connecté Depuis" "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_online": "En Ligne",
"status_offline": "Hors Ligne", "status_offline": "Hors Ligne",
"create": { "create": {
@@ -141,6 +152,7 @@
"download_config": "Télécharger la Configuration", "download_config": "Télécharger la Configuration",
"realtime_log": "Journal en Temps Réel", "realtime_log": "Journal en Temps Réel",
"remote_terminal": "Terminal à Distance", "remote_terminal": "Terminal à Distance",
"upgrade": "Mise à Niveau",
"pause": "Pause", "pause": "Pause",
"resume": "Reprendre", "resume": "Reprendre",
"delete": "Supprimer" "delete": "Supprimer"
@@ -167,6 +179,27 @@
"status_pause": "En Pause", "status_pause": "En Pause",
"status_unknown": "Inconnu" "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": { "table": {
"pagination": { "pagination": {
"rowsPerPage": "lignes par page", "rowsPerPage": "lignes par page",
@@ -521,27 +554,6 @@
"add": "Ajouter" "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": { "traffic": {
"chart": { "chart": {
"inbound": "Entrant", "inbound": "Entrant",
@@ -1033,4 +1045,4 @@
"save_success": "Enregistrement réussi", "save_success": "Enregistrement réussi",
"save_error": "Échec de l'enregistrement" "save_error": "Échec de l'enregistrement"
} }
} }
+34 -22
View File
@@ -81,6 +81,17 @@
"address": "客戶端地址", "address": "客戶端地址",
"connectTime": "連接時間" "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_online": "在線",
"status_offline": "離線", "status_offline": "離線",
"create": { "create": {
@@ -141,6 +152,7 @@
"download_config": "下載配置", "download_config": "下載配置",
"realtime_log": "實時日志", "realtime_log": "實時日志",
"remote_terminal": "遠程終端", "remote_terminal": "遠程終端",
"upgrade": "升級客戶端",
"pause": "暫停", "pause": "暫停",
"resume": "啟動", "resume": "啟動",
"delete": "刪除" "delete": "刪除"
@@ -167,6 +179,27 @@
"status_pause": "已暫停", "status_pause": "已暫停",
"status_unknown": "未知" "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": { "table": {
"pagination": { "pagination": {
"rowsPerPage": "行 每頁", "rowsPerPage": "行 每頁",
@@ -513,27 +546,6 @@
"add": "添加" "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": { "traffic": {
"chart": { "chart": {
"inbound": "入站", "inbound": "入站",
@@ -1023,4 +1035,4 @@
"save_success": "保存成功", "save_success": "保存成功",
"save_error": "保存失敗" "save_error": "保存失敗"
} }
} }
+33 -21
View File
@@ -81,6 +81,17 @@
"address": "客户端地址", "address": "客户端地址",
"connectTime": "连接时间" "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_online": "在线",
"status_offline": "离线", "status_offline": "离线",
"create": { "create": {
@@ -141,6 +152,7 @@
"download_config": "下载配置", "download_config": "下载配置",
"realtime_log": "实时日志", "realtime_log": "实时日志",
"remote_terminal": "远程终端", "remote_terminal": "远程终端",
"upgrade": "升级 frp-panel",
"pause": "暂停", "pause": "暂停",
"resume": "启动", "resume": "启动",
"delete": "删除" "delete": "删除"
@@ -167,6 +179,27 @@
"status_pause": "已暂停", "status_pause": "已暂停",
"status_unknown": "未知" "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": { "table": {
"pagination": { "pagination": {
"rowsPerPage": "行 每页", "rowsPerPage": "行 每页",
@@ -513,27 +546,6 @@
"add": "添加" "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": { "traffic": {
"chart": { "chart": {
"inbound": "入站", "inbound": "入站",
+233
View File
@@ -744,6 +744,68 @@ export interface RedeployWorkerResponse {
*/ */
status?: Status; 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 // @generated message type with reflection information, may provide speed optimized methods
class InitClientRequest$Type extends MessageType<InitClientRequest> { class InitClientRequest$Type extends MessageType<InitClientRequest> {
constructor() { constructor() {
@@ -3730,3 +3792,174 @@ class RedeployWorkerResponse$Type extends MessageType<RedeployWorkerResponse> {
* @generated MessageType for protobuf message api_client.RedeployWorkerResponse * @generated MessageType for protobuf message api_client.RedeployWorkerResponse
*/ */
export const RedeployWorkerResponse = new RedeployWorkerResponse$Type(); export const RedeployWorkerResponse = new RedeployWorkerResponse$Type();
// @generated message type with reflection information, may provide speed optimized methods
class UpgradeFrppRequest$Type extends MessageType<UpgradeFrppRequest> {
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>): UpgradeFrppRequest {
const message = globalThis.Object.create((this.messagePrototype!));
message.clientIds = [];
message.serviceArgs = [];
if (value !== undefined)
reflectionMergePartial<UpgradeFrppRequest>(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<UpgradeFrppResponse> {
constructor() {
super("api_client.UpgradeFrppResponse", [
{ no: 1, name: "status", kind: "message", T: () => Status }
]);
}
create(value?: PartialMessage<UpgradeFrppResponse>): UpgradeFrppResponse {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<UpgradeFrppResponse>(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();