mirror of https://github.com/1Panel-dev/1Panel
Browse Source
增加 Supervisor 状态读取 初始化 启动 重启 设置 日志 功能 Refs https://github.com/1Panel-dev/1Panel/issues/1754 Refs https://github.com/1Panel-dev/1Panel/issues/1409 Refs https://github.com/1Panel-dev/1Panel/issues/1388 Refs https://github.com/1Panel-dev/1Panel/issues/379 Refs https://github.com/1Panel-dev/1Panel/issues/353 Refs https://github.com/1Panel-dev/1Panel/issues/331pull/1795/head
zhengkunwang
1 year ago
committed by
GitHub
30 changed files with 2347 additions and 10 deletions
@ -0,0 +1,174 @@
|
||||
package v1 |
||||
|
||||
import ( |
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" |
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request" |
||||
"github.com/1Panel-dev/1Panel/backend/constant" |
||||
"github.com/1Panel-dev/1Panel/backend/global" |
||||
"github.com/gin-gonic/gin" |
||||
) |
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Get tool
|
||||
// @Description 获取主机工具状态
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool [post]
|
||||
func (b *BaseApi) GetToolStatus(c *gin.Context) { |
||||
var req request.HostToolReq |
||||
if err := c.ShouldBindJSON(&req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
if err := global.VALID.Struct(req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
|
||||
config, err := hostToolService.GetToolStatus(req) |
||||
if err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) |
||||
return |
||||
} |
||||
helper.SuccessWithData(c, config) |
||||
} |
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Create Host tool Config
|
||||
// @Description 创建主机工具配置
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool/create [post]
|
||||
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建 [type] 配置","formatEN":"create [type] config"}
|
||||
func (b *BaseApi) InitToolConfig(c *gin.Context) { |
||||
var req request.HostToolCreate |
||||
if err := c.ShouldBindJSON(&req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
if err := global.VALID.Struct(req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
|
||||
if err := hostToolService.CreateToolConfig(req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) |
||||
return |
||||
} |
||||
helper.SuccessWithOutData(c) |
||||
} |
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Operate tool
|
||||
// @Description 操作主机工具
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operate","type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] [type] ","formatEN":"[operate] [type]"}
|
||||
func (b *BaseApi) OperateTool(c *gin.Context) { |
||||
var req request.HostToolReq |
||||
if err := c.ShouldBindJSON(&req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
if err := global.VALID.Struct(req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
|
||||
err := hostToolService.OperateTool(req) |
||||
if err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) |
||||
return |
||||
} |
||||
helper.SuccessWithOutData(c) |
||||
} |
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Get tool config
|
||||
// @Description 操作主机工具配置文件
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolConfig true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool/config [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] 主机工具配置文件 ","formatEN":"[operate] tool config"}
|
||||
func (b *BaseApi) OperateToolConfig(c *gin.Context) { |
||||
var req request.HostToolConfig |
||||
if err := c.ShouldBindJSON(&req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
if err := global.VALID.Struct(req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
|
||||
config, err := hostToolService.OperateToolConfig(req) |
||||
if err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) |
||||
return |
||||
} |
||||
helper.SuccessWithData(c, config) |
||||
} |
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Get tool
|
||||
// @Description 获取主机工具日志
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolLogReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool/log [post]
|
||||
func (b *BaseApi) GetToolLog(c *gin.Context) { |
||||
var req request.HostToolLogReq |
||||
if err := c.ShouldBindJSON(&req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
if err := global.VALID.Struct(req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
|
||||
logContent, err := hostToolService.GetToolLog(req) |
||||
if err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) |
||||
return |
||||
} |
||||
helper.SuccessWithData(c, logContent) |
||||
} |
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Create Supervisor process
|
||||
// @Description 操作守护进程
|
||||
// @Accept json
|
||||
// @Param request body request.SupervisorProcessConfig true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool/supervisor/process [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] 守护进程 ","formatEN":"[operate] process"}
|
||||
func (b *BaseApi) OperateProcess(c *gin.Context) { |
||||
var req request.SupervisorProcessConfig |
||||
if err := c.ShouldBindJSON(&req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
if err := global.VALID.Struct(req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
|
||||
err := hostToolService.OperateSupervisorProcess(req) |
||||
if err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) |
||||
return |
||||
} |
||||
helper.SuccessWithOutData(c) |
||||
} |
@ -0,0 +1,34 @@
|
||||
package request |
||||
|
||||
type HostToolReq struct { |
||||
Type string `json:"type" validate:"required,oneof=supervisord"` |
||||
Operate string `json:"operate" validate:"oneof=status restart start stop"` |
||||
} |
||||
|
||||
type HostToolCreate struct { |
||||
Type string `json:"type" validate:"required"` |
||||
SupervisorConfig |
||||
} |
||||
|
||||
type SupervisorConfig struct { |
||||
ConfigPath string `json:"configPath"` |
||||
} |
||||
|
||||
type HostToolLogReq struct { |
||||
Type string `json:"type" validate:"required,oneof=supervisord"` |
||||
} |
||||
|
||||
type HostToolConfig struct { |
||||
Type string `json:"type" validate:"required,oneof=supervisord"` |
||||
Operate string `json:"operate" validate:"oneof=get set"` |
||||
Content string `json:"content"` |
||||
} |
||||
|
||||
type SupervisorProcessConfig struct { |
||||
Name string `json:"name"` |
||||
Operate string `json:"operate"` |
||||
Command string `json:"command"` |
||||
User string `json:"user"` |
||||
Dir string `json:"dir"` |
||||
Numprocs string `json:"numprocs"` |
||||
} |
@ -0,0 +1,22 @@
|
||||
package response |
||||
|
||||
type HostToolRes struct { |
||||
Type string `json:"type"` |
||||
Config interface{} `json:"config"` |
||||
} |
||||
|
||||
type Supervisor struct { |
||||
ConfigPath string `json:"configPath"` |
||||
IncludeDir string `json:"includeDir"` |
||||
LogPath string `json:"logPath"` |
||||
IsExist bool `json:"isExist"` |
||||
Init bool `json:"init"` |
||||
Msg string `json:"msg"` |
||||
Version string `json:"version"` |
||||
Status string `json:"status"` |
||||
CtlExist bool `json:"ctlExist"` |
||||
} |
||||
|
||||
type HostToolConfig struct { |
||||
Content string `json:"content"` |
||||
} |
@ -0,0 +1,259 @@
|
||||
package service |
||||
|
||||
import ( |
||||
"bytes" |
||||
"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/constant" |
||||
"github.com/1Panel-dev/1Panel/backend/global" |
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd" |
||||
"github.com/1Panel-dev/1Panel/backend/utils/files" |
||||
"github.com/1Panel-dev/1Panel/backend/utils/ini_conf" |
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl" |
||||
"github.com/pkg/errors" |
||||
"gopkg.in/ini.v1" |
||||
"os/exec" |
||||
"path" |
||||
"strings" |
||||
) |
||||
|
||||
type HostToolService struct{} |
||||
|
||||
type IHostToolService interface { |
||||
GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error) |
||||
CreateToolConfig(req request.HostToolCreate) error |
||||
OperateTool(req request.HostToolReq) error |
||||
OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error) |
||||
GetToolLog(req request.HostToolLogReq) (string, error) |
||||
OperateSupervisorProcess(req request.SupervisorProcessConfig) error |
||||
} |
||||
|
||||
func NewIHostToolService() IHostToolService { |
||||
return &HostToolService{} |
||||
} |
||||
|
||||
func (h *HostToolService) GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error) { |
||||
res := &response.HostToolRes{} |
||||
res.Type = req.Type |
||||
switch req.Type { |
||||
case constant.Supervisord: |
||||
exist, err := systemctl.IsExist(constant.Supervisord) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
supervisorConfig := &response.Supervisor{} |
||||
if !exist { |
||||
supervisorConfig.IsExist = false |
||||
return res, nil |
||||
} |
||||
supervisorConfig.IsExist = true |
||||
|
||||
versionRes, _ := cmd.Exec("supervisord -v") |
||||
supervisorConfig.Version = strings.TrimSuffix(versionRes, "\n") |
||||
_, ctlRrr := exec.LookPath("supervisorctl") |
||||
supervisorConfig.CtlExist = ctlRrr == nil |
||||
|
||||
active, err := systemctl.IsActive(constant.Supervisord) |
||||
if err != nil { |
||||
supervisorConfig.Status = "unhealthy" |
||||
supervisorConfig.Msg = err.Error() |
||||
res.Config = supervisorConfig |
||||
return res, nil |
||||
} |
||||
if active { |
||||
supervisorConfig.Status = "running" |
||||
} else { |
||||
supervisorConfig.Status = "stopped" |
||||
} |
||||
|
||||
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath)) |
||||
if pathSet.ID != 0 || pathSet.Value != "" { |
||||
supervisorConfig.ConfigPath = pathSet.Value |
||||
res.Config = supervisorConfig |
||||
return res, nil |
||||
} |
||||
supervisorConfig.Init = true |
||||
|
||||
servicePath := "/usr/lib/systemd/system/supervisord.service" |
||||
fileOp := files.NewFileOp() |
||||
if !fileOp.Stat(servicePath) { |
||||
servicePath = "/lib/systemd/system/supervisord.service" |
||||
} |
||||
if fileOp.Stat(servicePath) { |
||||
startCmd, _ := ini_conf.GetIniValue(servicePath, "Service", "ExecStart") |
||||
if startCmd != "" { |
||||
args := strings.Fields(startCmd) |
||||
cIndex := -1 |
||||
for i, arg := range args { |
||||
if arg == "-c" { |
||||
cIndex = i |
||||
break |
||||
} |
||||
} |
||||
if cIndex != -1 && cIndex+1 < len(args) { |
||||
supervisorConfig.ConfigPath = args[cIndex+1] |
||||
} |
||||
} |
||||
} else { |
||||
configPath := "/etc/supervisord.conf" |
||||
if !fileOp.Stat(configPath) { |
||||
configPath = "/etc/supervisor/supervisord.conf" |
||||
if !fileOp.Stat("configPath") { |
||||
return nil, errors.New("ErrConfigNotFound") |
||||
} |
||||
} |
||||
} |
||||
|
||||
res.Config = supervisorConfig |
||||
} |
||||
return res, nil |
||||
} |
||||
|
||||
func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error { |
||||
switch req.Type { |
||||
case constant.Supervisord: |
||||
fileOp := files.NewFileOp() |
||||
if !fileOp.Stat(req.ConfigPath) { |
||||
return errors.New("ErrConfigNotFound") |
||||
} |
||||
cfg, err := ini.Load(req.ConfigPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
service, err := cfg.GetSection("include") |
||||
if err != nil { |
||||
return err |
||||
} |
||||
targetKey, err := service.GetKey("files") |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if targetKey != nil { |
||||
_, err = service.NewKey(";files", targetKey.Value()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
supervisorDir := path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord") |
||||
includeDir := path.Join(supervisorDir, "supervisor.d") |
||||
if !fileOp.Stat(includeDir) { |
||||
if err = fileOp.CreateDir(includeDir, 0755); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
logDir := path.Join(supervisorDir, "log") |
||||
if !fileOp.Stat(logDir) { |
||||
if err = fileOp.CreateDir(logDir, 0755); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
includePath := path.Join(includeDir, "*.ini") |
||||
targetKey.SetValue(includePath) |
||||
if err = cfg.SaveTo(req.ConfigPath); err != nil { |
||||
return err |
||||
} |
||||
if err = settingRepo.Create(constant.SupervisorConfigPath, req.ConfigPath); err != nil { |
||||
return err |
||||
} |
||||
go func() { |
||||
if err = systemctl.Restart(constant.Supervisord); err != nil { |
||||
global.LOG.Errorf("[init] restart supervisord failed err %s", err.Error()) |
||||
} |
||||
}() |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (h *HostToolService) OperateTool(req request.HostToolReq) error { |
||||
return systemctl.Operate(req.Operate, req.Type) |
||||
} |
||||
|
||||
func (h *HostToolService) OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error) { |
||||
fileOp := files.NewFileOp() |
||||
res := &response.HostToolConfig{} |
||||
configPath := "" |
||||
switch req.Type { |
||||
case constant.Supervisord: |
||||
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath)) |
||||
if pathSet.ID != 0 || pathSet.Value != "" { |
||||
configPath = pathSet.Value |
||||
} |
||||
} |
||||
configPath = "/etc/supervisord.conf" |
||||
switch req.Operate { |
||||
case "get": |
||||
content, err := fileOp.GetContent(configPath) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
res.Content = string(content) |
||||
case "set": |
||||
file, err := fileOp.OpenFile(configPath) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
oldContent, err := fileOp.GetContent(configPath) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
fileInfo, err := file.Stat() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if err = fileOp.WriteFile(configPath, strings.NewReader(req.Content), fileInfo.Mode()); err != nil { |
||||
return nil, err |
||||
} |
||||
if err = systemctl.Restart(req.Type); err != nil { |
||||
_ = fileOp.WriteFile(configPath, bytes.NewReader(oldContent), fileInfo.Mode()) |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
return res, nil |
||||
} |
||||
|
||||
func (h *HostToolService) GetToolLog(req request.HostToolLogReq) (string, error) { |
||||
fileOp := files.NewFileOp() |
||||
logfilePath := "" |
||||
switch req.Type { |
||||
case constant.Supervisord: |
||||
configPath := "/etc/supervisord.conf" |
||||
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath)) |
||||
if pathSet.ID != 0 || pathSet.Value != "" { |
||||
configPath = pathSet.Value |
||||
} |
||||
logfilePath, _ = ini_conf.GetIniValue(configPath, "supervisord", "logfile") |
||||
} |
||||
oldContent, err := fileOp.GetContent(logfilePath) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return string(oldContent), nil |
||||
} |
||||
|
||||
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") |
||||
|
||||
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", 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 configFile.SaveTo(path.Join(includeDir, fmt.Sprintf("%s.ini", req.Name))) |
||||
} |
@ -0,0 +1,6 @@
|
||||
package constant |
||||
|
||||
const ( |
||||
Supervisord = "supervisord" |
||||
SupervisorConfigPath = "SupervisorConfigPath" |
||||
) |
@ -0,0 +1,36 @@
|
||||
package ini_conf |
||||
|
||||
import "gopkg.in/ini.v1" |
||||
|
||||
func GetIniValue(filePath, Group, Key string) (string, error) { |
||||
cfg, err := ini.Load(filePath) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
service, err := cfg.GetSection(Group) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
startKey, err := service.GetKey(Key) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return startKey.Value(), nil |
||||
} |
||||
|
||||
func SetIniValue(filePath, Group, Key, value string) error { |
||||
cfg, err := ini.Load(filePath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
service, err := cfg.GetSection(Group) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
targetKey := service.Key(Key) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
targetKey.SetValue(value) |
||||
return cfg.SaveTo(filePath) |
||||
} |
@ -0,0 +1,62 @@
|
||||
package systemctl |
||||
|
||||
import ( |
||||
"fmt" |
||||
"github.com/pkg/errors" |
||||
"os/exec" |
||||
"strings" |
||||
) |
||||
|
||||
func RunSystemCtl(args ...string) (string, error) { |
||||
cmd := exec.Command("systemctl", args...) |
||||
output, err := cmd.CombinedOutput() |
||||
if err != nil { |
||||
return string(output), fmt.Errorf("failed to run command: %w", err) |
||||
} |
||||
return string(output), nil |
||||
} |
||||
|
||||
func IsActive(serviceName string) (bool, error) { |
||||
out, err := RunSystemCtl("is-active", serviceName) |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
return out == "active\n", nil |
||||
} |
||||
|
||||
func IsExist(serviceName string) (bool, error) { |
||||
out, err := RunSystemCtl("list-unit-files") |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
return strings.Contains(out, serviceName+".service"), nil |
||||
} |
||||
|
||||
func handlerErr(out string, err error) error { |
||||
if err != nil { |
||||
if out != "" { |
||||
return errors.New(out) |
||||
} |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func Restart(serviceName string) error { |
||||
out, err := RunSystemCtl("restart", serviceName) |
||||
return handlerErr(out, err) |
||||
} |
||||
func Start(serviceName string) error { |
||||
out, err := RunSystemCtl("start", serviceName) |
||||
return handlerErr(out, err) |
||||
} |
||||
|
||||
func Stop(serviceName string) error { |
||||
out, err := RunSystemCtl("stop", serviceName) |
||||
return handlerErr(out, err) |
||||
} |
||||
|
||||
func Operate(operate, serviceName string) error { |
||||
out, err := RunSystemCtl(operate, serviceName) |
||||
return handlerErr(out, err) |
||||
} |
@ -0,0 +1,43 @@
|
||||
export namespace HostTool { |
||||
export interface HostTool { |
||||
type: string; |
||||
config: {}; |
||||
} |
||||
|
||||
export interface Supersivor extends HostTool { |
||||
configPath: string; |
||||
includeDir: string; |
||||
logPath: string; |
||||
isExist: boolean; |
||||
init: boolean; |
||||
msg: string; |
||||
version: string; |
||||
status: string; |
||||
ctlExist: boolean; |
||||
} |
||||
|
||||
export interface SupersivorConfig { |
||||
type: string; |
||||
operate: string; |
||||
content?: string; |
||||
} |
||||
|
||||
export interface SupersivorConfigRes { |
||||
type: string; |
||||
content: string; |
||||
} |
||||
|
||||
export interface SupersivorInit { |
||||
type: string; |
||||
configPath: string; |
||||
} |
||||
|
||||
export interface SupersivorProcess { |
||||
operate: string; |
||||
name: string; |
||||
command: string; |
||||
user: string; |
||||
dir: string; |
||||
numprocs: string; |
||||
} |
||||
} |
@ -0,0 +1,26 @@
|
||||
import http from '@/api'; |
||||
import { HostTool } from '../interface/host-tool'; |
||||
|
||||
export const GetSupervisorStatus = () => { |
||||
return http.post<HostTool.HostTool>(`/hosts/tool`, { type: 'supervisord', operate: 'status' }); |
||||
}; |
||||
|
||||
export const OperateSupervisor = (operate: string) => { |
||||
return http.post<any>(`/hosts/tool/operate`, { type: 'supervisord', operate: operate }); |
||||
}; |
||||
|
||||
export const OperateSupervisorConfig = (req: HostTool.SupersivorConfig) => { |
||||
return http.post<HostTool.SupersivorConfigRes>(`/hosts/tool/config`, req); |
||||
}; |
||||
|
||||
export const GetSupervisorLog = () => { |
||||
return http.post<any>(`/hosts/tool/log`, { type: 'supervisord' }); |
||||
}; |
||||
|
||||
export const InitSupervisor = (req: HostTool.SupersivorInit) => { |
||||
return http.post<any>(`/hosts/tool/init`, req); |
||||
}; |
||||
|
||||
export const OperateSupervisorProcess = (req: HostTool.SupersivorProcess) => { |
||||
return http.post<any>(`/hosts/tool/supervisor/process`, req); |
||||
}; |
@ -0,0 +1,20 @@
|
||||
<template> |
||||
<div> |
||||
<RouterButton :buttons="buttons" /> |
||||
<LayoutContent> |
||||
<router-view></router-view> |
||||
</LayoutContent> |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import i18n from '@/lang'; |
||||
import RouterButton from '@/components/router-button/index.vue'; |
||||
|
||||
const buttons = [ |
||||
{ |
||||
label: i18n.global.t('menu.supervisor'), |
||||
path: '/hosts/tool/supervisor', |
||||
}, |
||||
]; |
||||
</script> |
@ -0,0 +1,28 @@
|
||||
<template> |
||||
<LayoutContent :title="$t('tool.supervisor.config')" :reload="true"> |
||||
<template #buttons> |
||||
<el-button type="primary" :plain="activeName !== '1'" @click="changeTab('1')"> |
||||
{{ $t('nginx.configResource') }} |
||||
</el-button> |
||||
<el-button type="primary" :plain="activeName !== '2'" @click="changeTab('2')"> |
||||
{{ $t('website.log') }} |
||||
</el-button> |
||||
</template> |
||||
<template #main> |
||||
<Source v-if="activeName === '1'"></Source> |
||||
<Log v-if="activeName === '2'"></Log> |
||||
</template> |
||||
</LayoutContent> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { ref } from 'vue'; |
||||
import Source from './source/index.vue'; |
||||
import Log from './log/index.vue'; |
||||
|
||||
const activeName = ref('1'); |
||||
|
||||
const changeTab = (index: string) => { |
||||
activeName.value = index; |
||||
}; |
||||
</script> |
@ -0,0 +1,40 @@
|
||||
<template> |
||||
<div v-loading="loading"> |
||||
<codemirror |
||||
:autofocus="true" |
||||
:placeholder="$t('commons.msg.noneData')" |
||||
:indent-with-tab="true" |
||||
:tabSize="4" |
||||
style="height: calc(100vh - 375px)" |
||||
:lineWrapping="true" |
||||
:matchBrackets="true" |
||||
theme="cobalt" |
||||
:styleActiveLine="true" |
||||
:extensions="extensions" |
||||
v-model="content" |
||||
/> |
||||
</div> |
||||
</template> |
||||
<script lang="ts" setup> |
||||
import { onMounted, ref } from 'vue'; |
||||
import { Codemirror } from 'vue-codemirror'; |
||||
import { GetSupervisorLog } from '@/api/modules/host-tool'; |
||||
import { javascript } from '@codemirror/lang-javascript'; |
||||
import { oneDark } from '@codemirror/theme-one-dark'; |
||||
|
||||
const extensions = [javascript(), oneDark]; |
||||
|
||||
let content = ref(''); |
||||
let loading = ref(false); |
||||
|
||||
const getConfig = async () => { |
||||
const res = await GetSupervisorLog(); |
||||
content.value = res.data; |
||||
}; |
||||
|
||||
onMounted(() => { |
||||
getConfig(); |
||||
}); |
||||
</script> |
||||
|
||||
<style scoped></style> |
@ -0,0 +1,63 @@
|
||||
<template> |
||||
<div v-loading="loading"> |
||||
<codemirror |
||||
:autofocus="true" |
||||
:placeholder="$t('commons.msg.noneData')" |
||||
:indent-with-tab="true" |
||||
:tabSize="4" |
||||
style="height: calc(100vh - 375px)" |
||||
:lineWrapping="true" |
||||
:matchBrackets="true" |
||||
theme="cobalt" |
||||
:styleActiveLine="true" |
||||
:extensions="extensions" |
||||
:mode="'text/x-ini'" |
||||
v-model="content" |
||||
/> |
||||
<div style="margin-top: 10px"> |
||||
<el-button type="primary" @click="submit()" :disabled="loading"> |
||||
{{ $t('commons.button.save') }} |
||||
</el-button> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script lang="ts" setup> |
||||
import { onMounted, ref } from 'vue'; |
||||
import { Codemirror } from 'vue-codemirror'; |
||||
import { StreamLanguage } from '@codemirror/language'; |
||||
import { properties } from '@codemirror/legacy-modes/mode/properties'; |
||||
import { oneDark } from '@codemirror/theme-one-dark'; |
||||
import i18n from '@/lang'; |
||||
import { MsgSuccess } from '@/utils/message'; |
||||
import { OperateSupervisorConfig } from '@/api/modules/host-tool'; |
||||
|
||||
const extensions = [StreamLanguage.define(properties), oneDark]; |
||||
|
||||
let data = ref(); |
||||
let content = ref(''); |
||||
let loading = ref(false); |
||||
|
||||
const submit = () => { |
||||
loading.value = true; |
||||
OperateSupervisorConfig({ type: 'supervisord', operate: 'set', content: content.value }) |
||||
.then(() => { |
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess')); |
||||
getConfig(); |
||||
}) |
||||
.finally(() => { |
||||
loading.value = false; |
||||
}); |
||||
}; |
||||
|
||||
const getConfig = async () => { |
||||
const res = await OperateSupervisorConfig({ type: 'supervisord', operate: 'get' }); |
||||
data.value = res.data; |
||||
content.value = data.value.content; |
||||
}; |
||||
|
||||
onMounted(() => { |
||||
getConfig(); |
||||
}); |
||||
</script> |
||||
|
||||
<style scoped></style> |
@ -0,0 +1,108 @@
|
||||
<template> |
||||
<el-drawer :close-on-click-modal="false" v-model="open" size="30%"> |
||||
<template #header> |
||||
<DrawerHeader :header="$t('commons.button.create')" :back="handleClose" /> |
||||
</template> |
||||
<el-row v-loading="loading"> |
||||
<el-col :span="22" :offset="1"> |
||||
<el-form ref="processForm" label-position="top" :model="process" label-width="100px" :rules="rules"> |
||||
<el-form-item :label="$t('commons.table.name')" prop="name"> |
||||
<el-input v-model.trim="process.name"></el-input> |
||||
</el-form-item> |
||||
<el-form-item :label="$t('tool.supervisor.user')" prop="user"> |
||||
<el-input v-model.trim="process.user"></el-input> |
||||
</el-form-item> |
||||
<el-form-item :label="$t('tool.supervisor.dir')" prop="dir"> |
||||
<el-input v-model.trim="process.dir"> |
||||
<template #prepend><FileList @choose="getPath" :dir="true"></FileList></template> |
||||
</el-input> |
||||
</el-form-item> |
||||
<el-form-item :label="$t('tool.supervisor.command')" prop="command"> |
||||
<el-input v-model.trim="process.command"></el-input> |
||||
</el-form-item> |
||||
<el-form-item :label="$t('tool.supervisor.numprocs')" prop="numprocs"> |
||||
<el-input v-model.trim="process.numprocs"></el-input> |
||||
</el-form-item> |
||||
</el-form> |
||||
</el-col> |
||||
</el-row> |
||||
<template #footer> |
||||
<span class="dialog-footer"> |
||||
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button> |
||||
<el-button type="primary" @click="submit(processForm)" :disabled="loading"> |
||||
{{ $t('commons.button.confirm') }} |
||||
</el-button> |
||||
</span> |
||||
</template> |
||||
</el-drawer> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { OperateSupervisorProcess } from '@/api/modules/host-tool'; |
||||
import { Rules } from '@/global/form-rules'; |
||||
import FileList from '@/components/file-list/index.vue'; |
||||
import i18n from '@/lang'; |
||||
import { FormInstance } from 'element-plus'; |
||||
import { ref } from 'vue'; |
||||
import { MsgSuccess } from '@/utils/message'; |
||||
|
||||
const open = ref(false); |
||||
const loading = ref(false); |
||||
const processForm = ref<FormInstance>(); |
||||
const rules = ref({ |
||||
name: [Rules.requiredInput], |
||||
dir: [Rules.requiredInput], |
||||
command: [Rules.requiredInput], |
||||
user: [Rules.requiredInput], |
||||
numprocs: [Rules.requiredInput], |
||||
}); |
||||
const process = ref({ |
||||
operate: 'create', |
||||
name: '', |
||||
command: '', |
||||
user: '', |
||||
dir: '', |
||||
numprocs: '1', |
||||
}); |
||||
|
||||
const em = defineEmits(['close']); |
||||
const handleClose = () => { |
||||
open.value = false; |
||||
resetForm(); |
||||
em('close', open); |
||||
}; |
||||
|
||||
const getPath = (path: string) => { |
||||
process.value.dir = path; |
||||
}; |
||||
|
||||
const resetForm = () => { |
||||
processForm.value?.resetFields(); |
||||
}; |
||||
|
||||
const acceptParams = () => { |
||||
open.value = true; |
||||
}; |
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => { |
||||
if (!formEl) return; |
||||
await formEl.validate((valid) => { |
||||
if (!valid) { |
||||
return; |
||||
} |
||||
loading.value = true; |
||||
OperateSupervisorProcess(process.value) |
||||
.then(() => { |
||||
open.value = false; |
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess')); |
||||
}) |
||||
.finally(() => { |
||||
loading.value = false; |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
defineExpose({ |
||||
acceptParams, |
||||
}); |
||||
</script> |
@ -0,0 +1,48 @@
|
||||
<template> |
||||
<div> |
||||
<ToolRouter /> |
||||
<LayoutContent :title="$t('tool.supervisor.list')" v-loading="loading"> |
||||
<template #app> |
||||
<SuperVisorStatus @setting="setting" v-model:loading="loading" @is-exist="isExist" /> |
||||
</template> |
||||
<template v-if="isExistSuperVisor && !setSuperVisor" #toolbar> |
||||
<el-button type="primary" @click="openCreate"> |
||||
{{ $t('commons.button.create') + $t('tool.supervisor.list') }} |
||||
</el-button> |
||||
</template> |
||||
<template #main v-if="isExistSuperVisor && !setSuperVisor"> |
||||
<ComplexTable></ComplexTable> |
||||
</template> |
||||
<ConfigSuperVisor v-if="setSuperVisor" /> |
||||
</LayoutContent> |
||||
<Create ref="createRef"></Create> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
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 Create from './create/index.vue'; |
||||
|
||||
const loading = ref(false); |
||||
const setSuperVisor = ref(false); |
||||
const isExistSuperVisor = ref(false); |
||||
const createRef = ref(); |
||||
|
||||
const setting = () => { |
||||
setSuperVisor.value = true; |
||||
}; |
||||
|
||||
const isExist = (isExist: boolean) => { |
||||
isExistSuperVisor.value = isExist; |
||||
}; |
||||
|
||||
const openCreate = () => { |
||||
createRef.value.acceptParams(); |
||||
}; |
||||
|
||||
onMounted(() => {}); |
||||
</script> |
@ -0,0 +1,140 @@
|
||||
<template> |
||||
<div> |
||||
<div class="app-status tool-status" v-if="data.isExist"> |
||||
<el-card> |
||||
<div> |
||||
<el-tag effect="dark" type="success">{{ 'Supervisor' }}</el-tag> |
||||
<Status class="status-content" :key="data.status" :status="data.status"></Status> |
||||
<el-tag class="status-content">{{ $t('app.version') }}:{{ data.version }}</el-tag> |
||||
<span class="buttons"> |
||||
<el-button type="primary" v-if="data.status != 'running'" link @click="onOperate('start')"> |
||||
{{ $t('app.start') }} |
||||
</el-button> |
||||
<el-button type="primary" v-if="data.status == 'running'" link @click="onOperate('stop')"> |
||||
{{ $t('app.stop') }} |
||||
</el-button> |
||||
<el-divider direction="vertical" /> |
||||
<el-button type="primary" link @click="onOperate('restart')"> |
||||
{{ $t('app.restart') }} |
||||
</el-button> |
||||
<el-divider direction="vertical" /> |
||||
<el-button |
||||
type="primary" |
||||
link |
||||
:disabled="data.status !== 'running' || !data.ctlExist" |
||||
@click="setting" |
||||
> |
||||
{{ $t('commons.button.set') }} |
||||
</el-button> |
||||
</span> |
||||
</div> |
||||
</el-card> |
||||
</div> |
||||
<LayoutContent :title="$t('tool.supervisor.list')" :divider="true" v-if="!data.isExist || !data.ctlExist"> |
||||
<template #main> |
||||
<div class="app-warn"> |
||||
<div> |
||||
<span v-if="!data.isExist">{{ $t('tool.supervisor.notSupport') }}</span> |
||||
<span v-if="!data.ctlExist">{{ $t('tool.supervisor.notSupportCrl') }}</span> |
||||
<span @click="toDoc()"> |
||||
<el-icon><Position /></el-icon> |
||||
{{ $t('firewall.quickJump') }} |
||||
</span> |
||||
<div> |
||||
<img src="@/assets/images/no_app.svg" /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</LayoutContent> |
||||
<InitPage ref="initRef" @close="getStatus"></InitPage> |
||||
</div> |
||||
</template> |
||||
<script lang="ts" setup> |
||||
import { GetSupervisorStatus, OperateSupervisor } from '@/api/modules/host-tool'; |
||||
import { onMounted, reactive, ref } from 'vue'; |
||||
import Status from '@/components/status/index.vue'; |
||||
import { ElMessageBox } from 'element-plus'; |
||||
import i18n from '@/lang'; |
||||
import { MsgSuccess } from '@/utils/message'; |
||||
import { HostTool } from '@/api/interface/host-tool'; |
||||
import InitPage from './init/index.vue'; |
||||
|
||||
let operateReq = reactive({ |
||||
installId: 0, |
||||
operate: '', |
||||
}); |
||||
const initRef = ref(); |
||||
const data = ref({ |
||||
isExist: false, |
||||
version: '', |
||||
status: 'running', |
||||
init: false, |
||||
configPath: '', |
||||
ctlExist: false, |
||||
}); |
||||
|
||||
const em = defineEmits(['setting', 'isExist', 'before', 'update:loading', 'update:maskShow']); |
||||
|
||||
const setting = () => { |
||||
em('setting', false); |
||||
}; |
||||
|
||||
const toDoc = async () => { |
||||
window.open('https://1panel.cn/docs/user_manual/hosts/firewall/', '_blank'); |
||||
}; |
||||
|
||||
const onOperate = async (operation: string) => { |
||||
operateReq.operate = operation; |
||||
ElMessageBox.confirm( |
||||
i18n.global.t('tool.supervisor.operatorHelper', [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(() => { |
||||
em('update:loading', true); |
||||
em('before'); |
||||
OperateSupervisor(operation) |
||||
.then(() => { |
||||
getStatus(); |
||||
em('update:loading', false); |
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); |
||||
}) |
||||
.catch(() => { |
||||
em('update:loading', false); |
||||
}); |
||||
}) |
||||
.catch(() => {}); |
||||
}; |
||||
|
||||
const getStatus = async () => { |
||||
try { |
||||
em('update:loading', true); |
||||
const res = await GetSupervisorStatus(); |
||||
data.value = res.data.config as HostTool.Supersivor; |
||||
if (!data.value.isExist || !data.value.ctlExist) { |
||||
em('isExist', false); |
||||
} else { |
||||
em('isExist', true); |
||||
} |
||||
if (data.value.init) { |
||||
initRef.value.acceptParams(data.value.configPath); |
||||
} |
||||
} catch (error) {} |
||||
em('update:loading', false); |
||||
}; |
||||
|
||||
onMounted(() => { |
||||
getStatus(); |
||||
}); |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.tool-status { |
||||
margin-top: 20px; |
||||
} |
||||
</style> |
@ -0,0 +1,78 @@
|
||||
<template> |
||||
<el-drawer :close-on-click-modal="false" v-model="open" size="30%" :show-close="false"> |
||||
<template #header> |
||||
<span>{{ $t('commons.button.init') }}</span> |
||||
</template> |
||||
<el-row v-loading="loading"> |
||||
<el-col :span="22" :offset="1"> |
||||
<el-form ref="initForm" label-position="top" :model="initModel" label-width="100px" :rules="rules"> |
||||
<el-form-item :label="$t('tool.supervisor.primaryConfig')" prop="primaryConfig"> |
||||
<el-input v-model.trim="initModel.primaryConfig"></el-input> |
||||
</el-form-item> |
||||
<el-alert |
||||
:title="$t('tool.supervisor.initWarn')" |
||||
class="common-prompt" |
||||
:closable="false" |
||||
type="error" |
||||
/> |
||||
</el-form> |
||||
</el-col> |
||||
</el-row> |
||||
<template #footer> |
||||
<span class="dialog-footer"> |
||||
<el-button type="primary" @click="submit(initForm)" :disabled="loading"> |
||||
{{ $t('commons.button.confirm') }} |
||||
</el-button> |
||||
</span> |
||||
</template> |
||||
</el-drawer> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { InitSupervisor } from '@/api/modules/host-tool'; |
||||
import { Rules } from '@/global/form-rules'; |
||||
import i18n from '@/lang'; |
||||
import { FormInstance } from 'element-plus'; |
||||
import { ref } from 'vue'; |
||||
import { MsgSuccess } from '@/utils/message'; |
||||
|
||||
const open = ref(false); |
||||
const loading = ref(false); |
||||
const initForm = ref<FormInstance>(); |
||||
const rules = ref({ |
||||
primaryConfig: [Rules.requiredInput], |
||||
}); |
||||
const initModel = ref({ |
||||
primaryConfig: '', |
||||
}); |
||||
|
||||
const em = defineEmits(['close']); |
||||
|
||||
const acceptParams = (primaryConfig: string) => { |
||||
initModel.value.primaryConfig = primaryConfig; |
||||
open.value = true; |
||||
}; |
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => { |
||||
if (!formEl) return; |
||||
await formEl.validate((valid) => { |
||||
if (!valid) { |
||||
return; |
||||
} |
||||
loading.value = true; |
||||
InitSupervisor({ type: 'supervisord', configPath: initModel.value.primaryConfig }) |
||||
.then(() => { |
||||
open.value = false; |
||||
em('close', true); |
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess')); |
||||
}) |
||||
.finally(() => { |
||||
loading.value = false; |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
defineExpose({ |
||||
acceptParams, |
||||
}); |
||||
</script> |
Loading…
Reference in new issue