zhengkunwang 1 year ago committed by GitHub
parent
commit
b59ccc52ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      Makefile
  2. 2
      backend/app/api/v1/entry.go
  3. 174
      backend/app/api/v1/host_tool.go
  4. 34
      backend/app/dto/request/host_tool.go
  5. 22
      backend/app/dto/response/host_tool.go
  6. 259
      backend/app/service/host_tool.go
  7. 6
      backend/constant/host_tool.go
  8. 4
      backend/i18n/lang/en.yaml
  9. 4
      backend/i18n/lang/zh-Hant.yaml
  10. 4
      backend/i18n/lang/zh.yaml
  11. 7
      backend/router/ro_host.go
  12. 36
      backend/utils/ini_conf/ini.go
  13. 62
      backend/utils/systemctl/systemctl.go
  14. 410
      cmd/server/docs/docs.go
  15. 410
      cmd/server/docs/swagger.json
  16. 263
      cmd/server/docs/swagger.yaml
  17. 43
      frontend/src/api/interface/host-tool.ts
  18. 26
      frontend/src/api/modules/host-tool.ts
  19. 17
      frontend/src/lang/modules/en.ts
  20. 17
      frontend/src/lang/modules/tw.ts
  21. 17
      frontend/src/lang/modules/zh.ts
  22. 11
      frontend/src/routers/modules/host.ts
  23. 20
      frontend/src/views/host/tool/index.vue
  24. 28
      frontend/src/views/host/tool/supervisor/config/index.vue
  25. 40
      frontend/src/views/host/tool/supervisor/config/log/index.vue
  26. 63
      frontend/src/views/host/tool/supervisor/config/source/index.vue
  27. 108
      frontend/src/views/host/tool/supervisor/create/index.vue
  28. 48
      frontend/src/views/host/tool/supervisor/index.vue
  29. 140
      frontend/src/views/host/tool/supervisor/status/index.vue
  30. 78
      frontend/src/views/host/tool/supervisor/status/init/index.vue

4
Makefile

@ -29,10 +29,6 @@ build_backend_on_darwin:
cd $(SERVER_PATH) \
&& GOOS=linux GOARCH=amd64 $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_backend_on_archlinux:
cd $(SERVER_PATH) \
&& GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_all: build_frontend build_backend_on_linux
build_on_local: clean_assets build_frontend build_backend_on_darwin upx_bin

2
backend/app/api/v1/entry.go

@ -51,4 +51,6 @@ var (
runtimeService = service.NewRuntimeService()
processService = service.NewIProcessService()
hostToolService = service.NewIHostToolService()
)

174
backend/app/api/v1/host_tool.go

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

34
backend/app/dto/request/host_tool.go

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

22
backend/app/dto/response/host_tool.go

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

259
backend/app/service/host_tool.go

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

6
backend/constant/host_tool.go

@ -0,0 +1,6 @@
package constant
const (
Supervisord = "supervisord"
SupervisorConfigPath = "SupervisorConfigPath"
)

4
backend/i18n/lang/en.yaml

@ -97,5 +97,7 @@ ErrDelWithWebsite: "The operating environment has been associated with a website
#setting
ErrBackupInUsed: "The backup account is already being used in a cronjob and cannot be deleted."
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"

4
backend/i18n/lang/zh-Hant.yaml

@ -97,5 +97,7 @@ ErrDelWithWebsite: "運行環境已經關聯網站,無法刪除"
#setting
ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除"
ErrOSSConn: "無法成功請求最新版本,請檢查伺服器是否能夠連接到外部網絡環境。"
#tool
ErrConfigNotFound: "配置文件不存在"

4
backend/i18n/lang/zh.yaml

@ -97,5 +97,7 @@ ErrDelWithWebsite: "运行环境已经关联网站,无法删除"
#setting
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
ErrOSSConn: "无法成功请求最新版本,请检查服务器是否能够连接到外部网络环境。"
#tool
ErrConfigNotFound: "配置文件不存在"

7
backend/router/ro_host.go

@ -47,5 +47,12 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
hostRouter.POST("/command/del", baseApi.DeleteCommand)
hostRouter.POST("/command/search", baseApi.SearchCommand)
hostRouter.POST("/command/update", baseApi.UpdateCommand)
hostRouter.POST("/tool", baseApi.GetToolStatus)
hostRouter.POST("/tool/init", baseApi.InitToolConfig)
hostRouter.POST("/tool/operate", baseApi.OperateTool)
hostRouter.POST("/tool/config", baseApi.OperateToolConfig)
hostRouter.POST("/tool/log", baseApi.GetToolLog)
hostRouter.POST("/tool/supervisor/process", baseApi.OperateProcess)
}
}

