diff --git a/.github/workflows/workerd-docker-tag.workflow.yml b/.github/workflows/workerd-docker-tag.workflow.yml index cdde6c1..1742dd6 100644 --- a/.github/workflows/workerd-docker-tag.workflow.yml +++ b/.github/workflows/workerd-docker-tag.workflow.yml @@ -44,5 +44,6 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} run: | + mv .ko.workerd.yaml .ko.yaml echo "${password}" | ko login docker.io --username ${username} --password-stdin ko build ./cmd/frpp --sbom=none --bare -t ${{ steps.get_version.outputs.VERSION }}-workerd diff --git a/biz/client/rpc_pull_workers.go b/biz/client/rpc_pull_workers.go index b32dda3..e4e72d8 100644 --- a/biz/client/rpc_pull_workers.go +++ b/biz/client/rpc_pull_workers.go @@ -3,6 +3,7 @@ package client import ( "context" + "github.com/VaalaCat/frp-panel/defs" "github.com/VaalaCat/frp-panel/pb" "github.com/VaalaCat/frp-panel/services/app" "github.com/VaalaCat/frp-panel/services/workerd" @@ -28,7 +29,7 @@ func PullWorkers(appInstance app.Application, clientID, clientSecret string) err }, }) if err != nil { - logger.Logger(ctx).WithError(err).Error("cannot list client workers") + logger.Logger(ctx).WithError(err).Error("cannot list client workers, do not change anything") return err } @@ -37,12 +38,15 @@ func PullWorkers(appInstance app.Application, clientID, clientSecret string) err return nil } + logger.Logger(ctx).Infof("client [%s] has [%d] workers, check their status", clientID, len(resp.GetWorkers())) ctrl := ctx.GetApp().GetWorkersManager() for _, worker := range resp.GetWorkers() { - _, err := ctrl.GetWorkerStatus(ctx, worker.GetWorkerId()) - if err == nil { + status, err := ctrl.GetWorkerStatus(ctx, worker.GetWorkerId()) + if err == nil && status == defs.WorkerStatus_Running { logger.Logger(ctx).Infof("worker [%s] already running", worker.GetWorkerId()) continue + } else { + logger.Logger(ctx).Infof("worker [%s] status is [%s] or maybe has error: [%+v], will restart", worker.GetWorkerId(), status, err) } ctrl.RunWorker(ctx, worker.GetWorkerId(), workerd.NewWorkerdController(worker, ctx.GetApp().GetConfig().Client.Worker.WorkerdWorkDir)) } diff --git a/biz/client/start_client.go b/biz/client/start_client.go index da64bfd..9733d2d 100644 --- a/biz/client/start_client.go +++ b/biz/client/start_client.go @@ -14,6 +14,13 @@ func StartFRPCHandler(ctx *app.Context, req *pb.StartFRPCRequest) (*pb.StartFRPC return nil, err } + if ctx.GetApp().GetConfig().Client.Features.EnableFunctions { + if err := PullWorkers(ctx.GetApp(), req.GetClientId(), ctx.GetApp().GetConfig().Client.Secret); err != nil { + logger.Logger(ctx).WithError(err).Error("cannot pull client workers") + return nil, err + } + } + return &pb.StartFRPCResponse{ Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"}, }, nil diff --git a/biz/client/stop_client.go b/biz/client/stop_client.go index 04af033..f98c608 100644 --- a/biz/client/stop_client.go +++ b/biz/client/stop_client.go @@ -12,6 +12,10 @@ func StopFRPCHandler(ctx *app.Context, req *pb.StopFRPCRequest) (*pb.StopFRPCRes ctx.GetApp().GetClientController().StopAll() ctx.GetApp().GetClientController().DeleteAll() + if ctx.GetApp().GetConfig().Client.Features.EnableFunctions { + ctx.GetApp().GetWorkersManager().StopAllWorkers(ctx) + } + return &pb.StopFRPCResponse{ 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 b6c8ce1..2f2b42f 100644 --- a/biz/master/handler.go +++ b/biz/master/handler.go @@ -93,6 +93,7 @@ func ConfigureRouter(appInstance app.Application, router *gin.Engine) { workerHandler.POST("/list", app.Wrapper(appInstance, worker.ListWorkers)) workerHandler.POST("/remove", app.Wrapper(appInstance, worker.RemoveWorker)) workerHandler.POST("/update", app.Wrapper(appInstance, worker.UpdateWorker)) + workerHandler.POST("/redeploy", app.Wrapper(appInstance, worker.RedeployWorker)) workerHandler.POST("/create_ingress", app.Wrapper(appInstance, worker.CreateWorkerIngress)) workerHandler.POST("/get_ingress", app.Wrapper(appInstance, worker.GetWorkerIngress)) } diff --git a/biz/master/worker/redeploy_worker.go b/biz/master/worker/redeploy_worker.go new file mode 100644 index 0000000..c1e16d5 --- /dev/null +++ b/biz/master/worker/redeploy_worker.go @@ -0,0 +1,96 @@ +package worker + +import ( + "fmt" + + "github.com/VaalaCat/frp-panel/common" + "github.com/VaalaCat/frp-panel/models" + "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" + "github.com/VaalaCat/frp-panel/utils/logger" + "github.com/samber/lo" +) + +func RedeployWorker(ctx *app.Context, req *pb.RedeployWorkerRequest) (*pb.RedeployWorkerResponse, error) { + var ( + clientIds = req.GetClientIds() + workerId = req.GetWorkerId() + userInfo = common.GetUserInfo(ctx) + oldClientIds []string + ) + + if len(workerId) == 0 { + logger.Logger(ctx).Errorf("redeploy worker, worker id req: [%s]", req.String()) + return nil, fmt.Errorf("worker id is empty") + + } + + workerToUpdate, err := dao.NewQuery(ctx).GetWorkerByWorkerID(userInfo, workerId) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("redeploy worker cannot get worker, id: [%s]", workerId) + return nil, fmt.Errorf("cannot get worker, id: [%s]", workerId) + } + + clis := []*models.Client{} + if len(clientIds) != 0 { + clis, err = dao.NewQuery(ctx).GetClientsByClientIDs(userInfo, clientIds) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("redeploy worker cannot get client, id: [%s]", utils.MarshalForJson(clientIds)) + return nil, fmt.Errorf("cannot get client, id: [%s]", utils.MarshalForJson(clientIds)) + } + } else { + logger.Logger(ctx).Infof("redeploy worker, no clientId, redeploy on all clients") + } + + var clisToRedeploy []string + allCliIds := lo.Map(workerToUpdate.Clients, func(c models.Client, _ int) string { return c.ClientID }) + reqCliIds := lo.SliceToMap(clis, func(c *models.Client) (string, struct{}) { return c.ClientID, struct{}{} }) + // 如果大于0,需要过滤 + if len(clis) > 0 { + clisToRedeploy = lo.Filter(allCliIds, func(c string, _ int) bool { + _, ok := reqCliIds[c] + return ok + }) + } else { + clisToRedeploy = allCliIds + } + + go func() { + bgCtx := ctx.Background() + + for _, cliId := range clisToRedeploy { + removeResp := &pb.RemoveWorkerResponse{} + err := rpc.CallClientWrapper(bgCtx, cliId, pb.Event_EVENT_REMOVE_WORKER, &pb.RemoveWorkerRequest{ + ClientId: &cliId, + WorkerId: &workerToUpdate.ID, + }, removeResp) + if err != nil { + logger.Logger(bgCtx).WithError(err).Errorf("remove old worker event send to client error, clients: [%s], worker name: [%s]", cliId, workerToUpdate.Name) + } + + createResp := &pb.CreateWorkerResponse{} + err = rpc.CallClientWrapper(bgCtx, cliId, pb.Event_EVENT_CREATE_WORKER, &pb.CreateWorkerRequest{ + ClientId: &cliId, + Worker: workerToUpdate.ToPB(), + }, createResp) + if err != nil { + logger.Logger(bgCtx).WithError(err).Errorf("update new worker event send to client error, client id: [%s], worker name: [%s]", cliId, workerToUpdate.Name) + } + } + + logger.Logger(ctx).Infof("redeploy worker event send to client success, clients: [%s], worker name: [%s], remove old worker send to those clients: %s", + utils.MarshalForJson(clis), workerToUpdate.Name, utils.MarshalForJson(oldClientIds)) + }() + + logger.Logger(ctx).Infof("redeploy worker success, id: [%s], clients: %s", workerId, utils.MarshalForJson(clientIds)) + + return &pb.RedeployWorkerResponse{ + Status: &pb.Status{ + Code: pb.RespCode_RESP_CODE_SUCCESS, + Message: "ok", + }, + }, nil +} diff --git a/biz/master/worker/update_worker.go b/biz/master/worker/update_worker.go index e51a76d..29077d3 100644 --- a/biz/master/worker/update_worker.go +++ b/biz/master/worker/update_worker.go @@ -28,17 +28,22 @@ func UpdateWorker(ctx *app.Context, req *pb.UpdateWorkerRequest) (*pb.UpdateWork return nil, fmt.Errorf("cannot get worker, id: [%s]", wrokerReq.GetWorkerId()) } - clis, err := dao.NewQuery(ctx).GetClientsByClientIDs(userInfo, clientIds) - if err != nil { - logger.Logger(ctx).WithError(err).Errorf("cannot get client, id: [%s]", utils.MarshalForJson(clientIds)) - return nil, fmt.Errorf("cannot get client, id: [%s]", utils.MarshalForJson(clientIds)) + clis := []*models.Client{} + if len(clientIds) != 0 { + clis, err = dao.NewQuery(ctx).GetClientsByClientIDs(userInfo, clientIds) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot get client, id: [%s]", utils.MarshalForJson(clientIds)) + return nil, fmt.Errorf("cannot get client, id: [%s]", utils.MarshalForJson(clientIds)) + } } updatedFields := []string{} if len(clientIds) != 0 { oldClientIds = lo.Map(workerToUpdate.Clients, func(c models.Client, _ int) string { return c.ClientID }) - workerToUpdate.Clients = lo.Map(clis, func(c *models.Client, _ int) models.Client { return *c }) + if len(clis) > 0 { + workerToUpdate.Clients = lo.Map(clis, func(c *models.Client, _ int) models.Client { return *c }) + } updatedFields = append(updatedFields, "client_id") } else { oldClientIds = lo.Map(workerToUpdate.Clients, func(c models.Client, _ int) string { return c.ClientID }) diff --git a/cmd/frpp/shared/providers.go b/cmd/frpp/shared/providers.go index 5329461..0f66062 100644 --- a/cmd/frpp/shared/providers.go +++ b/cmd/frpp/shared/providers.go @@ -386,7 +386,7 @@ func NewWorkersManager(lx fx.Lifecycle, mgr app.WorkerExecManager, appInstance a lx.Append(fx.Hook{ OnStop: func(ctx context.Context) error { - workerMgr.StopAll() + workerMgr.StopAllWorkers(app.NewContext(ctx, appInstance)) logger.Logger(ctx).Info("stop all workers") return nil }, diff --git a/common/request.go b/common/request.go index 7af98f9..c8ccb5a 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.GetWorkerStatusRequest | pb.InstallWorkerdRequest | pb.RedeployWorkerRequest | pb.StartSteamLogRequest } diff --git a/common/response.go b/common/response.go index ba97604..13360bd 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.GetWorkerStatusResponse | pb.InstallWorkerdResponse | pb.RedeployWorkerResponse | pb.StartSteamLogResponse } diff --git a/idl/api_client.proto b/idl/api_client.proto index c42877c..88ededa 100644 --- a/idl/api_client.proto +++ b/idl/api_client.proto @@ -275,3 +275,12 @@ message InstallWorkerdRequest { message InstallWorkerdResponse { optional common.Status status = 1; } + +message RedeployWorkerRequest { + optional string worker_id = 1; + repeated string client_ids = 2; +} + +message RedeployWorkerResponse { + optional common.Status status = 1; +} \ No newline at end of file diff --git a/install.ps1 b/install.ps1 index 46bedb9..88ef31f 100644 --- a/install.ps1 +++ b/install.ps1 @@ -43,4 +43,4 @@ New-Item -Path "C:\frpp" -ItemType Directory -ErrorAction SilentlyContinue Move-Item -Path "C:\frpp.exe" -Destination "C:\frpp\frpp.exe" C:\frpp\frpp.exe install $args C:\frpp\frpp.exe start -Write-Host "Enjoy It!" -BackgroundColor DarkGreen -ForegroundColor Red +Write-Host "Enjoy It!" -BackgroundColor DarkGreen -ForegroundColor Red \ No newline at end of file diff --git a/install.sh b/install.sh index 1541ae9..dc0f105 100755 --- a/install.sh +++ b/install.sh @@ -3,13 +3,47 @@ OS=$(uname -s) ARCH=$(uname -m) +# --- Argument Parsing --- +custom_proxy="" +frp_panel_args=() # Use an array to hold arguments for frp-panel + +# Parse arguments +while [[ "$#" -gt 0 ]]; do + case "$1" in + --github-proxy) + if [ -z "$2" ]; then + echo "Error: --github-proxy requires a URL argument." + exit 1 + fi + custom_proxy="$2" + echo "使用自定义的 GitHub 镜像: $custom_proxy" + shift 2 # shift past argument name and value + ;; + *) + # Collect remaining arguments for frp-panel + frp_panel_args+=("$1") # Add the argument to the array + shift 1 # shift past argument + ;; + esac +done +# --- End Argument Parsing --- + + +# --- Network Check and Prefix Setting --- +prefix="" if ping -c 1 -W 1 google.com > /dev/null 2>&1; then - prefix="" echo "检测到您的网络可以连接到 Google,不使用镜像下载" else - prefix="https://ghfast.top/" - echo "检测到您的网络无法连接到 Google,使用镜像下载" + if [ -n "$custom_proxy" ]; then + prefix="$custom_proxy" + echo "检测到您的网络无法连接到 Google,使用用户提供的镜像下载: $prefix" + else + prefix="https://ghfast.top/" + echo "检测到您的网络无法连接到 Google,使用默认镜像下载: $prefix" + fi fi +# --- End Network Check and Prefix Setting --- + current_dir=$(pwd) temp_dir=$(mktemp -d) @@ -31,6 +65,10 @@ case "$OS" in armv6l) wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/latest/download/frp-panel-linux-armv6l" ;; + *) + echo "Unsupported Linux architecture: $ARCH" + exit 1 + ;; esac ;; Darwin) @@ -41,6 +79,10 @@ case "$OS" in arm64) wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/latest/download/frp-panel-darwin-arm64" ;; + *) + echo "Unsupported Darwin architecture: $ARCH" + exit 1 + ;; esac ;; *) @@ -49,17 +91,18 @@ case "$OS" in ;; esac +if [ ! -f frp-panel ]; then + echo "Error: Download failed. frp-panel executable not found." + exit 1 +fi + sudo chmod +x frp-panel cd "$current_dir" new_executable_path="$temp_dir/frp-panel" -get_start_params() { - read -p "请输入启动参数:" params - echo "$params" -} - +# Function to find the frpp executable path from systemd service file find_frpp_executable() { service_file=$(systemctl show -p FragmentPath frpp.service 2>/dev/null | cut -d'=' -f2) if [[ -z "$service_file" || ! -f "$service_file" ]]; then @@ -75,25 +118,21 @@ find_frpp_executable() { echo "$executable_path" } -if [ -n "$1" ]; then - start_params="$@" -else - start_params=$(get_start_params) -fi - +# --- Install or Update frp-panel --- if systemctl list-units --type=service | grep -q frpp; then - echo "frpp 服务存在" + echo "frpp 服务存在,进行更新" executable_path=$(find_frpp_executable) if [ -z "$executable_path" ]; then echo "无法找到 frpp 服务的执行文件路径,请检查systemd文件" exit 1 fi echo "更新程序到原路径:$executable_path" - sudo rm -rf "$executable_path" + sudo rm -f "$executable_path" # Use -f to avoid prompt if file is read-only sudo cp "$new_executable_path" "$executable_path" - $executable_path version - sudo $executable_path uninstall - sudo $executable_path install $start_params + sudo "$executable_path" version + sudo "$executable_path" uninstall + # Pass collected frp_panel_args to install command + sudo "$executable_path" install "${frp_panel_args[@]}" sudo systemctl daemon-reload echo "参数已重写,请执行 cat /etc/systemd/system/frpp.service 仔细检查启动命令,避免无法启动" echo "执行 sudo systemctl restart frpp 重启服务" @@ -103,9 +142,11 @@ else echo "frpp 服务不存在,进行安装" fi +# Copy the new executable to the current directory for initial install sudo cp "$new_executable_path" . -sudo ./frp-panel install $start_params +# Run the install command with collected frp_panel_args +sudo ./frp-panel install "${frp_panel_args[@]}" echo "frp-panel 服务安装完成, 安装路径:$(pwd)/frp-panel" @@ -120,3 +161,7 @@ echo "frp-panel 服务已启动" sudo systemctl restart frpp sudo systemctl enable frpp + +# Clean up temporary directory +rm -rf "$temp_dir" +echo "清理临时文件夹: $temp_dir" diff --git a/pb/api_client.pb.go b/pb/api_client.pb.go index 05f32d5..ce8de62 100644 --- a/pb/api_client.pb.go +++ b/pb/api_client.pb.go @@ -2830,6 +2830,102 @@ func (x *InstallWorkerdResponse) GetStatus() *Status { return nil } +type RedeployWorkerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + WorkerId *string `protobuf:"bytes,1,opt,name=worker_id,json=workerId,proto3,oneof" json:"worker_id,omitempty"` + ClientIds []string `protobuf:"bytes,2,rep,name=client_ids,json=clientIds,proto3" json:"client_ids,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RedeployWorkerRequest) Reset() { + *x = RedeployWorkerRequest{} + mi := &file_api_client_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RedeployWorkerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RedeployWorkerRequest) ProtoMessage() {} + +func (x *RedeployWorkerRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[54] + 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 RedeployWorkerRequest.ProtoReflect.Descriptor instead. +func (*RedeployWorkerRequest) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{54} +} + +func (x *RedeployWorkerRequest) GetWorkerId() string { + if x != nil && x.WorkerId != nil { + return *x.WorkerId + } + return "" +} + +func (x *RedeployWorkerRequest) GetClientIds() []string { + if x != nil { + return x.ClientIds + } + return nil +} + +type RedeployWorkerResponse 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 *RedeployWorkerResponse) Reset() { + *x = RedeployWorkerResponse{} + mi := &file_api_client_proto_msgTypes[55] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RedeployWorkerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RedeployWorkerResponse) ProtoMessage() {} + +func (x *RedeployWorkerResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[55] + 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 RedeployWorkerResponse.ProtoReflect.Descriptor instead. +func (*RedeployWorkerResponse) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{55} +} + +func (x *RedeployWorkerResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + var File_api_client_proto protoreflect.FileDescriptor const file_api_client_proto_rawDesc = "" + @@ -3153,6 +3249,15 @@ const file_api_client_proto_rawDesc = "" + "\r_download_url\"P\n" + "\x16InstallWorkerdResponse\x12+\n" + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" + + "\a_status\"f\n" + + "\x15RedeployWorkerRequest\x12 \n" + + "\tworker_id\x18\x01 \x01(\tH\x00R\bworkerId\x88\x01\x01\x12\x1d\n" + + "\n" + + "client_ids\x18\x02 \x03(\tR\tclientIdsB\f\n" + + "\n" + + "_worker_id\"P\n" + + "\x16RedeployWorkerResponse\x12+\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" + "\a_statusB\aZ\x05../pbb\x06proto3" var ( @@ -3167,7 +3272,7 @@ func file_api_client_proto_rawDescGZIP() []byte { return file_api_client_proto_rawDescData } -var file_api_client_proto_msgTypes = make([]protoimpl.MessageInfo, 55) +var file_api_client_proto_msgTypes = make([]protoimpl.MessageInfo, 57) var file_api_client_proto_goTypes = []any{ (*InitClientRequest)(nil), // 0: api_client.InitClientRequest (*InitClientResponse)(nil), // 1: api_client.InitClientResponse @@ -3223,60 +3328,63 @@ var file_api_client_proto_goTypes = []any{ (*GetWorkerStatusResponse)(nil), // 51: api_client.GetWorkerStatusResponse (*InstallWorkerdRequest)(nil), // 52: api_client.InstallWorkerdRequest (*InstallWorkerdResponse)(nil), // 53: api_client.InstallWorkerdResponse - nil, // 54: api_client.GetWorkerStatusResponse.WorkerStatusEntry - (*Status)(nil), // 55: common.Status - (*Client)(nil), // 56: common.Client - (*ProxyInfo)(nil), // 57: common.ProxyInfo - (*ProxyConfig)(nil), // 58: common.ProxyConfig - (*ProxyWorkingStatus)(nil), // 59: common.ProxyWorkingStatus - (*Worker)(nil), // 60: common.Worker + (*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 } var file_api_client_proto_depIdxs = []int32{ - 55, // 0: api_client.InitClientResponse.status:type_name -> common.Status - 55, // 1: api_client.ListClientsResponse.status:type_name -> common.Status - 56, // 2: api_client.ListClientsResponse.clients:type_name -> common.Client - 55, // 3: api_client.GetClientResponse.status:type_name -> common.Status - 56, // 4: api_client.GetClientResponse.client:type_name -> common.Client - 55, // 5: api_client.DeleteClientResponse.status:type_name -> common.Status - 55, // 6: api_client.UpdateFRPCResponse.status:type_name -> common.Status - 55, // 7: api_client.RemoveFRPCResponse.status:type_name -> common.Status - 55, // 8: api_client.StopFRPCResponse.status:type_name -> common.Status - 55, // 9: api_client.StartFRPCResponse.status:type_name -> common.Status - 55, // 10: api_client.GetProxyStatsByClientIDResponse.status:type_name -> common.Status - 57, // 11: api_client.GetProxyStatsByClientIDResponse.proxy_infos:type_name -> common.ProxyInfo - 55, // 12: api_client.ListProxyConfigsResponse.status:type_name -> common.Status - 58, // 13: api_client.ListProxyConfigsResponse.proxy_configs:type_name -> common.ProxyConfig - 55, // 14: api_client.CreateProxyConfigResponse.status:type_name -> common.Status - 55, // 15: api_client.DeleteProxyConfigResponse.status:type_name -> common.Status - 55, // 16: api_client.UpdateProxyConfigResponse.status:type_name -> common.Status - 55, // 17: api_client.GetProxyConfigResponse.status:type_name -> common.Status - 58, // 18: api_client.GetProxyConfigResponse.proxy_config:type_name -> common.ProxyConfig - 59, // 19: api_client.GetProxyConfigResponse.working_status:type_name -> common.ProxyWorkingStatus - 55, // 20: api_client.StopProxyResponse.status:type_name -> common.Status - 55, // 21: api_client.StartProxyResponse.status:type_name -> common.Status - 60, // 22: api_client.CreateWorkerRequest.worker:type_name -> common.Worker - 55, // 23: api_client.CreateWorkerResponse.status:type_name -> common.Status - 55, // 24: api_client.RemoveWorkerResponse.status:type_name -> common.Status - 60, // 25: api_client.UpdateWorkerRequest.worker:type_name -> common.Worker - 55, // 26: api_client.UpdateWorkerResponse.status:type_name -> common.Status - 55, // 27: api_client.RunWorkerResponse.status:type_name -> common.Status - 55, // 28: api_client.StopWorkerResponse.status:type_name -> common.Status - 55, // 29: api_client.ListWorkersResponse.status:type_name -> common.Status - 60, // 30: api_client.ListWorkersResponse.workers:type_name -> common.Worker - 55, // 31: api_client.CreateWorkerIngressResponse.status:type_name -> common.Status - 55, // 32: api_client.GetWorkerIngressResponse.status:type_name -> common.Status - 58, // 33: api_client.GetWorkerIngressResponse.proxy_configs:type_name -> common.ProxyConfig - 55, // 34: api_client.GetWorkerResponse.status:type_name -> common.Status - 60, // 35: api_client.GetWorkerResponse.worker:type_name -> common.Worker - 56, // 36: api_client.GetWorkerResponse.clients:type_name -> common.Client - 55, // 37: api_client.GetWorkerStatusResponse.status:type_name -> common.Status - 54, // 38: api_client.GetWorkerStatusResponse.worker_status:type_name -> api_client.GetWorkerStatusResponse.WorkerStatusEntry - 55, // 39: api_client.InstallWorkerdResponse.status:type_name -> common.Status - 40, // [40:40] is the sub-list for method output_type - 40, // [40:40] is the sub-list for method input_type - 40, // [40:40] is the sub-list for extension type_name - 40, // [40:40] is the sub-list for extension extendee - 0, // [0:40] is the sub-list for field type_name + 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 } func init() { file_api_client_proto_init() } @@ -3339,13 +3447,15 @@ func file_api_client_proto_init() { file_api_client_proto_msgTypes[51].OneofWrappers = []any{} file_api_client_proto_msgTypes[52].OneofWrappers = []any{} file_api_client_proto_msgTypes[53].OneofWrappers = []any{} + file_api_client_proto_msgTypes[54].OneofWrappers = []any{} + file_api_client_proto_msgTypes[55].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: 55, + NumMessages: 57, NumExtensions: 0, NumServices: 0, }, diff --git a/services/app/provider.go b/services/app/provider.go index 9744c3d..0658fa1 100644 --- a/services/app/provider.go +++ b/services/app/provider.go @@ -192,6 +192,7 @@ type WorkerController interface { // services/workerd/workers_manager.go type WorkersManager interface { + StopAllWorkers(ctx *Context) GetWorker(ctx *Context, id string) (WorkerController, bool) RunWorker(ctx *Context, id string, worker WorkerController) error StopWorker(ctx *Context, id string) error diff --git a/services/master/grpc_server.go b/services/master/grpc_server.go index ddeb887..7b151df 100644 --- a/services/master/grpc_server.go +++ b/services/master/grpc_server.go @@ -29,15 +29,23 @@ type server struct { // ListClientWorkers implements pb.MasterServer. func (s *server) ListClientWorkers(ctx context.Context, req *pb.ListClientWorkersRequest) (*pb.ListClientWorkersResponse, error) { - logger.Logger(ctx).Infof("list client workers, clientID: [%+v]", req.GetBase().GetClientId()) + logger.Logger(ctx).Infof("list client workers, clientID: [%s]", req.GetBase().GetClientId()) appCtx := app.NewContext(ctx, s.appInstance) - if _, err := client.ValidateClientRequest(appCtx, req.GetBase()); err != nil { + if client, err := client.ValidateClientRequest(appCtx, req.GetBase()); err != nil { logger.Logger(ctx).WithError(err).Errorf("cannot validate client request") return nil, err + } else if client.Stopped { + logger.Logger(appCtx).Infof("list client workers, client [%s] is stopped", req.GetBase().GetClientId()) + return &pb.ListClientWorkersResponse{ + Status: &pb.Status{ + Code: pb.RespCode_RESP_CODE_NOT_FOUND, + Message: "client stopped", + }, + }, nil } - logger.Logger(appCtx).Infof("validate client success, clientID: [%+v]", req.GetBase().GetClientId()) + logger.Logger(appCtx).Infof("validate client success, clientID: [%s]", req.GetBase().GetClientId()) return worker.ListClientWorkers(appCtx, req) } diff --git a/services/workerd/workers_manager.go b/services/workerd/workers_manager.go index 16438cd..c807143 100644 --- a/services/workerd/workers_manager.go +++ b/services/workerd/workers_manager.go @@ -46,9 +46,9 @@ func (m *workersManager) StopWorker(ctx *app.Context, id string) error { return nil } -func (m *workersManager) StopAll() { +func (m *workersManager) StopAllWorkers(ctx *app.Context) { m.workers.Range(func(k string, v app.WorkerController) bool { - v.StopWorker(nil) + v.StopWorker(ctx) return true }) diff --git a/www/api/worker.ts b/www/api/worker.ts index 0f03de5..538728b 100644 --- a/www/api/worker.ts +++ b/www/api/worker.ts @@ -15,6 +15,8 @@ import { InstallWorkerdResponse, ListWorkersRequest, ListWorkersResponse, + RedeployWorkerRequest, + RedeployWorkerResponse, RemoveWorkerRequest, RemoveWorkerResponse, UpdateWorkerRequest, @@ -67,3 +69,8 @@ export const installWorkerd = async (req: InstallWorkerdRequest) => { const res = await http.post(API_PATH + '/client/install_workerd', InstallWorkerdRequest.toJson(req)) return InstallWorkerdResponse.fromJson((res.data as BaseResponse).body) } + +export const redeployWorker = async (req: RedeployWorkerRequest) => { + const res = await http.post(API_PATH + '/worker/redeploy', RedeployWorkerRequest.toJson(req)) + return RedeployWorkerResponse.fromJson((res.data as BaseResponse).body) +} \ No newline at end of file diff --git a/www/components/frpc/client_item.tsx b/www/components/frpc/client_item.tsx index f90a9b3..89bd07f 100644 --- a/www/components/frpc/client_item.tsx +++ b/www/components/frpc/client_item.tsx @@ -26,7 +26,7 @@ import { useMutation, useQuery } from '@tanstack/react-query' import { deleteClient, listClient } from '@/api/client' import { useRouter } from 'next/router' import { useStore } from '@nanostores/react' -import { $platformInfo, $useServerGithubProxyUrl } from '@/store/user' +import { $frontendPreference, $platformInfo, $useServerGithubProxyUrl } from '@/store/user' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { getClientsStatus } from '@/api/platform' import { Client, ClientType } from '@/lib/pb/common' @@ -126,6 +126,7 @@ export const ClientID = ({ client }: { client: ClientTableSchema }) => { const { t } = useTranslation() const platformInfo = useStore($platformInfo) const useGithubProxyUrl = useStore($useServerGithubProxyUrl) + const frontendPreference = useStore($frontendPreference) if (!platformInfo) { return ( @@ -149,20 +150,23 @@ export const ClientID = ({ client }: { client: ClientTableSchema }) => {
{t('client.install.description')}
{t('server.install.description')}