From f333cce4d068aef1690e33e2962c21f5f3bfa2f0 Mon Sep 17 00:00:00 2001 From: VaalaCat Date: Wed, 7 May 2025 16:59:33 +0000 Subject: [PATCH] feat: redeploy worker --- .../workflows/workerd-docker-tag.workflow.yml | 1 + biz/client/rpc_pull_workers.go | 10 +- biz/client/start_client.go | 7 + biz/client/stop_client.go | 4 + biz/master/handler.go | 1 + biz/master/worker/redeploy_worker.go | 96 ++++++++ biz/master/worker/update_worker.go | 15 +- cmd/frpp/shared/providers.go | 2 +- common/request.go | 2 +- common/response.go | 2 +- idl/api_client.proto | 9 + install.ps1 | 2 +- install.sh | 85 +++++-- pb/api_client.pb.go | 218 +++++++++++++----- services/app/provider.go | 1 + services/master/grpc_server.go | 14 +- services/workerd/workers_manager.go | 4 +- www/api/worker.ts | 7 + www/components/frpc/client_item.tsx | 50 ++-- www/components/frps/server_item.tsx | 47 +++- www/components/worker/worker_item.tsx | 19 +- www/i18n/locales/en.json | 2 + www/i18n/locales/zh.json | 2 + www/lib/consts.ts | 5 +- www/lib/pb/api_client.ts | 122 ++++++++++ www/store/user.ts | 9 + 26 files changed, 618 insertions(+), 118 deletions(-) create mode 100644 biz/master/worker/redeploy_worker.go 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')}

