feat: redeploy worker

This commit is contained in:
VaalaCat
2025-05-07 16:59:33 +00:00
parent 710c8721a3
commit f333cce4d0
26 changed files with 618 additions and 118 deletions

View File

@@ -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

View File

@@ -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))
}

View File

@@ -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

View File

@@ -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

View File

@@ -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))
}

View File

@@ -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
}

View File

@@ -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 })

View File

@@ -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
},

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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"

View File

@@ -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,
},

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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
})

View File

@@ -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)
}

View File

@@ -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 }) => {
<p className="text-sm text-muted-foreground">{t('client.install.description')}</p>
</div>
<div className="grid gap-2">
<div className="flex flex-row justify-start items-center gap-4 mb-2">
<Checkbox onCheckedChange={$useServerGithubProxyUrl.set} defaultChecked={useGithubProxyUrl} />
<div className="grid grid-cols-2 items-center gap-4 justify-items-center">
<Label>{t('client.install.use_github_proxy_url')}</Label>
<Checkbox onCheckedChange={$useServerGithubProxyUrl.set} defaultChecked={useGithubProxyUrl} />
</div>
<div className="grid grid-cols-2 items-center gap-4 justify-items-center">
<Label>{t('client.install.github_proxy_url')}</Label>
<Input value={frontendPreference.githubProxyUrl} onChange={(e) =>
$frontendPreference.set({ ...frontendPreference, githubProxyUrl: e.target.value })} />
</div>
<div className="grid grid-cols-2 items-center gap-4">
<Input
readOnly
value={WindowsInstallCommand('client', client, platformInfo, useGithubProxyUrl)}
className="flex-1"
/>
<Button
onClick={() =>
navigator.clipboard.writeText(
WindowsInstallCommand('client', client, platformInfo, useGithubProxyUrl),
WindowsInstallCommand('client', client, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl),
)
}
disabled={!platformInfo}
@@ -171,16 +175,22 @@ export const ClientID = ({ client }: { client: ClientTableSchema }) => {
>
{t('client.install.windows')}
</Button>
</div>
<div className="grid grid-cols-2 items-center gap-4">
<Input
readOnly
value={LinuxInstallCommand('client', client, platformInfo, useGithubProxyUrl)}
value={WindowsInstallCommand('client', client, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl)}
className="flex-1"
/>
</div>
<div className="grid grid-cols-2 items-center gap-4">
<Button
onClick={() =>
navigator.clipboard.writeText(LinuxInstallCommand('client', client, platformInfo, useGithubProxyUrl))
navigator.clipboard.writeText(LinuxInstallCommand('client', client, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl))
}
disabled={!platformInfo}
size="sm"
@@ -188,6 +198,14 @@ export const ClientID = ({ client }: { client: ClientTableSchema }) => {
>
{t('client.install.linux')}
</Button>
<Input
readOnly
value={LinuxInstallCommand('client', client, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl)}
className="flex-1"
/>
</div>
</div>
</div>
@@ -327,6 +345,7 @@ export const ClientActions: React.FC<ClientItemProps> = ({ 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<ClientItemProps> = ({ 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'))

View File

@@ -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<ServerTableSchema>[] = [
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 }) => {
<p className="text-sm text-muted-foreground">{t('server.install.description')}</p>
</div>
<div className="grid gap-2">
<div className="grid grid-cols-2 items-center gap-4 justify-items-center">
<Label>{t('client.install.use_github_proxy_url')}</Label>
<Checkbox onCheckedChange={$useServerGithubProxyUrl.set} defaultChecked={useGithubProxyUrl} />
</div>
<div className="grid grid-cols-2 items-center gap-4 justify-items-center">
<Label>{t('client.install.github_proxy_url')}</Label>
<Input value={frontendPreference.githubProxyUrl} onChange={(e) =>
$frontendPreference.set({ ...frontendPreference, githubProxyUrl: e.target.value })} />
</div>
<div className="grid grid-cols-2 items-center gap-4">
<Input
readOnly
value={WindowsInstallCommand('server', server, platformInfo)}
className="flex-1"
/>
<Button
onClick={() => navigator.clipboard.writeText(WindowsInstallCommand('server', server, platformInfo))}
onClick={() => navigator.clipboard.writeText(WindowsInstallCommand('server', server, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl))}
disabled={!platformInfo}
size="sm"
variant="outline"
>
{t('server.install.windows')}
</Button>
</div>
<div className="grid grid-cols-2 items-center gap-4">
<Input
readOnly
value={LinuxInstallCommand('server', server, platformInfo)}
value={WindowsInstallCommand('server', server, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl)}
className="flex-1"
/>
</div>
<div className="grid grid-cols-2 items-center gap-4">
<Button
onClick={() => navigator.clipboard.writeText(LinuxInstallCommand('server', server, platformInfo))}
onClick={() => navigator.clipboard.writeText(LinuxInstallCommand('server', server, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl))}
disabled={!platformInfo}
size="sm"
variant="outline"
>
{t('server.install.linux')}
</Button>
<Input
readOnly
value={LinuxInstallCommand('server', server, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl)}
className="flex-1"
/>
</div>
</div>
</div>

View File

@@ -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<WorkerActionsProps> = ({ 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 (
<Dialog>
<DropdownMenu>
@@ -153,6 +165,11 @@ export const WorkerActions: React.FC<WorkerActionsProps> = ({ worker }) => {
>
{t('worker.actions_menu.edit')}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => { redeploy.mutate() }}
>
{t('worker.actions_menu.redeploy_worker')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DialogTrigger asChild>
<DropdownMenuItem className="text-destructive">{t('worker.actions_menu.delete')}</DropdownMenuItem>

View File

@@ -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"
},

View File

@@ -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": "编辑"
},

View File

@@ -113,7 +113,10 @@ export const LinuxInstallCommand = <T extends Client | Server>(
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 = <T extends Client | Server>(item: T, info: GetPlatformInfoResponse) => {

View File

@@ -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<InitClientRequest> {
constructor() {
@@ -3608,3 +3630,103 @@ class InstallWorkerdResponse$Type extends MessageType<InstallWorkerdResponse> {
* @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<RedeployWorkerRequest> {
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>): RedeployWorkerRequest {
const message = globalThis.Object.create((this.messagePrototype!));
message.clientIds = [];
if (value !== undefined)
reflectionMergePartial<RedeployWorkerRequest>(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<RedeployWorkerResponse> {
constructor() {
super("api_client.RedeployWorkerResponse", [
{ no: 1, name: "status", kind: "message", T: () => Status }
]);
}
create(value?: PartialMessage<RedeployWorkerResponse>): RedeployWorkerResponse {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<RedeployWorkerResponse>(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();

View File

@@ -19,3 +19,12 @@ export const $useServerGithubProxyUrl = persistentAtom<boolean>('use_server_gith
encode: JSON.stringify,
decode: JSON.parse,
})
export type FrontendPreference = {
githubProxyUrl?: string
}
export const $frontendPreference = persistentAtom<FrontendPreference>('frontend_preference', {}, {
encode: JSON.stringify,
decode: JSON.parse,
})