36
backend/utils/ini_conf/ini.go

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

62
backend/utils/systemctl/systemctl.go

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

410
cmd/server/docs/docs.go

@ -3910,6 +3910,21 @@ const docTemplate = `{
}
}
},
"/databases/load/:from": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "从服务器获取",
"tags": [
"Database Mysql"
],
"summary": "Load mysql database from remote",
"responses": {}
}
},
"/databases/options": {
"get": {
"security": [
@ -4274,6 +4289,28 @@ const docTemplate = `{
}
}
},
"/databases/remote/:name": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取远程数据库",
"tags": [
"Database"
],
"summary": "Get remote databases",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.RemoteDBInfo"
}
}
}
}
},
"/databases/remote/del": {
"post": {
"security": [
@ -4332,7 +4369,7 @@ const docTemplate = `{
"ApiKeyAuth": []
}
],
"description": "获取快速命令列表",
"description": "获取远程数据库列表",
"tags": [
"Database"
],
@ -5923,6 +5960,241 @@ const docTemplate = `{
}
}
},
"/host/tool": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取主机工具状态",
"consumes": [
"application/json"
],
"tags": [
"Host tool"
],
"summary": "Get tool",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.HostToolReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/host/tool/config": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "操作主机工具配置文件",
"consumes": [
"application/json"
],
"tags": [
"Host tool"
],
"summary": "Get tool config",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.HostToolConfig"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"operate"
],
"formatEN": "[operate] tool config",
"formatZH": "[operate] 主机工具配置文件 ",
"paramKeys": []
}
}
},
"/host/tool/create": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "创建主机工具配置",
"consumes": [
"application/json"
],
"tags": [
"Host tool"
],
"summary": "Create Host tool Config",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.HostToolCreate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"type"
],
"formatEN": "create [type] config",
"formatZH": "创建 [type] 配置",
"paramKeys": []
}
}
},
"/host/tool/log": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取主机工具日志",
"consumes": [
"application/json"
],
"tags": [
"Host tool"
],
"summary": "Get tool",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.HostToolLogReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/host/tool/operate": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "操作主机工具",
"consumes": [
"application/json"
],
"tags": [
"Host tool"
],
"summary": "Operate tool",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.HostToolReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"operate",
"type"
],
"formatEN": "[operate] [type]",
"formatZH": "[operate] [type] ",
"paramKeys": []
}
}
},
"/host/tool/supervisor/process": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "操作守护进程",
"consumes": [
"application/json"
],
"tags": [
"Host tool"
],
"summary": "Create Supervisor process",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.SupervisorProcessConfig"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"operate"
],
"formatEN": "[operate] process",
"formatZH": "[operate] 守护进程 ",
"paramKeys": []
}
}
},
"/hosts": {
"post": {
"security": [
@ -13317,6 +13589,41 @@ const docTemplate = `{
}
}
},
"dto.RemoteDBInfo": {
"type": "object",
"properties": {
"address": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"description": {
"type": "string"
},
"from": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"password": {
"type": "string"
},
"port": {
"type": "integer"
},
"username": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"dto.RemoteDBOption": {
"type": "object",
"properties": {
@ -14447,6 +14754,9 @@ const docTemplate = `{
"operate"
],
"properties": {
"backup": {
"type": "boolean"
},
"backupId": {
"type": "integer"
},
@ -14856,6 +15166,81 @@ const docTemplate = `{
}
}
},
"request.HostToolConfig": {
"type": "object",
"required": [
"type"
],
"properties": {
"content": {
"type": "string"
},
"operate": {
"type": "string",
"enum": [
"get",
"set"
]
},
"type": {
"type": "string",
"enum": [
"supervisord"
]
}
}
},
"request.HostToolCreate": {
"type": "object",
"required": [
"type"
],
"properties": {
"configPath": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"request.HostToolLogReq": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"supervisord"
]
}
}
},
"request.HostToolReq": {
"type": "object",
"required": [
"type"
],
"properties": {
"operate": {
"type": "string",
"enum": [
"status",
"restart",
"start",
"stop"
]
},
"type": {
"type": "string",
"enum": [
"supervisord"
]
}
}
},
"request.NewAppInstall": {
"type": "object",
"properties": {
@ -15288,6 +15673,29 @@ const docTemplate = `{
}
}
},
"request.SupervisorProcessConfig": {
"type": "object",
"properties": {
"command": {
"type": "string"
},
"dir": {
"type": "string"
},
"name": {
"type": "string"
},
"numprocs": {
"type": "string"
},
"operate": {
"type": "string"
},
"user": {
"type": "string"
}
}
},
"request.WebsiteAcmeAccountCreate": {
"type": "object",
"required": [

410
cmd/server/docs/swagger.json

@ -3903,6 +3903,21 @@
}
}
},
"/databases/load/:from": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "从服务器获取",
"tags": [
"Database Mysql"
],
"summary": "Load mysql database from remote",
"responses": {}
}
},
"/databases/options": {
"get": {
"security": [
@ -4267,6 +4282,28 @@
}
}
},
"/databases/remote/:name": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取远程数据库",
"tags": [
"Database"
],
"summary": "Get remote databases",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.RemoteDBInfo"
}
}
}
}
},
"/databases/remote/del": {
"post": {
"security": [
@ -4325,7 +4362,7 @@
"ApiKeyAuth": []
}
],
"description": "获取快速命令列表",
"description": "获取远程数据库列表",
"tags": [
"Database"
],
@ -5916,6 +5953,241 @@
}
}
},
"/host/tool": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取主机工具状态",
"consumes": [
"application/json"
],
"tags": [
"Host tool"
],
"summary": "Get tool",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.HostToolReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/host/tool/config": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "操作主机工具配置文件",
"consumes": [
"application/json"
],
"tags": [
"Host tool"
],
"summary": "Get tool config",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.HostToolConfig"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"operate"
],
"formatEN": "[operate] tool config",
"formatZH": "[operate] 主机工具配置文件 ",
"paramKeys": []
}
}
},
"/host/tool/create": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "创建主机工具配置",
"consumes": [
"application/json"
],
"tags": [
"Host tool"
],
"summary": "Create Host tool Config",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.HostToolCreate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"type"
],
"formatEN": "create [type] config",
"formatZH": "创建 [type] 配置",
"paramKeys": []
}
}
},
"/host/tool/log": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取主机工具日志",
"consumes": [
"application/json"
],
"tags": [
"Host tool"
],
"summary": "Get tool",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.HostToolLogReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/host/tool/operate": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "操作主机工具",
"consumes": [
"application/json"
],
"tags": [
"Host tool"
],
"summary": "Operate tool",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.HostToolReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"operate",
"type"
],
"formatEN": "[operate] [type]",
"formatZH": "[operate] [type] ",
"paramKeys": []
}
}
},
"/host/tool/supervisor/process": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "操作守护进程",
"consumes": [
"application/json"
],
"tags": [
"Host tool"
],
"summary": "Create Supervisor process",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.SupervisorProcessConfig"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"operate"
],
"formatEN": "[operate] process",
"formatZH": "[operate] 守护进程 ",
"paramKeys": []
}
}
},
"/hosts": {
"post": {
"security": [
@ -13310,6 +13582,41 @@
}
}
},
"dto.RemoteDBInfo": {
"type": "object",
"properties": {
"address": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"description": {
"type": "string"
},
"from": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"password": {
"type": "string"
},
"port": {
"type": "integer"
},
"username": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"dto.RemoteDBOption": {
"type": "object",
"properties": {
@ -14440,6 +14747,9 @@
"operate"
],
"properties": {
"backup": {
"type": "boolean"
},
"backupId": {
"type": "integer"
},
@ -14849,6 +15159,81 @@
}
}
},
"request.HostToolConfig": {
"type": "object",
"required": [
"type"
],
"properties": {
"content": {
"type": "string"
},
"operate": {
"type": "string",
"enum": [
"get",
"set"
]
},
"type": {
"type": "string",
"enum": [
"supervisord"
]
}
}
},
"request.HostToolCreate": {
"type": "object",
"required": [
"type"
],
"properties": {
"configPath": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"request.HostToolLogReq": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"supervisord"
]
}
}
},
"request.HostToolReq": {
"type": "object",
"required": [
"type"
],
"properties": {
"operate": {
"type": "string",
"enum": [
"status",
"restart",
"start",
"stop"
]
},
"type": {
"type": "string",
"enum": [
"supervisord"
]
}
}
},
"request.NewAppInstall": {
"type": "object",
"properties": {
@ -15281,6 +15666,29 @@
}
}
},
"request.SupervisorProcessConfig": {
"type": "object",
"properties": {
"command": {
"type": "string"
},
"dir": {
"type": "string"
},
"name": {
"type": "string"
},
"numprocs": {
"type": "string"
},
"operate": {
"type": "string"
},
"user": {
"type": "string"
}
}
},
"request.WebsiteAcmeAccountCreate": {
"type": "object",
"required": [

263
cmd/server/docs/swagger.yaml

@ -1574,6 +1574,29 @@ definitions:
- username
- version
type: object
dto.RemoteDBInfo:
properties:
address:
type: string
createdAt:
type: string
description:
type: string
from:
type: string
id:
type: integer
name:
type: string
password:
type: string
port:
type: integer
username:
type: string
version:
type: string
type: object
dto.RemoteDBOption:
properties:
address:
@ -2320,6 +2343,8 @@ definitions:
type: object
request.AppInstalledOperate:
properties:
backup:
type: boolean
backupId:
type: integer
deleteBackup:
@ -2597,6 +2622,56 @@ definitions:
- path
- url
type: object
request.HostToolConfig:
properties:
content:
type: string
operate:
enum:
- get
- set
type: string
type:
enum:
- supervisord
type: string
required:
- type
type: object
request.HostToolCreate:
properties:
configPath:
type: string
type:
type: string
required:
- type
type: object
request.HostToolLogReq:
properties:
type:
enum:
- supervisord
type: string
required:
- type
type: object
request.HostToolReq:
properties:
operate:
enum:
- status
- restart
- start
- stop
type: string
type:
enum:
- supervisord
type: string
required:
- type
type: object
request.NewAppInstall:
properties:
advanced:
@ -2888,6 +2963,21 @@ definitions:
- pageSize
- path
type: object
request.SupervisorProcessConfig:
properties:
command:
type: string
dir:
type: string
name:
type: string
numprocs:
type: string
operate:
type: string
user:
type: string
type: object
request.WebsiteAcmeAccountCreate:
properties:
email:
@ -6215,6 +6305,15 @@ paths:
formatEN: The description of the mysql database [name] is modified => [description]
formatZH: mysql 数据库 [name] 描述信息修改 [description]
paramKeys: []
/databases/load/:from:
get:
description: 从服务器获取
responses: {}
security:
- ApiKeyAuth: []
summary: Load mysql database from remote
tags:
- Database Mysql
/databases/options:
get:
consumes:
@ -6444,6 +6543,19 @@ paths:
formatEN: create remote database [name][type]
formatZH: 创建远程数据库 [name][type]
paramKeys: []
/databases/remote/:name:
get:
description: 获取远程数据库
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.RemoteDBInfo'
security:
- ApiKeyAuth: []
summary: Get remote databases
tags:
- Database
/databases/remote/del:
post:
consumes:
@ -6479,7 +6591,7 @@ paths:
paramKeys: []
/databases/remote/list/:type:
get:
description: 获取快速命令列表
description: 获取远程数据库列表
responses:
"200":
description: OK
@ -7495,6 +7607,155 @@ paths:
formatEN: update SSH setting [key] => [value]
formatZH: 修改 SSH 配置 [key] => [value]
paramKeys: []
/host/tool:
post:
consumes:
- application/json
description: 获取主机工具状态
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolReq'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Get tool
tags:
- Host tool
/host/tool/config:
post:
consumes:
- application/json
description: 操作主机工具配置文件
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolConfig'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Get tool config
tags:
- Host tool
x-panel-log:
BeforeFuntions: []
bodyKeys:
- operate
formatEN: '[operate] tool config'
formatZH: '[operate] 主机工具配置文件 '
paramKeys: []
/host/tool/create:
post:
consumes:
- application/json
description: 创建主机工具配置
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolCreate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Create Host tool Config
tags:
- Host tool
x-panel-log:
BeforeFuntions: []
bodyKeys:
- type
formatEN: create [type] config
formatZH: 创建 [type] 配置
paramKeys: []
/host/tool/log:
post:
consumes:
- application/json
description: 获取主机工具日志
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolLogReq'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Get tool
tags:
- Host tool
/host/tool/operate:
post:
consumes:
- application/json
description: 操作主机工具
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolReq'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Operate tool
tags:
- Host tool
x-panel-log:
BeforeFuntions: []
bodyKeys:
- operate
- type
formatEN: '[operate] [type]'
formatZH: '[operate] [type] '
paramKeys: []
/host/tool/supervisor/process:
post:
consumes:
- application/json
description: 操作守护进程
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.SupervisorProcessConfig'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Create Supervisor process
tags:
- Host tool
x-panel-log:
BeforeFuntions: []
bodyKeys:
- operate
formatEN: '[operate] process'
formatZH: '[operate] 守护进程 '
paramKeys: []
/hosts:
post:
consumes:

43
frontend/src/api/interface/host-tool.ts

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

26
frontend/src/api/modules/host-tool.ts

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

17
frontend/src/lang/modules/en.ts

@ -254,6 +254,7 @@ const message = {
processManage: 'Process',
process: 'Process',
network: 'Network',
supervisor: 'Supervisor',
},
home: {
overview: 'Overview',
@ -1651,6 +1652,22 @@ const message = {
stopProcessWarn: 'Are you sure you want to end this process (PID:{0})? ',
processName: 'ProcessName',
},
tool: {
supervisor: {
notSupport: 'Supervisor is not detected, please refer to the official document for installation',
list: 'Daemon process',
config: 'Supervisor configuration',
primaryConfig: 'Main configuration file location',
notSupportCrl: 'The supervisorctl is not detected, please refer to the official document for installation',
user: 'start user',
command: 'start command',
dir: 'run directory',
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? ',
},
},
};
export default {

17
frontend/src/lang/modules/tw.ts

@ -251,6 +251,7 @@ const message = {
processManage: '進程管理',
process: '進程',
network: '網絡',
supervisor: '進程守護',
},
home: {
overview: '概覽',
@ -1568,6 +1569,22 @@ const message = {
stopProcessWarn: '是否確定結束此進程 (PID:{0})',
processName: '進程名稱',
},
tool: {
supervisor: {
notSupport: '未檢測到 Supervisor請參考官方文檔進行安裝',
list: '守護進程',
config: 'Supervisor 配置',
primaryConfig: '主配置文件位置',
notSupportCrl: '未檢測到 supervisorctl請參考官方文檔進行安裝',
user: '啟動用戶',
command: '啟動命令',
dir: '運行目錄',
numprocs: '進程數量',
initWarn:
'由於無法兼容原有配置初始化 Supervisor 會修改配置文件的 files 參數導致已有的進程全部停止請提前確認風險修改後的進程配置文件夾在 <1Panel安裝目錄>/1panel/tools/supervisord/supervisor.d ',
operatorHelper: '將對 Supervisor 進行 {0} 操作是否繼續 ',
},
},
};
export default {
...fit2cloudTwLocale,

17
frontend/src/lang/modules/zh.ts

@ -251,6 +251,7 @@ const message = {
processManage: '进程管理',
process: '进程',
network: '网络',
supervisor: '进程守护',
},
home: {
overview: '概览',
@ -1570,6 +1571,22 @@ const message = {
stopProcessWarn: '是否确定结束此进程 (PID:{0})',
processName: '进程名称',
},
tool: {
supervisor: {
notSupport: '未检测到 Supervisor请参考官方文档进行安装',
list: '守护进程',
config: 'Supervisor 配置',
primaryConfig: '主配置文件位置',
notSupportCrl: '未检测到 supervisorctl请参考官方文档进行安装',
user: '启动用户',
command: '启动命令',
dir: '运行目录',
numprocs: '进程数量',
initWarn:
'由于无法兼容原有配置初始化 Supervisor 会修改配置文件的 files 参数导致已有的进程全部停止请提前确认风险修改后的进程配置文件夹在 <1Panel安装目录>/1panel/tools/supervisord/supervisor.d ',
operatorHelper: '将对 Supervisor 进行 {0} 操作是否继续',
},
},
};
export default {
...fit2cloudZhLocale,

11
frontend/src/routers/modules/host.ts

@ -90,6 +90,17 @@ const hostRouter = {
requiresAuth: false,
},
},
{
path: '/hosts/tool/supersivor',
name: 'Supervisor',
component: () => import('@/views/host/tool/supervisor/index.vue'),
meta: {
title: 'menu.supervisor',
activeMenu: '/hosts/tool/supersivor',
keepAlive: true,
requiresAuth: false,
},
},
{
path: '/hosts/ssh/ssh',
name: 'SSH',

20
frontend/src/views/host/tool/index.vue

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

28
frontend/src/views/host/tool/supervisor/config/index.vue

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

40
frontend/src/views/host/tool/supervisor/config/log/index.vue

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

63
frontend/src/views/host/tool/supervisor/config/source/index.vue

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

108
frontend/src/views/host/tool/supervisor/create/index.vue

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

48
frontend/src/views/host/tool/supervisor/index.vue

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

140
frontend/src/views/host/tool/supervisor/status/index.vue

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

78
frontend/src/views/host/tool/supervisor/status/init/index.vue

@ -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…
Cancel
Save