diff --git a/backend/app/api/v1/host_tool.go b/backend/app/api/v1/host_tool.go index 0c46850ac..6e55b70e2 100644 --- a/backend/app/api/v1/host_tool.go +++ b/backend/app/api/v1/host_tool.go @@ -172,3 +172,42 @@ func (b *BaseApi) OperateProcess(c *gin.Context) { } helper.SuccessWithOutData(c) } + +// @Tags Host tool +// @Summary Get Supervisor process config +// @Description 获取 Supervisor 进程配置 +// @Accept json +// @Success 200 +// @Security ApiKeyAuth +// @Router /host/tool/supervisor/process [get] +func (b *BaseApi) GetProcess(c *gin.Context) { + configs, err := hostToolService.GetSupervisorProcessConfig() + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, configs) +} + +// @Tags Host tool +// @Summary Get Supervisor process config +// @Description 操作 Supervisor 进程文件 +// @Accept json +// @Param request body request.SupervisorProcessFileReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /host/tool/supervisor/process/file [post] +// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] Supervisor 进程文件 ","formatEN":"[operate] Supervisor Process Config file"} +func (b *BaseApi) GetProcessFile(c *gin.Context) { + var req request.SupervisorProcessFileReq + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + content, err := hostToolService.OperateSupervisorProcessFile(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, content) +} diff --git a/backend/app/api/v1/remote_db.go b/backend/app/api/v1/remote_db.go index a276e1d5e..ecf8d0739 100644 --- a/backend/app/api/v1/remote_db.go +++ b/backend/app/api/v1/remote_db.go @@ -85,7 +85,7 @@ func (b *BaseApi) ListRemoteDB(c *gin.Context) { // @Tags Database // @Summary Get remote databases // @Description 获取远程数据库 -// @Success 200 dto.RemoteDBOption +// @Success 200 {object} dto.RemoteDBInfo // @Security ApiKeyAuth // @Router /databases/remote/:name [get] func (b *BaseApi) GetRemoteDB(c *gin.Context) { diff --git a/backend/app/dto/request/host_tool.go b/backend/app/dto/request/host_tool.go index 5f76db256..e9863e8df 100644 --- a/backend/app/dto/request/host_tool.go +++ b/backend/app/dto/request/host_tool.go @@ -32,3 +32,9 @@ type SupervisorProcessConfig struct { Dir string `json:"dir"` Numprocs string `json:"numprocs"` } +type SupervisorProcessFileReq struct { + Name string `json:"name" validate:"required"` + Operate string `json:"operate" validate:"required,oneof=get clear update" ` + Content string `json:"content"` + File string `json:"file" validate:"required,oneof=out.log err.log config"` +} diff --git a/backend/app/dto/response/host_tool.go b/backend/app/dto/response/host_tool.go index 21db3a53a..76d38716f 100644 --- a/backend/app/dto/response/host_tool.go +++ b/backend/app/dto/response/host_tool.go @@ -20,3 +20,20 @@ type Supervisor struct { type HostToolConfig struct { Content string `json:"content"` } + +type SupervisorProcessConfig struct { + Name string `json:"name"` + Command string `json:"command"` + User string `json:"user"` + Dir string `json:"dir"` + Numprocs string `json:"numprocs"` + Msg string `json:"msg"` + Status []ProcessStatus `json:"status"` +} + +type ProcessStatus struct { + Name string `json:"name"` + Status string `json:"status"` + PID string `json:"PID"` + Uptime string `json:"uptime"` +} diff --git a/backend/app/service/host_tool.go b/backend/app/service/host_tool.go index 70985010f..9e0e3b583 100644 --- a/backend/app/service/host_tool.go +++ b/backend/app/service/host_tool.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/response" + "github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/utils/cmd" @@ -14,7 +15,9 @@ import ( "github.com/pkg/errors" "gopkg.in/ini.v1" "os/exec" + "os/user" "path" + "strconv" "strings" ) @@ -27,6 +30,8 @@ type IHostToolService interface { OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error) GetToolLog(req request.HostToolLogReq) (string, error) OperateSupervisorProcess(req request.SupervisorProcessConfig) error + GetSupervisorProcessConfig() ([]response.SupervisorProcessConfig, error) + OperateSupervisorProcessFile(req request.SupervisorProcessFileReq) (string, error) } func NewIHostToolService() IHostToolService { @@ -99,7 +104,7 @@ func (h *HostToolService) GetToolStatus(req request.HostToolReq) (*response.Host configPath := "/etc/supervisord.conf" if !fileOp.Stat(configPath) { configPath = "/etc/supervisor/supervisord.conf" - if !fileOp.Stat("configPath") { + if !fileOp.Stat(configPath) { return nil, errors.New("ErrConfigNotFound") } } @@ -180,7 +185,6 @@ func (h *HostToolService) OperateToolConfig(req request.HostToolConfig) (*respon configPath = pathSet.Value } } - configPath = "/etc/supervisord.conf" switch req.Operate { case "get": content, err := fileOp.GetContent(configPath) @@ -233,27 +237,277 @@ func (h *HostToolService) GetToolLog(req request.HostToolLogReq) (string, error) } func (h *HostToolService) OperateSupervisorProcess(req request.SupervisorProcessConfig) error { - configFile := ini.Empty() - supervisordDir := path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord") - logDir := path.Join(supervisordDir, "log") - includeDir := path.Join(supervisordDir, "supervisor.d") + var ( + supervisordDir = path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord") + logDir = path.Join(supervisordDir, "log") + includeDir = path.Join(supervisordDir, "supervisor.d") + outLog = path.Join(logDir, fmt.Sprintf("%s.out.log", req.Name)) + errLog = path.Join(logDir, fmt.Sprintf("%s.err.log", req.Name)) + iniPath = path.Join(includeDir, fmt.Sprintf("%s.ini", req.Name)) + fileOp = files.NewFileOp() + ) + if req.Operate == "edit" || req.Operate == "create" { + if !fileOp.Stat(req.Dir) { + return buserr.New("ErrConfigDirNotFound") + } + _, err := user.Lookup(req.User) + if err != nil { + return buserr.WithMap("ErrUserFindErr", map[string]interface{}{"name": req.User, "err": err.Error()}, err) + } + } + + switch req.Operate { + case "create": + if fileOp.Stat(iniPath) { + return buserr.New("ErrConfigAlreadyExist") + } + configFile := ini.Empty() + section, err := configFile.NewSection(fmt.Sprintf("program:%s", req.Name)) + if err != nil { + return err + } + _, _ = section.NewKey("command", req.Command) + _, _ = section.NewKey("directory", req.Dir) + _, _ = section.NewKey("autorestart", "true") + _, _ = section.NewKey("startsecs", "3") + _, _ = section.NewKey("stdout_logfile", outLog) + _, _ = section.NewKey("stderr_logfile", errLog) + _, _ = section.NewKey("stdout_logfile_maxbytes", "2MB") + _, _ = section.NewKey("stderr_logfile_maxbytes", "2MB") + _, _ = section.NewKey("user", req.User) + _, _ = section.NewKey("priority", "999") + _, _ = section.NewKey("numprocs", req.Numprocs) + _, _ = section.NewKey("process_name", "%(program_name)s_%(process_num)02d") - section, err := configFile.NewSection(fmt.Sprintf("program:%s", req.Name)) + if err = configFile.SaveTo(iniPath); err != nil { + return err + } + return operateSupervisorCtl("reload", "", "") + case "edit": + configFile, err := ini.Load(iniPath) + if err != nil { + return err + } + section, err := configFile.GetSection(fmt.Sprintf("program:%s", req.Name)) + if err != nil { + return err + } + + commandKey := section.Key("command") + commandKey.SetValue(req.Command) + directoryKey := section.Key("directory") + directoryKey.SetValue(req.Dir) + userKey := section.Key("user") + userKey.SetValue(req.User) + numprocsKey := section.Key("numprocs") + numprocsKey.SetValue(req.Numprocs) + + if err = configFile.SaveTo(iniPath); err != nil { + return err + } + return operateSupervisorCtl("reload", "", "") + case "restart": + return operateSupervisorCtl("restart", req.Name, "") + case "start": + return operateSupervisorCtl("start", req.Name, "") + case "stop": + return operateSupervisorCtl("stop", req.Name, "") + case "delete": + _ = operateSupervisorCtl("remove", "", req.Name) + _ = files.NewFileOp().DeleteFile(iniPath) + _ = files.NewFileOp().DeleteFile(outLog) + _ = files.NewFileOp().DeleteFile(errLog) + _ = operateSupervisorCtl("reload", "", "") + } + + return nil +} + +func (h *HostToolService) GetSupervisorProcessConfig() ([]response.SupervisorProcessConfig, error) { + var ( + result []response.SupervisorProcessConfig + ) + configDir := path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord", "supervisor.d") + fileList, _ := NewIFileService().GetFileList(request.FileOption{FileOption: files.FileOption{Path: configDir, Expand: true, Page: 1, PageSize: 100}}) + if len(fileList.Items) == 0 { + return result, nil + } + for _, configFile := range fileList.Items { + f, err := ini.Load(configFile.Path) + if err != nil { + global.LOG.Errorf("get %s file err %s", configFile.Name, err.Error()) + continue + } + if strings.HasSuffix(configFile.Name, ".ini") { + config := response.SupervisorProcessConfig{} + name := strings.TrimSuffix(configFile.Name, ".ini") + config.Name = name + section, err := f.GetSection(fmt.Sprintf("program:%s", name)) + if err != nil { + global.LOG.Errorf("get %s file section err %s", configFile.Name, err.Error()) + continue + } + if command, _ := section.GetKey("command"); command != nil { + config.Command = command.Value() + } + if directory, _ := section.GetKey("directory"); directory != nil { + config.Dir = directory.Value() + } + if user, _ := section.GetKey("user"); user != nil { + config.User = user.Value() + } + if numprocs, _ := section.GetKey("numprocs"); numprocs != nil { + config.Numprocs = numprocs.Value() + } + _ = getProcessStatus(&config) + result = append(result, config) + } + } + return result, nil +} + +func (h *HostToolService) OperateSupervisorProcessFile(req request.SupervisorProcessFileReq) (string, error) { + var ( + fileOp = files.NewFileOp() + group = fmt.Sprintf("program:%s", req.Name) + configPath = path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord", "supervisor.d", fmt.Sprintf("%s.ini", req.Name)) + ) + switch req.File { + case "err.log": + logPath, err := ini_conf.GetIniValue(configPath, group, "stderr_logfile") + if err != nil { + return "", err + } + switch req.Operate { + case "get": + content, err := fileOp.GetContent(logPath) + if err != nil { + return "", err + } + return string(content), nil + case "clear": + if err = fileOp.WriteFile(logPath, strings.NewReader(""), 0755); err != nil { + return "", err + } + } + + case "out.log": + logPath, err := ini_conf.GetIniValue(configPath, group, "stdout_logfile") + if err != nil { + return "", err + } + switch req.Operate { + case "get": + content, err := fileOp.GetContent(logPath) + if err != nil { + return "", err + } + return string(content), nil + case "clear": + if err = fileOp.WriteFile(logPath, strings.NewReader(""), 0755); err != nil { + return "", err + } + } + + case "config": + switch req.Operate { + case "get": + content, err := fileOp.GetContent(configPath) + if err != nil { + return "", err + } + return string(content), nil + case "update": + if req.Content == "" { + return "", buserr.New("ErrConfigIsNull") + } + if err := fileOp.WriteFile(configPath, strings.NewReader(req.Content), 0755); err != nil { + return "", err + } + return "", operateSupervisorCtl("update", "", req.Name) + } + + } + return "", nil +} + +func operateSupervisorCtl(operate, name, group string) error { + processNames := []string{operate} + if name != "" { + includeDir := path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord", "supervisor.d") + f, err := ini.Load(path.Join(includeDir, fmt.Sprintf("%s.ini", name))) + if err != nil { + return err + } + section, err := f.GetSection(fmt.Sprintf("program:%s", name)) + if err != nil { + return err + } + numprocsNum := "" + if numprocs, _ := section.GetKey("numprocs"); numprocs != nil { + numprocsNum = numprocs.Value() + } + if numprocsNum == "" { + return buserr.New("ErrConfigParse") + } + processNames = append(processNames, getProcessName(name, numprocsNum)...) + } + if group != "" { + processNames = append(processNames, group) + } + + output, err := exec.Command("supervisorctl", processNames...).Output() if err != nil { + if output != nil { + return errors.New(string(output)) + } return err } - _, _ = section.NewKey("command", req.Command) - _, _ = section.NewKey("directory", req.Dir) - _, _ = section.NewKey("autorestart", "true") - _, _ = section.NewKey("startsecs", "3") - _, _ = section.NewKey("stdout_logfile", path.Join(logDir, fmt.Sprintf("%s.out.log", req.Name))) - _, _ = section.NewKey("stderr_logfile", path.Join(logDir, fmt.Sprintf("%s.err.log", req.Name))) - _, _ = section.NewKey("stdout_logfile_maxbytes", "2MB") - _, _ = section.NewKey("stderr_logfile_maxbytes", "2MB") - _, _ = section.NewKey("user", req.User) - _, _ = section.NewKey("priority", "999") - _, _ = section.NewKey("numprocs", req.Numprocs) - _, _ = section.NewKey("process_name", "%(program_name)s_%(process_num)02d") + return nil +} + +func getProcessName(name, numprocs string) []string { + var ( + processNames []string + ) + num, err := strconv.Atoi(numprocs) + if err != nil { + return processNames + } + if num == 1 { + processNames = append(processNames, fmt.Sprintf("%s:%s_00", name, name)) + } else { + for i := 0; i < num; i++ { + processName := fmt.Sprintf("%s:%s_0%s", name, name, strconv.Itoa(i)) + if i >= 10 { + processName = fmt.Sprintf("%s:%s_%s", name, name, strconv.Itoa(i)) + } + processNames = append(processNames, processName) + } + } + return processNames +} - return configFile.SaveTo(path.Join(includeDir, fmt.Sprintf("%s.ini", req.Name))) +func getProcessStatus(config *response.SupervisorProcessConfig) error { + var ( + processNames = []string{"status"} + ) + processNames = append(processNames, getProcessName(config.Name, config.Numprocs)...) + output, _ := exec.Command("supervisorctl", processNames...).Output() + lines := strings.Split(string(output), "\n") + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) >= 5 { + status := response.ProcessStatus{ + Name: fields[0], + Status: fields[1], + } + if fields[1] == "RUNNING" { + status.PID = strings.TrimSuffix(fields[3], ",") + status.Uptime = fields[5] + } + config.Status = append(config.Status, status) + } + } + return nil } diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index ad606c86b..66ccb7139 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -100,4 +100,9 @@ ErrBackupInUsed: "The backup account is already being used in a cronjob and cann ErrOSSConn: "Unable to successfully request the latest version. Please check if the server can connect to the external network environment." #tool -ErrConfigNotFound: "Configuration file does not exist" \ No newline at end of file +ErrConfigNotFound: "Configuration file does not exist" +ErrConfigParse: "Configuration file format error" +ErrConfigIsNull: "The configuration file is not allowed to be empty" +ErrConfigDirNotFound: "The running directory does not exist" +ErrConfigAlreadyExist: "A configuration file with the same name already exists" +ErrUserFindErr: "Failed to find user {{ .name }} {{ .err }}" \ No newline at end of file diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index 9f23a10f6..1d9e4312d 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -101,3 +101,8 @@ ErrOSSConn: "無法成功請求最新版本,請檢查伺服器是否能夠連 #tool ErrConfigNotFound: "配置文件不存在" +ErrConfigParse: "配置文件格式有誤" +ErrConfigIsNull: "配置文件不允許為空" +ErrConfigDirNotFound: "運行目錄不存在" +ErrConfigAlreadyExist: "已存在同名配置文件" +ErrUserFindErr: "用戶 {{ .name }} 查找失敗 {{ .err }}" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index 90a5c49e1..5437b6f1c 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -101,3 +101,8 @@ ErrOSSConn: "无法成功请求最新版本,请检查服务器是否能够连 #tool ErrConfigNotFound: "配置文件不存在" +ErrConfigParse: "配置文件格式有误" +ErrConfigIsNull: "配置文件不允许为空" +ErrConfigDirNotFound: "运行目录不存在" +ErrConfigAlreadyExist: "已存在同名配置文件" +ErrUserFindErr: "用户 {{ .name }} 查找失败 {{ .err }}" diff --git a/backend/router/ro_host.go b/backend/router/ro_host.go index fec9bc3db..1fc329235 100644 --- a/backend/router/ro_host.go +++ b/backend/router/ro_host.go @@ -54,5 +54,7 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) { hostRouter.POST("/tool/config", baseApi.OperateToolConfig) hostRouter.POST("/tool/log", baseApi.GetToolLog) hostRouter.POST("/tool/supervisor/process", baseApi.OperateProcess) + hostRouter.GET("/tool/supervisor/process", baseApi.GetProcess) + hostRouter.POST("/tool/supervisor/process/file", baseApi.GetProcessFile) } } diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 022f4b884..4c7bbbc98 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -6154,6 +6154,26 @@ const docTemplate = `{ } }, "/host/tool/supervisor/process": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 Supervisor 进程配置", + "consumes": [ + "application/json" + ], + "tags": [ + "Host tool" + ], + "summary": "Get Supervisor process config", + "responses": { + "200": { + "description": "OK" + } + } + }, "post": { "security": [ { @@ -6195,6 +6215,48 @@ const docTemplate = `{ } } }, + "/host/tool/supervisor/process/file": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "操作 Supervisor 进程文件", + "consumes": [ + "application/json" + ], + "tags": [ + "Host tool" + ], + "summary": "Get Supervisor process config", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SupervisorProcessFileReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFuntions": [], + "bodyKeys": [ + "operate" + ], + "formatEN": "[operate] [operate] Supervisor 进程文件", + "formatZH": "[operate] Supervisor 进程文件 ", + "paramKeys": [] + } + } + }, "/hosts": { "post": { "security": [ @@ -15696,6 +15758,37 @@ const docTemplate = `{ } } }, + "request.SupervisorProcessFileReq": { + "type": "object", + "required": [ + "file", + "name", + "operate" + ], + "properties": { + "content": { + "type": "string" + }, + "file": { + "type": "string", + "enum": [ + "out.log", + "err.log" + ] + }, + "name": { + "type": "string" + }, + "operate": { + "type": "string", + "enum": [ + "get", + "clear", + "update" + ] + } + } + }, "request.WebsiteAcmeAccountCreate": { "type": "object", "required": [ diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index f982d89d2..c21a9bea9 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -6147,6 +6147,26 @@ } }, "/host/tool/supervisor/process": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 Supervisor 进程配置", + "consumes": [ + "application/json" + ], + "tags": [ + "Host tool" + ], + "summary": "Get Supervisor process config", + "responses": { + "200": { + "description": "OK" + } + } + }, "post": { "security": [ { @@ -6188,6 +6208,48 @@ } } }, + "/host/tool/supervisor/process/file": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "操作 Supervisor 进程文件", + "consumes": [ + "application/json" + ], + "tags": [ + "Host tool" + ], + "summary": "Get Supervisor process config", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SupervisorProcessFileReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFuntions": [], + "bodyKeys": [ + "operate" + ], + "formatEN": "[operate] [operate] Supervisor 进程文件", + "formatZH": "[operate] Supervisor 进程文件 ", + "paramKeys": [] + } + } + }, "/hosts": { "post": { "security": [ @@ -15689,6 +15751,37 @@ } } }, + "request.SupervisorProcessFileReq": { + "type": "object", + "required": [ + "file", + "name", + "operate" + ], + "properties": { + "content": { + "type": "string" + }, + "file": { + "type": "string", + "enum": [ + "out.log", + "err.log" + ] + }, + "name": { + "type": "string" + }, + "operate": { + "type": "string", + "enum": [ + "get", + "clear", + "update" + ] + } + } + }, "request.WebsiteAcmeAccountCreate": { "type": "object", "required": [ diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 33dbbe430..43e57b94d 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -2978,6 +2978,28 @@ definitions: user: type: string type: object + request.SupervisorProcessFileReq: + properties: + content: + type: string + file: + enum: + - out.log + - err.log + type: string + name: + type: string + operate: + enum: + - get + - clear + - update + type: string + required: + - file + - name + - operate + type: object request.WebsiteAcmeAccountCreate: properties: email: @@ -7730,6 +7752,18 @@ paths: formatZH: '[operate] [type] ' paramKeys: [] /host/tool/supervisor/process: + get: + consumes: + - application/json + description: 获取 Supervisor 进程配置 + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Get Supervisor process config + tags: + - Host tool post: consumes: - application/json @@ -7756,6 +7790,33 @@ paths: formatEN: '[operate] process' formatZH: '[operate] 守护进程 ' paramKeys: [] + /host/tool/supervisor/process/file: + post: + consumes: + - application/json + description: 操作 Supervisor 进程文件 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/request.SupervisorProcessFileReq' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Get Supervisor process config + tags: + - Host tool + x-panel-log: + BeforeFuntions: [] + bodyKeys: + - operate + formatEN: '[operate] [operate] Supervisor 进程文件' + formatZH: '[operate] Supervisor 进程文件 ' + paramKeys: [] /hosts: post: consumes: diff --git a/frontend/src/api/interface/host-tool.ts b/frontend/src/api/interface/host-tool.ts index b4a067181..cb60deda6 100644 --- a/frontend/src/api/interface/host-tool.ts +++ b/frontend/src/api/interface/host-tool.ts @@ -39,5 +39,25 @@ export namespace HostTool { user: string; dir: string; numprocs: string; + status?: ProcessStatus[]; + } + + export interface ProcessStatus { + PID: string; + status: string; + uptime: string; + name: string; + } + + export interface ProcessReq { + operate: string; + name: string; + } + + export interface ProcessFileReq { + operate: string; + name: string; + content?: string; + file: string; } } diff --git a/frontend/src/api/modules/host-tool.ts b/frontend/src/api/modules/host-tool.ts index 5ddba77c7..6c2d69063 100644 --- a/frontend/src/api/modules/host-tool.ts +++ b/frontend/src/api/modules/host-tool.ts @@ -21,6 +21,18 @@ export const InitSupervisor = (req: HostTool.SupersivorInit) => { return http.post(`/hosts/tool/init`, req); }; -export const OperateSupervisorProcess = (req: HostTool.SupersivorProcess) => { +export const CreateSupervisorProcess = (req: HostTool.SupersivorProcess) => { return http.post(`/hosts/tool/supervisor/process`, req); }; + +export const OperateSupervisorProcess = (req: HostTool.ProcessReq) => { + return http.post(`/hosts/tool/supervisor/process`, req, 100000); +}; + +export const GetSupervisorProcess = () => { + return http.get(`/hosts/tool/supervisor/process`); +}; + +export const OperateSupervisorProcessFile = (req: HostTool.ProcessFileReq) => { + return http.post(`/hosts/tool/supervisor/process/file`, req, 100000); +}; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 9b770a6c1..edffb08f2 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1225,6 +1225,7 @@ const message = { appHelper: 'Please view the installation instructions of some applications on the application details page', backupApp: 'Backup application before upgrade', backupAppHelper: 'If the upgrade fails, you can use the application backup to roll back', + delete: 'Delete', }, website: { website: 'Website', @@ -1665,7 +1666,8 @@ const message = { numprocs: 'Number of processes', initWarn: 'Because it is not compatible with the original configuration, initializing Supervisor will modify the files parameter of the configuration file, causing all existing processes to stop, please confirm the risk in advance. The modified process configuration folder is in <1Panel installation directory>/1panel/tools/supervisord/supervisor.d', - operatorHelper: 'Operation {0} will be performed on Supervisor, continue? ', + operatorHelper: 'Operation {1} will be performed on {0}, continue? ', + uptime: 'running time', }, }, }; diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 6006c4ee3..7806fb767 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1170,6 +1170,7 @@ const message = { appHelper: '部分應用的安裝使用說明請在應用詳情頁查看', backupApp: '升級前備份應用', backupAppHelper: '升級失敗可以使用應用備份回滾', + delete: '刪除', }, website: { website: '網站', @@ -1582,7 +1583,8 @@ const message = { numprocs: '進程數量', initWarn: '由於無法兼容原有配置,初始化 Supervisor 會修改配置文件的 files 參數,導致已有的進程全部停止,請提前確認風險。修改後的進程配置文件夾在 <1Panel安裝目錄>/1panel/tools/supervisord/supervisor.d 中', - operatorHelper: '將對 Supervisor 進行 {0} 操作,是否繼續? ', + operatorHelper: '將對 {0} 進行 {1} 操作,是否繼續? ', + uptime: '運行時長', }, }, }; diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index e418768c8..66a938e85 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1170,6 +1170,7 @@ const message = { appHelper: '部分应用的安装使用说明请在应用详情页查看', backupApp: '升级前备份应用', backupAppHelper: '升级失败可以使用应用备份回滚', + delete: '删除', }, website: { website: '网站', @@ -1584,7 +1585,8 @@ const message = { numprocs: '进程数量', initWarn: '由于无法兼容原有配置,初始化 Supervisor 会修改配置文件的 files 参数,导致已有的进程全部停止,请提前确认风险。修改后的进程配置文件夹在 <1Panel安装目录>/1panel/tools/supervisord/supervisor.d 中', - operatorHelper: '将对 Supervisor 进行 {0} 操作,是否继续?', + operatorHelper: '将对 {0} 进行 {1} 操作,是否继续?', + uptime: '运行时长', }, }, }; diff --git a/frontend/src/views/host/tool/supervisor/create/index.vue b/frontend/src/views/host/tool/supervisor/create/index.vue index 0ec58d59e..1ab888e10 100644 --- a/frontend/src/views/host/tool/supervisor/create/index.vue +++ b/frontend/src/views/host/tool/supervisor/create/index.vue @@ -1,13 +1,16 @@ diff --git a/frontend/src/views/host/tool/supervisor/index.vue b/frontend/src/views/host/tool/supervisor/index.vue index 0f44f816b..d83dc2973 100644 --- a/frontend/src/views/host/tool/supervisor/index.vue +++ b/frontend/src/views/host/tool/supervisor/index.vue @@ -1,9 +1,18 @@ @@ -24,13 +74,24 @@ import ToolRouter from '@/views/host/tool/index.vue'; import SuperVisorStatus from './status/index.vue'; import { ref } from '@vue/runtime-core'; import ConfigSuperVisor from './config/index.vue'; -import { onMounted } from 'vue'; +import { computed, onMounted } from 'vue'; import Create from './create/index.vue'; +import File from './file/index.vue'; +import { GetSupervisorProcess, OperateSupervisorProcess } from '@/api/modules/host-tool'; +import { GlobalStore } from '@/store'; +import i18n from '@/lang'; +import { HostTool } from '@/api/interface/host-tool'; +import { MsgSuccess } from '@/utils/message'; +const globalStore = GlobalStore(); const loading = ref(false); const setSuperVisor = ref(false); const isExistSuperVisor = ref(false); +const isRunningSuperVisor = ref(true); const createRef = ref(); +const fileRef = ref(); +const data = ref(); +const maskShow = ref(true); const setting = () => { setSuperVisor.value = true; @@ -40,9 +101,128 @@ const isExist = (isExist: boolean) => { isExistSuperVisor.value = isExist; }; +const isRunning = (running: boolean) => { + isRunningSuperVisor.value = running; +}; + const openCreate = () => { createRef.value.acceptParams(); }; -onMounted(() => {}); +const search = async () => { + loading.value = true; + try { + const res = await GetSupervisorProcess(); + console.log(res); + data.value = res.data; + } catch (error) {} + loading.value = false; +}; + +const mobile = computed(() => { + return globalStore.isMobile(); +}); + +const operate = async (operation: string, name: string) => { + try { + ElMessageBox.confirm( + i18n.global.t('tool.supervisor.operatorHelper', [name, i18n.global.t('app.' + operation)]), + i18n.global.t('app.' + operation), + { + confirmButtonText: i18n.global.t('commons.button.confirm'), + cancelButtonText: i18n.global.t('commons.button.cancel'), + type: 'info', + }, + ) + .then(() => { + loading.value = true; + OperateSupervisorProcess({ operate: operation, name: name }) + .then(() => { + MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); + search(); + }) + .catch(() => {}) + .finally(() => { + loading.value = false; + }); + }) + .catch(() => {}); + } catch (error) {} +}; + +const getFile = (name: string, file: string) => { + fileRef.value.acceptParams(name, file, 'get'); +}; + +const edit = (row: HostTool.SupersivorProcess) => { + createRef.value.acceptParams('edit', row); +}; + +const buttons = [ + { + label: i18n.global.t('commons.button.edit'), + click: function (row: HostTool.SupersivorProcess) { + edit(row); + }, + }, + { + label: i18n.global.t('website.proxyFile'), + click: function (row: HostTool.SupersivorProcess) { + getFile(row.name, 'config'); + }, + }, + { + label: i18n.global.t('website.log'), + click: function (row: HostTool.SupersivorProcess) { + getFile(row.name, 'out.log'); + }, + }, + { + label: i18n.global.t('app.start'), + click: function (row: HostTool.SupersivorProcess) { + operate('start', row.name); + }, + disabled: (row: any) => { + if (row.status == undefined) { + return true; + } else { + return row.status && row.status[0].status == 'RUNNING'; + } + }, + }, + { + label: i18n.global.t('app.stop'), + click: function (row: HostTool.SupersivorProcess) { + operate('stop', row.name); + }, + disabled: (row: any) => { + if (row.status == undefined) { + return true; + } + return row.status && row.status[0].status != 'RUNNING'; + }, + }, + { + label: i18n.global.t('commons.button.restart'), + click: function (row: HostTool.SupersivorProcess) { + operate('restart', row.name); + }, + disabled: (row: any): boolean => { + if (row.status == undefined) { + return true; + } + return row.status && row.status[0].status != 'RUNNING'; + }, + }, + { + label: i18n.global.t('commons.button.delete'), + click: function (row: HostTool.SupersivorProcess) { + operate('delete', row.name); + }, + }, +]; + +onMounted(() => { + search(); +}); diff --git a/frontend/src/views/host/tool/supervisor/status/index.vue b/frontend/src/views/host/tool/supervisor/status/index.vue index 073190f11..cadf9129e 100644 --- a/frontend/src/views/host/tool/supervisor/status/index.vue +++ b/frontend/src/views/host/tool/supervisor/status/index.vue @@ -74,7 +74,7 @@ const data = ref({ ctlExist: false, }); -const em = defineEmits(['setting', 'isExist', 'before', 'update:loading', 'update:maskShow']); +const em = defineEmits(['setting', 'isExist', 'isRunning', 'update:loading', 'update:maskShow']); const setting = () => { em('setting', false); @@ -85,9 +85,10 @@ const toDoc = async () => { }; const onOperate = async (operation: string) => { + em('update:maskShow', false); operateReq.operate = operation; ElMessageBox.confirm( - i18n.global.t('tool.supervisor.operatorHelper', [i18n.global.t('app.' + operation)]), + i18n.global.t('tool.supervisor.operatorHelper', ['Supervisor', i18n.global.t('app.' + operation)]), i18n.global.t('app.' + operation), { confirmButtonText: i18n.global.t('commons.button.confirm'), @@ -97,9 +98,9 @@ const onOperate = async (operation: string) => { ) .then(() => { em('update:loading', true); - em('before'); OperateSupervisor(operation) .then(() => { + em('update:maskShow', true); getStatus(); em('update:loading', false); MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); @@ -108,7 +109,9 @@ const onOperate = async (operation: string) => { em('update:loading', false); }); }) - .catch(() => {}); + .catch(() => { + em('update:maskShow', true); + }); }; const getStatus = async () => { @@ -116,6 +119,7 @@ const getStatus = async () => { em('update:loading', true); const res = await GetSupervisorStatus(); data.value = res.data.config as HostTool.Supersivor; + em('isRunning', data.value.status === 'running'); if (!data.value.isExist || !data.value.ctlExist) { em('isExist', false); } else {