diff --git a/.github/workflows/workerd-docker.workflow.yml b/.github/workflows/workerd-docker.workflow.yml new file mode 100644 index 0000000..ab71f62 --- /dev/null +++ b/.github/workflows/workerd-docker.workflow.yml @@ -0,0 +1,48 @@ +name: Docker image with workerd +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + build-static: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: npm setup + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: "1.24.x" + - name: npm install and build + run: | + cd www + npm install && npm install -g pnpm + - name: Install dependencies + run: | + go mod tidy + go install google.golang.org/protobuf/cmd/protoc-gen-go@latest + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + - name: Install Protoc + uses: arduino/setup-protoc@v3 + - name: Compile server + run: bash ./build.sh --current + - name: Setup ko + uses: ko-build/setup-ko@v0.9 + env: + KO_DOCKER_REPO: docker.io/vaalacat/frp-panel + - name: Build image with ko + env: + 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 latest-workerd diff --git a/.ko.workerd.yaml b/.ko.workerd.yaml new file mode 100644 index 0000000..f84ac53 --- /dev/null +++ b/.ko.workerd.yaml @@ -0,0 +1,16 @@ +defaultBaseImage: jacoblincool/workerd + +builds: + - id: frpp + dir: . + main: ./cmd/frpp + ldflags: + - -s -w + - -X github.com/VaalaCat/frp-panel/conf.buildDate={{.Date}} + - -X github.com/VaalaCat/frp-panel/conf.gitCommit={{.Git.FullCommit}} + - -X github.com/VaalaCat/frp-panel/conf.gitVersion={{.Git.Tag}} + - -X github.com/VaalaCat/frp-panel/conf.gitBranch={{.Git.Branch}} + +defaultPlatforms: + - linux/arm64 + - linux/amd64 diff --git a/biz/client/create_worker.go b/biz/client/create_worker.go new file mode 100644 index 0000000..79a3f97 --- /dev/null +++ b/biz/client/create_worker.go @@ -0,0 +1,31 @@ +package client + +import ( + "fmt" + + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/services/app" + "github.com/VaalaCat/frp-panel/services/workerd" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +func CreateWorker(ctx *app.Context, req *pb.CreateWorkerRequest) (*pb.CreateWorkerResponse, error) { + if !ctx.GetApp().GetConfig().Client.Features.EnableFunctions { + logger.Logger(ctx).Errorf("function features are not enabled") + return nil, fmt.Errorf("function features are not enabled") + } + + mgr := ctx.GetApp().GetWorkersManager() + + ctrl := workerd.NewWorkerdController(req.GetWorker(), ctx.GetApp().GetConfig().Client.Worker.WorkerdWorkDir) + + if err := mgr.RunWorker(ctx, req.GetWorker().GetWorkerId(), ctrl); err != nil { + return nil, err + } + + logger.Logger(ctx).Infof("create worker success, id: [%s], running at: [%s]", req.GetWorker().GetWorkerId(), req.GetWorker().GetSocket().GetAddress()) + + return &pb.CreateWorkerResponse{ + Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"}, + }, nil +} diff --git a/biz/client/get_worker_status.go b/biz/client/get_worker_status.go new file mode 100644 index 0000000..70491e7 --- /dev/null +++ b/biz/client/get_worker_status.go @@ -0,0 +1,33 @@ +package client + +import ( + "fmt" + + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/services/app" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +func GetWorkerStatus(ctx *app.Context, req *pb.GetWorkerStatusRequest) (*pb.GetWorkerStatusResponse, error) { + if !ctx.GetApp().GetConfig().Client.Features.EnableFunctions { + logger.Logger(ctx).Errorf("function features are not enabled") + return nil, fmt.Errorf("function features are not enabled") + } + + clientId := ctx.GetApp().GetConfig().Client.ID + + workersMgr := ctx.GetApp().GetWorkersManager() + + status, err := workersMgr.GetWorkerStatus(ctx, req.GetWorkerId()) + if err != nil { + logger.Logger(ctx).Errorf("failed to get worker status: %v", err) + return nil, fmt.Errorf("failed to get worker status: %v", err) + } + logger.Logger(ctx).Infof("get worker status for worker [%s], status: [%s]", req.GetWorkerId(), status) + return &pb.GetWorkerStatusResponse{ + Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"}, + WorkerStatus: map[string]string{ + clientId: string(status), + }, + }, nil +} diff --git a/biz/client/install_workerd.go b/biz/client/install_workerd.go new file mode 100644 index 0000000..902b5aa --- /dev/null +++ b/biz/client/install_workerd.go @@ -0,0 +1,40 @@ +package client + +import ( + "fmt" + "os" + + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/services/app" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +func InstallWorkerd(ctx *app.Context, req *pb.InstallWorkerdRequest) (*pb.InstallWorkerdResponse, error) { + if !ctx.GetApp().GetConfig().Client.Features.EnableFunctions { + logger.Logger(ctx).Errorf("function features are not enabled") + return nil, fmt.Errorf("function features are not enabled") + } + + workersMgr := ctx.GetApp().GetWorkersManager() + + cwd, err := os.Getwd() + if err != nil { + logger.Logger(ctx).Errorf("failed to get current working directory: %v, will install workerd in /usr/local/bin", err) + } + + binPath, err := workersMgr.InstallWorkerd(ctx, req.GetDownloadUrl(), cwd) + if err != nil { + logger.Logger(ctx).Errorf("failed to install workerd: %v", err) + return nil, fmt.Errorf("failed to install workerd: %v", err) + } + + execMgr := ctx.GetApp().GetWorkerExecManager() + execMgr.UpdateBinaryPath(binPath) + + return &pb.InstallWorkerdResponse{ + Status: &pb.Status{ + Code: pb.RespCode_RESP_CODE_SUCCESS, + Message: "ok", + }, + }, nil +} diff --git a/biz/client/remove_worker.go b/biz/client/remove_worker.go new file mode 100644 index 0000000..e447c32 --- /dev/null +++ b/biz/client/remove_worker.go @@ -0,0 +1,31 @@ +package client + +import ( + "fmt" + + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/services/app" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +func RemoveWorker(ctx *app.Context, req *pb.RemoveWorkerRequest) (*pb.RemoveWorkerResponse, error) { + if !ctx.GetApp().GetConfig().Client.Features.EnableFunctions { + logger.Logger(ctx).Errorf("function features are not enabled") + return nil, fmt.Errorf("function features are not enabled") + } + + mgr := ctx.GetApp().GetWorkersManager() + + workerId := req.GetWorkerId() + logger.Logger(ctx).Infof("start remove worker, id: [%s]", workerId) + + if err := mgr.StopWorker(ctx, workerId); err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot remove worker, id: [%s]", workerId) + return nil, err + } + + logger.Logger(ctx).Infof("remove worker success, id: [%s]", workerId) + return &pb.RemoveWorkerResponse{ + Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"}, + }, nil +} diff --git a/biz/client/rpc_handler.go b/biz/client/rpc_handler.go index f1132ff..813b4d1 100644 --- a/biz/client/rpc_handler.go +++ b/biz/client/rpc_handler.go @@ -19,7 +19,7 @@ func HandleServerMessage(appInstance app.Application, req *pb.ServerMessage) *pb } }() c := context.Background() - logger.Logger(c).Infof("client get a server message, origin is: [%+v]", req) + logger.Logger(c).Infof("client get a server message, clientId: [%s], event: [%s], sessionId: [%s]", req.GetClientId(), req.GetEvent().String(), req.GetSessionId()) switch req.Event { case pb.Event_EVENT_UPDATE_FRPC: return app.WrapperServerMsg(appInstance, req, UpdateFrpcHander) @@ -37,6 +37,14 @@ func HandleServerMessage(appInstance app.Application, req *pb.ServerMessage) *pb return app.WrapperServerMsg(appInstance, req, StartPTYConnect) case pb.Event_EVENT_GET_PROXY_INFO: return app.WrapperServerMsg(appInstance, req, GetProxyConfig) + case pb.Event_EVENT_CREATE_WORKER: + return app.WrapperServerMsg(appInstance, req, CreateWorker) + case pb.Event_EVENT_REMOVE_WORKER: + return app.WrapperServerMsg(appInstance, req, RemoveWorker) + case pb.Event_EVENT_GET_WORKER_STATUS: + return app.WrapperServerMsg(appInstance, req, GetWorkerStatus) + case pb.Event_EVENT_INSTALL_WORKERD: + return app.WrapperServerMsg(appInstance, req, InstallWorkerd) case pb.Event_EVENT_PING: rawData, _ := proto.Marshal(conf.GetVersion().ToProto()) return &pb.ClientMessage{ diff --git a/biz/client/rpc_pull_workers.go b/biz/client/rpc_pull_workers.go new file mode 100644 index 0000000..b1671ee --- /dev/null +++ b/biz/client/rpc_pull_workers.go @@ -0,0 +1,48 @@ +package client + +import ( + "context" + + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/services/app" + "github.com/VaalaCat/frp-panel/services/workerd" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +func PullWorkers(appInstance app.Application, clientID, clientSecret string) error { + ctx := app.NewContext(context.Background(), appInstance) + + if !ctx.GetApp().GetConfig().Client.Features.EnableFunctions { + logger.Logger(ctx).Infof("function features are not enabled") + return nil + } + + logger.Logger(ctx).Infof("start to pull workers belong to client, clientID: [%s]", clientID) + + cli := ctx.GetApp().GetMasterCli() + + resp, err := cli.Call().ListClientWorkers(ctx, &pb.ListClientWorkersRequest{ + Base: &pb.ClientBase{ + ClientId: clientID, + ClientSecret: clientSecret, + }, + }) + if err != nil { + logger.Logger(ctx).WithError(err).Error("cannot list client workers") + return err + } + + if len(resp.GetWorkers()) == 0 { + logger.Logger(ctx).Infof("client [%s] has no workers", clientID) + return nil + } + + ctrl := ctx.GetApp().GetWorkersManager() + for _, worker := range resp.GetWorkers() { + ctrl.RunWorker(ctx, worker.GetWorkerId(), workerd.NewWorkerdController(worker, ctx.GetApp().GetConfig().Client.Worker.WorkerdWorkDir)) + } + + logger.Logger(ctx).Infof("pull workers belong to client success, clientID: [%s], will run [%d] workers", clientID, len(resp.GetWorkers())) + + return nil +} diff --git a/biz/master/client/start_client.go b/biz/master/client/start_client.go index b16d88e..4ae70f0 100644 --- a/biz/master/client/start_client.go +++ b/biz/master/client/start_client.go @@ -29,11 +29,13 @@ func StartFRPCHandler(ctx *app.Context, req *pb.StartFRPCRequest) (*pb.StartFRPC }, nil } - client, err := dao.NewQuery(ctx).GetClientByClientID(userInfo, clientID) + cli, err := dao.NewQuery(ctx).GetClientByClientID(userInfo, clientID) if err != nil { return nil, err } + client := cli.ClientEntity + client.Stopped = false if err = dao.NewQuery(ctx).UpdateClient(userInfo, client); err != nil { diff --git a/biz/master/client/stop_client.go b/biz/master/client/stop_client.go index 1822c45..b2aa596 100644 --- a/biz/master/client/stop_client.go +++ b/biz/master/client/stop_client.go @@ -29,11 +29,13 @@ func StopFRPCHandler(ctx *app.Context, req *pb.StopFRPCRequest) (*pb.StopFRPCRes }, nil } - client, err := dao.NewQuery(ctx).GetClientByClientID(userInfo, clientID) + cli, err := dao.NewQuery(ctx).GetClientByClientID(userInfo, clientID) if err != nil { return nil, err } + client := cli.ClientEntity + client.Stopped = true if err = dao.NewQuery(ctx).UpdateClient(userInfo, client); err != nil { diff --git a/biz/master/client/update_tunnel.go b/biz/master/client/update_tunnel.go index 9df541f..61b036e 100644 --- a/biz/master/client/update_tunnel.go +++ b/biz/master/client/update_tunnel.go @@ -36,7 +36,7 @@ func UpdateFrpcHander(c *app.Context, req *pb.UpdateFRPCRequest) (*pb.UpdateFRPC }, err } - cli, err := dao.NewQuery(c).GetClientByClientID(userInfo, reqClientID) + cliRecord, err := dao.NewQuery(c).GetClientByClientID(userInfo, reqClientID) if err != nil { logger.Logger(c).WithError(err).Errorf("cannot get client, id: [%s]", reqClientID) return &pb.UpdateFRPCResponse{ @@ -44,6 +44,8 @@ func UpdateFrpcHander(c *app.Context, req *pb.UpdateFRPCRequest) (*pb.UpdateFRPC }, fmt.Errorf("cannot get client") } + cli := cliRecord.ClientEntity + if cli.IsShadow { cli, err = ChildClientForServer(c, serverID, cli) if err != nil { diff --git a/biz/master/handler.go b/biz/master/handler.go index d31b0c2..b6c8ce1 100644 --- a/biz/master/handler.go +++ b/biz/master/handler.go @@ -11,6 +11,7 @@ import ( "github.com/VaalaCat/frp-panel/biz/master/shell" "github.com/VaalaCat/frp-panel/biz/master/streamlog" "github.com/VaalaCat/frp-panel/biz/master/user" + "github.com/VaalaCat/frp-panel/biz/master/worker" "github.com/VaalaCat/frp-panel/middleware" "github.com/VaalaCat/frp-panel/services/app" "github.com/gin-gonic/gin" @@ -51,6 +52,7 @@ func ConfigureRouter(appInstance app.Application, router *gin.Engine) { clientRouter.POST("/init", app.Wrapper(appInstance, client.InitClientHandler)) clientRouter.POST("/delete", app.Wrapper(appInstance, client.DeleteClientHandler)) clientRouter.POST("/list", app.Wrapper(appInstance, client.ListClientsHandler)) + clientRouter.POST("/install_workerd", app.Wrapper(appInstance, worker.InstallWorkerd)) } serverRouter := v1.Group("/server") { @@ -83,6 +85,17 @@ func ConfigureRouter(appInstance app.Application, router *gin.Engine) { proxyRouter.POST("/start_proxy", app.Wrapper(appInstance, proxy.StartProxy)) proxyRouter.POST("/stop_proxy", app.Wrapper(appInstance, proxy.StopProxy)) } + workerHandler := v1.Group("/worker") + { + workerHandler.POST("/get", app.Wrapper(appInstance, worker.GetWorker)) + workerHandler.POST("/status", app.Wrapper(appInstance, worker.GetWorkerStatus)) + workerHandler.POST("/create", app.Wrapper(appInstance, worker.CreateWorker)) + 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("/create_ingress", app.Wrapper(appInstance, worker.CreateWorkerIngress)) + workerHandler.POST("/get_ingress", app.Wrapper(appInstance, worker.GetWorkerIngress)) + } v1.GET("/pty/:clientID", shell.PTYHandler(appInstance)) v1.GET("/log", streamlog.GetLogHandler(appInstance)) } diff --git a/biz/master/proxy/create_proxy_config.go b/biz/master/proxy/create_proxy_config.go index b452bdd..6e0bd01 100644 --- a/biz/master/proxy/create_proxy_config.go +++ b/biz/master/proxy/create_proxy_config.go @@ -30,7 +30,7 @@ func CreateProxyConfig(c *app.Context, req *pb.CreateProxyConfigRequest) (*pb.Cr serverID = req.GetServerId() ) - clientEntity, err := getClientWithMakeShadow(c, clientID, serverID) + clientEntity, err := GetClientWithMakeShadow(c, clientID, serverID) if err != nil { logger.Logger(c).WithError(err).Errorf("cannot get client, id: [%s]", clientID) return nil, err @@ -42,13 +42,6 @@ func CreateProxyConfig(c *app.Context, req *pb.CreateProxyConfigRequest) (*pb.Cr return nil, err } - proxyCfg := &models.ProxyConfigEntity{} - - if err := proxyCfg.FillClientConfig(clientEntity); err != nil { - logger.Logger(c).WithError(err).Errorf("cannot fill client config, id: [%s]", clientID) - return nil, err - } - typedProxyCfgs, err := utils.LoadProxiesFromContent(req.GetConfig()) if err != nil { logger.Logger(c).WithError(err).Errorf("cannot load proxies from content") @@ -59,43 +52,86 @@ func CreateProxyConfig(c *app.Context, req *pb.CreateProxyConfigRequest) (*pb.Cr return nil, fmt.Errorf("invalid config") } - if err := proxyCfg.FillTypedProxyConfig(typedProxyCfgs[0]); err != nil { - logger.Logger(c).WithError(err).Errorf("cannot fill typed proxy config") + if err := CreateProxyConfigWithTypedConfig(c, CreateProxyConfigWithTypedConfigParam{ + ClientID: clientID, + ServerID: serverID, + ProxyCfg: typedProxyCfgs[0], + ClientEntity: clientEntity, + Overwrite: req.GetOverwrite(), + }); err != nil { + logger.Logger(c).WithError(err).Errorf("cannot create proxy config") return nil, err } + return &pb.CreateProxyConfigResponse{ + Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"}, + }, nil +} + +type CreateProxyConfigWithTypedConfigParam struct { + ClientID string + ServerID string + ProxyCfg v1.TypedProxyConfig + ClientEntity *models.ClientEntity + Overwrite bool + WorkerID *string +} + +func CreateProxyConfigWithTypedConfig(c *app.Context, param CreateProxyConfigWithTypedConfigParam) error { + var ( + userInfo = common.GetUserInfo(c) + clientID = param.ClientID + serverID = param.ServerID + clientEntity = param.ClientEntity + typedProxyCfg = param.ProxyCfg + err error + overwrite = param.Overwrite + ) + + proxyCfg := &models.ProxyConfigEntity{} + + if err := proxyCfg.FillClientConfig(clientEntity); err != nil { + logger.Logger(c).WithError(err).Errorf("cannot fill client config, id: [%s]", clientID) + return err + } + + if err := proxyCfg.FillTypedProxyConfig(typedProxyCfg); err != nil { + logger.Logger(c).WithError(err).Errorf("cannot fill typed proxy config") + return err + } + var existedProxyCfg *models.ProxyConfig existedProxyCfg, err = dao.NewQuery(c).GetProxyConfigByOriginClientIDAndName(userInfo, clientID, proxyCfg.Name) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { logger.Logger(c).WithError(err).Errorf("cannot get proxy config, id: [%s]", clientID) - return nil, err + return err } - if !req.GetOverwrite() && err == nil { + if !overwrite && err == nil { logger.Logger(c).Errorf("proxy config already exist, cfg: [%+v]", proxyCfg) - return nil, fmt.Errorf("proxy config already exist") + return fmt.Errorf("proxy config already exist") } // update client config if oldCfg, err := clientEntity.GetConfigContent(); err != nil { logger.Logger(c).WithError(err).Errorf("cannot get client config, id: [%s]", clientID) - return nil, err + return err } else { oldCfg.Proxies = lo.Filter(oldCfg.Proxies, func(proxy v1.TypedProxyConfig, _ int) bool { - return proxy.GetBaseConfig().Name != typedProxyCfgs[0].GetBaseConfig().Name + return proxy.GetBaseConfig().Name != typedProxyCfg.GetBaseConfig().Name }) - oldCfg.Proxies = append(oldCfg.Proxies, typedProxyCfgs...) + oldCfg.Proxies = append(oldCfg.Proxies, typedProxyCfg) if err := clientEntity.SetConfigContent(*oldCfg); err != nil { logger.Logger(c).WithError(err).Errorf("cannot set client config, id: [%s]", clientID) - return nil, err + return err } } rawCfg, err := clientEntity.MarshalJSONConfig() if err != nil { logger.Logger(c).WithError(err).Errorf("cannot marshal client config, id: [%s]", clientID) - return nil, err + return err } _, err = client.UpdateFrpcHander(c, &pb.UpdateFRPCRequest{ @@ -117,11 +153,9 @@ func CreateProxyConfig(c *app.Context, req *pb.CreateProxyConfigRequest) (*pb.Cr Name: &proxyCfg.Name, }); err != nil { logger.Logger(c).WithError(err).Errorf("cannot delete old proxy, client: [%s], server: [%s], proxy: [%s]", clientID, clientEntity.ServerID, proxyCfg.Name) - return nil, err + return err } } - return &pb.CreateProxyConfigResponse{ - Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"}, - }, nil + return nil } diff --git a/biz/master/proxy/delete_proxy_config.go b/biz/master/proxy/delete_proxy_config.go index 49fa908..b91b026 100644 --- a/biz/master/proxy/delete_proxy_config.go +++ b/biz/master/proxy/delete_proxy_config.go @@ -49,7 +49,7 @@ func DeleteProxyConfig(c *app.Context, req *pb.DeleteProxyConfigRequest) (*pb.De return nil, err } - if err := dao.NewQuery(c).UpdateClient(userInfo, cli); err != nil { + if err := dao.NewQuery(c).UpdateClient(userInfo, cli.ClientEntity); err != nil { logger.Logger(c).WithError(err).Errorf("cannot update client, id: [%s]", clientID) return nil, err } diff --git a/biz/master/proxy/helper.go b/biz/master/proxy/helper.go index 0716b57..01ae852 100644 --- a/biz/master/proxy/helper.go +++ b/biz/master/proxy/helper.go @@ -29,11 +29,11 @@ func convertProxyStatsList(proxyList []*models.ProxyStatsEntity) []*pb.ProxyInfo }) } -// getClientWithMakeShadow +// GetClientWithMakeShadow // 1. 检查是否有已连接该服务端的客户端 // 2. 检查是否有Shadow客户端 // 3. 如果没有,则新建Shadow客户端和子客户端 -func getClientWithMakeShadow(c *app.Context, clientID, serverID string) (*models.ClientEntity, error) { +func GetClientWithMakeShadow(c *app.Context, clientID, serverID string) (*models.ClientEntity, error) { userInfo := common.GetUserInfo(c) clientEntity, err := dao.NewQuery(c).GetClientByFilter(userInfo, &models.ClientEntity{OriginClientID: clientID, ServerID: serverID}, lo.ToPtr(false)) if errors.Is(err, gorm.ErrRecordNotFound) { diff --git a/biz/master/proxy/start_proxy.go b/biz/master/proxy/start_proxy.go index b36f159..27afc23 100644 --- a/biz/master/proxy/start_proxy.go +++ b/biz/master/proxy/start_proxy.go @@ -21,7 +21,7 @@ func StartProxy(ctx *app.Context, req *pb.StartProxyRequest) (*pb.StartProxyResp proxyName = req.GetName() ) - clientEntity, err := getClientWithMakeShadow(ctx, clientID, serverID) + clientEntity, err := GetClientWithMakeShadow(ctx, clientID, serverID) if err != nil { logger.Logger(ctx).WithError(err).Errorf("cannot get client, id: [%s]", clientID) return nil, err diff --git a/biz/master/proxy/stop_proxy.go b/biz/master/proxy/stop_proxy.go index 64eb104..f2f1289 100644 --- a/biz/master/proxy/stop_proxy.go +++ b/biz/master/proxy/stop_proxy.go @@ -20,7 +20,7 @@ func StopProxy(ctx *app.Context, req *pb.StopProxyRequest) (*pb.StopProxyRespons proxyName = req.GetName() ) - clientEntity, err := getClientWithMakeShadow(ctx, clientID, serverID) + clientEntity, err := GetClientWithMakeShadow(ctx, clientID, serverID) if err != nil { logger.Logger(ctx).WithError(err).Errorf("cannot get client, id: [%s]", clientID) return nil, err diff --git a/biz/master/proxy/update_proxy_config.go b/biz/master/proxy/update_proxy_config.go index 32976f9..3c13e20 100644 --- a/biz/master/proxy/update_proxy_config.go +++ b/biz/master/proxy/update_proxy_config.go @@ -26,12 +26,14 @@ func UpdateProxyConfig(c *app.Context, req *pb.UpdateProxyConfigRequest) (*pb.Up serverID = req.GetServerId() ) - clientEntity, err := dao.NewQuery(c).GetClientByClientID(userInfo, clientID) + cli, err := dao.NewQuery(c).GetClientByClientID(userInfo, clientID) if err != nil { logger.Logger(c).WithError(err).Errorf("cannot get client, id: [%s]", clientID) return nil, err } + clientEntity := cli.ClientEntity + if clientEntity.ServerID != serverID { logger.Logger(c).Errorf("client and server not match, find or create client, client: [%s], server: [%s]", clientID, serverID) originClient, err := dao.NewQuery(c).GetClientByClientID(userInfo, clientEntity.OriginClientID) @@ -40,7 +42,7 @@ func UpdateProxyConfig(c *app.Context, req *pb.UpdateProxyConfigRequest) (*pb.Up return nil, err } - clientEntity, err = client.ChildClientForServer(c, serverID, originClient) + clientEntity, err = client.ChildClientForServer(c, serverID, originClient.ClientEntity) if err != nil { logger.Logger(c).WithError(err).Errorf("cannot create child client, id: [%s]", clientID) return nil, err diff --git a/biz/master/worker/create_worker.go b/biz/master/worker/create_worker.go new file mode 100644 index 0000000..a58e3b9 --- /dev/null +++ b/biz/master/worker/create_worker.go @@ -0,0 +1,72 @@ +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/services/workerd" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +func CreateWorker(ctx *app.Context, req *pb.CreateWorkerRequest) (*pb.CreateWorkerResponse, error) { + var ( + userInfo = common.GetUserInfo(ctx) + clientId = req.GetClientId() + reqWorker = req.GetWorker() + ) + + if err := validateCreateWorker(req); err != nil { + logger.Logger(ctx).WithError(err).Errorf("invalid create worker request, origin is: [%s]", req.String()) + return nil, err + } + + cli, err := dao.NewQuery(ctx).GetClientByClientID(userInfo, clientId) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot get client, id: [%s], workerName: [%s]", clientId, reqWorker.GetName()) + return nil, err + } + + workerd.FillWorkerValue(reqWorker, uint(userInfo.GetUserID())) + + workerToCreate := (&models.Worker{}).FromPB(reqWorker) + workerToCreate.WorkerModel = nil + + workerToCreate.Clients = append(workerToCreate.Clients, *cli) + + if err := dao.NewQuery(ctx).CreateWorker(userInfo, workerToCreate); err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot create worker, workerName: [%s]", workerToCreate.Name) + return nil, err + } + + go func() { + bgCtx := ctx.Background() + resp := &pb.CreateWorkerResponse{} + err := rpc.CallClientWrapper(bgCtx, clientId, pb.Event_EVENT_CREATE_WORKER, req, resp) + if err != nil { + logger.Logger(bgCtx).WithError(err).Errorf("create worker event send to client error, client id: [%s], worker name: [%s]", clientId, workerToCreate.Name) + } + }() + + logger.Logger(ctx).Infof("create worker success, workerName: [%s], start to create worker's proxy", workerToCreate.Name) + return &pb.CreateWorkerResponse{ + Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"}, + WorkerId: &workerToCreate.ID, + }, nil +} + +func validateCreateWorker(req *pb.CreateWorkerRequest) error { + if len(req.GetClientId()) == 0 { + return fmt.Errorf("invalid client id") + } + + if req.GetWorker() == nil { + return fmt.Errorf("invalid worker") + } + + return nil +} diff --git a/biz/master/worker/create_worker_ingress.go b/biz/master/worker/create_worker_ingress.go new file mode 100644 index 0000000..3a67826 --- /dev/null +++ b/biz/master/worker/create_worker_ingress.go @@ -0,0 +1,106 @@ +package worker + +import ( + "fmt" + "strings" + + "github.com/VaalaCat/frp-panel/biz/master/proxy" + "github.com/VaalaCat/frp-panel/common" + "github.com/VaalaCat/frp-panel/defs" + "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/utils/logger" + v1 "github.com/fatedier/frp/pkg/config/v1" +) + +func IngressName(worker *models.Worker, cli *models.ClientEntity) string { + return fmt.Sprintf("ingress-%s-%s", strings.Split(worker.ID, "-")[0], cli.OriginClientID) +} + +func CreateWorkerIngress(ctx *app.Context, req *pb.CreateWorkerIngressRequest) (*pb.CreateWorkerIngressResponse, error) { + + if err := validateCreateWorkerIngressRequest(req); err != nil { + logger.Logger(ctx).WithError(err).Errorf("invalid create worker ingress request, origin is: [%s]", req.String()) + return nil, err + } + + var ( + clientId = req.GetClientId() + serverId = req.GetServerId() + workerId = req.GetWorkerId() + userInfo = common.GetUserInfo(ctx) + ) + + clientEntity, err := proxy.GetClientWithMakeShadow(ctx, clientId, serverId) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot get client, id: [%s]", clientId) + return nil, err + } + + _, err = dao.NewQuery(ctx).GetServerByServerID(userInfo, serverId) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot get server, id: [%s]", serverId) + return nil, err + } + + workerToExpose, err := dao.NewQuery(ctx).GetWorkerByWorkerID(userInfo, workerId) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot get worker, id: [%s]", workerId) + return nil, err + } + + httpProxyCfg := v1.HTTPProxyConfig{ + ProxyBaseConfig: v1.ProxyBaseConfig{ + Name: IngressName(workerToExpose, clientEntity), + Type: string(v1.ProxyTypeHTTP), + Annotations: map[string]string{ + defs.FrpProxyAnnotationsKey_Ingress: "true", + defs.FrpProxyAnnotationsKey_WorkerId: workerId, + }, + ProxyBackend: v1.ProxyBackend{ + Plugin: v1.TypedClientPluginOptions{ + Type: v1.PluginUnixDomainSocket, + ClientPluginOptions: &v1.UnixDomainSocketPluginOptions{ + Type: v1.PluginUnixDomainSocket, + UnixPath: fmt.Sprintf("@%s", strings.TrimPrefix(workerToExpose.Socket.Data.GetAddress(), "unix-abstract:")), + }, + }, + }, + }, + DomainConfig: v1.DomainConfig{ + SubDomain: workerId, + }, + } + + if err := proxy.CreateProxyConfigWithTypedConfig(ctx, proxy.CreateProxyConfigWithTypedConfigParam{ + ClientID: clientId, + ServerID: serverId, + ProxyCfg: v1.TypedProxyConfig{ + Type: string(v1.ProxyTypeHTTP), + ProxyConfigurer: &httpProxyCfg, + }, + ClientEntity: clientEntity, + Overwrite: true, + }); err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot create proxy config, client id: [%s], server id: [%s], worker id: [%s]", clientId, serverId, workerId) + return nil, err + } + + return &pb.CreateWorkerIngressResponse{ + Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"}, + }, nil +} + +func validateCreateWorkerIngressRequest(req *pb.CreateWorkerIngressRequest) error { + if req == nil { + return fmt.Errorf("invalid request") + } + + if len(req.GetClientId()) == 0 || len(req.GetServerId()) == 0 || len(req.GetWorkerId()) == 0 { + return fmt.Errorf("invalid request, client id: [%s], server id: [%s], worker id: [%s]", req.GetClientId(), req.GetServerId(), req.GetWorkerId()) + } + + return nil +} diff --git a/biz/master/worker/get_worker.go b/biz/master/worker/get_worker.go new file mode 100644 index 0000000..e988bad --- /dev/null +++ b/biz/master/worker/get_worker.go @@ -0,0 +1,46 @@ +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/utils/logger" + "github.com/samber/lo" +) + +func GetWorker(ctx *app.Context, req *pb.GetWorkerRequest) (*pb.GetWorkerResponse, error) { + logger.Logger(ctx).Infof("get worker req: %s", req.String()) + var ( + workerID = req.GetWorkerId() + userInfo = common.GetUserInfo(ctx) + ) + + if len(workerID) == 0 { + logger.Logger(ctx).Errorf("worker id is empty") + return nil, fmt.Errorf("worker id is empty") + } + + workerRecord, err := dao.NewQuery(ctx).GetWorkerByWorkerID(userInfo, workerID) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("get worker by id failed") + return nil, err + } + + return &pb.GetWorkerResponse{ + Status: &pb.Status{ + Code: pb.RespCode_RESP_CODE_SUCCESS, + Message: "ok", + }, + Worker: workerRecord.ToPB(), + Clients: lo.Map(workerRecord.Clients, func(client models.Client, index int) *pb.Client { + c := client.ToPB() + c.Config = nil + c.Secret = nil + return c + }), + }, nil +} diff --git a/biz/master/worker/get_worker_ingress.go b/biz/master/worker/get_worker_ingress.go new file mode 100644 index 0000000..f155b16 --- /dev/null +++ b/biz/master/worker/get_worker_ingress.go @@ -0,0 +1,32 @@ +package worker + +import ( + "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/utils/logger" + "github.com/samber/lo" +) + +func GetWorkerIngress(ctx *app.Context, req *pb.GetWorkerIngressRequest) (*pb.GetWorkerIngressResponse, error) { + logger.Logger(ctx).Infof("get worker: [%s] ingress", req.GetWorkerId()) + var ( + workerId = req.GetWorkerId() + userInfo = common.GetUserInfo(ctx) + ) + + proxyCfgs, err := dao.NewQuery(ctx).GetProxyConfigsByWorkerId(userInfo, workerId) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("failed to get proxy configs for worker: [%s]", workerId) + return nil, err + } + + logger.Logger(ctx).Infof("got proxy configs for worker: [%s] success, ingresses length: [%d]", workerId, len(proxyCfgs)) + return &pb.GetWorkerIngressResponse{ + ProxyConfigs: lo.Map(proxyCfgs, func(item *models.ProxyConfig, index int) *pb.ProxyConfig { + return item.ToPB() + }), + }, nil +} diff --git a/biz/master/worker/get_worker_status.go b/biz/master/worker/get_worker_status.go new file mode 100644 index 0000000..f065022 --- /dev/null +++ b/biz/master/worker/get_worker_status.go @@ -0,0 +1,65 @@ +package worker + +import ( + "fmt" + "maps" + + "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/logger" + "github.com/samber/lo" + "github.com/sourcegraph/conc/pool" +) + +func GetWorkerStatus(ctx *app.Context, req *pb.GetWorkerStatusRequest) (*pb.GetWorkerStatusResponse, error) { + var ( + workerID = req.GetWorkerId() + userInfo = common.GetUserInfo(ctx) + ) + + if len(workerID) == 0 { + logger.Logger(ctx).Errorf("worker id is empty") + return nil, fmt.Errorf("worker id is empty") + } + + workerRecord, err := dao.NewQuery(ctx).GetWorkerByWorkerID(userInfo, workerID) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("get worker by id failed") + return nil, err + } + + clientIds := lo.Map(workerRecord.Clients, func(cli models.Client, _ int) string { + return cli.ClientID + }) + + var pool pool.ResultErrorPool[*pb.GetWorkerStatusResponse] + for _, clientID := range clientIds { + pool.Go(func() (*pb.GetWorkerStatusResponse, error) { + bgCtx := ctx.Background() + cliResp := &pb.GetWorkerStatusResponse{} + err := rpc.CallClientWrapper(bgCtx, clientID, pb.Event_EVENT_GET_WORKER_STATUS, &pb.GetWorkerStatusRequest{}, cliResp) + return cliResp, err + }) + } + + resps, err := pool.Wait() + if err != nil { + logger.Logger(ctx).WithError(err).Warnf("get worker status failed") + } + + statusMap := map[string]string{} + + for _, r := range resps { + s := r.GetWorkerStatus() + maps.Copy(statusMap, s) + } + + return &pb.GetWorkerStatusResponse{ + Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"}, + WorkerStatus: statusMap, + }, nil +} diff --git a/biz/master/worker/install_workerd.go b/biz/master/worker/install_workerd.go new file mode 100644 index 0000000..8926dd0 --- /dev/null +++ b/biz/master/worker/install_workerd.go @@ -0,0 +1,38 @@ +package worker + +import ( + "github.com/VaalaCat/frp-panel/common" + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/services/app" + "github.com/VaalaCat/frp-panel/services/dao" + "github.com/VaalaCat/frp-panel/services/rpc" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +func InstallWorkerd(ctx *app.Context, req *pb.InstallWorkerdRequest) (*pb.InstallWorkerdResponse, error) { + var ( + userInfo = common.GetUserInfo(ctx) + clientId = req.GetClientId() + ) + logger.Logger(ctx).Infof("installw orkerd called with userInfo: %v, clientId: %s", userInfo, clientId) + + _, err := dao.NewQuery(ctx).GetClientByClientID(userInfo, clientId) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("failed to get client by clientID: %s", clientId) + return nil, err + } + + resp := &pb.InstallWorkerdResponse{} + if err := rpc.CallClientWrapper(ctx, clientId, pb.Event_EVENT_INSTALL_WORKERD, req, resp); err != nil { + logger.Logger(ctx).WithError(err).Errorf("failed to call install workerd with clientId: %s", clientId) + return nil, err + } + logger.Logger(ctx).Infof("install workerd success with clientId: %s", clientId) + + return &pb.InstallWorkerdResponse{ + Status: &pb.Status{ + Code: pb.RespCode_RESP_CODE_SUCCESS, + Message: "ok", + }, + }, nil +} diff --git a/biz/master/worker/list_worker.go b/biz/master/worker/list_worker.go new file mode 100644 index 0000000..699ac1e --- /dev/null +++ b/biz/master/worker/list_worker.go @@ -0,0 +1,100 @@ +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/utils/logger" + "github.com/samber/lo" +) + +func ListWorkers(ctx *app.Context, req *pb.ListWorkersRequest) (*pb.ListWorkersResponse, error) { + var ( + userInfo = common.GetUserInfo(ctx) + page = int(req.GetPage()) + pageSize = int(req.GetPageSize()) + keyword = req.GetKeyword() + err error + workers []*models.Worker + workerCounts int64 + hasKeyword = len(keyword) > 0 + ) + + if page == 0 { + page = 1 + } + if pageSize == 0 { + pageSize = 10 + } + + if hasKeyword { + workers, err = dao.NewQuery(ctx).ListWorkersWithKeyword(userInfo, page, pageSize, keyword) + } else { + workers, err = dao.NewQuery(ctx).ListWorkers(userInfo, page, pageSize) + } + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot list workers, page: [%d], pageSize: [%d], keyword: [%s]", page, pageSize, keyword) + return &pb.ListWorkersResponse{ + Status: &pb.Status{Code: pb.RespCode_RESP_CODE_NOT_FOUND, Message: err.Error()}, + }, fmt.Errorf("cannot list workers, page: [%d], pageSize: [%d], keyword: [%s]", page, pageSize, keyword) + } + + if hasKeyword { + workerCounts, err = dao.NewQuery(ctx).CountWorkersWithKeyword(userInfo, keyword) + } else { + workerCounts, err = dao.NewQuery(ctx).CountWorkers(userInfo) + } + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot count workers, keyword: [%s]", keyword) + return &pb.ListWorkersResponse{ + Status: &pb.Status{Code: pb.RespCode_RESP_CODE_NOT_FOUND, Message: err.Error()}, + }, fmt.Errorf("cannot count workers, keyword: [%s]", keyword) + } + + return &pb.ListWorkersResponse{ + Status: &pb.Status{ + Code: pb.RespCode_RESP_CODE_SUCCESS, + Message: "success", + }, + Total: lo.ToPtr(int32(workerCounts)), + Workers: lo.Map(workers, func(w *models.Worker, _ int) *pb.Worker { + k := w.ToPB() + k.Code = nil + k.ConfigTemplate = nil + return k + }), + }, nil +} + +func ListClientWorkers(ctx *app.Context, req *pb.ListClientWorkersRequest) (*pb.ListClientWorkersResponse, error) { + var ( + err error + workers []*models.Worker + clientId = req.GetBase().GetClientId() + ) + + workers, err = dao.NewQuery(ctx).AdminListWorkersByClientID(clientId) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot list workers, clientId: [%s]", clientId) + return &pb.ListClientWorkersResponse{ + Status: &pb.Status{Code: pb.RespCode_RESP_CODE_NOT_FOUND, Message: err.Error()}, + }, fmt.Errorf("cannot list workers, clientId: [%s]", clientId) + } + + logger.Logger(ctx).Infof("list workers, clientId: [%s], worker len: [%d]", clientId, len(workers)) + + return &pb.ListClientWorkersResponse{ + Status: &pb.Status{ + Code: pb.RespCode_RESP_CODE_SUCCESS, + Message: "success", + }, + Workers: lo.Map(workers, func(w *models.Worker, _ int) *pb.Worker { + k := w.ToPB() + return k + }), + }, nil +} diff --git a/biz/master/worker/remove_worker.go b/biz/master/worker/remove_worker.go new file mode 100644 index 0000000..36c1a9e --- /dev/null +++ b/biz/master/worker/remove_worker.go @@ -0,0 +1,75 @@ +package worker + +import ( + "github.com/VaalaCat/frp-panel/biz/master/proxy" + "github.com/VaalaCat/frp-panel/common" + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/services/app" + "github.com/VaalaCat/frp-panel/services/dao" + "github.com/VaalaCat/frp-panel/services/rpc" + "github.com/VaalaCat/frp-panel/utils/logger" + "github.com/samber/lo" +) + +func RemoveWorker(ctx *app.Context, req *pb.RemoveWorkerRequest) (*pb.RemoveWorkerResponse, error) { + var ( + userInfo = common.GetUserInfo(ctx) + workerId = req.GetWorkerId() + ) + + logger.Logger(ctx).Infof("start remove worker, id: [%s]", workerId) + + workerToDelete, err := dao.NewQuery(ctx).GetWorkerByWorkerID(userInfo, workerId) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot get worker, id: [%s]", workerId) + return nil, err + } + + if ingressesToDelete, err := dao.NewQuery(ctx).GetProxyConfigsByWorkerId(userInfo, workerId); err == nil { + for _, ingressToDelete := range ingressesToDelete { + logger.Logger(ctx).Infof("start to remove worker ingress on server: [%s] client: [%s], name: [%s]", ingressToDelete.ServerID, ingressToDelete.ClientID, ingressToDelete.Name) + + resp, err := proxy.DeleteProxyConfig(ctx, &pb.DeleteProxyConfigRequest{ + ServerId: lo.ToPtr(ingressToDelete.ServerID), + ClientId: lo.ToPtr(ingressToDelete.ClientID), + Name: lo.ToPtr(ingressToDelete.Name), + }) + + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot remove worker ingress on server: [%s] client: [%s], name: [%s], resp is: [%s]", + ingressToDelete.ServerID, ingressToDelete.ClientID, ingressToDelete.Name, resp.String()) + return nil, err + } + logger.Logger(ctx).Infof("remove worker ingress success on server: [%s] client: [%s], name: [%s]", ingressToDelete.ServerID, ingressToDelete.ClientID, ingressToDelete.Name) + } + } + + if err := dao.NewQuery(ctx).DeleteWorker(userInfo, workerId); err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot remove worker, id: [%s]", workerId) + return nil, err + } + + go func() { + bgCtx := ctx.Background() + hasErr := false + + for _, cli := range workerToDelete.Clients { + resp := &pb.RemoveWorkerResponse{} + if err := rpc.CallClientWrapper(bgCtx, cli.ClientID, pb.Event_EVENT_REMOVE_WORKER, req, resp); err != nil { + logger.Logger(bgCtx).WithError(err).Errorf("remove event send to client error, client id: [%s]", cli.ClientID) + hasErr = true + continue + } + } + + if hasErr { + logger.Logger(bgCtx).Errorf("remove event send to client error") + } + }() + + logger.Logger(ctx).Infof("remove worker success, id: [%s]", workerId) + + return &pb.RemoveWorkerResponse{ + Status: &pb.Status{Code: pb.RespCode_RESP_CODE_SUCCESS, Message: "ok"}, + }, nil +} diff --git a/biz/master/worker/run_worker.go b/biz/master/worker/run_worker.go new file mode 100644 index 0000000..4df0094 --- /dev/null +++ b/biz/master/worker/run_worker.go @@ -0,0 +1 @@ +package worker diff --git a/biz/master/worker/stop_worker.go b/biz/master/worker/stop_worker.go new file mode 100644 index 0000000..4df0094 --- /dev/null +++ b/biz/master/worker/stop_worker.go @@ -0,0 +1 @@ +package worker diff --git a/biz/master/worker/update_worker.go b/biz/master/worker/update_worker.go new file mode 100644 index 0000000..e51a76d --- /dev/null +++ b/biz/master/worker/update_worker.go @@ -0,0 +1,104 @@ +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 UpdateWorker(ctx *app.Context, req *pb.UpdateWorkerRequest) (*pb.UpdateWorkerResponse, error) { + var ( + clientIds = req.GetClientIds() + wrokerReq = req.GetWorker() + userInfo = common.GetUserInfo(ctx) + oldClientIds []string + ) + + workerToUpdate, err := dao.NewQuery(ctx).GetWorkerByWorkerID(userInfo, wrokerReq.GetWorkerId()) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot get worker, id: [%s]", wrokerReq.GetWorkerId()) + 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)) + } + + 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 }) + updatedFields = append(updatedFields, "client_id") + } else { + oldClientIds = lo.Map(workerToUpdate.Clients, func(c models.Client, _ int) string { return c.ClientID }) + } + + if len(wrokerReq.GetName()) != 0 { + workerToUpdate.Name = wrokerReq.GetName() + updatedFields = append(updatedFields, "name") + } + + if len(wrokerReq.GetCode()) != 0 { + workerToUpdate.Code = wrokerReq.GetCode() + updatedFields = append(updatedFields, "code") + } + + if len(wrokerReq.GetConfigTemplate()) != 0 { + workerToUpdate.ConfigTemplate = wrokerReq.GetConfigTemplate() + updatedFields = append(updatedFields, "config_template") + } + + if err := dao.NewQuery(ctx).UpdateWorker(userInfo, workerToUpdate); err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot update worker, id: [%s]", wrokerReq.GetWorkerId()) + return nil, fmt.Errorf("cannot update worker, id: [%s]", wrokerReq.GetWorkerId()) + } + + go func() { + bgCtx := ctx.Background() + + for _, oldClientId := range oldClientIds { + removeResp := &pb.RemoveWorkerResponse{} + err := rpc.CallClientWrapper(bgCtx, oldClientId, pb.Event_EVENT_REMOVE_WORKER, &pb.RemoveWorkerRequest{ + ClientId: &oldClientId, + 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]", oldClientId, workerToUpdate.Name) + } + } + + for _, newClient := range clis { + createResp := &pb.CreateWorkerResponse{} + err = rpc.CallClientWrapper(bgCtx, newClient.ClientID, pb.Event_EVENT_CREATE_WORKER, &pb.CreateWorkerRequest{ + ClientId: &newClient.ClientID, + 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]", newClient.ClientID, workerToUpdate.Name) + } + } + + logger.Logger(ctx).Infof("update 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("update worker success, id: [%s], updated fields: %s", wrokerReq.GetWorkerId(), utils.MarshalForJson(updatedFields)) + + return &pb.UpdateWorkerResponse{ + Status: &pb.Status{ + Code: pb.RespCode_RESP_CODE_SUCCESS, + Message: "ok", + }, + }, nil +} diff --git a/cmd/frpp/shared/client.go b/cmd/frpp/shared/client.go index ddf3e2e..a729929 100644 --- a/cmd/frpp/shared/client.go +++ b/cmd/frpp/shared/client.go @@ -21,10 +21,11 @@ type runClientParam struct { Lc fx.Lifecycle - Ctx *app.Context - AppInstance app.Application - TaskManager watcher.Client `name:"clientTaskManager"` - Cfg conf.Config + Ctx *app.Context + AppInstance app.Application + TaskManager watcher.Client `name:"clientTaskManager"` + WorkersManager app.WorkersManager + Cfg conf.Config } func runClient(param runClientParam) { @@ -45,6 +46,8 @@ func runClient(param runClientParam) { param.TaskManager.AddDurationTask(defs.PullConfigDuration, bizclient.PullConfig, appInstance, clientID, clientSecret) + param.TaskManager.AddDurationTask(defs.PullClientWorkersDuration, + bizclient.PullWorkers, appInstance, clientID, clientSecret) var wg conc.WaitGroup param.Lc.Append(fx.Hook{ @@ -62,7 +65,10 @@ func runClient(param runClientParam) { ) appInstance.SetClientRPCHandler(cliRpcHandler) + // --- init once start --- initClientOnce(appInstance, clientID, clientSecret) + initClientWorkerOnce(appInstance, clientID, clientSecret) + // --- init once stop ---- wg.Go(cliRpcHandler.Run) wg.Go(param.TaskManager.Run) @@ -84,3 +90,10 @@ func initClientOnce(appInstance app.Application, clientID, clientSecret string) logger.Logger(context.Background()).WithError(err).Errorf("cannot pull client config, wait for retry") } } + +func initClientWorkerOnce(appInstance app.Application, clientID, clientSecret string) { + err := bizclient.PullWorkers(appInstance, clientID, clientSecret) + if err != nil { + logger.Logger(context.Background()).WithError(err).Errorf("cannot pull client workers, wait for retry") + } +} diff --git a/cmd/frpp/shared/cmd.go b/cmd/frpp/shared/cmd.go index f5f72e5..7d75575 100644 --- a/cmd/frpp/shared/cmd.go +++ b/cmd/frpp/shared/cmd.go @@ -15,6 +15,7 @@ import ( "github.com/VaalaCat/frp-panel/utils" "github.com/VaalaCat/frp-panel/utils/logger" "github.com/joho/godotenv" + "github.com/samber/lo" "github.com/spf13/cobra" "github.com/spf13/pflag" "go.uber.org/fx" @@ -383,13 +384,13 @@ func patchConfig(appInstance app.Application, commonArgs CommonArgs) conf.Config tmpCfg.Client.RPCUrl = *commonArgs.RpcUrl } - if commonArgs.RpcPort != nil || commonArgs.ApiPort != nil || - commonArgs.ApiScheme != nil || - commonArgs.RpcHost != nil || commonArgs.ApiHost != nil { - logger.Logger(c).Warnf("deprecatedenv configs !!! pls use api url and rpc url \n\n rpc host: %s, rpc port: %d, api host: %s, api port: %d, api scheme: %s", + if lo.FromPtrOr(commonArgs.RpcPort, 0) != 0 || lo.FromPtrOr(commonArgs.ApiPort, 0) != 0 || + lo.FromPtrOr(commonArgs.ApiScheme, "") != "" || + lo.FromPtrOr(commonArgs.RpcHost, "") != "" || lo.FromPtrOr(commonArgs.ApiHost, "") != "" { + logger.Logger(c).Warnf("deprecatedenv configs !!! pls use api url and rpc url \n\n rpc host: %s, rpc port: %d, api host: %s, api port: %d, api scheme: %s, \n\n args: %s", tmpCfg.Master.RPCHost, tmpCfg.Master.RPCPort, tmpCfg.Master.APIHost, tmpCfg.Master.APIPort, - tmpCfg.Master.APIScheme) + tmpCfg.Master.APIScheme, utils.MarshalForJson(tmpCfg)) } else if len(tmpCfg.Client.APIUrl) > 0 || len(tmpCfg.Client.RPCUrl) > 0 { logger.Logger(c).Infof("env config, api url: %s, rpc url: %s", tmpCfg.Client.APIUrl, tmpCfg.Client.RPCUrl) } diff --git a/cmd/frpp/shared/modules.go b/cmd/frpp/shared/modules.go index 458762e..4d21292 100644 --- a/cmd/frpp/shared/modules.go +++ b/cmd/frpp/shared/modules.go @@ -7,6 +7,8 @@ import ( var ( clientMod = fx.Module("cmd.client", fx.Provide( + NewWorkerExecManager, + NewWorkersManager, fx.Annotate(NewWatcher, fx.ResultTags(`name:"clientTaskManager"`)), )) diff --git a/cmd/frpp/shared/providers.go b/cmd/frpp/shared/providers.go index 154f689..5329461 100644 --- a/cmd/frpp/shared/providers.go +++ b/cmd/frpp/shared/providers.go @@ -6,6 +6,7 @@ import ( "embed" "net" "net/http" + "os" "path/filepath" "sync" @@ -26,6 +27,7 @@ import ( "github.com/VaalaCat/frp-panel/services/rbac" "github.com/VaalaCat/frp-panel/services/rpc" "github.com/VaalaCat/frp-panel/services/watcher" + "github.com/VaalaCat/frp-panel/services/workerd" "github.com/VaalaCat/frp-panel/utils" "github.com/VaalaCat/frp-panel/utils/logger" "github.com/VaalaCat/frp-panel/utils/wsgrpc" @@ -373,3 +375,39 @@ func NewEnforcer(param struct { param.AppInstance.SetEnforcer(e) return e } + +func NewWorkersManager(lx fx.Lifecycle, mgr app.WorkerExecManager, appInstance app.Application) app.WorkersManager { + if !appInstance.GetConfig().Client.Features.EnableFunctions { + return nil + } + + workerMgr := workerd.NewWorkersManager() + appInstance.SetWorkersManager(workerMgr) + + lx.Append(fx.Hook{ + OnStop: func(ctx context.Context) error { + workerMgr.StopAll() + logger.Logger(ctx).Info("stop all workers") + return nil + }, + }) + + return workerMgr +} + +func NewWorkerExecManager(cfg conf.Config, appInstance app.Application) app.WorkerExecManager { + if !appInstance.GetConfig().Client.Features.EnableFunctions { + return nil + } + + workerdBinPath := cfg.Client.Worker.WorkerdBinaryPath + + if err := os.MkdirAll(cfg.Client.Worker.WorkerdWorkDir, os.ModePerm); err != nil { + logger.Logger(context.Background()).WithError(err).Fatalf("create work dir failed, path: [%s]", cfg.Client.Worker.WorkerdWorkDir) + } + + mgr := workerd.NewExecManager(workerdBinPath, + []string{"serve", "--watch", "--verbose"}) + appInstance.SetWorkerExecManager(mgr) + return mgr +} diff --git a/common/request.go b/common/request.go index def9aef..7251f8d 100644 --- a/common/request.go +++ b/common/request.go @@ -25,7 +25,10 @@ type ReqType interface { pb.GetProxyStatsByClientIDRequest | pb.GetProxyStatsByServerIDRequest | pb.CreateProxyConfigRequest | pb.ListProxyConfigsRequest | pb.UpdateProxyConfigRequest | pb.DeleteProxyConfigRequest | pb.GetProxyConfigRequest | pb.SignTokenRequest | - pb.StartProxyRequest | pb.StopProxyRequest + 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 } func GetProtoRequest[T ReqType](c *gin.Context) (r *T, err error) { diff --git a/common/response.go b/common/response.go index 911351a..62efb4c 100644 --- a/common/response.go +++ b/common/response.go @@ -26,7 +26,10 @@ type RespType interface { pb.GetProxyStatsByClientIDResponse | pb.GetProxyStatsByServerIDResponse | pb.CreateProxyConfigResponse | pb.ListProxyConfigsResponse | pb.UpdateProxyConfigResponse | pb.DeleteProxyConfigResponse | pb.GetProxyConfigResponse | pb.SignTokenResponse | - pb.StartProxyResponse | pb.StopProxyResponse + 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 } func OKResp[T RespType](c *gin.Context, origin *T) { @@ -96,6 +99,14 @@ func getEvent(origin interface{}) (pb.Event, protoreflect.ProtoMessage, error) { return pb.Event_EVENT_STOP_FRPS, ptr, nil case *pb.GetProxyConfigResponse: return pb.Event_EVENT_GET_PROXY_INFO, ptr, nil + case *pb.CreateWorkerResponse: + return pb.Event_EVENT_CREATE_WORKER, ptr, nil + case *pb.RemoveWorkerResponse: + return pb.Event_EVENT_REMOVE_WORKER, ptr, nil + case *pb.GetWorkerStatusResponse: + return pb.Event_EVENT_GET_WORKER_STATUS, ptr, nil + case *pb.InstallWorkerdResponse: + return pb.Event_EVENT_INSTALL_WORKERD, ptr, nil default: return 0, nil, fmt.Errorf("cannot unmarshal unknown type: %T", origin) } diff --git a/conf/settings.go b/conf/settings.go index d2bff1f..fdda36c 100644 --- a/conf/settings.go +++ b/conf/settings.go @@ -5,8 +5,10 @@ import ( "encoding/json" "fmt" "os" + "strings" "github.com/VaalaCat/frp-panel/defs" + "github.com/VaalaCat/frp-panel/utils" "github.com/VaalaCat/frp-panel/utils/logger" "github.com/gin-gonic/gin" "github.com/ilyakaznacheev/cleanenv" @@ -52,12 +54,25 @@ type Config struct { RPCUrl string `env:"RPC_URL" env-description:"rpc url, support ws or wss or grpc scheme, eg: ws://127.0.0.1:9000"` APIUrl string `env:"API_URL" env-description:"api url, support http or https scheme, eg: http://127.0.0.1:9000"` TLSInsecureSkipVerify bool `env:"TLS_INSECURE_SKIP_VERIFY" env-default:"true" env-description:"skip tls verify"` + Worker struct { + WorkerdBinaryPath string `env:"WORKERD_BINARY_PATH" env-description:"workerd binary path"` + WorkerdWorkDir string `env:"WORKERD_WORK_DIR" env-default:"/tmp/frpp/workerd" env-description:"workerd work dir"` + WorkerdDownloadURL struct { + UseProxy bool `env:"USE_PROXY" env-default:"true" env-description:"use proxy"` + LinuxArm64 string `env:"LINUX_ARM64" env-default:"https://github.com/cloudflare/workerd/releases/download/v1.20250505.0/workerd-linux-arm64.gz"` + LinuxX8664 string `env:"LINUX_X86_64" env-default:"https://github.com/cloudflare/workerd/releases/download/v1.20250505.0/workerd-linux-64.gz"` + } `env-prefix:"WORKERD_DOWNLOAD_URL_" env-description:"workerd download url"` + } `env-prefix:"WORKER_" env-description:"worker's config"` + Features struct { + EnableFunctions bool `env:"ENABLE_FUNCTIONS" env-default:"true" env-description:"enable functions"` + } `env-prefix:"FEATURES_" env-description:"features config"` } `env-prefix:"CLIENT_"` IsDebug bool `env:"IS_DEBUG" env-default:"false" env-description:"is debug mode"` Logger struct { DefaultLoggerLevel string `env:"DEFAULT_LOGGER_LEVEL" env-default:"info" env-description:"frp-panel internal default logger level"` FRPLoggerLevel string `env:"FRP_LOGGER_LEVEL" env-default:"info" env-description:"frp logger level"` } `env-prefix:"LOGGER_"` + HTTP_PROXY string `env:"HTTP_PROXY" env-description:"http proxy"` } func NewConfig() Config { @@ -115,6 +130,21 @@ func (cfg *Config) Complete() { if len(cfg.Client.ID) == 0 { cfg.Client.ID = hostname } + + cwd, err := os.Getwd() + if err != nil { + fmt.Println("failed to get current working directory:", err) + os.Exit(1) + } + + if len(cfg.Client.Worker.WorkerdBinaryPath) == 0 { + w, _ := utils.FindExecutableNames(func(name string) bool { + return strings.HasPrefix(name, "workerd") + }, cwd, "/") + if len(w) > 0 { + cfg.Client.Worker.WorkerdBinaryPath = w[0] + } + } } func (cfg Config) PrintStr() string { diff --git a/defs/const.go b/defs/const.go index bc97368..aa63b2c 100644 --- a/defs/const.go +++ b/defs/const.go @@ -68,8 +68,9 @@ const ( ) const ( - PullConfigDuration = 30 * time.Second - PushProxyInfoDuration = 30 * time.Second + PullConfigDuration = 30 * time.Second + PushProxyInfoDuration = 30 * time.Second + PullClientWorkersDuration = 30 * time.Second ) const ( @@ -100,6 +101,50 @@ const ( const ( UserRole_Admin = "admin" UserRole_Normal = "normal" + CapFileName = "workerd.capnp" + WorkerInfoPath = "workers" + WorkerCodePath = "src" + DBTypeSqlite = "sqlite" + + DefaultHostName = "127.0.0.1" + DefaultNodeName = "default" + DefaultExternalPath = "/" + DefaultEntry = "entry.js" + DefaultSocketTemplate = "unix-abstract:/tmp/frpp-worker-%s.sock" + DefaultCode = `export default { + async fetch(req, env) { + try { + let resp = new Response("worker: " + req.url + " is online! -- " + new Date()) + return resp + } catch(e) { + return new Response(e.stack, { status: 500 }) + } + } +};` + + DefaultConfigTemplate = `using Workerd = import "/workerd/workerd.capnp"; + +const config :Workerd.Config = ( + services = [ + (name = "{{.WorkerId}}", worker = .v{{.WorkerId}}Worker), + ], + + sockets = [ + ( + name = "{{.WorkerId}}", + address = "{{.Socket.Address}}", + http=(), + service="{{.WorkerId}}" + ), + ] +); + +const v{{.WorkerId}}Worker :Workerd.Worker = ( + modules = [ + (name = "{{.CodeEntry}}", esModule = embed "src/{{.CodeEntry}}"), + ], + compatibilityDate = "2023-04-03", +);` ) type TokenStatus string @@ -109,3 +154,23 @@ const ( TokenStatusInactive TokenStatus = "inactive" TokenStatusRevoked TokenStatus = "revoked" ) + +const ( + KeyNodeName = "node_name" + KeyNodeSecret = "node_secret" + KeyNodeProto = "node_proto" + KeyWorkerProto = "worker_proto" +) + +type WorkerStatus string + +const ( + WorkerStatus_Unknown WorkerStatus = "unknown" + WorkerStatus_Running WorkerStatus = "running" + WorkerStatus_Inactive WorkerStatus = "inactive" +) + +const ( + FrpProxyAnnotationsKey_Ingress = "ingress" + FrpProxyAnnotationsKey_WorkerId = "worker_id" +) diff --git a/go.mod b/go.mod index eba84e1..1bef844 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.24.1 require ( github.com/UserExistsError/conpty v0.1.4 github.com/casbin/casbin/v2 v2.105.0 - github.com/casbin/gorm-adapter/v3 v3.32.0 + github.com/casbin/gorm-adapter/v3 v3.29.0 github.com/coocood/freecache v1.2.4 github.com/creack/pty v1.1.24 github.com/fatedier/frp v0.62.0 @@ -25,23 +25,26 @@ require ( github.com/jackpal/gateway v1.0.16 github.com/joho/godotenv v1.5.1 github.com/kardianos/service v1.2.2 + github.com/lucasepe/codename v0.2.0 github.com/samber/lo v1.47.0 - github.com/shirou/gopsutil/v4 v4.24.11 + github.com/shirou/gopsutil/v4 v4.25.4 github.com/sirupsen/logrus v1.9.3 github.com/sourcegraph/conc v0.3.0 github.com/spf13/cast v1.7.1 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.10.0 github.com/tidwall/pretty v1.2.1 github.com/tiendc/go-deepcopy v1.2.0 go.uber.org/fx v1.23.0 + go.uber.org/multierr v1.11.0 golang.org/x/crypto v0.37.0 golang.org/x/net v0.39.0 google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.36.5 gorm.io/driver/mysql v1.5.7 gorm.io/driver/postgres v1.5.9 - gorm.io/gorm v1.25.12 + gorm.io/gorm v1.25.11 k8s.io/apimachinery v0.28.8 ) @@ -60,7 +63,7 @@ require ( github.com/coreos/go-oidc/v3 v3.14.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/ebitengine/purego v0.8.1 // indirect + github.com/ebitengine/purego v0.8.2 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect @@ -121,7 +124,6 @@ require ( github.com/robfig/cron/v3 v3.0.1 // indirect github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/stretchr/testify v1.10.0 // indirect github.com/templexxx/cpu v0.1.1 // indirect github.com/templexxx/xorsimd v0.4.3 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect @@ -136,7 +138,6 @@ require ( go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/dig v1.18.1 // indirect go.uber.org/mock v0.5.1 // indirect - go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect @@ -154,7 +155,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/sqlserver v1.5.3 // indirect - gorm.io/plugin/dbresolver v1.5.3 // indirect + gorm.io/plugin/dbresolver v1.5.2 // indirect gvisor.dev/gvisor v0.0.0-20250425231648-60ec4e7a009d // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect modernc.org/libc v1.22.5 // indirect diff --git a/go.sum b/go.sum index 904a663..eb7c491 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/casbin/casbin/v2 v2.105.0 h1:dLj5P6pLApBRat9SADGiLxLZjiDPvA1bsPkyV4PGx6I= github.com/casbin/casbin/v2 v2.105.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco= -github.com/casbin/gorm-adapter/v3 v3.32.0 h1:Au+IOILBIE9clox5BJhI2nA3p9t7Ep1ePlupdGbGfus= -github.com/casbin/gorm-adapter/v3 v3.32.0/go.mod h1:Zre/H8p17mpv5U3EaWgPoxLILLdXO3gHW5aoQQpUDZI= +github.com/casbin/gorm-adapter/v3 v3.29.0 h1:MpbF5JVFYOwVGaTkvwDNUV4k+5CYwAu6v83ofZHRfvM= +github.com/casbin/gorm-adapter/v3 v3.29.0/go.mod h1:C0Ew2tNYtdvDK1f+yEiKZt8XL0fcaurQhOHgxMSBM54= github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc= github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -68,8 +68,8 @@ github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/ github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= -github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -228,6 +228,8 @@ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lucasepe/codename v0.2.0 h1:zkW9mKWSO8jjVIYFyZWE9FPvBtFVJxgMpQcMkf4Vv20= +github.com/lucasepe/codename v0.2.0/go.mod h1:RDcExRuZPWp5Uz+BosvpROFTrxpt5r1vSzBObHdBdDM= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -297,8 +299,8 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= -github.com/shirou/gopsutil/v4 v4.24.11 h1:WaU9xqGFKvFfsUv94SXcUPD7rCkU0vr/asVdQOBZNj8= -github.com/shirou/gopsutil/v4 v4.24.11/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8= +github.com/shirou/gopsutil/v4 v4.25.4 h1:cdtFO363VEOOFrUCjZRh4XVJkb548lyF0q0uTeMqYPw= +github.com/shirou/gopsutil/v4 v4.25.4/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= @@ -372,8 +374,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs= go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -475,7 +477,6 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= @@ -533,6 +534,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= @@ -541,10 +543,10 @@ gorm.io/driver/sqlserver v1.5.3 h1:rjupPS4PVw+rjJkfvr8jn2lJ8BMhT4UW5FwuJY0P3Z0= gorm.io/driver/sqlserver v1.5.3/go.mod h1:B+CZ0/7oFJ6tAlefsKoyxdgDCXJKSgwS2bMOQZT0I00= gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= -gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= -gorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU= -gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE= +gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg= +gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +gorm.io/plugin/dbresolver v1.5.2 h1:Iut7lW4TXNoVs++I+ra3zxjSxTRj4ocIeFEVp4lLhII= +gorm.io/plugin/dbresolver v1.5.2/go.mod h1:jPh59GOQbO7v7v28ZKZPd45tr+u3vyT+8tHdfdfOWcU= gvisor.dev/gvisor v0.0.0-20250425231648-60ec4e7a009d h1:cCKla0V7sa6eixh74LtGQXakTu5QJEzkcX7DzNRhFOE= gvisor.dev/gvisor v0.0.0-20250425231648-60ec4e7a009d/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/idl/api_client.proto b/idl/api_client.proto index 51fe53c..c42877c 100644 --- a/idl/api_client.proto +++ b/idl/api_client.proto @@ -167,3 +167,111 @@ message StartProxyRequest { message StartProxyResponse { optional common.Status status = 1; } + +message CreateWorkerRequest { + optional string client_id = 1; + optional common.Worker worker = 2; +} + +message CreateWorkerResponse { + optional common.Status status = 1; + optional string worker_id = 2; +} + +message RemoveWorkerRequest { + optional string client_id = 1; + optional string worker_id = 2; +} + +message RemoveWorkerResponse { + optional common.Status status = 1; +} + +message UpdateWorkerRequest { + repeated string client_ids = 1; + optional common.Worker worker = 2; +} + +message UpdateWorkerResponse { + optional common.Status status = 1; +} + +message RunWorkerRequest { + optional string client_id = 1; + optional string worker_id = 2; +} + +message RunWorkerResponse { + optional common.Status status = 1; +} + +message StopWorkerRequest { + optional string client_id = 1; + optional string worker_id = 2; +} + +message StopWorkerResponse { + optional common.Status status = 1; +} + +message ListWorkersRequest { + optional int32 page = 1; + optional int32 page_size = 2; + optional string keyword = 3; + optional string client_id = 4; + optional string server_id = 5; +} + +message ListWorkersResponse { + optional common.Status status = 1; + optional int32 total = 2; + repeated common.Worker workers = 3; +} + +// 为 client 在一个 server 创建ingress +message CreateWorkerIngressRequest { + optional string client_id = 1; + optional string server_id = 2; + optional string worker_id = 3; +} + +message CreateWorkerIngressResponse { + optional common.Status status = 1; +} + +message GetWorkerIngressRequest { + optional string worker_id = 1; +} + +message GetWorkerIngressResponse { + optional common.Status status = 1; + repeated common.ProxyConfig proxy_configs = 2; +} + +message GetWorkerRequest { + optional string worker_id = 1; +} + +message GetWorkerResponse { + optional common.Status status = 1; + optional common.Worker worker = 2; + repeated common.Client clients = 3; // worker 已经部署到的 client +} + +message GetWorkerStatusRequest { + optional string worker_id = 1; +} + +message GetWorkerStatusResponse { + optional common.Status status = 1; + map worker_status = 2; // client_id -> status +} + +message InstallWorkerdRequest { + optional string client_id = 1; + optional string download_url = 2; +} + +message InstallWorkerdResponse { + optional common.Status status = 1; +} diff --git a/idl/common.proto b/idl/common.proto index ab466b7..9987317 100644 --- a/idl/common.proto +++ b/idl/common.proto @@ -97,3 +97,25 @@ message ProxyWorkingStatus { optional string err = 4; optional string remote_addr = 5; } + +message Worker { + optional string worker_id = 1; + optional string name = 2; // worker's name, also use at worker routing, must be unique, default is UID + optional uint32 user_id = 3; // worker's user id + optional uint32 tenant_id = 4; + optional Socket socket = 5; // worker's socket, platfrom will obtain free port while init worker + optional string code_entry = 6; // worker's entry file, default is 'entry.js' + optional string code = 7; // worker's code + optional string config_template = 8; // worker's capnp file template +} + +// one WorkerList for one workerd instance +message WorkerList { + repeated Worker workers = 1; + optional string nodename = 2; // workerd runner host name, for HA +} + +message Socket { + optional string name = 1; + optional string address = 2; +} diff --git a/idl/rpc_master.proto b/idl/rpc_master.proto index 1a90e73..43867e5 100644 --- a/idl/rpc_master.proto +++ b/idl/rpc_master.proto @@ -24,6 +24,10 @@ enum Event { EVENT_STOP_STREAM_LOG = 16; EVENT_START_PTY_CONNECT = 17; EVENT_GET_PROXY_INFO = 18; + EVENT_CREATE_WORKER = 19; + EVENT_REMOVE_WORKER = 20; + EVENT_GET_WORKER_STATUS = 21; + EVENT_INSTALL_WORKERD = 22; } message ServerBase { @@ -122,10 +126,20 @@ message PTYServerMessage { bool done = 4; } +message ListClientWorkersRequest { + ClientBase base = 255; +} + +message ListClientWorkersResponse { + common.Status status = 1; + repeated common.Worker workers = 2; +} + service Master { rpc ServerSend(stream ClientMessage) returns(stream ServerMessage); rpc PullClientConfig(PullClientConfigReq) returns(PullClientConfigResp); rpc PullServerConfig(PullServerConfigReq) returns(PullServerConfigResp); + rpc ListClientWorkers(ListClientWorkersRequest) returns(ListClientWorkersResponse); rpc FRPCAuth(FRPAuthRequest) returns(FRPAuthResponse); rpc PushProxyInfo(PushProxyInfoReq) returns(PushProxyInfoResp); rpc PushClientStreamLog(stream PushClientStreamLogReq) returns(PushStreamLogResp); diff --git a/models/client.go b/models/client.go index e93d7e8..e9bc004 100644 --- a/models/client.go +++ b/models/client.go @@ -4,6 +4,7 @@ import ( "encoding/json" "time" + "github.com/VaalaCat/frp-panel/pb" "github.com/VaalaCat/frp-panel/utils" v1 "github.com/fatedier/frp/pkg/config/v1" "github.com/samber/lo" @@ -12,6 +13,7 @@ import ( type Client struct { *ClientEntity + Workers []*Worker `json:"workers,omitempty" gorm:"many2many:worker_clients;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` } type ClientEntity struct { @@ -75,3 +77,22 @@ func (c *ClientEntity) MarshalJSONConfig() ([]byte, error) { } return json.Marshal(cliCfg) } + +func (c *ClientEntity) ToPB() *pb.Client { + resp := &pb.Client{ + Id: &c.ClientID, + Secret: &c.ConnectSecret, + Config: lo.ToPtr(string(c.ConfigContent)), + Comment: &c.Comment, + ServerId: &c.ServerID, + Stopped: &c.Stopped, + OriginClientId: &c.OriginClientID, + FrpsUrl: &c.FrpsUrl, + Ephemeral: &c.Ephemeral, + } + if c.LastSeenAt != nil { + resp.LastSeenAt = lo.ToPtr(c.LastSeenAt.UnixMilli()) + } + + return resp +} diff --git a/models/db.go b/models/db.go index e52b7a8..18fccfb 100644 --- a/models/db.go +++ b/models/db.go @@ -35,6 +35,9 @@ func (dbm *dbManagerImpl) Init() { if err := db.AutoMigrate(&HistoryProxyStats{}); err != nil { logger.Logger(context.Background()).WithError(err).Fatalf("cannot init db table [%s]", (&HistoryProxyStats{}).TableName()) } + if err := db.AutoMigrate(&Worker{}); err != nil { + logger.Logger(context.Background()).WithError(err).Fatalf("cannot init db table [%s]", (&Worker{}).TableName()) + } if err := db.AutoMigrate(&ProxyConfig{}); err != nil { logger.Logger(context.Background()).WithError(err).Fatalf("cannot init db table [%s]", (&ProxyConfig{}).TableName()) } diff --git a/models/proxy_config.go b/models/proxy_config.go index 529158a..4828a15 100644 --- a/models/proxy_config.go +++ b/models/proxy_config.go @@ -3,13 +3,19 @@ package models import ( "fmt" + "github.com/VaalaCat/frp-panel/defs" + "github.com/VaalaCat/frp-panel/pb" v1 "github.com/fatedier/frp/pkg/config/v1" + "github.com/samber/lo" "gorm.io/gorm" ) type ProxyConfig struct { *gorm.Model *ProxyConfigEntity + + WorkerID string `gorm:"type:varchar(255);index"` // 引用的worker + Worker Worker } type ProxyConfigEntity struct { @@ -53,3 +59,36 @@ func (p *ProxyConfigEntity) GetTypedProxyConfig() (v1.TypedProxyConfig, error) { err := cfg.UnmarshalJSON(p.Content) return cfg, err } + +func (p *ProxyConfig) GetTypedProxyConfig() (v1.TypedProxyConfig, error) { + return p.ProxyConfigEntity.GetTypedProxyConfig() +} + +func (p *ProxyConfig) FillClientConfig(cli *ClientEntity) error { + return p.ProxyConfigEntity.FillClientConfig(cli) +} + +func (p *ProxyConfig) FillTypedProxyConfig(cfg v1.TypedProxyConfig) error { + annotations := cfg.GetBaseConfig().Annotations + if len(annotations) > 0 { + if annotations[defs.FrpProxyAnnotationsKey_Ingress] != "" && len(annotations[defs.FrpProxyAnnotationsKey_WorkerId]) > 0 { + workerId := annotations[defs.FrpProxyAnnotationsKey_WorkerId] + p.WorkerID = workerId + } + } + + return p.ProxyConfigEntity.FillTypedProxyConfig(cfg) +} + +func (p *ProxyConfig) ToPB() *pb.ProxyConfig { + return &pb.ProxyConfig{ + Id: lo.ToPtr(uint32(p.ID)), + Name: lo.ToPtr(p.Name), + Type: lo.ToPtr(p.Type), + Config: lo.ToPtr(string(p.Content)), + Stopped: lo.ToPtr(p.Stopped), + ServerId: lo.ToPtr(p.ServerID), + ClientId: lo.ToPtr(p.ClientID), + OriginClientId: lo.ToPtr(p.OriginClientID), + } +} diff --git a/models/worker.go b/models/worker.go new file mode 100644 index 0000000..39a910a --- /dev/null +++ b/models/worker.go @@ -0,0 +1,86 @@ +package models + +import ( + "time" + + "github.com/VaalaCat/frp-panel/pb" + "github.com/samber/lo" + "gorm.io/gorm" +) + +type WorkerModel struct { + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` +} + +type Worker struct { + *WorkerModel + *WorkerEntity + Clients []Client `gorm:"many2many:worker_clients;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` +} + +type WorkerEntity struct { + ID string `gorm:"type:varchar(255);uniqueIndex;not null;primaryKey"` + Name string `gorm:"type:varchar(255);index"` + UserId uint32 `gorm:"index"` + TenantId uint32 `gorm:"index"` + Socket JSON[*pb.Socket] + CodeEntry string + Code string + ConfigTemplate string +} + +func (w *Worker) TableName() string { + return "workers" +} + +func (w *WorkerEntity) FromPB(worker *pb.Worker) *WorkerEntity { + w.ID = worker.GetWorkerId() + w.Name = worker.GetName() + w.UserId = uint32(worker.GetUserId()) + w.TenantId = uint32(worker.GetTenantId()) + w.Socket = JSON[*pb.Socket]{Data: worker.GetSocket()} + w.CodeEntry = worker.GetCodeEntry() + w.Code = worker.GetCode() + w.ConfigTemplate = worker.GetConfigTemplate() + + return w +} + +func (w *WorkerEntity) ToPB() *pb.Worker { + return &pb.Worker{ + WorkerId: lo.ToPtr(w.ID), + Name: lo.ToPtr(w.Name), + UserId: lo.ToPtr(uint32(w.UserId)), + TenantId: lo.ToPtr(uint32(w.TenantId)), + Socket: w.Socket.Data, + CodeEntry: lo.ToPtr(w.CodeEntry), + Code: lo.ToPtr(w.Code), + ConfigTemplate: lo.ToPtr(w.ConfigTemplate), + } +} + +func (w *Worker) FromPB(worker *pb.Worker) *Worker { + if w.WorkerEntity == nil { + w.WorkerEntity = &WorkerEntity{} + } + if w.WorkerModel == nil { + w.WorkerModel = &WorkerModel{} + } + + w.WorkerEntity = w.WorkerEntity.FromPB(worker) + return w +} + +func (w *Worker) ToPB() *pb.Worker { + if w.WorkerEntity == nil { + w.WorkerEntity = &WorkerEntity{} + } + if w.WorkerModel == nil { + w.WorkerModel = &WorkerModel{} + } + + ret := w.WorkerEntity.ToPB() + return ret +} diff --git a/pb/api_client.pb.go b/pb/api_client.pb.go index a532e5b..05f32d5 100644 --- a/pb/api_client.pb.go +++ b/pb/api_client.pb.go @@ -1709,6 +1709,1127 @@ func (x *StartProxyResponse) GetStatus() *Status { return nil } +type CreateWorkerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ClientId *string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3,oneof" json:"client_id,omitempty"` + Worker *Worker `protobuf:"bytes,2,opt,name=worker,proto3,oneof" json:"worker,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateWorkerRequest) Reset() { + *x = CreateWorkerRequest{} + mi := &file_api_client_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateWorkerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateWorkerRequest) ProtoMessage() {} + +func (x *CreateWorkerRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[32] + 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 CreateWorkerRequest.ProtoReflect.Descriptor instead. +func (*CreateWorkerRequest) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{32} +} + +func (x *CreateWorkerRequest) GetClientId() string { + if x != nil && x.ClientId != nil { + return *x.ClientId + } + return "" +} + +func (x *CreateWorkerRequest) GetWorker() *Worker { + if x != nil { + return x.Worker + } + return nil +} + +type CreateWorkerResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status *Status `protobuf:"bytes,1,opt,name=status,proto3,oneof" json:"status,omitempty"` + WorkerId *string `protobuf:"bytes,2,opt,name=worker_id,json=workerId,proto3,oneof" json:"worker_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateWorkerResponse) Reset() { + *x = CreateWorkerResponse{} + mi := &file_api_client_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateWorkerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateWorkerResponse) ProtoMessage() {} + +func (x *CreateWorkerResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[33] + 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 CreateWorkerResponse.ProtoReflect.Descriptor instead. +func (*CreateWorkerResponse) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{33} +} + +func (x *CreateWorkerResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + +func (x *CreateWorkerResponse) GetWorkerId() string { + if x != nil && x.WorkerId != nil { + return *x.WorkerId + } + return "" +} + +type RemoveWorkerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ClientId *string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3,oneof" json:"client_id,omitempty"` + WorkerId *string `protobuf:"bytes,2,opt,name=worker_id,json=workerId,proto3,oneof" json:"worker_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveWorkerRequest) Reset() { + *x = RemoveWorkerRequest{} + mi := &file_api_client_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveWorkerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveWorkerRequest) ProtoMessage() {} + +func (x *RemoveWorkerRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[34] + 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 RemoveWorkerRequest.ProtoReflect.Descriptor instead. +func (*RemoveWorkerRequest) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{34} +} + +func (x *RemoveWorkerRequest) GetClientId() string { + if x != nil && x.ClientId != nil { + return *x.ClientId + } + return "" +} + +func (x *RemoveWorkerRequest) GetWorkerId() string { + if x != nil && x.WorkerId != nil { + return *x.WorkerId + } + return "" +} + +type RemoveWorkerResponse 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 *RemoveWorkerResponse) Reset() { + *x = RemoveWorkerResponse{} + mi := &file_api_client_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveWorkerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveWorkerResponse) ProtoMessage() {} + +func (x *RemoveWorkerResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[35] + 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 RemoveWorkerResponse.ProtoReflect.Descriptor instead. +func (*RemoveWorkerResponse) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{35} +} + +func (x *RemoveWorkerResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + +type UpdateWorkerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ClientIds []string `protobuf:"bytes,1,rep,name=client_ids,json=clientIds,proto3" json:"client_ids,omitempty"` + Worker *Worker `protobuf:"bytes,2,opt,name=worker,proto3,oneof" json:"worker,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateWorkerRequest) Reset() { + *x = UpdateWorkerRequest{} + mi := &file_api_client_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateWorkerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateWorkerRequest) ProtoMessage() {} + +func (x *UpdateWorkerRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[36] + 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 UpdateWorkerRequest.ProtoReflect.Descriptor instead. +func (*UpdateWorkerRequest) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{36} +} + +func (x *UpdateWorkerRequest) GetClientIds() []string { + if x != nil { + return x.ClientIds + } + return nil +} + +func (x *UpdateWorkerRequest) GetWorker() *Worker { + if x != nil { + return x.Worker + } + return nil +} + +type UpdateWorkerResponse 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 *UpdateWorkerResponse) Reset() { + *x = UpdateWorkerResponse{} + mi := &file_api_client_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateWorkerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateWorkerResponse) ProtoMessage() {} + +func (x *UpdateWorkerResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[37] + 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 UpdateWorkerResponse.ProtoReflect.Descriptor instead. +func (*UpdateWorkerResponse) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{37} +} + +func (x *UpdateWorkerResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + +type RunWorkerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ClientId *string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3,oneof" json:"client_id,omitempty"` + WorkerId *string `protobuf:"bytes,2,opt,name=worker_id,json=workerId,proto3,oneof" json:"worker_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RunWorkerRequest) Reset() { + *x = RunWorkerRequest{} + mi := &file_api_client_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RunWorkerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RunWorkerRequest) ProtoMessage() {} + +func (x *RunWorkerRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[38] + 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 RunWorkerRequest.ProtoReflect.Descriptor instead. +func (*RunWorkerRequest) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{38} +} + +func (x *RunWorkerRequest) GetClientId() string { + if x != nil && x.ClientId != nil { + return *x.ClientId + } + return "" +} + +func (x *RunWorkerRequest) GetWorkerId() string { + if x != nil && x.WorkerId != nil { + return *x.WorkerId + } + return "" +} + +type RunWorkerResponse 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 *RunWorkerResponse) Reset() { + *x = RunWorkerResponse{} + mi := &file_api_client_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RunWorkerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RunWorkerResponse) ProtoMessage() {} + +func (x *RunWorkerResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[39] + 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 RunWorkerResponse.ProtoReflect.Descriptor instead. +func (*RunWorkerResponse) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{39} +} + +func (x *RunWorkerResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + +type StopWorkerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ClientId *string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3,oneof" json:"client_id,omitempty"` + WorkerId *string `protobuf:"bytes,2,opt,name=worker_id,json=workerId,proto3,oneof" json:"worker_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StopWorkerRequest) Reset() { + *x = StopWorkerRequest{} + mi := &file_api_client_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StopWorkerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StopWorkerRequest) ProtoMessage() {} + +func (x *StopWorkerRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[40] + 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 StopWorkerRequest.ProtoReflect.Descriptor instead. +func (*StopWorkerRequest) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{40} +} + +func (x *StopWorkerRequest) GetClientId() string { + if x != nil && x.ClientId != nil { + return *x.ClientId + } + return "" +} + +func (x *StopWorkerRequest) GetWorkerId() string { + if x != nil && x.WorkerId != nil { + return *x.WorkerId + } + return "" +} + +type StopWorkerResponse 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 *StopWorkerResponse) Reset() { + *x = StopWorkerResponse{} + mi := &file_api_client_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StopWorkerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StopWorkerResponse) ProtoMessage() {} + +func (x *StopWorkerResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[41] + 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 StopWorkerResponse.ProtoReflect.Descriptor instead. +func (*StopWorkerResponse) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{41} +} + +func (x *StopWorkerResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + +type ListWorkersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Page *int32 `protobuf:"varint,1,opt,name=page,proto3,oneof" json:"page,omitempty"` + PageSize *int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3,oneof" json:"page_size,omitempty"` + Keyword *string `protobuf:"bytes,3,opt,name=keyword,proto3,oneof" json:"keyword,omitempty"` + ClientId *string `protobuf:"bytes,4,opt,name=client_id,json=clientId,proto3,oneof" json:"client_id,omitempty"` + ServerId *string `protobuf:"bytes,5,opt,name=server_id,json=serverId,proto3,oneof" json:"server_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListWorkersRequest) Reset() { + *x = ListWorkersRequest{} + mi := &file_api_client_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListWorkersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListWorkersRequest) ProtoMessage() {} + +func (x *ListWorkersRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[42] + 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 ListWorkersRequest.ProtoReflect.Descriptor instead. +func (*ListWorkersRequest) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{42} +} + +func (x *ListWorkersRequest) GetPage() int32 { + if x != nil && x.Page != nil { + return *x.Page + } + return 0 +} + +func (x *ListWorkersRequest) GetPageSize() int32 { + if x != nil && x.PageSize != nil { + return *x.PageSize + } + return 0 +} + +func (x *ListWorkersRequest) GetKeyword() string { + if x != nil && x.Keyword != nil { + return *x.Keyword + } + return "" +} + +func (x *ListWorkersRequest) GetClientId() string { + if x != nil && x.ClientId != nil { + return *x.ClientId + } + return "" +} + +func (x *ListWorkersRequest) GetServerId() string { + if x != nil && x.ServerId != nil { + return *x.ServerId + } + return "" +} + +type ListWorkersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status *Status `protobuf:"bytes,1,opt,name=status,proto3,oneof" json:"status,omitempty"` + Total *int32 `protobuf:"varint,2,opt,name=total,proto3,oneof" json:"total,omitempty"` + Workers []*Worker `protobuf:"bytes,3,rep,name=workers,proto3" json:"workers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListWorkersResponse) Reset() { + *x = ListWorkersResponse{} + mi := &file_api_client_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListWorkersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListWorkersResponse) ProtoMessage() {} + +func (x *ListWorkersResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[43] + 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 ListWorkersResponse.ProtoReflect.Descriptor instead. +func (*ListWorkersResponse) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{43} +} + +func (x *ListWorkersResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + +func (x *ListWorkersResponse) GetTotal() int32 { + if x != nil && x.Total != nil { + return *x.Total + } + return 0 +} + +func (x *ListWorkersResponse) GetWorkers() []*Worker { + if x != nil { + return x.Workers + } + return nil +} + +// 为 client 在一个 server 创建ingress +type CreateWorkerIngressRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ClientId *string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3,oneof" json:"client_id,omitempty"` + ServerId *string `protobuf:"bytes,2,opt,name=server_id,json=serverId,proto3,oneof" json:"server_id,omitempty"` + WorkerId *string `protobuf:"bytes,3,opt,name=worker_id,json=workerId,proto3,oneof" json:"worker_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateWorkerIngressRequest) Reset() { + *x = CreateWorkerIngressRequest{} + mi := &file_api_client_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateWorkerIngressRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateWorkerIngressRequest) ProtoMessage() {} + +func (x *CreateWorkerIngressRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[44] + 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 CreateWorkerIngressRequest.ProtoReflect.Descriptor instead. +func (*CreateWorkerIngressRequest) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{44} +} + +func (x *CreateWorkerIngressRequest) GetClientId() string { + if x != nil && x.ClientId != nil { + return *x.ClientId + } + return "" +} + +func (x *CreateWorkerIngressRequest) GetServerId() string { + if x != nil && x.ServerId != nil { + return *x.ServerId + } + return "" +} + +func (x *CreateWorkerIngressRequest) GetWorkerId() string { + if x != nil && x.WorkerId != nil { + return *x.WorkerId + } + return "" +} + +type CreateWorkerIngressResponse 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 *CreateWorkerIngressResponse) Reset() { + *x = CreateWorkerIngressResponse{} + mi := &file_api_client_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateWorkerIngressResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateWorkerIngressResponse) ProtoMessage() {} + +func (x *CreateWorkerIngressResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[45] + 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 CreateWorkerIngressResponse.ProtoReflect.Descriptor instead. +func (*CreateWorkerIngressResponse) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{45} +} + +func (x *CreateWorkerIngressResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + +type GetWorkerIngressRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + WorkerId *string `protobuf:"bytes,1,opt,name=worker_id,json=workerId,proto3,oneof" json:"worker_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetWorkerIngressRequest) Reset() { + *x = GetWorkerIngressRequest{} + mi := &file_api_client_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetWorkerIngressRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetWorkerIngressRequest) ProtoMessage() {} + +func (x *GetWorkerIngressRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[46] + 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 GetWorkerIngressRequest.ProtoReflect.Descriptor instead. +func (*GetWorkerIngressRequest) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{46} +} + +func (x *GetWorkerIngressRequest) GetWorkerId() string { + if x != nil && x.WorkerId != nil { + return *x.WorkerId + } + return "" +} + +type GetWorkerIngressResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status *Status `protobuf:"bytes,1,opt,name=status,proto3,oneof" json:"status,omitempty"` + ProxyConfigs []*ProxyConfig `protobuf:"bytes,2,rep,name=proxy_configs,json=proxyConfigs,proto3" json:"proxy_configs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetWorkerIngressResponse) Reset() { + *x = GetWorkerIngressResponse{} + mi := &file_api_client_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetWorkerIngressResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetWorkerIngressResponse) ProtoMessage() {} + +func (x *GetWorkerIngressResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[47] + 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 GetWorkerIngressResponse.ProtoReflect.Descriptor instead. +func (*GetWorkerIngressResponse) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{47} +} + +func (x *GetWorkerIngressResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + +func (x *GetWorkerIngressResponse) GetProxyConfigs() []*ProxyConfig { + if x != nil { + return x.ProxyConfigs + } + return nil +} + +type GetWorkerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + WorkerId *string `protobuf:"bytes,1,opt,name=worker_id,json=workerId,proto3,oneof" json:"worker_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetWorkerRequest) Reset() { + *x = GetWorkerRequest{} + mi := &file_api_client_proto_msgTypes[48] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetWorkerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetWorkerRequest) ProtoMessage() {} + +func (x *GetWorkerRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[48] + 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 GetWorkerRequest.ProtoReflect.Descriptor instead. +func (*GetWorkerRequest) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{48} +} + +func (x *GetWorkerRequest) GetWorkerId() string { + if x != nil && x.WorkerId != nil { + return *x.WorkerId + } + return "" +} + +type GetWorkerResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status *Status `protobuf:"bytes,1,opt,name=status,proto3,oneof" json:"status,omitempty"` + Worker *Worker `protobuf:"bytes,2,opt,name=worker,proto3,oneof" json:"worker,omitempty"` + Clients []*Client `protobuf:"bytes,3,rep,name=clients,proto3" json:"clients,omitempty"` // worker 已经部署到的 client + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetWorkerResponse) Reset() { + *x = GetWorkerResponse{} + mi := &file_api_client_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetWorkerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetWorkerResponse) ProtoMessage() {} + +func (x *GetWorkerResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[49] + 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 GetWorkerResponse.ProtoReflect.Descriptor instead. +func (*GetWorkerResponse) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{49} +} + +func (x *GetWorkerResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + +func (x *GetWorkerResponse) GetWorker() *Worker { + if x != nil { + return x.Worker + } + return nil +} + +func (x *GetWorkerResponse) GetClients() []*Client { + if x != nil { + return x.Clients + } + return nil +} + +type GetWorkerStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + WorkerId *string `protobuf:"bytes,1,opt,name=worker_id,json=workerId,proto3,oneof" json:"worker_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetWorkerStatusRequest) Reset() { + *x = GetWorkerStatusRequest{} + mi := &file_api_client_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetWorkerStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetWorkerStatusRequest) ProtoMessage() {} + +func (x *GetWorkerStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[50] + 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 GetWorkerStatusRequest.ProtoReflect.Descriptor instead. +func (*GetWorkerStatusRequest) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{50} +} + +func (x *GetWorkerStatusRequest) GetWorkerId() string { + if x != nil && x.WorkerId != nil { + return *x.WorkerId + } + return "" +} + +type GetWorkerStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status *Status `protobuf:"bytes,1,opt,name=status,proto3,oneof" json:"status,omitempty"` + WorkerStatus map[string]string `protobuf:"bytes,2,rep,name=worker_status,json=workerStatus,proto3" json:"worker_status,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // client_id -> status + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetWorkerStatusResponse) Reset() { + *x = GetWorkerStatusResponse{} + mi := &file_api_client_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetWorkerStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetWorkerStatusResponse) ProtoMessage() {} + +func (x *GetWorkerStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[51] + 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 GetWorkerStatusResponse.ProtoReflect.Descriptor instead. +func (*GetWorkerStatusResponse) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{51} +} + +func (x *GetWorkerStatusResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + +func (x *GetWorkerStatusResponse) GetWorkerStatus() map[string]string { + if x != nil { + return x.WorkerStatus + } + return nil +} + +type InstallWorkerdRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ClientId *string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3,oneof" json:"client_id,omitempty"` + DownloadUrl *string `protobuf:"bytes,2,opt,name=download_url,json=downloadUrl,proto3,oneof" json:"download_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InstallWorkerdRequest) Reset() { + *x = InstallWorkerdRequest{} + mi := &file_api_client_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InstallWorkerdRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstallWorkerdRequest) ProtoMessage() {} + +func (x *InstallWorkerdRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[52] + 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 InstallWorkerdRequest.ProtoReflect.Descriptor instead. +func (*InstallWorkerdRequest) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{52} +} + +func (x *InstallWorkerdRequest) GetClientId() string { + if x != nil && x.ClientId != nil { + return *x.ClientId + } + return "" +} + +func (x *InstallWorkerdRequest) GetDownloadUrl() string { + if x != nil && x.DownloadUrl != nil { + return *x.DownloadUrl + } + return "" +} + +type InstallWorkerdResponse 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 *InstallWorkerdResponse) Reset() { + *x = InstallWorkerdResponse{} + mi := &file_api_client_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InstallWorkerdResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstallWorkerdResponse) ProtoMessage() {} + +func (x *InstallWorkerdResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_client_proto_msgTypes[53] + 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 InstallWorkerdResponse.ProtoReflect.Descriptor instead. +func (*InstallWorkerdResponse) Descriptor() ([]byte, []int) { + return file_api_client_proto_rawDescGZIP(), []int{53} +} + +func (x *InstallWorkerdResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + var File_api_client_proto protoreflect.FileDescriptor const file_api_client_proto_rawDesc = "" + @@ -1910,6 +3031,128 @@ const file_api_client_proto_rawDesc = "" + "\x05_name\"L\n" + "\x12StartProxyResponse\x12+\n" + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" + + "\a_status\"}\n" + + "\x13CreateWorkerRequest\x12 \n" + + "\tclient_id\x18\x01 \x01(\tH\x00R\bclientId\x88\x01\x01\x12+\n" + + "\x06worker\x18\x02 \x01(\v2\x0e.common.WorkerH\x01R\x06worker\x88\x01\x01B\f\n" + + "\n" + + "_client_idB\t\n" + + "\a_worker\"~\n" + + "\x14CreateWorkerResponse\x12+\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01\x12 \n" + + "\tworker_id\x18\x02 \x01(\tH\x01R\bworkerId\x88\x01\x01B\t\n" + + "\a_statusB\f\n" + + "\n" + + "_worker_id\"u\n" + + "\x13RemoveWorkerRequest\x12 \n" + + "\tclient_id\x18\x01 \x01(\tH\x00R\bclientId\x88\x01\x01\x12 \n" + + "\tworker_id\x18\x02 \x01(\tH\x01R\bworkerId\x88\x01\x01B\f\n" + + "\n" + + "_client_idB\f\n" + + "\n" + + "_worker_id\"N\n" + + "\x14RemoveWorkerResponse\x12+\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" + + "\a_status\"l\n" + + "\x13UpdateWorkerRequest\x12\x1d\n" + + "\n" + + "client_ids\x18\x01 \x03(\tR\tclientIds\x12+\n" + + "\x06worker\x18\x02 \x01(\v2\x0e.common.WorkerH\x00R\x06worker\x88\x01\x01B\t\n" + + "\a_worker\"N\n" + + "\x14UpdateWorkerResponse\x12+\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" + + "\a_status\"r\n" + + "\x10RunWorkerRequest\x12 \n" + + "\tclient_id\x18\x01 \x01(\tH\x00R\bclientId\x88\x01\x01\x12 \n" + + "\tworker_id\x18\x02 \x01(\tH\x01R\bworkerId\x88\x01\x01B\f\n" + + "\n" + + "_client_idB\f\n" + + "\n" + + "_worker_id\"K\n" + + "\x11RunWorkerResponse\x12+\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" + + "\a_status\"s\n" + + "\x11StopWorkerRequest\x12 \n" + + "\tclient_id\x18\x01 \x01(\tH\x00R\bclientId\x88\x01\x01\x12 \n" + + "\tworker_id\x18\x02 \x01(\tH\x01R\bworkerId\x88\x01\x01B\f\n" + + "\n" + + "_client_idB\f\n" + + "\n" + + "_worker_id\"L\n" + + "\x12StopWorkerResponse\x12+\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" + + "\a_status\"\xf1\x01\n" + + "\x12ListWorkersRequest\x12\x17\n" + + "\x04page\x18\x01 \x01(\x05H\x00R\x04page\x88\x01\x01\x12 \n" + + "\tpage_size\x18\x02 \x01(\x05H\x01R\bpageSize\x88\x01\x01\x12\x1d\n" + + "\akeyword\x18\x03 \x01(\tH\x02R\akeyword\x88\x01\x01\x12 \n" + + "\tclient_id\x18\x04 \x01(\tH\x03R\bclientId\x88\x01\x01\x12 \n" + + "\tserver_id\x18\x05 \x01(\tH\x04R\bserverId\x88\x01\x01B\a\n" + + "\x05_pageB\f\n" + + "\n" + + "_page_sizeB\n" + + "\n" + + "\b_keywordB\f\n" + + "\n" + + "_client_idB\f\n" + + "\n" + + "_server_id\"\x9c\x01\n" + + "\x13ListWorkersResponse\x12+\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01\x12\x19\n" + + "\x05total\x18\x02 \x01(\x05H\x01R\x05total\x88\x01\x01\x12(\n" + + "\aworkers\x18\x03 \x03(\v2\x0e.common.WorkerR\aworkersB\t\n" + + "\a_statusB\b\n" + + "\x06_total\"\xac\x01\n" + + "\x1aCreateWorkerIngressRequest\x12 \n" + + "\tclient_id\x18\x01 \x01(\tH\x00R\bclientId\x88\x01\x01\x12 \n" + + "\tserver_id\x18\x02 \x01(\tH\x01R\bserverId\x88\x01\x01\x12 \n" + + "\tworker_id\x18\x03 \x01(\tH\x02R\bworkerId\x88\x01\x01B\f\n" + + "\n" + + "_client_idB\f\n" + + "\n" + + "_server_idB\f\n" + + "\n" + + "_worker_id\"U\n" + + "\x1bCreateWorkerIngressResponse\x12+\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" + + "\a_status\"I\n" + + "\x17GetWorkerIngressRequest\x12 \n" + + "\tworker_id\x18\x01 \x01(\tH\x00R\bworkerId\x88\x01\x01B\f\n" + + "\n" + + "_worker_id\"\x8c\x01\n" + + "\x18GetWorkerIngressResponse\x12+\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01\x128\n" + + "\rproxy_configs\x18\x02 \x03(\v2\x13.common.ProxyConfigR\fproxyConfigsB\t\n" + + "\a_status\"B\n" + + "\x10GetWorkerRequest\x12 \n" + + "\tworker_id\x18\x01 \x01(\tH\x00R\bworkerId\x88\x01\x01B\f\n" + + "\n" + + "_worker_id\"\xad\x01\n" + + "\x11GetWorkerResponse\x12+\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01\x12+\n" + + "\x06worker\x18\x02 \x01(\v2\x0e.common.WorkerH\x01R\x06worker\x88\x01\x01\x12(\n" + + "\aclients\x18\x03 \x03(\v2\x0e.common.ClientR\aclientsB\t\n" + + "\a_statusB\t\n" + + "\a_worker\"H\n" + + "\x16GetWorkerStatusRequest\x12 \n" + + "\tworker_id\x18\x01 \x01(\tH\x00R\bworkerId\x88\x01\x01B\f\n" + + "\n" + + "_worker_id\"\xee\x01\n" + + "\x17GetWorkerStatusResponse\x12+\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01\x12Z\n" + + "\rworker_status\x18\x02 \x03(\v25.api_client.GetWorkerStatusResponse.WorkerStatusEntryR\fworkerStatus\x1a?\n" + + "\x11WorkerStatusEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\t\n" + + "\a_status\"\x80\x01\n" + + "\x15InstallWorkerdRequest\x12 \n" + + "\tclient_id\x18\x01 \x01(\tH\x00R\bclientId\x88\x01\x01\x12&\n" + + "\fdownload_url\x18\x02 \x01(\tH\x01R\vdownloadUrl\x88\x01\x01B\f\n" + + "\n" + + "_client_idB\x0f\n" + + "\r_download_url\"P\n" + + "\x16InstallWorkerdResponse\x12+\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" + "\a_statusB\aZ\x05../pbb\x06proto3" var ( @@ -1924,7 +3167,7 @@ func file_api_client_proto_rawDescGZIP() []byte { return file_api_client_proto_rawDescData } -var file_api_client_proto_msgTypes = make([]protoimpl.MessageInfo, 32) +var file_api_client_proto_msgTypes = make([]protoimpl.MessageInfo, 55) var file_api_client_proto_goTypes = []any{ (*InitClientRequest)(nil), // 0: api_client.InitClientRequest (*InitClientResponse)(nil), // 1: api_client.InitClientResponse @@ -1958,40 +3201,82 @@ var file_api_client_proto_goTypes = []any{ (*StopProxyResponse)(nil), // 29: api_client.StopProxyResponse (*StartProxyRequest)(nil), // 30: api_client.StartProxyRequest (*StartProxyResponse)(nil), // 31: api_client.StartProxyResponse - (*Status)(nil), // 32: common.Status - (*Client)(nil), // 33: common.Client - (*ProxyInfo)(nil), // 34: common.ProxyInfo - (*ProxyConfig)(nil), // 35: common.ProxyConfig - (*ProxyWorkingStatus)(nil), // 36: common.ProxyWorkingStatus + (*CreateWorkerRequest)(nil), // 32: api_client.CreateWorkerRequest + (*CreateWorkerResponse)(nil), // 33: api_client.CreateWorkerResponse + (*RemoveWorkerRequest)(nil), // 34: api_client.RemoveWorkerRequest + (*RemoveWorkerResponse)(nil), // 35: api_client.RemoveWorkerResponse + (*UpdateWorkerRequest)(nil), // 36: api_client.UpdateWorkerRequest + (*UpdateWorkerResponse)(nil), // 37: api_client.UpdateWorkerResponse + (*RunWorkerRequest)(nil), // 38: api_client.RunWorkerRequest + (*RunWorkerResponse)(nil), // 39: api_client.RunWorkerResponse + (*StopWorkerRequest)(nil), // 40: api_client.StopWorkerRequest + (*StopWorkerResponse)(nil), // 41: api_client.StopWorkerResponse + (*ListWorkersRequest)(nil), // 42: api_client.ListWorkersRequest + (*ListWorkersResponse)(nil), // 43: api_client.ListWorkersResponse + (*CreateWorkerIngressRequest)(nil), // 44: api_client.CreateWorkerIngressRequest + (*CreateWorkerIngressResponse)(nil), // 45: api_client.CreateWorkerIngressResponse + (*GetWorkerIngressRequest)(nil), // 46: api_client.GetWorkerIngressRequest + (*GetWorkerIngressResponse)(nil), // 47: api_client.GetWorkerIngressResponse + (*GetWorkerRequest)(nil), // 48: api_client.GetWorkerRequest + (*GetWorkerResponse)(nil), // 49: api_client.GetWorkerResponse + (*GetWorkerStatusRequest)(nil), // 50: api_client.GetWorkerStatusRequest + (*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 } var file_api_client_proto_depIdxs = []int32{ - 32, // 0: api_client.InitClientResponse.status:type_name -> common.Status - 32, // 1: api_client.ListClientsResponse.status:type_name -> common.Status - 33, // 2: api_client.ListClientsResponse.clients:type_name -> common.Client - 32, // 3: api_client.GetClientResponse.status:type_name -> common.Status - 33, // 4: api_client.GetClientResponse.client:type_name -> common.Client - 32, // 5: api_client.DeleteClientResponse.status:type_name -> common.Status - 32, // 6: api_client.UpdateFRPCResponse.status:type_name -> common.Status - 32, // 7: api_client.RemoveFRPCResponse.status:type_name -> common.Status - 32, // 8: api_client.StopFRPCResponse.status:type_name -> common.Status - 32, // 9: api_client.StartFRPCResponse.status:type_name -> common.Status - 32, // 10: api_client.GetProxyStatsByClientIDResponse.status:type_name -> common.Status - 34, // 11: api_client.GetProxyStatsByClientIDResponse.proxy_infos:type_name -> common.ProxyInfo - 32, // 12: api_client.ListProxyConfigsResponse.status:type_name -> common.Status - 35, // 13: api_client.ListProxyConfigsResponse.proxy_configs:type_name -> common.ProxyConfig - 32, // 14: api_client.CreateProxyConfigResponse.status:type_name -> common.Status - 32, // 15: api_client.DeleteProxyConfigResponse.status:type_name -> common.Status - 32, // 16: api_client.UpdateProxyConfigResponse.status:type_name -> common.Status - 32, // 17: api_client.GetProxyConfigResponse.status:type_name -> common.Status - 35, // 18: api_client.GetProxyConfigResponse.proxy_config:type_name -> common.ProxyConfig - 36, // 19: api_client.GetProxyConfigResponse.working_status:type_name -> common.ProxyWorkingStatus - 32, // 20: api_client.StopProxyResponse.status:type_name -> common.Status - 32, // 21: api_client.StartProxyResponse.status:type_name -> common.Status - 22, // [22:22] is the sub-list for method output_type - 22, // [22:22] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 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 } func init() { file_api_client_proto_init() } @@ -2032,13 +3317,35 @@ func file_api_client_proto_init() { file_api_client_proto_msgTypes[29].OneofWrappers = []any{} file_api_client_proto_msgTypes[30].OneofWrappers = []any{} file_api_client_proto_msgTypes[31].OneofWrappers = []any{} + file_api_client_proto_msgTypes[32].OneofWrappers = []any{} + file_api_client_proto_msgTypes[33].OneofWrappers = []any{} + file_api_client_proto_msgTypes[34].OneofWrappers = []any{} + file_api_client_proto_msgTypes[35].OneofWrappers = []any{} + file_api_client_proto_msgTypes[36].OneofWrappers = []any{} + file_api_client_proto_msgTypes[37].OneofWrappers = []any{} + file_api_client_proto_msgTypes[38].OneofWrappers = []any{} + file_api_client_proto_msgTypes[39].OneofWrappers = []any{} + file_api_client_proto_msgTypes[40].OneofWrappers = []any{} + file_api_client_proto_msgTypes[41].OneofWrappers = []any{} + file_api_client_proto_msgTypes[42].OneofWrappers = []any{} + file_api_client_proto_msgTypes[43].OneofWrappers = []any{} + file_api_client_proto_msgTypes[44].OneofWrappers = []any{} + file_api_client_proto_msgTypes[45].OneofWrappers = []any{} + file_api_client_proto_msgTypes[46].OneofWrappers = []any{} + file_api_client_proto_msgTypes[47].OneofWrappers = []any{} + file_api_client_proto_msgTypes[48].OneofWrappers = []any{} + file_api_client_proto_msgTypes[49].OneofWrappers = []any{} + file_api_client_proto_msgTypes[50].OneofWrappers = []any{} + file_api_client_proto_msgTypes[51].OneofWrappers = []any{} + file_api_client_proto_msgTypes[52].OneofWrappers = []any{} + file_api_client_proto_msgTypes[53].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: 32, + NumMessages: 55, NumExtensions: 0, NumServices: 0, }, diff --git a/pb/common.pb.go b/pb/common.pb.go index b9900a5..52dd42b 100644 --- a/pb/common.pb.go +++ b/pb/common.pb.go @@ -871,6 +871,211 @@ func (x *ProxyWorkingStatus) GetRemoteAddr() string { return "" } +type Worker struct { + state protoimpl.MessageState `protogen:"open.v1"` + WorkerId *string `protobuf:"bytes,1,opt,name=worker_id,json=workerId,proto3,oneof" json:"worker_id,omitempty"` + Name *string `protobuf:"bytes,2,opt,name=name,proto3,oneof" json:"name,omitempty"` // worker's name, also use at worker routing, must be unique, default is UID + UserId *uint32 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3,oneof" json:"user_id,omitempty"` // worker's user id + TenantId *uint32 `protobuf:"varint,4,opt,name=tenant_id,json=tenantId,proto3,oneof" json:"tenant_id,omitempty"` + Socket *Socket `protobuf:"bytes,5,opt,name=socket,proto3,oneof" json:"socket,omitempty"` // worker's socket, platfrom will obtain free port while init worker + CodeEntry *string `protobuf:"bytes,6,opt,name=code_entry,json=codeEntry,proto3,oneof" json:"code_entry,omitempty"` // worker's entry file, default is 'entry.js' + Code *string `protobuf:"bytes,7,opt,name=code,proto3,oneof" json:"code,omitempty"` // worker's code + ConfigTemplate *string `protobuf:"bytes,8,opt,name=config_template,json=configTemplate,proto3,oneof" json:"config_template,omitempty"` // worker's capnp file template + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Worker) Reset() { + *x = Worker{} + mi := &file_common_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Worker) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Worker) ProtoMessage() {} + +func (x *Worker) ProtoReflect() protoreflect.Message { + mi := &file_common_proto_msgTypes[9] + 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 Worker.ProtoReflect.Descriptor instead. +func (*Worker) Descriptor() ([]byte, []int) { + return file_common_proto_rawDescGZIP(), []int{9} +} + +func (x *Worker) GetWorkerId() string { + if x != nil && x.WorkerId != nil { + return *x.WorkerId + } + return "" +} + +func (x *Worker) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *Worker) GetUserId() uint32 { + if x != nil && x.UserId != nil { + return *x.UserId + } + return 0 +} + +func (x *Worker) GetTenantId() uint32 { + if x != nil && x.TenantId != nil { + return *x.TenantId + } + return 0 +} + +func (x *Worker) GetSocket() *Socket { + if x != nil { + return x.Socket + } + return nil +} + +func (x *Worker) GetCodeEntry() string { + if x != nil && x.CodeEntry != nil { + return *x.CodeEntry + } + return "" +} + +func (x *Worker) GetCode() string { + if x != nil && x.Code != nil { + return *x.Code + } + return "" +} + +func (x *Worker) GetConfigTemplate() string { + if x != nil && x.ConfigTemplate != nil { + return *x.ConfigTemplate + } + return "" +} + +// one WorkerList for one workerd instance +type WorkerList struct { + state protoimpl.MessageState `protogen:"open.v1"` + Workers []*Worker `protobuf:"bytes,1,rep,name=workers,proto3" json:"workers,omitempty"` + Nodename *string `protobuf:"bytes,2,opt,name=nodename,proto3,oneof" json:"nodename,omitempty"` // workerd runner host name, for HA + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WorkerList) Reset() { + *x = WorkerList{} + mi := &file_common_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WorkerList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkerList) ProtoMessage() {} + +func (x *WorkerList) ProtoReflect() protoreflect.Message { + mi := &file_common_proto_msgTypes[10] + 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 WorkerList.ProtoReflect.Descriptor instead. +func (*WorkerList) Descriptor() ([]byte, []int) { + return file_common_proto_rawDescGZIP(), []int{10} +} + +func (x *WorkerList) GetWorkers() []*Worker { + if x != nil { + return x.Workers + } + return nil +} + +func (x *WorkerList) GetNodename() string { + if x != nil && x.Nodename != nil { + return *x.Nodename + } + return "" +} + +type Socket struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name *string `protobuf:"bytes,1,opt,name=name,proto3,oneof" json:"name,omitempty"` + Address *string `protobuf:"bytes,2,opt,name=address,proto3,oneof" json:"address,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Socket) Reset() { + *x = Socket{} + mi := &file_common_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Socket) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Socket) ProtoMessage() {} + +func (x *Socket) ProtoReflect() protoreflect.Message { + mi := &file_common_proto_msgTypes[11] + 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 Socket.ProtoReflect.Descriptor instead. +func (*Socket) Descriptor() ([]byte, []int) { + return file_common_proto_rawDescGZIP(), []int{11} +} + +func (x *Socket) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *Socket) GetAddress() string { + if x != nil && x.Address != nil { + return *x.Address + } + return "" +} + var File_common_proto protoreflect.FileDescriptor const file_common_proto_rawDesc = "" + @@ -999,7 +1204,39 @@ const file_common_proto_rawDesc = "" + "\x05_typeB\t\n" + "\a_statusB\x06\n" + "\x04_errB\x0e\n" + - "\f_remote_addr*\xbc\x01\n" + + "\f_remote_addr\"\x83\x03\n" + + "\x06Worker\x12 \n" + + "\tworker_id\x18\x01 \x01(\tH\x00R\bworkerId\x88\x01\x01\x12\x17\n" + + "\x04name\x18\x02 \x01(\tH\x01R\x04name\x88\x01\x01\x12\x1c\n" + + "\auser_id\x18\x03 \x01(\rH\x02R\x06userId\x88\x01\x01\x12 \n" + + "\ttenant_id\x18\x04 \x01(\rH\x03R\btenantId\x88\x01\x01\x12+\n" + + "\x06socket\x18\x05 \x01(\v2\x0e.common.SocketH\x04R\x06socket\x88\x01\x01\x12\"\n" + + "\n" + + "code_entry\x18\x06 \x01(\tH\x05R\tcodeEntry\x88\x01\x01\x12\x17\n" + + "\x04code\x18\a \x01(\tH\x06R\x04code\x88\x01\x01\x12,\n" + + "\x0fconfig_template\x18\b \x01(\tH\aR\x0econfigTemplate\x88\x01\x01B\f\n" + + "\n" + + "_worker_idB\a\n" + + "\x05_nameB\n" + + "\n" + + "\b_user_idB\f\n" + + "\n" + + "_tenant_idB\t\n" + + "\a_socketB\r\n" + + "\v_code_entryB\a\n" + + "\x05_codeB\x12\n" + + "\x10_config_template\"d\n" + + "\n" + + "WorkerList\x12(\n" + + "\aworkers\x18\x01 \x03(\v2\x0e.common.WorkerR\aworkers\x12\x1f\n" + + "\bnodename\x18\x02 \x01(\tH\x00R\bnodename\x88\x01\x01B\v\n" + + "\t_nodename\"U\n" + + "\x06Socket\x12\x17\n" + + "\x04name\x18\x01 \x01(\tH\x00R\x04name\x88\x01\x01\x12\x1d\n" + + "\aaddress\x18\x02 \x01(\tH\x01R\aaddress\x88\x01\x01B\a\n" + + "\x05_nameB\n" + + "\n" + + "\b_address*\xbc\x01\n" + "\bRespCode\x12\x19\n" + "\x15RESP_CODE_UNSPECIFIED\x10\x00\x12\x15\n" + "\x11RESP_CODE_SUCCESS\x10\x01\x12\x17\n" + @@ -1027,7 +1264,7 @@ func file_common_proto_rawDescGZIP() []byte { } var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_common_proto_goTypes = []any{ (RespCode)(0), // 0: common.RespCode (ClientType)(0), // 1: common.ClientType @@ -1040,15 +1277,20 @@ var file_common_proto_goTypes = []any{ (*ProxyInfo)(nil), // 8: common.ProxyInfo (*ProxyConfig)(nil), // 9: common.ProxyConfig (*ProxyWorkingStatus)(nil), // 10: common.ProxyWorkingStatus + (*Worker)(nil), // 11: common.Worker + (*WorkerList)(nil), // 12: common.WorkerList + (*Socket)(nil), // 13: common.Socket } var file_common_proto_depIdxs = []int32{ - 0, // 0: common.Status.code:type_name -> common.RespCode - 2, // 1: common.CommonResponse.status:type_name -> common.Status - 2, // [2:2] is the sub-list for method output_type - 2, // [2:2] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 0, // 0: common.Status.code:type_name -> common.RespCode + 2, // 1: common.CommonResponse.status:type_name -> common.Status + 13, // 2: common.Worker.socket:type_name -> common.Socket + 11, // 3: common.WorkerList.workers:type_name -> common.Worker + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_common_proto_init() } @@ -1064,13 +1306,16 @@ func file_common_proto_init() { file_common_proto_msgTypes[6].OneofWrappers = []any{} file_common_proto_msgTypes[7].OneofWrappers = []any{} file_common_proto_msgTypes[8].OneofWrappers = []any{} + file_common_proto_msgTypes[9].OneofWrappers = []any{} + file_common_proto_msgTypes[10].OneofWrappers = []any{} + file_common_proto_msgTypes[11].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)), NumEnums: 2, - NumMessages: 9, + NumMessages: 12, NumExtensions: 0, NumServices: 0, }, diff --git a/pb/rpc_master.pb.go b/pb/rpc_master.pb.go index d6b6456..8dbfdf6 100644 --- a/pb/rpc_master.pb.go +++ b/pb/rpc_master.pb.go @@ -43,6 +43,10 @@ const ( Event_EVENT_STOP_STREAM_LOG Event = 16 Event_EVENT_START_PTY_CONNECT Event = 17 Event_EVENT_GET_PROXY_INFO Event = 18 + Event_EVENT_CREATE_WORKER Event = 19 + Event_EVENT_REMOVE_WORKER Event = 20 + Event_EVENT_GET_WORKER_STATUS Event = 21 + Event_EVENT_INSTALL_WORKERD Event = 22 ) // Enum value maps for Event. @@ -67,6 +71,10 @@ var ( 16: "EVENT_STOP_STREAM_LOG", 17: "EVENT_START_PTY_CONNECT", 18: "EVENT_GET_PROXY_INFO", + 19: "EVENT_CREATE_WORKER", + 20: "EVENT_REMOVE_WORKER", + 21: "EVENT_GET_WORKER_STATUS", + 22: "EVENT_INSTALL_WORKERD", } Event_value = map[string]int32{ "EVENT_UNSPECIFIED": 0, @@ -88,6 +96,10 @@ var ( "EVENT_STOP_STREAM_LOG": 16, "EVENT_START_PTY_CONNECT": 17, "EVENT_GET_PROXY_INFO": 18, + "EVENT_CREATE_WORKER": 19, + "EVENT_REMOVE_WORKER": 20, + "EVENT_GET_WORKER_STATUS": 21, + "EVENT_INSTALL_WORKERD": 22, } ) @@ -1096,6 +1108,102 @@ func (x *PTYServerMessage) GetDone() bool { return false } +type ListClientWorkersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Base *ClientBase `protobuf:"bytes,255,opt,name=base,proto3" json:"base,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListClientWorkersRequest) Reset() { + *x = ListClientWorkersRequest{} + mi := &file_rpc_master_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListClientWorkersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListClientWorkersRequest) ProtoMessage() {} + +func (x *ListClientWorkersRequest) ProtoReflect() protoreflect.Message { + mi := &file_rpc_master_proto_msgTypes[17] + 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 ListClientWorkersRequest.ProtoReflect.Descriptor instead. +func (*ListClientWorkersRequest) Descriptor() ([]byte, []int) { + return file_rpc_master_proto_rawDescGZIP(), []int{17} +} + +func (x *ListClientWorkersRequest) GetBase() *ClientBase { + if x != nil { + return x.Base + } + return nil +} + +type ListClientWorkersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status *Status `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + Workers []*Worker `protobuf:"bytes,2,rep,name=workers,proto3" json:"workers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListClientWorkersResponse) Reset() { + *x = ListClientWorkersResponse{} + mi := &file_rpc_master_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListClientWorkersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListClientWorkersResponse) ProtoMessage() {} + +func (x *ListClientWorkersResponse) ProtoReflect() protoreflect.Message { + mi := &file_rpc_master_proto_msgTypes[18] + 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 ListClientWorkersResponse.ProtoReflect.Descriptor instead. +func (*ListClientWorkersResponse) Descriptor() ([]byte, []int) { + return file_rpc_master_proto_rawDescGZIP(), []int{18} +} + +func (x *ListClientWorkersResponse) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + +func (x *ListClientWorkersResponse) GetWorkers() []*Worker { + if x != nil { + return x.Workers + } + return nil +} + var File_rpc_master_proto protoreflect.FileDescriptor const file_rpc_master_proto_rawDesc = "" + @@ -1172,7 +1280,12 @@ const file_rpc_master_proto_rawDesc = "" + "\x04done\x18\x04 \x01(\bR\x04doneB\a\n" + "\x05_dataB\t\n" + "\a_heightB\b\n" + - "\x06_width*\xb5\x03\n" + + "\x06_width\"C\n" + + "\x18ListClientWorkersRequest\x12'\n" + + "\x04base\x18\xff\x01 \x01(\v2\x12.master.ClientBaseR\x04base\"m\n" + + "\x19ListClientWorkersResponse\x12&\n" + + "\x06status\x18\x01 \x01(\v2\x0e.common.StatusR\x06status\x12(\n" + + "\aworkers\x18\x02 \x03(\v2\x0e.common.WorkerR\aworkers*\x9f\x04\n" + "\x05Event\x12\x15\n" + "\x11EVENT_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15EVENT_REGISTER_CLIENT\x10\x01\x12\x19\n" + @@ -1196,12 +1309,17 @@ const file_rpc_master_proto_rawDesc = "" + "\x16EVENT_START_STREAM_LOG\x10\x0f\x12\x19\n" + "\x15EVENT_STOP_STREAM_LOG\x10\x10\x12\x1b\n" + "\x17EVENT_START_PTY_CONNECT\x10\x11\x12\x18\n" + - "\x14EVENT_GET_PROXY_INFO\x10\x122\xd7\x04\n" + + "\x14EVENT_GET_PROXY_INFO\x10\x12\x12\x17\n" + + "\x13EVENT_CREATE_WORKER\x10\x13\x12\x17\n" + + "\x13EVENT_REMOVE_WORKER\x10\x14\x12\x1b\n" + + "\x17EVENT_GET_WORKER_STATUS\x10\x15\x12\x19\n" + + "\x15EVENT_INSTALL_WORKERD\x10\x162\xb1\x05\n" + "\x06Master\x12>\n" + "\n" + "ServerSend\x12\x15.master.ClientMessage\x1a\x15.master.ServerMessage(\x010\x01\x12M\n" + "\x10PullClientConfig\x12\x1b.master.PullClientConfigReq\x1a\x1c.master.PullClientConfigResp\x12M\n" + - "\x10PullServerConfig\x12\x1b.master.PullServerConfigReq\x1a\x1c.master.PullServerConfigResp\x12;\n" + + "\x10PullServerConfig\x12\x1b.master.PullServerConfigReq\x1a\x1c.master.PullServerConfigResp\x12X\n" + + "\x11ListClientWorkers\x12 .master.ListClientWorkersRequest\x1a!.master.ListClientWorkersResponse\x12;\n" + "\bFRPCAuth\x12\x16.master.FRPAuthRequest\x1a\x17.master.FRPAuthResponse\x12D\n" + "\rPushProxyInfo\x12\x18.master.PushProxyInfoReq\x1a\x19.master.PushProxyInfoResp\x12R\n" + "\x13PushClientStreamLog\x12\x1e.master.PushClientStreamLogReq\x1a\x19.master.PushStreamLogResp(\x01\x12R\n" + @@ -1222,71 +1340,79 @@ func file_rpc_master_proto_rawDescGZIP() []byte { } var file_rpc_master_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_rpc_master_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_rpc_master_proto_msgTypes = make([]protoimpl.MessageInfo, 19) var file_rpc_master_proto_goTypes = []any{ - (Event)(0), // 0: master.Event - (*ServerBase)(nil), // 1: master.ServerBase - (*ClientBase)(nil), // 2: master.ClientBase - (*ServerMessage)(nil), // 3: master.ServerMessage - (*ClientMessage)(nil), // 4: master.ClientMessage - (*PullClientConfigReq)(nil), // 5: master.PullClientConfigReq - (*PullClientConfigResp)(nil), // 6: master.PullClientConfigResp - (*PullServerConfigReq)(nil), // 7: master.PullServerConfigReq - (*PullServerConfigResp)(nil), // 8: master.PullServerConfigResp - (*FRPAuthRequest)(nil), // 9: master.FRPAuthRequest - (*FRPAuthResponse)(nil), // 10: master.FRPAuthResponse - (*PushProxyInfoReq)(nil), // 11: master.PushProxyInfoReq - (*PushProxyInfoResp)(nil), // 12: master.PushProxyInfoResp - (*PushServerStreamLogReq)(nil), // 13: master.PushServerStreamLogReq - (*PushClientStreamLogReq)(nil), // 14: master.PushClientStreamLogReq - (*PushStreamLogResp)(nil), // 15: master.PushStreamLogResp - (*PTYClientMessage)(nil), // 16: master.PTYClientMessage - (*PTYServerMessage)(nil), // 17: master.PTYServerMessage - (*Status)(nil), // 18: common.Status - (*Client)(nil), // 19: common.Client - (*Server)(nil), // 20: common.Server - (*ProxyInfo)(nil), // 21: common.ProxyInfo + (Event)(0), // 0: master.Event + (*ServerBase)(nil), // 1: master.ServerBase + (*ClientBase)(nil), // 2: master.ClientBase + (*ServerMessage)(nil), // 3: master.ServerMessage + (*ClientMessage)(nil), // 4: master.ClientMessage + (*PullClientConfigReq)(nil), // 5: master.PullClientConfigReq + (*PullClientConfigResp)(nil), // 6: master.PullClientConfigResp + (*PullServerConfigReq)(nil), // 7: master.PullServerConfigReq + (*PullServerConfigResp)(nil), // 8: master.PullServerConfigResp + (*FRPAuthRequest)(nil), // 9: master.FRPAuthRequest + (*FRPAuthResponse)(nil), // 10: master.FRPAuthResponse + (*PushProxyInfoReq)(nil), // 11: master.PushProxyInfoReq + (*PushProxyInfoResp)(nil), // 12: master.PushProxyInfoResp + (*PushServerStreamLogReq)(nil), // 13: master.PushServerStreamLogReq + (*PushClientStreamLogReq)(nil), // 14: master.PushClientStreamLogReq + (*PushStreamLogResp)(nil), // 15: master.PushStreamLogResp + (*PTYClientMessage)(nil), // 16: master.PTYClientMessage + (*PTYServerMessage)(nil), // 17: master.PTYServerMessage + (*ListClientWorkersRequest)(nil), // 18: master.ListClientWorkersRequest + (*ListClientWorkersResponse)(nil), // 19: master.ListClientWorkersResponse + (*Status)(nil), // 20: common.Status + (*Client)(nil), // 21: common.Client + (*Server)(nil), // 22: common.Server + (*ProxyInfo)(nil), // 23: common.ProxyInfo + (*Worker)(nil), // 24: common.Worker } var file_rpc_master_proto_depIdxs = []int32{ 0, // 0: master.ServerMessage.event:type_name -> master.Event 0, // 1: master.ClientMessage.event:type_name -> master.Event 2, // 2: master.PullClientConfigReq.base:type_name -> master.ClientBase - 18, // 3: master.PullClientConfigResp.status:type_name -> common.Status - 19, // 4: master.PullClientConfigResp.client:type_name -> common.Client + 20, // 3: master.PullClientConfigResp.status:type_name -> common.Status + 21, // 4: master.PullClientConfigResp.client:type_name -> common.Client 1, // 5: master.PullServerConfigReq.base:type_name -> master.ServerBase - 18, // 6: master.PullServerConfigResp.status:type_name -> common.Status - 20, // 7: master.PullServerConfigResp.server:type_name -> common.Server + 20, // 6: master.PullServerConfigResp.status:type_name -> common.Status + 22, // 7: master.PullServerConfigResp.server:type_name -> common.Server 1, // 8: master.FRPAuthRequest.base:type_name -> master.ServerBase - 18, // 9: master.FRPAuthResponse.status:type_name -> common.Status + 20, // 9: master.FRPAuthResponse.status:type_name -> common.Status 1, // 10: master.PushProxyInfoReq.base:type_name -> master.ServerBase - 21, // 11: master.PushProxyInfoReq.proxy_infos:type_name -> common.ProxyInfo - 18, // 12: master.PushProxyInfoResp.status:type_name -> common.Status + 23, // 11: master.PushProxyInfoReq.proxy_infos:type_name -> common.ProxyInfo + 20, // 12: master.PushProxyInfoResp.status:type_name -> common.Status 1, // 13: master.PushServerStreamLogReq.base:type_name -> master.ServerBase 2, // 14: master.PushClientStreamLogReq.base:type_name -> master.ClientBase - 18, // 15: master.PushStreamLogResp.status:type_name -> common.Status + 20, // 15: master.PushStreamLogResp.status:type_name -> common.Status 1, // 16: master.PTYClientMessage.server_base:type_name -> master.ServerBase 2, // 17: master.PTYClientMessage.client_base:type_name -> master.ClientBase - 4, // 18: master.Master.ServerSend:input_type -> master.ClientMessage - 5, // 19: master.Master.PullClientConfig:input_type -> master.PullClientConfigReq - 7, // 20: master.Master.PullServerConfig:input_type -> master.PullServerConfigReq - 9, // 21: master.Master.FRPCAuth:input_type -> master.FRPAuthRequest - 11, // 22: master.Master.PushProxyInfo:input_type -> master.PushProxyInfoReq - 14, // 23: master.Master.PushClientStreamLog:input_type -> master.PushClientStreamLogReq - 13, // 24: master.Master.PushServerStreamLog:input_type -> master.PushServerStreamLogReq - 16, // 25: master.Master.PTYConnect:input_type -> master.PTYClientMessage - 3, // 26: master.Master.ServerSend:output_type -> master.ServerMessage - 6, // 27: master.Master.PullClientConfig:output_type -> master.PullClientConfigResp - 8, // 28: master.Master.PullServerConfig:output_type -> master.PullServerConfigResp - 10, // 29: master.Master.FRPCAuth:output_type -> master.FRPAuthResponse - 12, // 30: master.Master.PushProxyInfo:output_type -> master.PushProxyInfoResp - 15, // 31: master.Master.PushClientStreamLog:output_type -> master.PushStreamLogResp - 15, // 32: master.Master.PushServerStreamLog:output_type -> master.PushStreamLogResp - 17, // 33: master.Master.PTYConnect:output_type -> master.PTYServerMessage - 26, // [26:34] is the sub-list for method output_type - 18, // [18:26] is the sub-list for method input_type - 18, // [18:18] is the sub-list for extension type_name - 18, // [18:18] is the sub-list for extension extendee - 0, // [0:18] is the sub-list for field type_name + 2, // 18: master.ListClientWorkersRequest.base:type_name -> master.ClientBase + 20, // 19: master.ListClientWorkersResponse.status:type_name -> common.Status + 24, // 20: master.ListClientWorkersResponse.workers:type_name -> common.Worker + 4, // 21: master.Master.ServerSend:input_type -> master.ClientMessage + 5, // 22: master.Master.PullClientConfig:input_type -> master.PullClientConfigReq + 7, // 23: master.Master.PullServerConfig:input_type -> master.PullServerConfigReq + 18, // 24: master.Master.ListClientWorkers:input_type -> master.ListClientWorkersRequest + 9, // 25: master.Master.FRPCAuth:input_type -> master.FRPAuthRequest + 11, // 26: master.Master.PushProxyInfo:input_type -> master.PushProxyInfoReq + 14, // 27: master.Master.PushClientStreamLog:input_type -> master.PushClientStreamLogReq + 13, // 28: master.Master.PushServerStreamLog:input_type -> master.PushServerStreamLogReq + 16, // 29: master.Master.PTYConnect:input_type -> master.PTYClientMessage + 3, // 30: master.Master.ServerSend:output_type -> master.ServerMessage + 6, // 31: master.Master.PullClientConfig:output_type -> master.PullClientConfigResp + 8, // 32: master.Master.PullServerConfig:output_type -> master.PullServerConfigResp + 19, // 33: master.Master.ListClientWorkers:output_type -> master.ListClientWorkersResponse + 10, // 34: master.Master.FRPCAuth:output_type -> master.FRPAuthResponse + 12, // 35: master.Master.PushProxyInfo:output_type -> master.PushProxyInfoResp + 15, // 36: master.Master.PushClientStreamLog:output_type -> master.PushStreamLogResp + 15, // 37: master.Master.PushServerStreamLog:output_type -> master.PushStreamLogResp + 17, // 38: master.Master.PTYConnect:output_type -> master.PTYServerMessage + 30, // [30:39] is the sub-list for method output_type + 21, // [21:30] is the sub-list for method input_type + 21, // [21:21] is the sub-list for extension type_name + 21, // [21:21] is the sub-list for extension extendee + 0, // [0:21] is the sub-list for field type_name } func init() { file_rpc_master_proto_init() } @@ -1306,7 +1432,7 @@ func file_rpc_master_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_rpc_master_proto_rawDesc), len(file_rpc_master_proto_rawDesc)), NumEnums: 1, - NumMessages: 17, + NumMessages: 19, NumExtensions: 0, NumServices: 1, }, diff --git a/pb/rpc_master_grpc.pb.go b/pb/rpc_master_grpc.pb.go index ac7691a..1f4a731 100644 --- a/pb/rpc_master_grpc.pb.go +++ b/pb/rpc_master_grpc.pb.go @@ -22,6 +22,7 @@ const ( Master_ServerSend_FullMethodName = "/master.Master/ServerSend" Master_PullClientConfig_FullMethodName = "/master.Master/PullClientConfig" Master_PullServerConfig_FullMethodName = "/master.Master/PullServerConfig" + Master_ListClientWorkers_FullMethodName = "/master.Master/ListClientWorkers" Master_FRPCAuth_FullMethodName = "/master.Master/FRPCAuth" Master_PushProxyInfo_FullMethodName = "/master.Master/PushProxyInfo" Master_PushClientStreamLog_FullMethodName = "/master.Master/PushClientStreamLog" @@ -36,6 +37,7 @@ type MasterClient interface { ServerSend(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ClientMessage, ServerMessage], error) PullClientConfig(ctx context.Context, in *PullClientConfigReq, opts ...grpc.CallOption) (*PullClientConfigResp, error) PullServerConfig(ctx context.Context, in *PullServerConfigReq, opts ...grpc.CallOption) (*PullServerConfigResp, error) + ListClientWorkers(ctx context.Context, in *ListClientWorkersRequest, opts ...grpc.CallOption) (*ListClientWorkersResponse, error) FRPCAuth(ctx context.Context, in *FRPAuthRequest, opts ...grpc.CallOption) (*FRPAuthResponse, error) PushProxyInfo(ctx context.Context, in *PushProxyInfoReq, opts ...grpc.CallOption) (*PushProxyInfoResp, error) PushClientStreamLog(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[PushClientStreamLogReq, PushStreamLogResp], error) @@ -84,6 +86,16 @@ func (c *masterClient) PullServerConfig(ctx context.Context, in *PullServerConfi return out, nil } +func (c *masterClient) ListClientWorkers(ctx context.Context, in *ListClientWorkersRequest, opts ...grpc.CallOption) (*ListClientWorkersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListClientWorkersResponse) + err := c.cc.Invoke(ctx, Master_ListClientWorkers_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *masterClient) FRPCAuth(ctx context.Context, in *FRPAuthRequest, opts ...grpc.CallOption) (*FRPAuthResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(FRPAuthResponse) @@ -150,6 +162,7 @@ type MasterServer interface { ServerSend(grpc.BidiStreamingServer[ClientMessage, ServerMessage]) error PullClientConfig(context.Context, *PullClientConfigReq) (*PullClientConfigResp, error) PullServerConfig(context.Context, *PullServerConfigReq) (*PullServerConfigResp, error) + ListClientWorkers(context.Context, *ListClientWorkersRequest) (*ListClientWorkersResponse, error) FRPCAuth(context.Context, *FRPAuthRequest) (*FRPAuthResponse, error) PushProxyInfo(context.Context, *PushProxyInfoReq) (*PushProxyInfoResp, error) PushClientStreamLog(grpc.ClientStreamingServer[PushClientStreamLogReq, PushStreamLogResp]) error @@ -174,6 +187,9 @@ func (UnimplementedMasterServer) PullClientConfig(context.Context, *PullClientCo func (UnimplementedMasterServer) PullServerConfig(context.Context, *PullServerConfigReq) (*PullServerConfigResp, error) { return nil, status.Errorf(codes.Unimplemented, "method PullServerConfig not implemented") } +func (UnimplementedMasterServer) ListClientWorkers(context.Context, *ListClientWorkersRequest) (*ListClientWorkersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListClientWorkers not implemented") +} func (UnimplementedMasterServer) FRPCAuth(context.Context, *FRPAuthRequest) (*FRPAuthResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method FRPCAuth not implemented") } @@ -253,6 +269,24 @@ func _Master_PullServerConfig_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _Master_ListClientWorkers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListClientWorkersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MasterServer).ListClientWorkers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Master_ListClientWorkers_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MasterServer).ListClientWorkers(ctx, req.(*ListClientWorkersRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Master_FRPCAuth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(FRPAuthRequest) if err := dec(in); err != nil { @@ -325,6 +359,10 @@ var Master_ServiceDesc = grpc.ServiceDesc{ MethodName: "PullServerConfig", Handler: _Master_PullServerConfig_Handler, }, + { + MethodName: "ListClientWorkers", + Handler: _Master_ListClientWorkers_Handler, + }, { MethodName: "FRPCAuth", Handler: _Master_FRPCAuth_Handler, diff --git a/services/app/app_impl.go b/services/app/app_impl.go index 5eac677..0e53398 100644 --- a/services/app/app_impl.go +++ b/services/app/app_impl.go @@ -12,20 +12,42 @@ type application struct { streamLogHookMgr StreamLogHookMgr masterCli MasterClient - shellPTYMgr ShellPTYMgr - clientLogManager ClientLogManager - clientRPCHandler ClientRPCHandler - dbManager DBManager - clientController ClientController - clientRecvMap *sync.Map - clientsManager ClientsManager - serverHandler ServerHandler - serverController ServerController - rpcCred credentials.TransportCredentials - conf conf.Config - currentRole string - permManager PermissionManager - enforcer *casbin.Enforcer + shellPTYMgr ShellPTYMgr + clientLogManager ClientLogManager + clientRPCHandler ClientRPCHandler + dbManager DBManager + clientController ClientController + clientRecvMap *sync.Map + clientsManager ClientsManager + serverHandler ServerHandler + serverController ServerController + rpcCred credentials.TransportCredentials + conf conf.Config + currentRole string + permManager PermissionManager + enforcer *casbin.Enforcer + workerExecManager WorkerExecManager + workersManager WorkersManager +} + +// GetWorkersManager implements Application. +func (a *application) GetWorkersManager() WorkersManager { + return a.workersManager +} + +// SetWorkersManager implements Application. +func (a *application) SetWorkersManager(w WorkersManager) { + a.workersManager = w +} + +// GetWorkerExecManager implements Application. +func (a *application) GetWorkerExecManager() WorkerExecManager { + return a.workerExecManager +} + +// SetWorkerExecManager implements Application. +func (a *application) SetWorkerExecManager(w WorkerExecManager) { + a.workerExecManager = w } // GetEnforcer implements Application. diff --git a/services/app/application.go b/services/app/application.go index d532c55..21d0ae2 100644 --- a/services/app/application.go +++ b/services/app/application.go @@ -43,6 +43,10 @@ type Application interface { SetEnforcer(*casbin.Enforcer) GetPermManager() PermissionManager SetPermManager(PermissionManager) + GetWorkerExecManager() WorkerExecManager + SetWorkerExecManager(WorkerExecManager) + GetWorkersManager() WorkersManager + SetWorkersManager(WorkersManager) } type Context struct { diff --git a/services/app/helper.go b/services/app/helper.go index d7d47a0..02463fa 100644 --- a/services/app/helper.go +++ b/services/app/helper.go @@ -61,7 +61,7 @@ func WrapperServerMsg[T common.ReqType, U common.RespType](appInstance Applicati cliMsg, err := common.ProtoResp(resp) if err != nil { - logger.Logger(context.Background()).WithError(err).Errorf("cannot marshal") + logger.Logger(context.Background()).WithError(err).Errorf("cannot marshal, may need to add this type to [getEvent] function") return &pb.ClientMessage{ Event: pb.Event_EVENT_ERROR, Data: []byte(err.Error()), diff --git a/services/app/provider.go b/services/app/provider.go index 7b2847b..bd52e1d 100644 --- a/services/app/provider.go +++ b/services/app/provider.go @@ -171,3 +171,30 @@ type PermissionManager interface { RevokeGroupPermission(groupID string, objType defs.RBACObj, objID string, action defs.RBACAction, tenantID int) (bool, error) RevokeUserPermission(userID int, objType defs.RBACObj, objID string, action defs.RBACAction, tenantID int) (bool, error) } + +// services/workerd/exec_manager.go +type WorkerExecManager interface { + RunCmd(workerId string, cwd string, argv []string) + ExitCmd(workerId string) + ExitAllCmd() + UpdateBinaryPath(path string) +} + +// services/workerd/workerd.go +type WorkerController interface { + RunWorker(c *Context) + StopWorker(c *Context) + // GetWorkerStatus(c *Context) defs.WorkerStatus + GarbageCollect() + Init(c *Context) error +} + +// services/workerd/workers_manager.go +type WorkersManager interface { + GetWorker(ctx *Context, id string) (WorkerController, bool) + RunWorker(ctx *Context, id string, worker WorkerController) error + StopWorker(ctx *Context, id string) error + GetWorkerStatus(ctx *Context, id string) (defs.WorkerStatus, error) + // install workerd bin to workerd bin path, if not specified, use default path /usr/local/bin/workerd + InstallWorkerd(ctx *Context, url string, path string) (string, error) +} diff --git a/services/dao/client.go b/services/dao/client.go index 37335f5..087fb02 100644 --- a/services/dao/client.go +++ b/services/dao/client.go @@ -29,7 +29,7 @@ func (q *queryImpl) ValidateClientSecret(clientID, clientSecret string) (*models return c.ClientEntity, nil } -func (q *queryImpl) AdminGetClientByClientID(clientID string) (*models.ClientEntity, error) { +func (q *queryImpl) AdminGetClientByClientID(clientID string) (*models.Client, error) { if clientID == "" { return nil, fmt.Errorf("invalid client id") } @@ -43,10 +43,10 @@ func (q *queryImpl) AdminGetClientByClientID(clientID string) (*models.ClientEnt if err != nil { return nil, err } - return c.ClientEntity, nil + return c, nil } -func (q *queryImpl) GetClientByClientID(userInfo models.UserInfo, clientID string) (*models.ClientEntity, error) { +func (q *queryImpl) GetClientByClientID(userInfo models.UserInfo, clientID string) (*models.Client, error) { if clientID == "" { return nil, fmt.Errorf("invalid client id") } @@ -62,7 +62,22 @@ func (q *queryImpl) GetClientByClientID(userInfo models.UserInfo, clientID strin if err != nil { return nil, err } - return c.ClientEntity, nil + return c, nil +} + +func (q *queryImpl) GetClientsByClientIDs(userInfo models.UserInfo, clientIDs []string) ([]*models.Client, error) { + if len(clientIDs) == 0 { + return nil, fmt.Errorf("invalid client ids") + } + + db := q.ctx.GetApp().GetDBManager().GetDefaultDB() + cs := []*models.Client{} + err := db.Where("client_id IN ?", clientIDs).Find(&cs).Error + if err != nil { + return nil, err + } + + return cs, nil } func (q *queryImpl) GetClientByFilter(userInfo models.UserInfo, client *models.ClientEntity, shadow *bool) (*models.ClientEntity, error) { diff --git a/services/dao/proxy.go b/services/dao/proxy.go index a8a15ae..c787218 100644 --- a/services/dao/proxy.go +++ b/services/dao/proxy.go @@ -548,3 +548,21 @@ func (q *queryImpl) CountProxyConfigsWithFiltersAndKeyword(userInfo models.UserI } return count, nil } + +func (q *queryImpl) GetProxyConfigsByWorkerId(userInfo models.UserInfo, workerID string) ([]*models.ProxyConfig, error) { + db := q.ctx.GetApp().GetDBManager().GetDefaultDB() + items := []*models.ProxyConfig{} + + err := db. + Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{ + UserID: userInfo.GetUserID(), + TenantID: userInfo.GetTenantID(), + }, + WorkerID: workerID, + }). + Find(&items).Error + if err != nil { + return nil, err + } + return items, nil +} diff --git a/services/dao/worker.go b/services/dao/worker.go new file mode 100644 index 0000000..2f30602 --- /dev/null +++ b/services/dao/worker.go @@ -0,0 +1,166 @@ +package dao + +import ( + "fmt" + + "github.com/VaalaCat/frp-panel/models" +) + +func (q *queryImpl) CreateWorker(userInfo models.UserInfo, worker *models.Worker) error { + db := q.ctx.GetApp().GetDBManager().GetDefaultDB() + + worker.UserId = uint32(userInfo.GetUserID()) + worker.TenantId = uint32(userInfo.GetTenantID()) + + worker.WorkerModel = nil + + return db.Create(worker).Error +} + +func (q *queryImpl) DeleteWorker(userInfo models.UserInfo, workerID string) error { + db := q.ctx.GetApp().GetDBManager().GetDefaultDB() + + return db.Unscoped().Where(&models.Worker{ + WorkerEntity: &models.WorkerEntity{ + ID: workerID, + UserId: uint32(userInfo.GetUserID()), + TenantId: uint32(userInfo.GetTenantID()), + }, + }).Delete(&models.Worker{}).Error +} + +func (q *queryImpl) UpdateWorker(userInfo models.UserInfo, worker *models.Worker) error { + if worker.WorkerEntity == nil { + return fmt.Errorf("invalid worker entity") + } + if len(worker.WorkerEntity.ID) == 0 { + return fmt.Errorf("invalid worker id") + } + + db := q.ctx.GetApp().GetDBManager().GetDefaultDB() + + if err := db.Unscoped().Model(&models.Worker{ + WorkerEntity: &models.WorkerEntity{ + ID: worker.ID, + UserId: uint32(userInfo.GetUserID()), + TenantId: uint32(userInfo.GetTenantID()), + }, + }).Association("Clients").Unscoped().Clear(); err != nil { + return err + } + + return db.Where(&models.Worker{ + WorkerEntity: &models.WorkerEntity{ + ID: worker.ID, + UserId: uint32(userInfo.GetUserID()), + TenantId: uint32(userInfo.GetTenantID()), + }, + }).Save(worker).Error +} + +func (q *queryImpl) GetWorkerByWorkerID(userInfo models.UserInfo, workerID string) (*models.Worker, error) { + db := q.ctx.GetApp().GetDBManager().GetDefaultDB() + w := &models.Worker{} + err := db.Where(&models.Worker{ + WorkerEntity: &models.WorkerEntity{ + ID: workerID, + UserId: uint32(userInfo.GetUserID()), + TenantId: uint32(userInfo.GetTenantID()), + }, + }).Preload("Clients").First(w).Error + if err != nil { + return nil, err + } + return w, nil +} + +func (q *queryImpl) ListWorkers(userInfo models.UserInfo, page, pageSize int) ([]*models.Worker, error) { + if page < 1 || pageSize < 1 || pageSize > 100 { + return nil, fmt.Errorf("invalid page or page size") + } + + db := q.ctx.GetApp().GetDBManager().GetDefaultDB() + offset := (page - 1) * pageSize + + var workers []*models.Worker + err := db.Where(&models.Worker{ + WorkerEntity: &models.WorkerEntity{ + UserId: uint32(userInfo.GetUserID()), + TenantId: uint32(userInfo.GetTenantID()), + }, + }).Offset(offset).Limit(pageSize).Preload("Clients").Find(&workers).Error + if err != nil { + return nil, err + } + + return workers, nil +} + +func (q *queryImpl) AdminListWorkersByClientID(clientID string) ([]*models.Worker, error) { + db := q.ctx.GetApp().GetDBManager().GetDefaultDB() + client, err := q.AdminGetClientByClientID(clientID) + if err != nil { + return nil, err + } + + err = db.Model(&client).Preload("Workers").First(&client).Error + if err != nil { + return nil, err + } + + return client.Workers, nil +} + +func (q *queryImpl) ListWorkersWithKeyword(userInfo models.UserInfo, page, pageSize int, keyword string) ([]*models.Worker, error) { + if page < 1 || pageSize < 1 || len(keyword) == 0 || pageSize > 100 { + return nil, fmt.Errorf("invalid page or page size or keyword") + } + + db := q.ctx.GetApp().GetDBManager().GetDefaultDB() + offset := (page - 1) * pageSize + + var workers []*models.Worker + err := db.Where("name like ?", "%"+keyword+"%"). + Where(&models.Worker{ + WorkerEntity: &models.WorkerEntity{ + UserId: uint32(userInfo.GetUserID()), + TenantId: uint32(userInfo.GetTenantID()), + }, + }).Offset(offset).Limit(pageSize).Preload("Clients").Find(&workers).Error + if err != nil { + return nil, err + } + + return workers, nil +} + +func (q *queryImpl) CountWorkers(userInfo models.UserInfo) (int64, error) { + db := q.ctx.GetApp().GetDBManager().GetDefaultDB() + var count int64 + err := db.Model(&models.Worker{}).Where(&models.Worker{ + WorkerEntity: &models.WorkerEntity{ + UserId: uint32(userInfo.GetUserID()), + TenantId: uint32(userInfo.GetTenantID()), + }, + }).Count(&count).Error + if err != nil { + return 0, err + } + return count, nil +} + +func (q *queryImpl) CountWorkersWithKeyword(userInfo models.UserInfo, keyword string) (int64, error) { + db := q.ctx.GetApp().GetDBManager().GetDefaultDB() + var count int64 + err := db.Model(&models.Worker{}).Where("name like ?", "%"+keyword+"%"). + Where(&models.Worker{ + WorkerEntity: &models.WorkerEntity{ + UserId: uint32(userInfo.GetUserID()), + TenantId: uint32(userInfo.GetTenantID()), + }, + }).Count(&count).Error + if err != nil { + return 0, err + } + return count, nil +} diff --git a/services/master/grpc_server.go b/services/master/grpc_server.go index 461a591..ddeb887 100644 --- a/services/master/grpc_server.go +++ b/services/master/grpc_server.go @@ -10,6 +10,7 @@ import ( masterserver "github.com/VaalaCat/frp-panel/biz/master/server" "github.com/VaalaCat/frp-panel/biz/master/shell" "github.com/VaalaCat/frp-panel/biz/master/streamlog" + "github.com/VaalaCat/frp-panel/biz/master/worker" "github.com/VaalaCat/frp-panel/conf" "github.com/VaalaCat/frp-panel/defs" "github.com/VaalaCat/frp-panel/pb" @@ -26,6 +27,21 @@ type server struct { appInstance app.Application } +// 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()) + appCtx := app.NewContext(ctx, s.appInstance) + + if _, err := client.ValidateClientRequest(appCtx, req.GetBase()); err != nil { + logger.Logger(ctx).WithError(err).Errorf("cannot validate client request") + return nil, err + } + + logger.Logger(appCtx).Infof("validate client success, clientID: [%+v]", req.GetBase().GetClientId()) + + return worker.ListClientWorkers(appCtx, req) +} + func newRpcServer(appInstance app.Application, creds credentials.TransportCredentials) *grpc.Server { // s := grpc.NewServer(grpc.Creds(insecure.NewCredentials())) // s := grpc.NewServer(grpc.Creds(creds)) diff --git a/services/port/manager.go b/services/port/manager.go new file mode 100644 index 0000000..5a71675 --- /dev/null +++ b/services/port/manager.go @@ -0,0 +1,48 @@ +package tunnel + +import ( + "context" + + "github.com/VaalaCat/frp-panel/defs" + "github.com/VaalaCat/frp-panel/utils" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +type PortManager interface { + ClaimWorkerPort(c context.Context, workerID string) int32 + GetWorkerPort(c context.Context, workerID string) (int32, bool) +} + +type portManager struct { + portMap *utils.SyncMap[string, int32] +} + +func (p *portManager) ClaimWorkerPort(c context.Context, workerID string) int32 { + port, err := utils.GetAvailablePort(defs.DefaultHostName) + if err != nil { + logger.Logger(c).WithError(err).Panic("get available port failed") + } + p.portMap.Store(workerID, int32(port)) + return int32(port) +} + +func (p *portManager) GetWorkerPort(c context.Context, workerID string) (int32, bool) { + return p.portMap.Load(workerID) +} + +var ( + mgr PortManager +) + +func NewPortManager() PortManager { + return &portManager{ + portMap: &utils.SyncMap[string, int32]{}, + } +} + +func GetPortManager() PortManager { + if mgr == nil { + mgr = NewPortManager() + } + return mgr +} diff --git a/services/workerd/exec_manager.go b/services/workerd/exec_manager.go new file mode 100644 index 0000000..829819e --- /dev/null +++ b/services/workerd/exec_manager.go @@ -0,0 +1,117 @@ +//go:build !windows + +package workerd + +import ( + "context" + "os" + "os/exec" + "syscall" + "time" + + "github.com/VaalaCat/frp-panel/services/app" + "github.com/VaalaCat/frp-panel/utils" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +type workerExecManager struct { + //用于外层循坏的退出 + signMap *utils.SyncMap[string, bool] + //用于执行cancel函数 + chanMap *utils.SyncMap[string, chan struct{}] + // 可执行文件路径 + binaryPath string + // 默认参数 + defaultArgs []string +} + +// var ExecManager *execManager + +func NewExecManager(binPath string, defaultArgs []string) app.WorkerExecManager { + + if len(defaultArgs) == 0 { + defaultArgs = []string{"--watch", "--verbose"} + } + + return &workerExecManager{ + signMap: new(utils.SyncMap[string, bool]), + chanMap: new(utils.SyncMap[string, chan struct{}]), + binaryPath: binPath, + defaultArgs: defaultArgs, + } +} + +func (m *workerExecManager) RunCmd(uid string, cwd string, argv []string) { + ctx := context.Background() + logger.Logger(context.Background()).Infof("start to run command, command id: [%s], argv: %s", uid, utils.MarshalForJson(argv)) + if _, ok := m.chanMap.Load(uid); ok { + logger.Logger(ctx).Infof("command id: [%s] is already running, ignore", uid) + return + } + + c := make(chan struct{}) + m.chanMap.Store(uid, c) + + ctx, cancel := context.WithCancel(context.Background()) + + go func(ctx context.Context, uid string, argv []string, m *workerExecManager) { + defer func(uid string, m *workerExecManager) { + m.signMap.Delete(uid) + }(uid, m) + + logger.Logger(ctx).Infof("command id: [%s] is running!", uid) + + for { + args := []string{} + + args = append(args, m.defaultArgs...) + args = append(args, argv...) + + cmd := exec.CommandContext(ctx, m.binaryPath, args...) + cmd.Dir = cwd + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: false} + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + logger.Logger(ctx).WithError(err).Errorf("command id: [%s] run failed, binary path: [%s], args: %s", uid, m.binaryPath, utils.MarshalForJson(args)) + } + + if exit, ok := m.signMap.Load(uid); ok && exit { + return + } + time.Sleep(3 * time.Second) + } + }(ctx, uid, argv, m) + + go func(cancel context.CancelFunc, uid string, m *workerExecManager) { + defer func(uid string, m *workerExecManager) { + m.chanMap.Delete(uid) + }(uid, m) + + if channel, ok := m.chanMap.Load(uid); ok { + <-channel + m.signMap.Store(uid, true) + cancel() + return + } else { + logger.Logger(ctx).Errorf("command id: [%s] is not running!", uid) + return + } + }(cancel, uid, m) +} + +func (m *workerExecManager) ExitCmd(uid string) { + if channel, ok := m.chanMap.Load(uid); ok { + channel <- struct{}{} + } +} + +func (m *workerExecManager) ExitAllCmd() { + for uid := range m.chanMap.ToMap() { + m.ExitCmd(uid) + } +} + +func (m *workerExecManager) UpdateBinaryPath(path string) { + m.binaryPath = path +} diff --git a/services/workerd/exec_manager_windows.go b/services/workerd/exec_manager_windows.go new file mode 100644 index 0000000..8a742bd --- /dev/null +++ b/services/workerd/exec_manager_windows.go @@ -0,0 +1,40 @@ +//go:build windows + +package workerd + +import ( + "context" + + "github.com/VaalaCat/frp-panel/services/app" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +type workerExecManager struct{} + +// ExitAllCmd implements app.WorkerExecManager. +func (w *workerExecManager) ExitAllCmd() { + ctx := context.Background() + logger.Logger(ctx).Errorf("windows has not implemented functions") +} + +// ExitCmd implements app.WorkerExecManager. +func (w *workerExecManager) ExitCmd(workerId string) { + ctx := context.Background() + logger.Logger(ctx).Errorf("windows has not implemented functions") +} + +// RunCmd implements app.WorkerExecManager. +func (w *workerExecManager) RunCmd(workerId string, cwd string, argv []string) { + ctx := context.Background() + logger.Logger(ctx).Errorf("windows has not implemented functions") +} + +// UpdateBinaryPath implements app.WorkerExecManager. +func (w *workerExecManager) UpdateBinaryPath(path string) { + ctx := context.Background() + logger.Logger(ctx).Errorf("windows has not implemented functions") +} + +func NewExecManager(binPath string, defaultArgs []string) app.WorkerExecManager { + return &workerExecManager{} +} diff --git a/services/workerd/file.go b/services/workerd/file.go new file mode 100644 index 0000000..d96e4bf --- /dev/null +++ b/services/workerd/file.go @@ -0,0 +1,44 @@ +package workerd + +import ( + "context" + "path/filepath" + + "github.com/VaalaCat/frp-panel/defs" + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/utils" +) + +func WriteWorkerCodeToFile(ctx context.Context, worker *pb.Worker, workerdCWD string) error { + return utils.WriteFile( + CodeFilePath(ctx, worker, workerdCWD), + string(worker.GetCode())) +} + +func CodeFilePath(ctx context.Context, worker *pb.Worker, workerdCWD string) string { + return filepath.Join( + WorkerCWDPath(ctx, worker, workerdCWD), + defs.WorkerCodePath, + worker.GetCodeEntry()) +} + +func WorkerCodeRootPath(ctx context.Context, worker *pb.Worker, workerdCWD string) string { + return filepath.Join( + WorkerCWDPath(ctx, worker, workerdCWD), + defs.WorkerCodePath) +} + +func WorkerCWDPath(ctx context.Context, worker *pb.Worker, workerdCWD string) string { + return filepath.Join( + workerdCWD, + defs.WorkerInfoPath, + worker.GetWorkerId(), + ) +} + +func ConfigFilePath(ctx context.Context, worker *pb.Worker, workerdCWD string) string { + return filepath.Join( + WorkerCWDPath(ctx, worker, workerdCWD), + defs.CapFileName, + ) +} diff --git a/services/workerd/helper.go b/services/workerd/helper.go new file mode 100644 index 0000000..a50f71d --- /dev/null +++ b/services/workerd/helper.go @@ -0,0 +1,49 @@ +package workerd + +import ( + "fmt" + "strings" + + "github.com/VaalaCat/frp-panel/defs" + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/utils" + "github.com/samber/lo" +) + +type Opt func(*pb.Worker) + +func FillWorkerValue(worker *pb.Worker, UserID uint, opt ...Opt) { + + worker.UserId = lo.ToPtr(uint32(UserID)) + + if len(worker.GetName()) == 0 { + worker.Name = lo.ToPtr(utils.NewCodeName(2)) + } + + if len(worker.GetCode()) == 0 { + worker.Code = lo.ToPtr(string(defs.DefaultCode)) + } + if len(worker.GetWorkerId()) == 0 { + worker.WorkerId = lo.ToPtr(utils.GenerateUUID()) + } + if len(worker.GetCodeEntry()) == 0 { + worker.CodeEntry = lo.ToPtr(string(defs.DefaultEntry)) + } + if len(worker.GetConfigTemplate()) == 0 { + worker.ConfigTemplate = lo.ToPtr(string(defs.DefaultConfigTemplate)) + } + + worker.Socket = &pb.Socket{ + Name: lo.ToPtr(worker.GetWorkerId()), + Address: lo.ToPtr(fmt.Sprintf(defs.DefaultSocketTemplate, worker.GetWorkerId())), + } + + for _, o := range opt { + o(worker) + } +} + +func SafeWorkerID(id string) string { + replacer := strings.NewReplacer("/", "", ".", "", "-", "") + return replacer.Replace(id) +} diff --git a/services/workerd/workerd.go b/services/workerd/workerd.go new file mode 100644 index 0000000..2d4ff75 --- /dev/null +++ b/services/workerd/workerd.go @@ -0,0 +1,93 @@ +package workerd + +import ( + "context" + "os" + "strings" + + "github.com/VaalaCat/frp-panel/defs" + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/services/app" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +var _ app.WorkerController = (*workerdController)(nil) + +type workerdController struct { + worker *pb.Worker + workerdCwd string + status *defs.WorkerStatus +} + +func NewWorkerdController(worker *pb.Worker, workerdCwd string) *workerdController { + return &workerdController{ + worker: worker, + workerdCwd: workerdCwd, + } +} + +func (w *workerdController) RunWorker(c *app.Context) { + if err := w.Init(c); err != nil { + logger.Logger(c).WithError(err).Errorf("init worker failed, workerId: [%s]", w.worker.GetWorkerId()) + return + } + + execMgr := c.GetApp().GetWorkerExecManager() + execMgr.RunCmd( + w.worker.GetWorkerId(), WorkerCWDPath(c, w.worker, w.workerdCwd), + []string{ConfigFilePath(c, w.worker, w.workerdCwd)}, + ) +} + +func (w *workerdController) StopWorker(c *app.Context) { + execMgr := c.GetApp().GetWorkerExecManager() + execMgr.ExitCmd(w.worker.GetWorkerId()) + w.GarbageCollect() +} + +func (w *workerdController) GetWorkerStatus(c *app.Context) defs.WorkerStatus { + if w.status == nil { + return defs.WorkerStatus_Unknown + } + return *w.status +} + +func (w *workerdController) Init(c *app.Context) error { + workerCodePath := WorkerCodeRootPath(c, w.worker, w.workerdCwd) + + // 1. 创建工作目录 + if err := os.MkdirAll(workerCodePath, os.ModePerm); err != nil { + logger.Logger(c).WithError(err).Errorf("create work dir failed, path: [%s]", workerCodePath) + return err + } + + // 2. 写入配置文件和代码文件 + if err := WriteWorkerCodeToFile(c, w.worker, w.workerdCwd); err != nil { + logger.Logger(c).WithError(err).Errorf("write worker code failed, workerId: [%s]", w.worker.GetWorkerId()) + return err + } + + if err := GenCapnpConfig(c, w.workerdCwd, &pb.WorkerList{Workers: []*pb.Worker{w.worker}}); err != nil { + logger.Logger(c).WithError(err).Errorf("gen worker capnp config failed, workerId: [%s]", w.worker.GetWorkerId()) + return err + } + + logger.Logger(c).Infof("init worker success, workerId: [%s], code path: [%s]", w.worker.GetWorkerId(), workerCodePath) + + return nil +} + +func (w *workerdController) GarbageCollect() { + ctx := context.Background() + + pathToRemove := WorkerCWDPath(ctx, w.worker, w.workerdCwd) + + if !strings.HasPrefix(pathToRemove, "/tmp") { + logger.Logger(ctx).Errorf("path not start with /tmp, do not remove path: [%s]", pathToRemove) + return + } + + if err := os.RemoveAll(pathToRemove); err != nil { + logger.Logger(ctx).WithError(err).Errorf("remove path failed, path: [%s]", pathToRemove) + } +} diff --git a/services/workerd/workerd_conf_gen.go b/services/workerd/workerd_conf_gen.go new file mode 100644 index 0000000..610cd35 --- /dev/null +++ b/services/workerd/workerd_conf_gen.go @@ -0,0 +1,67 @@ +package workerd + +import ( + "bytes" + "errors" + "html/template" + "path/filepath" + + "github.com/VaalaCat/frp-panel/defs" + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/utils" + "github.com/samber/lo" +) + +func BuildCapfile(workers []*pb.Worker) map[string]string { + if len(workers) == 0 { + return map[string]string{} + } + + results := map[string]string{} + for _, worker := range workers { + tmpWorker := &pb.Worker{ + WorkerId: lo.ToPtr(SafeWorkerID(worker.GetWorkerId())), + UserId: lo.ToPtr(worker.GetUserId()), + CodeEntry: lo.ToPtr(worker.GetCodeEntry()), + Socket: &pb.Socket{ + Name: lo.ToPtr(worker.GetWorkerId()), + Address: lo.ToPtr(worker.GetSocket().GetAddress()), + }, + ConfigTemplate: lo.ToPtr(worker.GetConfigTemplate()), + } + + writer := new(bytes.Buffer) + capTemplate := template.New("capfile") + workerTemplate := tmpWorker.GetConfigTemplate() + if workerTemplate == "" { + workerTemplate = defs.DefaultConfigTemplate + } + + capTemplate, err := capTemplate.Parse(workerTemplate) + if err != nil { + panic(err) + } + capTemplate.Execute(writer, tmpWorker) + + results[worker.GetWorkerId()] = writer.String() + } + return results +} + +func GenWorkerConfig(worker *pb.Worker, dir string) error { + if worker == nil || worker.GetWorkerId() == "" { + return errors.New("error worker") + } + fileMap := BuildCapfile([]*pb.Worker{worker}) + + fileContent, ok := fileMap[worker.GetWorkerId()] + if !ok { + return errors.New("BuildCapfile error") + } + + return utils.WriteFile( + filepath.Join( + dir, defs.WorkerInfoPath, + worker.GetWorkerId(), defs.CapFileName, + ), fileContent) +} diff --git a/services/workerd/workerd_conf_gen_all.go b/services/workerd/workerd_conf_gen_all.go new file mode 100644 index 0000000..a027928 --- /dev/null +++ b/services/workerd/workerd_conf_gen_all.go @@ -0,0 +1,37 @@ +package workerd + +import ( + "context" + "errors" + + "github.com/VaalaCat/frp-panel/pb" + "github.com/VaalaCat/frp-panel/utils" + "github.com/VaalaCat/frp-panel/utils/logger" + "github.com/samber/lo" + "github.com/sirupsen/logrus" +) + +func GenCapnpConfig(ctx context.Context, workerdDir string, workerList *pb.WorkerList) error { + var hasError bool + for _, worker := range workerList.Workers { + fileMap := BuildCapfile([]*pb.Worker{worker}) + + if fileContent, ok := fileMap[worker.GetWorkerId()]; ok { + err := utils.WriteFile( + ConfigFilePath(ctx, worker, workerdDir), + fileContent) + if err != nil { + logrus.WithError(err).Errorf("failed to write file, worker is: %+v", worker.Name) + hasError = true + } + } + } + + logger.Logger(ctx).Infof("GenCapnpConfig has error: %v, workerList: %+v", hasError, + lo.SliceToMap(workerList.GetWorkers(), func(w *pb.Worker) (string, bool) { return w.GetWorkerId(), true })) + + if hasError { + return errors.New("GenCapnpConfig has error") + } + return nil +} diff --git a/services/workerd/workerd_conf_gen_test.go b/services/workerd/workerd_conf_gen_test.go new file mode 100644 index 0000000..7de0859 --- /dev/null +++ b/services/workerd/workerd_conf_gen_test.go @@ -0,0 +1,93 @@ +package workerd + +import ( + "testing" + + "github.com/VaalaCat/frp-panel/pb" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" +) + +func TestBuildCapfile(t *testing.T) { + tests := []struct { + name string + wokers []*pb.Worker + expect func(t *testing.T, resp map[string]string) + }{ + { + name: "common case", + wokers: []*pb.Worker{ + { + WorkerId: lo.ToPtr("test"), + CodeEntry: lo.ToPtr("test/entry.js"), + Socket: &pb.Socket{ + Address: lo.ToPtr("unix:/test/test.sock"), + }, + }, + { + WorkerId: lo.ToPtr("test1"), + CodeEntry: lo.ToPtr("test1/entry.js"), + Socket: &pb.Socket{ + Address: lo.ToPtr("unix:/test1/test.sock"), + }, + }, + }, + + expect: func(t *testing.T, result map[string]string) { + assert.Equal(t, + `using Workerd = import "/workerd/workerd.capnp"; + +const config :Workerd.Config = ( + services = [ + (name = "test", worker = .vtestWorker), + ], + + sockets = [ + ( + name = "test", + address = "unix:/test/test.sock", + http=(), + service="test" + ), + ] +); + +const vtestWorker :Workerd.Worker = ( + modules = [ + (name = "test/entry.js", esModule = embed "src/test/entry.js"), + ], + compatibilityDate = "2023-04-03", +);`, result["test"]) + + assert.Equal(t, `using Workerd = import "/workerd/workerd.capnp"; + +const config :Workerd.Config = ( + services = [ + (name = "test1", worker = .vtest1Worker), + ], + + sockets = [ + ( + name = "test1", + address = "unix:/test1/test.sock", + http=(), + service="test1" + ), + ] +); + +const vtest1Worker :Workerd.Worker = ( + modules = [ + (name = "test1/entry.js", esModule = embed "src/test1/entry.js"), + ], + compatibilityDate = "2023-04-03", +);`, result["test1"]) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.expect(t, BuildCapfile(tt.wokers)) + }) + } +} diff --git a/services/workerd/workerd_test.go b/services/workerd/workerd_test.go new file mode 100644 index 0000000..c65f5fa --- /dev/null +++ b/services/workerd/workerd_test.go @@ -0,0 +1,43 @@ +package workerd + +import ( + "context" + "testing" + "time" + + "github.com/VaalaCat/frp-panel/pb" + "github.com/sourcegraph/conc" +) + +func TestRunWorker(t *testing.T) { + workerdCWD := "/home/coder/code/frp-panel/tmp/workerd" + workerID := "test" + workerdBinPath := "/home/coder/go/bin/workerd" + + c := context.Background() + defaultWorker := &pb.Worker{WorkerId: &workerID} + FillWorkerValue(defaultWorker, 1) + + if err := GenCapnpConfig(c, workerdCWD, &pb.WorkerList{Workers: []*pb.Worker{defaultWorker}}); err != nil { + panic(err) + } + + var wg conc.WaitGroup + + wg.Go(func() { + time.Sleep(10 * time.Second) + }) + + if err := WriteWorkerCodeToFile(c, defaultWorker, workerdCWD); err != nil { + panic(err) + } + + runner := NewExecManager(workerdBinPath, + []string{"serve", "--watch", "--verbose"}) + runner.RunCmd(workerID, WorkerCWDPath(c, defaultWorker, workerdCWD), + []string{ConfigFilePath(c, defaultWorker, workerdCWD)}) + + defer runner.ExitAllCmd() + + wg.Wait() +} diff --git a/services/workerd/workers_manager.go b/services/workerd/workers_manager.go new file mode 100644 index 0000000..16438cd --- /dev/null +++ b/services/workerd/workers_manager.go @@ -0,0 +1,126 @@ +package workerd + +import ( + "fmt" + "runtime" + + "github.com/VaalaCat/frp-panel/defs" + "github.com/VaalaCat/frp-panel/services/app" + "github.com/VaalaCat/frp-panel/utils" + "github.com/VaalaCat/frp-panel/utils/logger" +) + +type workersManager struct { + workers *utils.SyncMap[string, app.WorkerController] +} + +func NewWorkersManager() *workersManager { + return &workersManager{ + workers: &utils.SyncMap[string, app.WorkerController]{}, + } +} + +func (m *workersManager) GetWorker(ctx *app.Context, id string) (app.WorkerController, bool) { + return m.workers.Load(id) +} + +func (m *workersManager) RunWorker(ctx *app.Context, id string, worker app.WorkerController) error { + if !ctx.GetApp().GetConfig().Client.Features.EnableFunctions { + logger.Logger(ctx).Errorf("function features are not enabled") + return fmt.Errorf("function features are not enabled") + } + + worker.RunWorker(ctx) + + m.workers.Store(id, worker) + return nil +} + +func (m *workersManager) StopWorker(ctx *app.Context, id string) error { + worker, ok := m.workers.Load(id) + if !ok { + return fmt.Errorf("cannot find worker, id: %s", id) + } + worker.StopWorker(ctx) + m.workers.Delete(id) + return nil +} + +func (m *workersManager) StopAll() { + m.workers.Range(func(k string, v app.WorkerController) bool { + v.StopWorker(nil) + return true + }) + + tmpM := m.workers.ToMap() + for k := range tmpM { + m.workers.Delete(k) + } +} + +func (m *workersManager) GetWorkerStatus(ctx *app.Context, id string) (defs.WorkerStatus, error) { + ok, err := utils.ProcessExistsBySelf(id) + if err != nil { + return defs.WorkerStatus_Unknown, err + } + if ok { + return defs.WorkerStatus_Running, nil + } + return defs.WorkerStatus_Inactive, nil +} + +func (m *workersManager) InstallWorkerd(ctx *app.Context, url string, installDir string) (string, error) { + arch := runtime.GOARCH + os := runtime.GOOS + + workerDownloadCfg := ctx.GetApp().GetConfig().Client.Worker.WorkerdDownloadURL + + if os != "linux" { + return "", fmt.Errorf("unsupported os: %s", os) + } + if arch != "amd64" && arch != "arm64" { + return "", fmt.Errorf("unsupported arch: %s", arch) + } + + downloadUrl := "" + if len(url) > 0 { + downloadUrl = url + } else { + switch arch { + case "amd64": + downloadUrl = workerDownloadCfg.LinuxX8664 + case "arm64": + downloadUrl = workerDownloadCfg.LinuxArm64 + default: + return "", fmt.Errorf("unsupported arch: %s", arch) + } + } + + if workerDownloadCfg.UseProxy { + if len(ctx.GetApp().GetConfig().App.GithubProxyUrl) > 0 { + downloadUrl = fmt.Sprintf("%s/%s", ctx.GetApp().GetConfig().App.GithubProxyUrl, downloadUrl) + } + } + + proxyUrl := ctx.GetApp().GetConfig().HTTP_PROXY + + path, err := utils.DownloadFile(ctx, downloadUrl, proxyUrl) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("failed to download workerd, url: %s", downloadUrl) + return "", err + } + + if len(installDir) == 0 { + installDir = "/usr/local/bin" + } + + finalPath, err := utils.ExtractGZTo(path, "workerd", installDir) + if err != nil { + logger.Logger(ctx).WithError(err).Errorf("failed to extract workerd, path: %s", path) + return "", err + } + + logger.Logger(ctx).Infof("workerd installed successfully, path: %s", finalPath) + + return finalPath, nil +} diff --git a/utils/addr.go b/utils/addr.go new file mode 100644 index 0000000..db29e0c --- /dev/null +++ b/utils/addr.go @@ -0,0 +1,24 @@ +package utils + +import ( + "fmt" + "strings" +) + +func NodeHostPrefix(nodeName, nodeID string) string { + return fmt.Sprintf("%s%s", nodeName, nodeID) +} + +func NodeHost(nodeName, nodeID string, domainSuffix string) string { + suffix := strings.Trim(domainSuffix, ".") + return fmt.Sprintf("%s.%s", NodeHostPrefix(nodeName, nodeID), suffix) +} + +func WorkerHostPrefix(workerName string) string { + return workerName +} + +func WorkerHost(workerName, domainSuffix string) string { + suffix := strings.Trim(domainSuffix, ".") + return fmt.Sprintf("%s.%s", WorkerHostPrefix(workerName), suffix) +} diff --git a/utils/codename.go b/utils/codename.go new file mode 100644 index 0000000..7e7e8ee --- /dev/null +++ b/utils/codename.go @@ -0,0 +1,8 @@ +package utils + +import "github.com/lucasepe/codename" + +func NewCodeName(tokenLength int) string { + rng, _ := codename.DefaultRNG() + return codename.Generate(rng, tokenLength) +} diff --git a/utils/file.go b/utils/file.go new file mode 100644 index 0000000..141904b --- /dev/null +++ b/utils/file.go @@ -0,0 +1,134 @@ +package utils + +import ( + "archive/tar" + "archive/zip" + "bytes" + "errors" + "io" + "log" + "os" + "path/filepath" + "strings" +) + +const ( + httpFileMaxBytes = 100 * (1 << 20) // 50 MB +) + +func WriteFile(path string, content string) error { + if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { + return err + } + f, err := os.Create(path) + if err != nil { + return err + } + _, err = f.WriteString(content) + if err != nil { + return err + } + + return nil +} + +func CreateTarFromZip(zipReader *zip.Reader) ([]byte, error) { + var tarBuffer bytes.Buffer + err := writeTarArchive(&tarBuffer, zipReader) + if err != nil { + return nil, err + } + return tarBuffer.Bytes(), nil +} + +func writeTarArchive(w io.Writer, zipReader *zip.Reader) error { + tarWriter := tar.NewWriter(w) + defer tarWriter.Close() + + for _, file := range zipReader.File { + err := processFileInZipArchive(file, tarWriter) + if err != nil { + return err + } + } + return nil +} + +func processFileInZipArchive(file *zip.File, tarWriter *tar.Writer) error { + fileReader, err := file.Open() + if err != nil { + return err + } + defer fileReader.Close() + + err = tarWriter.WriteHeader(&tar.Header{ + Name: file.Name, + Size: file.FileInfo().Size(), + Mode: int64(file.Mode()), + ModTime: file.Modified, + // Note: Zip archives do not store ownership information. + Uid: 1000, + Gid: 1000, + }) + if err != nil { + return err + } + + n, err := io.CopyN(tarWriter, fileReader, httpFileMaxBytes) + log.Println(file.Name, n, err) + if errors.Is(err, io.EOF) { + err = nil + } + return err +} + +func CreateZipFromTar(tarReader *tar.Reader) ([]byte, error) { + var zipBuffer bytes.Buffer + err := WriteZipArchive(&zipBuffer, tarReader) + if err != nil { + return nil, err + } + return zipBuffer.Bytes(), nil +} + +func WriteZipArchive(w io.Writer, tarReader *tar.Reader) error { + zipWriter := zip.NewWriter(w) + defer zipWriter.Close() + + for { + tarHeader, err := tarReader.Next() + if errors.Is(err, io.EOF) { + break + } + + if err != nil { + return err + } + + zipHeader, err := zip.FileInfoHeader(tarHeader.FileInfo()) + if err != nil { + return err + } + zipHeader.Name = tarHeader.Name + // Some versions of unzip do not check the mode on a file entry and + // simply assume that entries with a trailing path separator (/) are + // directories, and that everything else is a file. Give them a hint. + if tarHeader.FileInfo().IsDir() && !strings.HasSuffix(tarHeader.Name, "/") { + zipHeader.Name += "/" + } + + zipEntry, err := zipWriter.CreateHeader(zipHeader) + if err != nil { + return err + } + + _, err = io.CopyN(zipEntry, tarReader, httpFileMaxBytes) + if errors.Is(err, io.EOF) { + err = nil + } + if err != nil { + return err + } + } + return nil // don't need to flush as we call `writer.Close()` +} diff --git a/utils/files.go b/utils/files.go index 170701a..8914061 100644 --- a/utils/files.go +++ b/utils/files.go @@ -1,8 +1,18 @@ package utils import ( + "compress/gzip" + "context" + "fmt" + "io" + "math/rand" "os" + "path" "path/filepath" + + "github.com/VaalaCat/frp-panel/utils/logger" + "github.com/imroc/req/v3" + "go.uber.org/multierr" ) func EnsureDirectoryExists(filePath string) error { @@ -16,3 +26,171 @@ func EnsureDirectoryExists(filePath string) error { } return nil } + +func FindExecutableNames(filter func(name string) bool, extraPaths ...string) ([]string, error) { + pathEnv := os.Getenv("PATH") + if pathEnv == "" { + return nil, fmt.Errorf("PATH environment variable is empty") + } + + var results []string + seen := make(map[string]struct{}) + var errs error + + pathToCheck := extraPaths + pathToCheck = append(pathToCheck, filepath.SplitList(pathEnv)...) + + for _, dir := range pathToCheck { + entries, err := os.ReadDir(dir) + if err != nil { + // cannot read this directory at all: skip it silently + continue + } + + for _, entry := range entries { + name := entry.Name() + if _, dup := seen[name]; dup { + continue + } + if !filter(name) { + continue + } + + // We've got a candidate name; try to stat it + info, err := entry.Info() + if err != nil { + // record the error for this matching name + errs = multierr.Append(errs, err) + continue + } + + // skip directories or non‐executable + if info.IsDir() || info.Mode()&0111 == 0 { + continue + } + + results = append(results, path.Join(dir, name)) + seen[name] = struct{}{} + } + } + + if len(results) > 0 { + return results, nil + } + if errs != nil { + // return only the aggregated errors + return nil, errs + } + // no matches and no file‐specific errors + return nil, nil + +} + +var TmpFileDir = path.Join(os.TempDir(), "vaala-frp-panel-download") + +// DownloadFile 下载文件到一个临时文件,返回临时文件路径 +func DownloadFile(ctx context.Context, url string, proxyUrl string) (string, error) { + os.MkdirAll(TmpFileDir, 0777) + + tmpPath, err := os.MkdirTemp(TmpFileDir, "downloads") + if err != nil { + return "", err + } + + tmpFileName := generateRandomFileName("download", ".tmp") + fileFullPath := path.Join(tmpPath, tmpFileName) + + cli := req.C() + if len(proxyUrl) > 0 { + cli = cli.SetProxyURL(proxyUrl) + } + + err = cli.NewParallelDownload(url). + SetConcurrency(5). + SetSegmentSize(1024 * 1024 * 1). + SetOutputFile(fileFullPath). + SetFileMode(0777). + SetTempRootDir(path.Join(TmpFileDir, "downloads_cache")). + Do() + if err != nil { + logger.Logger(ctx).WithError(err).Error("download file from url error") + return "", err + } + + return fileFullPath, nil +} + +// generateRandomFileName 生成一个随机文件名 +func generateRandomFileName(prefix, extension string) string { + randomStr := randomString(8) + fileName := fmt.Sprintf("%s_%s%s", prefix, randomStr, extension) + return fileName +} + +// randomString 生成一个指定长度的随机字符串 +func randomString(length int) string { + charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + bytes := make([]byte, length) + for i := range bytes { + bytes[i] = charset[rand.Intn(len(charset))] + } + + return string(bytes) +} + +// ExtractGZTo decompresses the srcGZ file into a temporary directory, +// renames the extracted file to newName, moves it to destDir, and sets executable permissions (0755). +// It returns the full path of the final file on success. +func ExtractGZTo(srcGZ, newName, destDir string) (string, error) { + // 1. Open source .gz file + f, err := os.Open(srcGZ) + if err != nil { + return "", fmt.Errorf("failed to open source gzip file %q: %w", srcGZ, err) + } + defer f.Close() + + // 2. Create gzip reader + zr, err := gzip.NewReader(f) + if err != nil { + return "", fmt.Errorf("failed to create gzip reader for %q: %w", srcGZ, err) + } + defer zr.Close() + + // 3. Create temporary directory + tmpDir, err := os.MkdirTemp("", "vaala-frp-panel-gz_extract_*") + if err != nil { + return "", fmt.Errorf("failed to create temporary directory: %w", err) + } + // Note: tmpDir is not auto-deleted. Caller may clean up if desired. + + // 4. Create the output file in the temp directory with the new name + tmpFilePath := filepath.Join(tmpDir, newName) + outFile, err := os.Create(tmpFilePath) + if err != nil { + return "", fmt.Errorf("failed to create temp file %q: %w", tmpFilePath, err) + } + defer outFile.Close() + + // 5. Decompress into temp file + if _, err := io.Copy(outFile, zr); err != nil { + return "", fmt.Errorf("failed to write decompressed data to %q: %w", tmpFilePath, err) + } + + // 6. Ensure destination directory exists + if err := os.MkdirAll(destDir, 0755); err != nil { + return "", fmt.Errorf("failed to create destination directory %q: %w", destDir, err) + } + + // 7. Move the file to the destination directory + finalPath := filepath.Join(destDir, newName) + if err := os.Rename(tmpFilePath, finalPath); err != nil { + return "", fmt.Errorf("failed to move file to %q: %w", finalPath, err) + } + + // 8. Set executable permission + if err := os.Chmod(finalPath, 0755); err != nil { + return "", fmt.Errorf("failed to set executable permission on %q: %w", finalPath, err) + } + + return finalPath, nil +} diff --git a/utils/port.go b/utils/port.go new file mode 100644 index 0000000..1b87c62 --- /dev/null +++ b/utils/port.go @@ -0,0 +1,52 @@ +package utils + +import ( + "fmt" + "net" + "time" + + "github.com/sirupsen/logrus" +) + +func GetAvailablePort(addr string) (int, error) { + address, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:0", addr)) + if err != nil { + return 0, err + } + + listener, err := net.ListenTCP("tcp", address) + if err != nil { + return 0, err + } + + defer listener.Close() + return listener.Addr().(*net.TCPAddr).Port, nil +} + +func IsPortAvailable(port int, addr string) bool { + + address := fmt.Sprintf("%s:%d", addr, port) + listener, err := net.Listen("tcp", address) + if err != nil { + logrus.Infof("port %s is taken: %s", address, err) + return false + } + + defer listener.Close() + return true +} + +func WaitForPort(host string, port int) { + for { + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port)) + if err == nil { + conn.Close() + break + } + + logrus.Warnf("Target port %s:%d is not open yet, waiting...\n", host, port) + time.Sleep(time.Second * 5) + } + logrus.Infof("Target port %s:%d is open", host, port) + time.Sleep(time.Second * 1) +} diff --git a/utils/process.go b/utils/process.go new file mode 100644 index 0000000..22eaabe --- /dev/null +++ b/utils/process.go @@ -0,0 +1,32 @@ +package utils + +import ( + "os" + "strings" + + "github.com/shirou/gopsutil/v4/process" +) + +func ProcessExistsBySelf(target string) (bool, error) { + selfPID := int32(os.Getpid()) + + procs, err := process.Processes() + if err != nil { + return false, err + } + + for _, p := range procs { + ppid, err := p.Ppid() + if err != nil || ppid != selfPID { + continue + } + cmdline, err := p.Cmdline() + if err != nil { + continue + } + if strings.Contains(cmdline, target) { + return true, nil + } + } + return false, nil +} diff --git a/utils/sync.go b/utils/sync.go index 013a05a..f8b8dfc 100644 --- a/utils/sync.go +++ b/utils/sync.go @@ -180,3 +180,16 @@ func (s *SyncMap[K, V]) Values() []V { return values } + +func (s *SyncMap[K, V]) ToMap() map[K]V { + s.mu.Lock() + defer s.mu.Unlock() + + var m = make(map[K]V, len(s.m)) + + for k, v := range s.m { + m[k] = v + } + + return m +} diff --git a/utils/uuid.go b/utils/uuid.go new file mode 100644 index 0000000..d3cd33f --- /dev/null +++ b/utils/uuid.go @@ -0,0 +1,15 @@ +package utils + +import ( + "strings" + + "github.com/google/uuid" +) + +func GenerateUUIDWithoutSeperator() string { + return strings.Replace(uuid.New().String(), "-", "", -1) +} + +func GenerateUUID() string { + return uuid.New().String() +} diff --git a/www/api/worker.ts b/www/api/worker.ts new file mode 100644 index 0000000..0f03de5 --- /dev/null +++ b/www/api/worker.ts @@ -0,0 +1,69 @@ +import http from '@/api/http' +import { API_PATH } from '@/lib/consts' +import { + CreateWorkerIngressRequest, + CreateWorkerIngressResponse, + CreateWorkerRequest, + CreateWorkerResponse, + GetWorkerIngressRequest, + GetWorkerIngressResponse, + GetWorkerRequest, + GetWorkerResponse, + GetWorkerStatusRequest, + GetWorkerStatusResponse, + InstallWorkerdRequest, + InstallWorkerdResponse, + ListWorkersRequest, + ListWorkersResponse, + RemoveWorkerRequest, + RemoveWorkerResponse, + UpdateWorkerRequest, + UpdateWorkerResponse, +} from '@/lib/pb/api_client' +import { BaseResponse } from '@/types/api' +import { constants } from 'node:buffer' + +export const getWorker = async (req: GetWorkerRequest) => { + const res = await http.post(API_PATH + '/worker/get', GetWorkerRequest.toJson(req)) + return GetWorkerResponse.fromJson((res.data as BaseResponse).body) +} + +export const createWorker = async (req: CreateWorkerRequest) => { + const res = await http.post(API_PATH + '/worker/create', CreateWorkerRequest.toJson(req)) + return CreateWorkerResponse.fromJson((res.data as BaseResponse).body) +} + +export const updateWorker = async (req: UpdateWorkerRequest) => { + const res = await http.post(API_PATH + '/worker/update', UpdateWorkerRequest.toJson(req)) + return UpdateWorkerResponse.fromJson((res.data as BaseResponse).body) +} + +export const removeWorker = async (req: RemoveWorkerRequest) => { + const res = await http.post(API_PATH + '/worker/remove', RemoveWorkerRequest.toJson(req)) + return RemoveWorkerResponse.fromJson((res.data as BaseResponse).body) +} + +export const listWorkers = async (req: ListWorkersRequest) => { + const res = await http.post(API_PATH + '/worker/list', ListWorkersRequest.toJson(req)) + return ListWorkersResponse.fromJson((res.data as BaseResponse).body) +} + +export const createWorkerIngress = async (req: CreateWorkerIngressRequest) => { + const res = await http.post(API_PATH + '/worker/create_ingress', CreateWorkerIngressRequest.toJson(req)) + return CreateWorkerIngressResponse.fromJson((res.data as BaseResponse).body) +} + +export const getWorkerIngress = async (req: GetWorkerIngressRequest) => { + const res = await http.post(API_PATH + '/worker/get_ingress', GetWorkerIngressRequest.toJson(req)) + return GetWorkerIngressResponse.fromJson((res.data as BaseResponse).body) +} + +export const getWorkerStatus = async (req: GetWorkerStatusRequest) => { + const res = await http.post(API_PATH + '/worker/status', GetWorkerStatusRequest.toJson(req)) + return GetWorkerStatusResponse.fromJson((res.data as BaseResponse).body) +} + +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) +} diff --git a/www/components/base/client-selector.tsx b/www/components/base/client-selector.tsx index 92bd8e6..9deaaa5 100644 --- a/www/components/base/client-selector.tsx +++ b/www/components/base/client-selector.tsx @@ -1,24 +1,24 @@ -"use client" +'use client' import React from 'react' import { keepPreviousData, useQuery } from '@tanstack/react-query' import { listClient } from '@/api/client' import { Combobox } from './combobox' import { useTranslation } from 'react-i18next' +import { Client } from '@/lib/pb/common' export interface ClientSelectorProps { clientID?: string setClientID: (clientID: string) => void + clients?: Client[] onOpenChange?: () => void } -export const ClientSelector: React.FC = ({ - clientID, - setClientID, - onOpenChange -}) => { +export const ClientSelector: React.FC = ({ clientID, setClientID, clients, onOpenChange }) => { const { t } = useTranslation() - const handleClientChange = (value: string) => { setClientID(value) } + const handleClientChange = (value: string) => { + setClientID(value) + } const [keyword, setKeyword] = React.useState('') const { data: clientList, refetch: refetchClients } = useQuery({ @@ -27,15 +27,23 @@ export const ClientSelector: React.FC = ({ return listClient({ page: 1, pageSize: 8, keyword: keyword }) }, placeholderData: keepPreviousData, + enabled: clients === undefined, }) return ( ({ - value: client.id || '', - label: client.id || '' - })) || []} + dataList={ + clients !== undefined + ? clients.map((client) => ({ + value: client.id || '', + label: client.id || '', + })) + : clientList?.clients.map((client) => ({ + value: client.id || '', + label: client.id || '', + })) || [] + } setValue={handleClientChange} value={clientID} onKeyWordChange={setKeyword} diff --git a/www/components/base/client_detail.tsx b/www/components/base/client_detail.tsx index 81878b8..2cd8398 100644 --- a/www/components/base/client_detail.tsx +++ b/www/components/base/client_detail.tsx @@ -1,11 +1,11 @@ -"use client" +'use client' -import { Popover, PopoverTrigger } from "@radix-ui/react-popover" -import { Badge } from "../ui/badge" -import { ClientStatus } from "@/lib/pb/api_master" -import { PopoverContent } from "../ui/popover" -import { useTranslation } from "react-i18next" -import { motion } from "framer-motion" +import { Popover, PopoverTrigger } from '@radix-ui/react-popover' +import { Badge } from '../ui/badge' +import { ClientStatus } from '@/lib/pb/api_master' +import { PopoverContent } from '../ui/popover' +import { useTranslation } from 'react-i18next' +import { motion } from 'framer-motion' import { formatDistanceToNow } from 'date-fns' import { zhCN, enUS } from 'date-fns/locale' @@ -13,31 +13,26 @@ export const ClientDetail = ({ clientStatus }: { clientStatus: ClientStatus }) = const { t, i18n } = useTranslation() const locale = i18n.language === 'zh' ? zhCN : enUS - const connectTime = clientStatus.connectTime ? - formatDistanceToNow(new Date(parseInt(clientStatus.connectTime.toString())), { - addSuffix: true, - locale - }) : '-' + const connectTime = clientStatus.connectTime + ? formatDistanceToNow(new Date(parseInt(clientStatus.connectTime.toString())), { + addSuffix: true, + locale, + }) + : '-' return ( - - + {clientStatus.version?.gitVersion || 'Unknown'} - -

- {t('client.detail.title')} -

+ +

{t('client.detail.title')}

{t('client.detail.version')} @@ -68,4 +63,4 @@ export const ClientDetail = ({ clientStatus }: { clientStatus: ClientStatus }) = ) -} \ No newline at end of file +} diff --git a/www/components/base/monaco.tsx b/www/components/base/monaco.tsx new file mode 100644 index 0000000..d6fe96f --- /dev/null +++ b/www/components/base/monaco.tsx @@ -0,0 +1,26 @@ +'use client' + +import React, { useLayoutEffect } from 'react' +import Editor, { loader } from '@monaco-editor/react' + +loader.config({ + paths: { + vs: 'https://fastly.jsdelivr.net/npm/monaco-editor@0.36.1/min/vs', + }, +}) + +export interface WorkerEditorProps { + code: string + onChange: (value: string) => void +} + +export function WorkerEditor({ code, onChange }: WorkerEditorProps) { + // 强制客户端渲染 + useLayoutEffect(() => {}, []) + + return ( +
+ onChange(v ?? '')} /> +
+ ) +} diff --git a/www/components/frpc/proxy_form.tsx b/www/components/frpc/proxy_form.tsx index 90e80df..028a13d 100644 --- a/www/components/frpc/proxy_form.tsx +++ b/www/components/frpc/proxy_form.tsx @@ -146,7 +146,7 @@ export const TCPProxyForm: React.FC = ({ const [pluginConfig, setPluginConfig] = useState(defaultConfig.plugin) const onSubmit = async (values: z.infer) => { - const cfgToSubmit = { ...values, plugin: pluginConfig, type: 'tcp', name: proxyName } as TCPProxyConfig + const cfgToSubmit = { ...defaultConfig, ...values, plugin: pluginConfig, type: 'tcp', name: proxyName } as TCPProxyConfig if (!TypedProxyConfigValid(cfgToSubmit)) { toast.error('Invalid configuration') return @@ -252,7 +252,7 @@ export const STCPProxyForm: React.FC = ({ const [pluginConfig, setPluginConfig] = useState(defaultConfig.plugin) const onSubmit = async (values: z.infer) => { - const cfgToSubmit = { ...values, plugin: pluginConfig, type: 'stcp', name: proxyName } as STCPProxyConfig + const cfgToSubmit = { ...defaultConfig, ...values, plugin: pluginConfig, type: 'stcp', name: proxyName } as STCPProxyConfig if (!TypedProxyConfigValid(cfgToSubmit)) { toast.error('Invalid configuration') return @@ -334,7 +334,7 @@ export const UDPProxyForm: React.FC = ({ const [pluginConfig, setPluginConfig] = useState(defaultConfig.plugin) const onSubmit = async (values: z.infer) => { - const cfgToSubmit = { ...values, plugin: pluginConfig, type: 'udp', name: proxyName } as UDPProxyConfig + const cfgToSubmit = { ...defaultConfig, ...values, plugin: pluginConfig, type: 'udp', name: proxyName } as UDPProxyConfig if (!TypedProxyConfigValid(cfgToSubmit)) { toast.error('Invalid configuration') return @@ -442,7 +442,7 @@ export const HTTPProxyForm: React.FC = ({ const [pluginConfig, setPluginConfig] = useState(defaultConfig.plugin) const onSubmit = async (values: z.infer) => { - const cfgToSubmit = { ...values, plugin: pluginConfig, type: 'http', name: proxyName } as HTTPProxyConfig + const cfgToSubmit = { ...defaultConfig, ...values, plugin: pluginConfig, type: 'http', name: proxyName } as HTTPProxyConfig if (!TypedProxyConfigValid(cfgToSubmit)) { toast.error('Invalid configuration') return diff --git a/www/components/proxy/mutate_proxy_config.tsx b/www/components/proxy/mutate_proxy_config.tsx index 52b1107..eab5c5d 100644 --- a/www/components/proxy/mutate_proxy_config.tsx +++ b/www/components/proxy/mutate_proxy_config.tsx @@ -1,4 +1,4 @@ -"use client" +'use client' import { useEffect, useState } from 'react' import { useMutation } from '@tanstack/react-query' @@ -33,6 +33,7 @@ export type ProxyConfigMutateDialogProps = { defaultProxyConfig?: TypedProxyConfig defaultOriginalProxyConfig?: ProxyConfig disableChangeProxyName?: boolean + onSuccess?: () => void } export const ProxyConfigMutateDialog = ({ ...props }: ProxyConfigMutateDialogProps) => { @@ -41,16 +42,14 @@ export const ProxyConfigMutateDialog = ({ ...props }: ProxyConfigMutateDialogPro return ( - - + {t('proxy.config.create_proxy')} - - {t('proxy.config.create_proxy_description')} - + {t('proxy.config.create_proxy_description')} @@ -58,7 +57,13 @@ export const ProxyConfigMutateDialog = ({ ...props }: ProxyConfigMutateDialogPro ) } -export const ProxyConfigMutateForm = ({ overwrite, defaultProxyConfig, defaultOriginalProxyConfig, disableChangeProxyName }: ProxyConfigMutateDialogProps) => { +export const ProxyConfigMutateForm = ({ + overwrite, + defaultProxyConfig, + defaultOriginalProxyConfig, + disableChangeProxyName, + onSuccess, +}: ProxyConfigMutateDialogProps) => { const { t } = useTranslation() const [newClientID, setNewClientID] = useState() const [newServerID, setNewServerID] = useState() @@ -66,28 +71,30 @@ export const ProxyConfigMutateForm = ({ overwrite, defaultProxyConfig, defaultOr const [proxyName, setProxyName] = useState('') const [proxyType, setProxyType] = useState('http') const [selectedServer, setSelectedServer] = useState() - const supportedProxyTypes: ProxyType[] = ["http", "tcp", "udp"] + const supportedProxyTypes: ProxyType[] = ['http', 'tcp', 'udp'] const createProxyConfigMutation = useMutation({ mutationKey: ['createProxyConfig', newClientID, newServerID], - mutationFn: () => createProxyConfig({ - clientId: newClientID!, - serverId: newServerID!, - config: ObjToUint8Array({ - proxies: proxyConfigs - } as ClientConfig), - overwrite, - }), + mutationFn: () => + createProxyConfig({ + clientId: newClientID!, + serverId: newServerID!, + config: ObjToUint8Array({ + proxies: proxyConfigs, + } as ClientConfig), + overwrite, + }), onSuccess: () => { toast(t('proxy.config.create_success')) $proxyTableRefetchTrigger.set(Math.random()) + onSuccess?.() }, onError: (e) => { toast(t('proxy.config.create_failed'), { description: JSON.stringify(e), }) $proxyTableRefetchTrigger.set(Math.random()) - } + }, }) useEffect(() => { @@ -116,27 +123,39 @@ export const ProxyConfigMutateForm = ({ overwrite, defaultProxyConfig, defaultOr ({ value: type, label: type }))} value={proxyType} - setValue={(value) => { setProxyType(value as ProxyType) }} + setValue={(value) => { + setProxyType(value as ProxyType) + }} /> - {proxyConfigs && selectedServer && proxyConfigs.length > 0 && - proxyConfigs[0] && TypedProxyConfigValid(proxyConfigs[0]) && -
-
- + {proxyConfigs && + selectedServer && + proxyConfigs.length > 0 && + proxyConfigs[0] && + TypedProxyConfigValid(proxyConfigs[0]) && ( +
+
+ +
-
- } + )} - setProxyName(e.target.value)} disabled={disableChangeProxyName} /> - {proxyName && newClientID && newServerID && 0 ? proxyConfigs[0] : undefined} - clientProxyConfigs={proxyConfigs} - setClientProxyConfigs={setProxyConfigs} - enablePreview={false} - />} + setProxyName(e.target.value)} + disabled={disableChangeProxyName} + /> + {proxyName && newClientID && newServerID && ( + 0 ? proxyConfigs[0] : undefined} + clientProxyConfigs={proxyConfigs} + setClientProxyConfigs={setProxyConfigs} + enablePreview={false} + /> + )} + }} + > + {t('proxy.config.submit')} + ) -} \ No newline at end of file +} diff --git a/www/components/proxy/proxy_config_actions.tsx b/www/components/proxy/proxy_config_actions.tsx index c2e0390..e0c109a 100644 --- a/www/components/proxy/proxy_config_actions.tsx +++ b/www/components/proxy/proxy_config_actions.tsx @@ -134,7 +134,7 @@ export function ProxyConfigActions({ serverID, clientID, name, row }: ProxyConfi return ( <> - + [] = [ - { - accessorKey: 'clientID', - header: function Header() { - const { t } = useTranslation() - return t('proxy.item.client_id') - }, - cell: ({ row }) => { - return
{row.original.originalProxyConfig.originClientId}
- }, - }, { accessorKey: 'name', header: function Header() { @@ -56,6 +46,16 @@ export const columns: ColumnDef[] = [ return
{row.original.type}
}, }, + { + accessorKey: 'clientID', + header: function Header() { + const { t } = useTranslation() + return t('proxy.item.client_id') + }, + cell: ({ row }) => { + return
{row.original.originalProxyConfig.originClientId}
+ }, + }, { accessorKey: 'serverID', header: function Header() { diff --git a/www/components/ui/tabs.tsx b/www/components/ui/tabs.tsx new file mode 100644 index 0000000..85d83be --- /dev/null +++ b/www/components/ui/tabs.tsx @@ -0,0 +1,53 @@ +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/www/components/worker/client_deploy.tsx b/www/components/worker/client_deploy.tsx new file mode 100644 index 0000000..c9852ab --- /dev/null +++ b/www/components/worker/client_deploy.tsx @@ -0,0 +1,219 @@ +'use client' + +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { ClientSelector } from '../base/client-selector' +import { Cpu, Download, Loader2, Trash } from 'lucide-react' +import { useMutation, useQuery } from '@tanstack/react-query' +import { getWorkerStatus, installWorkerd } from '@/api/worker' +import { Client } from '@/lib/pb/common' +import { + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from '@/components/ui/dialog' +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' +import { Badge } from '@/components/ui/badge' +import { toast } from 'sonner' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' + +interface ClientDeploymentProps { + workerId: string + deployedClientIDs: string[] + setDeployedClientIDs: (ids: string[]) => void + clients?: Client[] +} + +export function ClientDeployment({ + workerId, + deployedClientIDs, + setDeployedClientIDs, + clients = [], +}: ClientDeploymentProps) { + const { t } = useTranslation() + const [dialogOpen, setDialogOpen] = useState(false) + const [selectedClientId, setSelectedClientId] = useState('') + const [downloadUrl, setDownloadUrl] = useState('') + + const { data: statusResp } = useQuery({ + queryKey: ['workerStatus', workerId], + queryFn: () => getWorkerStatus({ workerId }), + enabled: !!workerId, + refetchInterval: 10000, + }) + + const installWorkerdMutation = useMutation({ + mutationFn: installWorkerd, + onSuccess: () => { + toast.success(t('worker.client_install_workerd.success')) + }, + onError: () => { + toast.error(t('worker.client_install_workerd.error')) + }, + }) + + const statusMap = statusResp?.workerStatus || {} + + const handleAddClient = () => { + if (selectedClientId && !deployedClientIDs.includes(selectedClientId)) { + setDeployedClientIDs([...deployedClientIDs, selectedClientId]) + setSelectedClientId('') + setDialogOpen(false) + } + } + + const handleRemoveClient = (clientId: string) => { + setDeployedClientIDs(deployedClientIDs.filter((id) => id !== clientId)) + } + + function getStatusInfo(status?: string): { + variant: 'outline' | 'default' | 'secondary' | 'destructive' + text: string + } { + if (status === 'running') { + return { variant: 'default', text: t('worker.status_running') } + } else if (status === 'stopped') { + return { variant: 'destructive', text: t('worker.status_stopped') } + } else if (status === 'error') { + return { variant: 'secondary', text: t('worker.status_error') } + } else { + return { variant: 'outline', text: t('worker.status_unknown') } + } + } + + return ( + + +
+ + + {t('worker.deploy.title')} + + + + + + + + {t('worker.deploy.select_client')} + {t('worker.deploy.client_description')} + + +
+ +
+ + + + +
+
+
+
+ +
+ {deployedClientIDs.length === 0 ? ( +
+ {t('worker.deploy.no_clients')} +
+ ) : ( +
+ {deployedClientIDs.map((clientId) => { + const clientStatus = statusMap[clientId] + const { variant, text } = getStatusInfo(clientStatus) + const client = clients.find((c) => c.id === clientId) + + return ( +
+
+ + + +

+ {client?.originClientId || clientId} +

+
+ {client?.originClientId || clientId} +
+
+ + {text} + +
+
+ + + +
+ ID: + {clientId} +
+
+ {clientId} +
+
+
+
+ + + + + + + {t('worker.client_install_workerd.title')} + {t('worker.client_install_workerd.description')} + + + setDownloadUrl(e.target.value)} + /> + + + + + + +
+
+ ) + })} +
+ )} +
+
+
+ ) +} diff --git a/www/components/worker/code_editor.tsx b/www/components/worker/code_editor.tsx new file mode 100644 index 0000000..9500c49 --- /dev/null +++ b/www/components/worker/code_editor.tsx @@ -0,0 +1,22 @@ +'use client' + +import React from 'react' +import dynamic from 'next/dynamic' + +// 动态加载编辑器组件 +const MonacoEditor = dynamic(() => import('@/components/base/monaco').then((m) => m.WorkerEditor), { + ssr: false, +}) + +interface WorkerCodeEditorProps { + code: string + onChange: (code: string) => void +} + +export function WorkerCodeEditor({ code, onChange }: WorkerCodeEditorProps) { + return ( +
+ +
+ ) +} diff --git a/www/components/worker/edit.tsx b/www/components/worker/edit.tsx new file mode 100644 index 0000000..55ab03c --- /dev/null +++ b/www/components/worker/edit.tsx @@ -0,0 +1,127 @@ +'use client' + +import React, { useState, useEffect } from 'react' +import { useRouter } from 'next/navigation' +import { useSearchParams } from 'next/navigation' +import { useQuery, useMutation } from '@tanstack/react-query' +import { toast } from 'sonner' +import { useTranslation } from 'react-i18next' + +import { getWorker, updateWorker } from '@/api/worker' +import { UpdateWorkerRequest } from '@/lib/pb/api_client' +import { Worker } from '@/lib/pb/common' + +import { Button } from '@/components/ui/button' +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs' +import { WorkerInfoCard } from './info_card' +import { WorkerIngress } from './ingress_section' +import { ClientDeployment } from './client_deploy' +import { WorkerCodeEditor } from './code_editor' +import { WorkerTemplateEditor } from './template_editor' + +export default function WorkerEdit() { + const router = useRouter() + const params = useSearchParams() + const workerId = params.get('workerId')! + const { t } = useTranslation() + + // 本地状态 + const [worker, setWorker] = useState({} as Worker) + const [code, setCode] = useState('') + const [template, setTemplate] = useState('') + const [deployedClientIDs, setDeployedClientIDs] = useState([]) + + // 获取 Worker + const { data: resp, refetch: refetchWorker } = useQuery({ + queryKey: ['getWorker', workerId], + queryFn: () => getWorker({ workerId }), + enabled: !!workerId, + }) + + useEffect(() => { + if (resp?.worker) { + setWorker(resp.worker) + setCode(resp.worker.code ?? '') + setTemplate(resp.worker.configTemplate ?? '') + // @ts-ignore + setDeployedClientIDs(resp.clients.map((client) => client.id).filter((id) => id !== undefined) || []) + } + }, [resp]) + + // 更新 Worker + const updateMut = useMutation({ + mutationFn: () => { + const req: UpdateWorkerRequest = { + clientIds: deployedClientIDs, + worker: { + ...worker, + code, + configTemplate: template, + }, + } + return updateWorker(req) + }, + onSuccess: () => { + toast.success(t('worker.edit.save_success')) + refetchWorker() + }, + onError: (e) => toast.error(`${t('worker.edit.save_error')}: ${e.message}`), + }) + + return ( +
+
+

+ {t('worker.edit.title')} + 「{worker?.name}」 +

+
+ + +
+
+ +
+ + + + {t('worker.edit.info_tab')} + + + {t('worker.edit.code_tab')} + + + {t('worker.edit.template_tab')} + + + + + + + {/*
*/} + + + {/*
*/} +
+ + + + + + + + +
+
+
+ ) +} diff --git a/www/components/worker/info_card.tsx b/www/components/worker/info_card.tsx new file mode 100644 index 0000000..30d1c00 --- /dev/null +++ b/www/components/worker/info_card.tsx @@ -0,0 +1,104 @@ +'use client' + +import React from 'react' +import { Client, Worker } from '@/lib/pb/common' +import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/card' +import { Label } from '@/components/ui/label' +import { Input } from '@/components/ui/input' +import { useTranslation } from 'react-i18next' +import { WorkerStatus } from './worker_status' +import { useQuery } from '@tanstack/react-query' +import { getWorkerIngress } from '@/api/worker' +import { InfoIcon } from 'lucide-react' +// import { Textarea } from '@/components/ui/textarea' +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' + +interface WorkerInfoCardProps { + worker: Worker + onChange: (worker: Worker) => void + clients?: Client[] +} + +export function WorkerInfoCard({ worker, onChange, clients = [] }: WorkerInfoCardProps) { + const { t } = useTranslation() + + // 获取 Worker Ingress 用于状态统计 + const { data: ingressResp } = useQuery({ + queryKey: ['getWorkerIngress', worker.workerId], + queryFn: () => getWorkerIngress({ workerId: worker.workerId || '' }), + enabled: !!worker.workerId, + }) + + return ( + + +
+ + + {t('worker.info.basic_info')} + + +
+ {t('worker.info.info_description')} +
+ +
+
+ +
+ + + + + + {worker.workerId || ''} + + +
+
+
+ +
+ onChange({ ...worker, name: e.target.value })} + className="h-9 text-sm" + placeholder={t('worker.info.name_placeholder')} + /> +
+
+
+ + {/* 暂不实现 */} + {/*
+ +