-
- +
+ +
+
+ + + $frontendPreference.set({ ...frontendPreference, githubProxyUrl: e.target.value })} />
- -
-
+
+
+
@@ -327,6 +345,7 @@ export const ClientActions: React.FC = ({ client, table }) => { const router = useRouter() const platformInfo = useStore($platformInfo) const useGithubProxyUrl = useStore($useServerGithubProxyUrl) + const frontendPreference = useStore($frontendPreference) const removeClient = useMutation({ mutationFn: deleteClient, @@ -412,7 +431,10 @@ export const ClientActions: React.FC = ({ client, table }) => { onClick={() => { try { if (platformInfo) { - navigator.clipboard.writeText(LinuxInstallCommand('client', client, platformInfo, useGithubProxyUrl)) + navigator.clipboard.writeText(LinuxInstallCommand('client', client, { + ...platformInfo, + githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl, + }, useGithubProxyUrl)) toast(t('client.actions_menu.copy_success')) } else { toast(t('client.actions_menu.copy_failed')) diff --git a/www/components/frps/server_item.tsx b/www/components/frps/server_item.tsx index a5af20c..7fb65c7 100644 --- a/www/components/frps/server_item.tsx +++ b/www/components/frps/server_item.tsx @@ -26,7 +26,7 @@ import { useMutation, useQuery } from '@tanstack/react-query' import { deleteServer } from '@/api/server' import { useRouter } from 'next/router' import { useStore } from '@nanostores/react' -import { $platformInfo } from '@/store/user' +import { $frontendPreference, $platformInfo, $useServerGithubProxyUrl } from '@/store/user' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { getClientsStatus } from '@/api/platform' import { ClientType } from '@/lib/pb/common' @@ -37,6 +37,8 @@ import { useTranslation } from 'react-i18next' import { Input } from '@/components/ui/input' import { toast } from 'sonner' import { $serverTableRefetchTrigger } from '@/store/refetch-trigger' +import { Checkbox } from '../ui/checkbox' +import { Label } from '../ui/label' export type ServerTableSchema = { id: string @@ -122,6 +124,8 @@ export const columns: ColumnDef[] = [ export const ServerID = ({ server }: { server: ServerTableSchema }) => { const { t } = useTranslation() const platformInfo = useStore($platformInfo) + const useGithubProxyUrl = useStore($useServerGithubProxyUrl) + const frontendPreference = useStore($frontendPreference) if (!platformInfo) { return ( @@ -145,35 +149,56 @@ export const ServerID = ({ server }: { server: ServerTableSchema }) => {

{t('server.install.description')}

+
+ + +
+
+ + + $frontendPreference.set({ ...frontendPreference, githubProxyUrl: e.target.value })} /> +
- -
-
+
+
+
diff --git a/www/components/worker/worker_item.tsx b/www/components/worker/worker_item.tsx index 0b55279..53de807 100644 --- a/www/components/worker/worker_item.tsx +++ b/www/components/worker/worker_item.tsx @@ -18,7 +18,7 @@ import { DialogClose, } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' -import { removeWorker, getWorker, getWorkerIngress, getWorkerStatus } from '@/api/worker' +import { removeWorker, getWorker, getWorkerIngress, getWorkerStatus, redeployWorker } from '@/api/worker' import { $workerTableRefetchTrigger } from '@/store/refetch-trigger' import { useTranslation } from 'react-i18next' import { useRouter } from 'next/router' @@ -133,6 +133,18 @@ export const WorkerActions: React.FC = ({ worker }) => { }, }) + const redeploy = useMutation({ + mutationFn: () => redeployWorker({ workerId: worker.workerId, clientIds: [] }), + onSuccess: () => { + toast.success(t('worker.actions_menu.redeploy_worker') + t('common.success')) + $workerTableRefetchTrigger.set(Math.random()) + }, + onError: (err: any) => { + toast(t('common.failed'), { description: err.message }) + $workerTableRefetchTrigger.set(Math.random()) + }, + }) + return ( @@ -153,6 +165,11 @@ export const WorkerActions: React.FC = ({ worker }) => { > {t('worker.actions_menu.edit')} + { redeploy.mutate() }} + > + {t('worker.actions_menu.redeploy_worker')} + {t('worker.actions_menu.delete')} diff --git a/www/i18n/locales/en.json b/www/i18n/locales/en.json index d6ae37f..fbf550c 100644 --- a/www/i18n/locales/en.json +++ b/www/i18n/locales/en.json @@ -38,6 +38,7 @@ "title": "Installation Command", "description": "Select your operating system and copy the installation command", "use_github_proxy_url": "Use GitHub Proxy URL", + "github_proxy_url": "GitHub Proxy URL", "windows": "Windows", "linux": "Linux" }, @@ -557,6 +558,7 @@ }, "actions_menu": { "delete": "Delete Worker", + "redeploy_worker": "Re-deploy Worker", "title": "Worker Actions", "edit": "Edit Worker" }, diff --git a/www/i18n/locales/zh.json b/www/i18n/locales/zh.json index 14ad56e..61878b7 100644 --- a/www/i18n/locales/zh.json +++ b/www/i18n/locales/zh.json @@ -38,6 +38,7 @@ "title": "安装命令", "description": "请选择您的操作系统并复制相应的安装命令", "use_github_proxy_url": "使用 GitHub 代理", + "github_proxy_url": "GitHub 代理 URL", "windows": "Windows", "linux": "Linux" }, @@ -555,6 +556,7 @@ }, "actions_menu": { "delete": "删除", + "redeploy_worker": "重启", "title": "操作", "edit": "编辑" }, diff --git a/www/lib/consts.ts b/www/lib/consts.ts index f18618c..0dd481e 100644 --- a/www/lib/consts.ts +++ b/www/lib/consts.ts @@ -113,7 +113,10 @@ export const LinuxInstallCommand = ( info: GetPlatformInfoResponse, github_proxy?: boolean, ) => { - return `curl -fSL ${github_proxy ? info.githubProxyUrl : ''}https://raw.githubusercontent.com/VaalaCat/frp-panel/main/install.sh | bash -s --${ExecCommandStr(type, item, info, ' ')}` + return `curl -fSL ${github_proxy ? info.githubProxyUrl : '' + }https://raw.githubusercontent.com/VaalaCat/frp-panel/main/install.sh | bash -s -- ${ + github_proxy ? `--github-proxy ${info.githubProxyUrl}` : '' + }${ExecCommandStr(type, item, info, ' ')}` } export const ClientEnvFile = (item: T, info: GetPlatformInfoResponse) => { diff --git a/www/lib/pb/api_client.ts b/www/lib/pb/api_client.ts index b166802..71eba5e 100644 --- a/www/lib/pb/api_client.ts +++ b/www/lib/pb/api_client.ts @@ -722,6 +722,28 @@ export interface InstallWorkerdResponse { */ status?: Status; } +/** + * @generated from protobuf message api_client.RedeployWorkerRequest + */ +export interface RedeployWorkerRequest { + /** + * @generated from protobuf field: optional string worker_id = 1; + */ + workerId?: string; + /** + * @generated from protobuf field: repeated string client_ids = 2; + */ + clientIds: string[]; +} +/** + * @generated from protobuf message api_client.RedeployWorkerResponse + */ +export interface RedeployWorkerResponse { + /** + * @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() { @@ -3608,3 +3630,103 @@ class InstallWorkerdResponse$Type extends MessageType { * @generated MessageType for protobuf message api_client.InstallWorkerdResponse */ export const InstallWorkerdResponse = new InstallWorkerdResponse$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class RedeployWorkerRequest$Type extends MessageType { + constructor() { + super("api_client.RedeployWorkerRequest", [ + { no: 1, name: "worker_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "client_ids", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): RedeployWorkerRequest { + const message = globalThis.Object.create((this.messagePrototype!)); + message.clientIds = []; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: RedeployWorkerRequest): RedeployWorkerRequest { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* optional string worker_id */ 1: + message.workerId = reader.string(); + break; + case /* repeated string client_ids */ 2: + message.clientIds.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: RedeployWorkerRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* optional string worker_id = 1; */ + if (message.workerId !== undefined) + writer.tag(1, WireType.LengthDelimited).string(message.workerId); + /* repeated string client_ids = 2; */ + for (let i = 0; i < message.clientIds.length; i++) + writer.tag(2, WireType.LengthDelimited).string(message.clientIds[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.RedeployWorkerRequest + */ +export const RedeployWorkerRequest = new RedeployWorkerRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class RedeployWorkerResponse$Type extends MessageType { + constructor() { + super("api_client.RedeployWorkerResponse", [ + { no: 1, name: "status", kind: "message", T: () => Status } + ]); + } + create(value?: PartialMessage): RedeployWorkerResponse { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: RedeployWorkerResponse): RedeployWorkerResponse { + 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: RedeployWorkerResponse, 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.RedeployWorkerResponse + */ +export const RedeployWorkerResponse = new RedeployWorkerResponse$Type(); diff --git a/www/store/user.ts b/www/store/user.ts index de11e95..70f5165 100644 --- a/www/store/user.ts +++ b/www/store/user.ts @@ -19,3 +19,12 @@ export const $useServerGithubProxyUrl = persistentAtom('use_server_gith encode: JSON.stringify, decode: JSON.parse, }) + +export type FrontendPreference = { + githubProxyUrl?: string +} + +export const $frontendPreference = persistentAtom('frontend_preference', {}, { + encode: JSON.stringify, + decode: JSON.parse, +})