mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 08:00:27 +00:00
Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
342c04b30a | ||
|
|
5ab8377ac1 | ||
|
|
74eb2c8dd6 | ||
|
|
f136d97941 | ||
|
|
a5629e3f42 | ||
|
|
515d6ca500 | ||
|
|
c0ccc0318d | ||
|
|
8196c11010 | ||
|
|
170bd22062 | ||
|
|
097a33c68f | ||
|
|
3be3bf9926 | ||
|
|
038f2b4f54 | ||
|
|
f84e284c20 | ||
|
|
48e75b5916 | ||
|
|
a12ceaee6e | ||
|
|
9050680115 | ||
|
|
0022a9b508 | ||
|
|
3437d62f31 | ||
|
|
dc880835d0 | ||
|
|
1663bf8677 | ||
|
|
aed236cbc9 | ||
|
|
90be74504e | ||
|
|
bba8aab18c | ||
|
|
47d135ecca | ||
|
|
d96fe49993 | ||
|
|
ac396b61ce | ||
|
|
6798d0c7ee | ||
|
|
1490ed7541 | ||
|
|
48b3032de6 | ||
|
|
ed12bec460 | ||
|
|
3c4a1ea614 | ||
|
|
79eb0288b4 | ||
|
|
99775e1eb4 | ||
|
|
db848b41d2 | ||
|
|
b5b11dff86 | ||
|
|
9cf4bfc606 | ||
|
|
221162d44f | ||
|
|
617970a1f8 |
@@ -68,4 +68,6 @@ var (
|
||||
favoriteService = service.NewIFavoriteService()
|
||||
|
||||
websiteCAService = service.NewIWebsiteCAService()
|
||||
|
||||
mcpServerService = service.NewIMcpServerService()
|
||||
)
|
||||
|
||||
@@ -410,11 +410,20 @@ func (b *BaseApi) CheckFile(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err := os.Stat(req.Path); err != nil {
|
||||
helper.SuccessWithData(c, false)
|
||||
fileOp := files.NewFileOp()
|
||||
if fileOp.Stat(req.Path) {
|
||||
helper.SuccessWithData(c, true)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, true)
|
||||
if req.WithInit {
|
||||
if err := fileOp.CreateDir(req.Path, 0644); err != nil {
|
||||
helper.SuccessWithData(c, false)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, true)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, false)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// @Success 200 {object} response.HostToolRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool [post]
|
||||
// @Router /hosts/tool [post]
|
||||
func (b *BaseApi) GetToolStatus(c *gin.Context) {
|
||||
var req request.HostToolReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -36,7 +36,7 @@ func (b *BaseApi) GetToolStatus(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/create [post]
|
||||
// @Router /hosts/tool/create [post]
|
||||
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 [type] 配置","formatEN":"create [type] config"}
|
||||
func (b *BaseApi) InitToolConfig(c *gin.Context) {
|
||||
var req request.HostToolCreate
|
||||
@@ -58,7 +58,7 @@ func (b *BaseApi) InitToolConfig(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/operate [post]
|
||||
// @Router /hosts/tool/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operate","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] [type] ","formatEN":"[operate] [type]"}
|
||||
func (b *BaseApi) OperateTool(c *gin.Context) {
|
||||
var req request.HostToolReq
|
||||
@@ -80,7 +80,7 @@ func (b *BaseApi) OperateTool(c *gin.Context) {
|
||||
// @Success 200 {object} response.HostToolConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/config [post]
|
||||
// @Router /hosts/tool/config [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 主机工具配置文件 ","formatEN":"[operate] tool config"}
|
||||
func (b *BaseApi) OperateToolConfig(c *gin.Context) {
|
||||
var req request.HostToolConfig
|
||||
@@ -103,7 +103,7 @@ func (b *BaseApi) OperateToolConfig(c *gin.Context) {
|
||||
// @Success 200 {string} logContent
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/log [post]
|
||||
// @Router /hosts/tool/log [post]
|
||||
func (b *BaseApi) GetToolLog(c *gin.Context) {
|
||||
var req request.HostToolLogReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -125,7 +125,7 @@ func (b *BaseApi) GetToolLog(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/supervisor/process [post]
|
||||
// @Router /hosts/tool/supervisor/process [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 守护进程 ","formatEN":"[operate] process"}
|
||||
func (b *BaseApi) OperateProcess(c *gin.Context) {
|
||||
var req request.SupervisorProcessConfig
|
||||
@@ -147,7 +147,7 @@ func (b *BaseApi) OperateProcess(c *gin.Context) {
|
||||
// @Success 200 {object} response.SupervisorProcessConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/supervisor/process [get]
|
||||
// @Router /hosts/tool/supervisor/process [get]
|
||||
func (b *BaseApi) GetProcess(c *gin.Context) {
|
||||
configs, err := hostToolService.GetSupervisorProcessConfig()
|
||||
if err != nil {
|
||||
@@ -164,7 +164,7 @@ func (b *BaseApi) GetProcess(c *gin.Context) {
|
||||
// @Success 200 {string} content
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/supervisor/process/file [post]
|
||||
// @Router /hosts/tool/supervisor/process/file [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] Supervisor 进程文件 ","formatEN":"[operate] Supervisor Process Config file"}
|
||||
func (b *BaseApi) GetProcessFile(c *gin.Context) {
|
||||
var req request.SupervisorProcessFileReq
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
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/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary List mcp servers
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerSearch true "request"
|
||||
// @Success 200 {object} response.McpServersRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/search [post]
|
||||
func (b *BaseApi) PageMcpServers(c *gin.Context) {
|
||||
var req request.McpServerSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
list := mcpServerService.Page(req)
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Create mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server [post]
|
||||
func (b *BaseApi) CreateMcpServer(c *gin.Context) {
|
||||
var req request.McpServerCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.Create(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Update mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server/update [post]
|
||||
func (b *BaseApi) UpdateMcpServer(c *gin.Context) {
|
||||
var req request.McpServerUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.Update(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Delete mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server/del [post]
|
||||
func (b *BaseApi) DeleteMcpServer(c *gin.Context) {
|
||||
var req request.McpServerDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.Delete(req.ID)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Operate mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server/op [post]
|
||||
func (b *BaseApi) OperateMcpServer(c *gin.Context) {
|
||||
var req request.McpServerOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.Operate(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Bind Domain for mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpBindDomain true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/domain/bind [post]
|
||||
func (b *BaseApi) BindMcpDomain(c *gin.Context) {
|
||||
var req request.McpBindDomain
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.BindDomain(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Update bind Domain for mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpBindDomainUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/domain/update [post]
|
||||
func (b *BaseApi) UpdateMcpBindDomain(c *gin.Context) {
|
||||
var req request.McpBindDomainUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.UpdateBindDomain(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Get bin Domain for mcp server
|
||||
// @Accept json
|
||||
// @Success 200 {object} response.McpBindDomainRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/domain/get [get]
|
||||
func (b *BaseApi) GetMcpBindDomain(c *gin.Context) {
|
||||
res, err := mcpServerService.GetBindDomain()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// @Success 200 {object} dto.SSHInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/search [post]
|
||||
// @Router /hosts/ssh/search [post]
|
||||
func (b *BaseApi) GetSSHInfo(c *gin.Context) {
|
||||
info, err := sshService.GetSSHInfo()
|
||||
if err != nil {
|
||||
@@ -29,7 +29,7 @@ func (b *BaseApi) GetSSHInfo(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/operate [post]
|
||||
// @Router /hosts/ssh/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] SSH ","formatEN":"[operation] SSH"}
|
||||
func (b *BaseApi) OperateSSH(c *gin.Context) {
|
||||
var req dto.Operate
|
||||
@@ -51,7 +51,7 @@ func (b *BaseApi) OperateSSH(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/update [post]
|
||||
// @Router /hosts/ssh/update [post]
|
||||
// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置 [key] => [value]","formatEN":"update SSH setting [key] => [value]"}
|
||||
func (b *BaseApi) UpdateSSH(c *gin.Context) {
|
||||
var req dto.SSHUpdate
|
||||
@@ -73,7 +73,7 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/conffile/update [post]
|
||||
// @Router /hosts/conffile/update [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置文件","formatEN":"update SSH conf"}
|
||||
func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
|
||||
var req dto.SSHConf
|
||||
@@ -95,7 +95,7 @@ func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/generate [post]
|
||||
// @Router /hosts/ssh/generate [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"}
|
||||
func (b *BaseApi) GenerateSSH(c *gin.Context) {
|
||||
var req dto.GenerateSSH
|
||||
@@ -117,7 +117,7 @@ func (b *BaseApi) GenerateSSH(c *gin.Context) {
|
||||
// @Success 200 {string} data
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/secret [post]
|
||||
// @Router /hosts/ssh/secret [post]
|
||||
func (b *BaseApi) LoadSSHSecret(c *gin.Context) {
|
||||
var req dto.GenerateLoad
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -139,7 +139,7 @@ func (b *BaseApi) LoadSSHSecret(c *gin.Context) {
|
||||
// @Success 200 {object} dto.SSHLog
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/log [post]
|
||||
// @Router /hosts/ssh/log [post]
|
||||
func (b *BaseApi) LoadSSHLogs(c *gin.Context) {
|
||||
var req dto.SearchSSHLog
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -159,7 +159,7 @@ func (b *BaseApi) LoadSSHLogs(c *gin.Context) {
|
||||
// @Success 200 {string} data
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/conf [get]
|
||||
// @Router /hosts/ssh/conf [get]
|
||||
func (b *BaseApi) LoadSSHConf(c *gin.Context) {
|
||||
data, err := sshService.LoadSSHConf()
|
||||
if err != nil {
|
||||
|
||||
@@ -68,9 +68,8 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
|
||||
|
||||
<-quitChan
|
||||
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
dt := time.Now().Add(time.Second)
|
||||
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
|
||||
}
|
||||
|
||||
func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
|
||||
@@ -97,7 +96,7 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
|
||||
}
|
||||
source := c.Query("source")
|
||||
var containerID string
|
||||
var initCmd string
|
||||
var initCmd []string
|
||||
switch source {
|
||||
case "redis":
|
||||
containerID, initCmd, err = loadRedisInitCmd(c)
|
||||
@@ -114,11 +113,11 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
pidMap := loadMapFromDockerTop(containerID)
|
||||
slave, err := terminal.NewCommand("clear && " + initCmd)
|
||||
slave, err := terminal.NewCommand(initCmd)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(containerID, strings.ReplaceAll(initCmd, fmt.Sprintf("docker exec -it %s ", containerID), ""), pidMap)
|
||||
defer killBash(containerID, strings.ReplaceAll(strings.Join(initCmd, " "), fmt.Sprintf("exec -it %s ", containerID), ""), pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
@@ -133,67 +132,68 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
dt := time.Now().Add(time.Second)
|
||||
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
|
||||
|
||||
}
|
||||
|
||||
func loadRedisInitCmd(c *gin.Context) (string, string, error) {
|
||||
func loadRedisInitCmd(c *gin.Context) (string, []string, error) {
|
||||
name := c.Query("name")
|
||||
from := c.Query("from")
|
||||
commands := "redis-cli"
|
||||
commands := []string{"exec", "-it"}
|
||||
database, err := databaseService.Get(name)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("no such database in db, err: %v", err)
|
||||
return "", nil, fmt.Errorf("no such database in db, err: %v", err)
|
||||
}
|
||||
if from == "local" {
|
||||
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"})
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("no such app in db, err: %v", err)
|
||||
return "", nil, fmt.Errorf("no such app in db, err: %v", err)
|
||||
}
|
||||
name = redisInfo.ContainerName
|
||||
commands = append(commands, []string{name, "redis-cli"}...)
|
||||
if len(database.Password) != 0 {
|
||||
commands = "redis-cli -a " + database.Password + " --no-auth-warning"
|
||||
commands = append(commands, []string{"-a", database.Password, "--no-auth-warning"}...)
|
||||
}
|
||||
} else {
|
||||
commands = fmt.Sprintf("redis-cli -h %s -p %v", database.Address, database.Port)
|
||||
if len(database.Password) != 0 {
|
||||
commands = fmt.Sprintf("redis-cli -h %s -p %v -a %s --no-auth-warning", database.Address, database.Port, database.Password)
|
||||
}
|
||||
name = "1Panel-redis-cli-tools"
|
||||
commands = append(commands, []string{name, "redis-cli", "-h", database.Address, "-p", fmt.Sprintf("%v", database.Port)}...)
|
||||
if len(database.Password) != 0 {
|
||||
commands = append(commands, []string{"-a", database.Password, "--no-auth-warning"}...)
|
||||
}
|
||||
}
|
||||
return name, fmt.Sprintf("docker exec -it %s %s", name, commands), nil
|
||||
return name, commands, nil
|
||||
}
|
||||
|
||||
func loadOllamaInitCmd(c *gin.Context) (string, string, error) {
|
||||
func loadOllamaInitCmd(c *gin.Context) (string, []string, error) {
|
||||
name := c.Query("name")
|
||||
if cmd.CheckIllegal(name) {
|
||||
return "", "", fmt.Errorf("ollama model %s contains illegal characters", name)
|
||||
return "", nil, fmt.Errorf("ollama model %s contains illegal characters", name)
|
||||
}
|
||||
ollamaInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: "", Type: "ollama"})
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("no such app in db, err: %v", err)
|
||||
return "", nil, fmt.Errorf("no such app in db, err: %v", err)
|
||||
}
|
||||
containerName := ollamaInfo.ContainerName
|
||||
return containerName, fmt.Sprintf("docker exec -it %s ollama run %s", containerName, name), nil
|
||||
return containerName, []string{"exec", "-it", containerName, "ollama", "run", name}, nil
|
||||
}
|
||||
|
||||
func loadContainerInitCmd(c *gin.Context) (string, string, error) {
|
||||
func loadContainerInitCmd(c *gin.Context) (string, []string, error) {
|
||||
containerID := c.Query("containerid")
|
||||
command := c.Query("command")
|
||||
user := c.Query("user")
|
||||
if cmd.CheckIllegal(user, containerID, command) {
|
||||
return "", "", fmt.Errorf("the command contains illegal characters. command: %s, user: %s, containerID: %s", command, user, containerID)
|
||||
return "", nil, fmt.Errorf("the command contains illegal characters. command: %s, user: %s, containerID: %s", command, user, containerID)
|
||||
}
|
||||
if len(command) == 0 || len(containerID) == 0 {
|
||||
return "", "", fmt.Errorf("error param of command: %s or containerID: %s", command, containerID)
|
||||
return "", nil, fmt.Errorf("error param of command: %s or containerID: %s", command, containerID)
|
||||
}
|
||||
command = fmt.Sprintf("docker exec -it %s %s", containerID, command)
|
||||
commands := []string{"exec", "-it", containerID, command}
|
||||
if len(user) != 0 {
|
||||
command = fmt.Sprintf("docker exec -it -u %s %s %s", user, containerID, command)
|
||||
commands = []string{"exec", "-it", "-u", user, containerID, command}
|
||||
}
|
||||
|
||||
return containerID, command, nil
|
||||
return containerID, commands, nil
|
||||
}
|
||||
|
||||
func wshandleError(ws *websocket.Conn, err error) bool {
|
||||
|
||||
@@ -71,14 +71,16 @@ type CronjobDownload struct {
|
||||
}
|
||||
|
||||
type CronjobClean struct {
|
||||
IsDelete bool `json:"isDelete"`
|
||||
CleanData bool `json:"cleanData"`
|
||||
CronjobID uint `json:"cronjobID" validate:"required"`
|
||||
IsDelete bool `json:"isDelete"`
|
||||
CleanData bool `json:"cleanData"`
|
||||
CleanRemoteData bool `json:"cleanRemoteData"`
|
||||
CronjobID uint `json:"cronjobID" validate:"required"`
|
||||
}
|
||||
|
||||
type CronjobBatchDelete struct {
|
||||
CleanData bool `json:"cleanData"`
|
||||
IDs []uint `json:"ids" validate:"required"`
|
||||
CleanData bool `json:"cleanData"`
|
||||
CleanRemoteData bool `json:"cleanRemoteData"`
|
||||
IDs []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
|
||||
type CronjobInfo struct {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package dto
|
||||
|
||||
type DockerComposeService struct {
|
||||
Image string `yaml:"image"`
|
||||
ContainerName string `yaml:"container_name"`
|
||||
Restart string `yaml:"restart"`
|
||||
Ports []string `yaml:"ports"`
|
||||
Environment []string `yaml:"environment"`
|
||||
Command []string `yaml:"command"`
|
||||
}
|
||||
@@ -76,7 +76,8 @@ type FileRename struct {
|
||||
}
|
||||
|
||||
type FilePathCheck struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
WithInit bool `json:"withInit"`
|
||||
}
|
||||
|
||||
type FilePathsCheck struct {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package request
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
|
||||
type McpServerSearch struct {
|
||||
dto.PageInfo
|
||||
Name string `json:"name"`
|
||||
Sync bool `json:"sync"`
|
||||
}
|
||||
|
||||
type McpServerCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Command string `json:"command" validate:"required"`
|
||||
Environments []Environment `json:"environments"`
|
||||
Volumes []Volume `json:"volumes"`
|
||||
Port int `json:"port" validate:"required"`
|
||||
ContainerName string `json:"containerName"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
SsePath string `json:"ssePath"`
|
||||
HostIP string `json:"hostIP"`
|
||||
}
|
||||
|
||||
type McpServerUpdate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
McpServerCreate
|
||||
}
|
||||
|
||||
type Environment struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Volume struct {
|
||||
Source string `json:"source"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
type McpServerDelete struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
type McpServerOperate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
}
|
||||
|
||||
type McpBindDomain struct {
|
||||
Domain string `json:"domain" validate:"required"`
|
||||
SSLID uint `json:"sslID"`
|
||||
IPList string `json:"ipList"`
|
||||
}
|
||||
|
||||
type McpBindDomainUpdate struct {
|
||||
WebsiteID uint `json:"websiteID" validate:"required"`
|
||||
SSLID uint `json:"sslID"`
|
||||
IPList string `json:"ipList"`
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
)
|
||||
|
||||
type McpServersRes struct {
|
||||
Items []McpServerDTO `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type McpServerDTO struct {
|
||||
model.McpServer
|
||||
Environments []request.Environment `json:"environments"`
|
||||
Volumes []request.Volume `json:"volumes"`
|
||||
}
|
||||
|
||||
type McpBindDomainRes struct {
|
||||
Domain string `json:"domain"`
|
||||
SSLID uint `json:"sslID"`
|
||||
AcmeAccountID uint `json:"acmeAccountID"`
|
||||
AllowIPs []string `json:"allowIPs"`
|
||||
WebsiteID uint `json:"websiteID"`
|
||||
ConnUrl string `json:"connUrl"`
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
type McpServer struct {
|
||||
BaseModel
|
||||
Name string `json:"name"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
Command string `json:"command"`
|
||||
ContainerName string `json:"containerName"`
|
||||
Message string `json:"message"`
|
||||
Port int `json:"port"`
|
||||
Status string `json:"status"`
|
||||
Env string `json:"env"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
SsePath string `json:"ssePath"`
|
||||
WebsiteID int `json:"websiteID"`
|
||||
Dir string `json:"dir"`
|
||||
HostIP string `json:"hostIP"`
|
||||
}
|
||||
@@ -16,6 +16,7 @@ type IGroupRepo interface {
|
||||
Delete(opts ...DBOption) error
|
||||
CancelDefault(groupType string) error
|
||||
WithByHostDefault() DBOption
|
||||
WithByWebsiteDefault() DBOption
|
||||
}
|
||||
|
||||
func NewIGroupRepo() IGroupRepo {
|
||||
@@ -56,6 +57,12 @@ func (u *GroupRepo) WithByHostDefault() DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *GroupRepo) WithByWebsiteDefault() DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("is_default = ? AND type = ?", 1, "website")
|
||||
}
|
||||
}
|
||||
|
||||
func (u *GroupRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package repo
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
|
||||
type McpServerRepo struct {
|
||||
}
|
||||
|
||||
type IMcpServerRepo interface {
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.McpServer, error)
|
||||
GetFirst(opts ...DBOption) (*model.McpServer, error)
|
||||
Create(mcpServer *model.McpServer) error
|
||||
Save(mcpServer *model.McpServer) error
|
||||
DeleteBy(opts ...DBOption) error
|
||||
List(opts ...DBOption) ([]model.McpServer, error)
|
||||
}
|
||||
|
||||
func NewIMcpServerRepo() IMcpServerRepo {
|
||||
return &McpServerRepo{}
|
||||
}
|
||||
|
||||
func (m McpServerRepo) Page(page, size int, opts ...DBOption) (int64, []model.McpServer, error) {
|
||||
var servers []model.McpServer
|
||||
db := getDb(opts...).Model(&model.McpServer{})
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&servers).Error
|
||||
return count, servers, err
|
||||
}
|
||||
|
||||
func (m McpServerRepo) GetFirst(opts ...DBOption) (*model.McpServer, error) {
|
||||
var mcpServer model.McpServer
|
||||
if err := getDb(opts...).First(&mcpServer).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mcpServer, nil
|
||||
}
|
||||
|
||||
func (m McpServerRepo) List(opts ...DBOption) ([]model.McpServer, error) {
|
||||
var mcpServers []model.McpServer
|
||||
if err := getDb(opts...).Find(&mcpServers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mcpServers, nil
|
||||
}
|
||||
|
||||
func (m McpServerRepo) Create(mcpServer *model.McpServer) error {
|
||||
return getDb().Create(mcpServer).Error
|
||||
}
|
||||
|
||||
func (m McpServerRepo) Save(mcpServer *model.McpServer) error {
|
||||
return getDb().Save(mcpServer).Error
|
||||
}
|
||||
|
||||
func (m McpServerRepo) DeleteBy(opts ...DBOption) error {
|
||||
return getDb(opts...).Delete(&model.McpServer{}).Error
|
||||
}
|
||||
@@ -839,6 +839,9 @@ func updateInstallInfoInDB(appKey, appName, param string, value interface{}) err
|
||||
"param": strings.ReplaceAll(appInstall.Param, oldVal, newVal),
|
||||
"env": strings.ReplaceAll(appInstall.Env, oldVal, newVal),
|
||||
}, commonRepo.WithByID(appInstall.ID))
|
||||
if appKey == "mysql" || appKey == "postgresql" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if param == "user-password" {
|
||||
oldVal = fmt.Sprintf("\"PANEL_DB_USER_PASSWORD\":\"%v\"", appInstall.UserPassword)
|
||||
|
||||
@@ -450,12 +450,14 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
||||
networkSettings := oldContainer.NetworkSettings
|
||||
bridgeNetworkSettings := networkSettings.Networks[data.Network]
|
||||
if bridgeNetworkSettings.IPAMConfig != nil {
|
||||
ipv4Address := bridgeNetworkSettings.IPAMConfig.IPv4Address
|
||||
data.Ipv4 = ipv4Address
|
||||
ipv6Address := bridgeNetworkSettings.IPAMConfig.IPv6Address
|
||||
data.Ipv6 = ipv6Address
|
||||
if data.Network != "bridge" {
|
||||
data.Ipv4 = bridgeNetworkSettings.IPAMConfig.IPv4Address
|
||||
data.Ipv6 = bridgeNetworkSettings.IPAMConfig.IPv6Address
|
||||
}
|
||||
} else {
|
||||
data.Ipv4 = bridgeNetworkSettings.IPAddress
|
||||
if data.Network != "bridge" {
|
||||
data.Ipv4 = bridgeNetworkSettings.IPAddress
|
||||
}
|
||||
}
|
||||
|
||||
data.Cmd = oldContainer.Config.Cmd
|
||||
|
||||
@@ -114,8 +114,17 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !req.CleanRemoteData {
|
||||
for key := range accountMap {
|
||||
if key != constant.Local {
|
||||
delete(accountMap, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
cronjob.RetainCopies = 0
|
||||
u.removeExpiredBackup(cronjob, accountMap, model.BackupRecord{})
|
||||
if len(accountMap) != 0 {
|
||||
u.removeExpiredBackup(cronjob, accountMap, model.BackupRecord{})
|
||||
}
|
||||
} else {
|
||||
u.removeExpiredLog(cronjob)
|
||||
}
|
||||
@@ -249,7 +258,7 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
|
||||
global.Cron.Remove(cron.EntryID(idItem))
|
||||
}
|
||||
global.LOG.Infof("stop cronjob entryID: %s", cronjob.EntryIDs)
|
||||
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData, IsDelete: true}); err != nil {
|
||||
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData, CleanRemoteData: req.CleanRemoteData, IsDelete: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cronjobRepo.Delete(commonRepo.WithByID(id)); err != nil {
|
||||
|
||||
@@ -322,7 +322,8 @@ func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, accountMap
|
||||
if len(account) != 0 {
|
||||
global.LOG.Debugf("start upload file to %s, dir: %s", account, path.Join(accountMap[account].backupPath, cloudSrc))
|
||||
if _, err := accountMap[account].client.Upload(file, path.Join(accountMap[account].backupPath, cloudSrc)); err != nil {
|
||||
return "", err
|
||||
global.LOG.Errorf("upload file to %s failed, err: %v", account, err)
|
||||
continue
|
||||
}
|
||||
global.LOG.Debugf("upload successful!")
|
||||
}
|
||||
@@ -349,12 +350,18 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
|
||||
if cronjob.Type == "snapshot" {
|
||||
for _, account := range accounts {
|
||||
if len(account) != 0 {
|
||||
if _, ok := accountMap[account]; !ok {
|
||||
continue
|
||||
}
|
||||
_, _ = accountMap[account].client.Delete(path.Join(accountMap[account].backupPath, "system_snapshot", records[i].FileName))
|
||||
}
|
||||
}
|
||||
_ = snapshotRepo.Delete(commonRepo.WithByName(strings.TrimSuffix(records[i].FileName, ".tar.gz")))
|
||||
} else {
|
||||
for _, account := range accounts {
|
||||
if _, ok := accountMap[account]; !ok {
|
||||
continue
|
||||
}
|
||||
if len(account) != 0 {
|
||||
_, _ = accountMap[account].client.Delete(path.Join(accountMap[account].backupPath, records[i].FileDir, records[i].FileName))
|
||||
}
|
||||
|
||||
@@ -46,4 +46,6 @@ var (
|
||||
phpExtensionsRepo = repo.NewIPHPExtensionsRepo()
|
||||
|
||||
favoriteRepo = repo.NewIFavoriteRepo()
|
||||
|
||||
mcpServerRepo = repo.NewIMcpServerRepo()
|
||||
)
|
||||
|
||||
@@ -0,0 +1,643 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser"
|
||||
"github.com/1Panel-dev/1Panel/cmd/server/mcp"
|
||||
"github.com/1Panel-dev/1Panel/cmd/server/nginx_conf"
|
||||
"github.com/subosito/gotenv"
|
||||
"gopkg.in/yaml.v3"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type McpServerService struct{}
|
||||
|
||||
type IMcpServerService interface {
|
||||
Page(req request.McpServerSearch) response.McpServersRes
|
||||
Create(create request.McpServerCreate) error
|
||||
Update(req request.McpServerUpdate) error
|
||||
Delete(id uint) error
|
||||
Operate(req request.McpServerOperate) error
|
||||
GetBindDomain() (response.McpBindDomainRes, error)
|
||||
BindDomain(req request.McpBindDomain) error
|
||||
UpdateBindDomain(req request.McpBindDomainUpdate) error
|
||||
}
|
||||
|
||||
func NewIMcpServerService() IMcpServerService {
|
||||
return &McpServerService{}
|
||||
}
|
||||
|
||||
func (m McpServerService) Page(req request.McpServerSearch) response.McpServersRes {
|
||||
var (
|
||||
res response.McpServersRes
|
||||
items []response.McpServerDTO
|
||||
)
|
||||
|
||||
total, data, _ := mcpServerRepo.Page(req.PageInfo.Page, req.PageInfo.PageSize)
|
||||
for _, item := range data {
|
||||
_ = syncMcpServerContainerStatus(&item)
|
||||
serverDTO := response.McpServerDTO{
|
||||
McpServer: item,
|
||||
Environments: make([]request.Environment, 0),
|
||||
Volumes: make([]request.Volume, 0),
|
||||
}
|
||||
project, _ := docker.GetComposeProject(item.Name, path.Join(constant.McpDir, item.Name), []byte(item.DockerCompose), []byte(item.Env), true)
|
||||
for _, service := range project.Services {
|
||||
if service.Environment != nil {
|
||||
for key, value := range service.Environment {
|
||||
serverDTO.Environments = append(serverDTO.Environments, request.Environment{
|
||||
Key: key,
|
||||
Value: *value,
|
||||
})
|
||||
}
|
||||
}
|
||||
if service.Volumes != nil {
|
||||
for _, volume := range service.Volumes {
|
||||
serverDTO.Volumes = append(serverDTO.Volumes, request.Volume{
|
||||
Source: volume.Source,
|
||||
Target: volume.Target,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
items = append(items, serverDTO)
|
||||
}
|
||||
res.Total = total
|
||||
res.Items = items
|
||||
return res
|
||||
}
|
||||
|
||||
func (m McpServerService) Update(req request.McpServerUpdate) error {
|
||||
mcpServer, err := mcpServerRepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mcpServer.Port != req.Port {
|
||||
if err := checkPortExist(req.Port); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if mcpServer.ContainerName != req.ContainerName {
|
||||
if err := checkContainerName(req.ContainerName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
mcpServer.Name = req.Name
|
||||
mcpServer.ContainerName = req.ContainerName
|
||||
mcpServer.Port = req.Port
|
||||
mcpServer.Command = req.Command
|
||||
mcpServer.BaseURL = req.BaseURL
|
||||
mcpServer.SsePath = req.SsePath
|
||||
mcpServer.HostIP = req.HostIP
|
||||
if err := handleCreateParams(mcpServer, req.Environments, req.Volumes); err != nil {
|
||||
return err
|
||||
}
|
||||
env := handleEnv(mcpServer)
|
||||
mcpDir := path.Join(constant.McpDir, mcpServer.Name)
|
||||
envPath := path.Join(mcpDir, ".env")
|
||||
if err := gotenv.Write(env, envPath); err != nil {
|
||||
return err
|
||||
}
|
||||
dockerComposePath := path.Join(mcpDir, "docker-compose.yml")
|
||||
if err := files.NewFileOp().SaveFile(dockerComposePath, mcpServer.DockerCompose, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
mcpServer.Status = constant.RuntimeStarting
|
||||
if err := mcpServerRepo.Save(mcpServer); err != nil {
|
||||
return err
|
||||
}
|
||||
go startMcp(mcpServer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m McpServerService) Create(create request.McpServerCreate) error {
|
||||
servers, _ := mcpServerRepo.List()
|
||||
for _, server := range servers {
|
||||
if server.Port == create.Port {
|
||||
return buserr.New("ErrPortInUsed")
|
||||
}
|
||||
if server.ContainerName == create.ContainerName {
|
||||
return buserr.New("ErrContainerName")
|
||||
}
|
||||
if server.Name == create.Name {
|
||||
return buserr.New(constant.ErrNameIsExist)
|
||||
}
|
||||
if server.SsePath == create.SsePath {
|
||||
return buserr.New("ErrSsePath")
|
||||
}
|
||||
}
|
||||
if err := checkPortExist(create.Port); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkContainerName(create.ContainerName); err != nil {
|
||||
return err
|
||||
}
|
||||
mcpDir := path.Join(constant.McpDir, create.Name)
|
||||
mcpServer := &model.McpServer{
|
||||
Name: create.Name,
|
||||
ContainerName: create.ContainerName,
|
||||
Port: create.Port,
|
||||
Command: create.Command,
|
||||
Status: constant.RuntimeStarting,
|
||||
BaseURL: create.BaseURL,
|
||||
SsePath: create.SsePath,
|
||||
Dir: mcpDir,
|
||||
HostIP: create.HostIP,
|
||||
}
|
||||
if err := handleCreateParams(mcpServer, create.Environments, create.Volumes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env := handleEnv(mcpServer)
|
||||
filesOP := files.NewFileOp()
|
||||
if !filesOP.Stat(mcpDir) {
|
||||
_ = filesOP.CreateDir(mcpDir, 0644)
|
||||
}
|
||||
envPath := path.Join(mcpDir, ".env")
|
||||
if err := gotenv.Write(env, envPath); err != nil {
|
||||
return err
|
||||
}
|
||||
dockerComposePath := path.Join(mcpDir, "docker-compose.yml")
|
||||
if err := filesOP.SaveFile(dockerComposePath, mcpServer.DockerCompose, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mcpServerRepo.Create(mcpServer); err != nil {
|
||||
return err
|
||||
}
|
||||
addProxy(mcpServer)
|
||||
go startMcp(mcpServer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m McpServerService) Delete(id uint) error {
|
||||
mcpServer, err := mcpServerRepo.GetFirst(commonRepo.WithByID(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
composePath := path.Join(constant.McpDir, mcpServer.Name, "docker-compose.yml")
|
||||
_, _ = compose.Down(composePath)
|
||||
_ = files.NewFileOp().DeleteDir(path.Join(constant.McpDir, mcpServer.Name))
|
||||
|
||||
websiteID := GetWebsiteID()
|
||||
if websiteID > 0 {
|
||||
websiteService := NewIWebsiteService()
|
||||
delProxyReq := request.WebsiteProxyDel{
|
||||
ID: websiteID,
|
||||
Name: mcpServer.Name,
|
||||
}
|
||||
_ = websiteService.DeleteProxy(delProxyReq)
|
||||
}
|
||||
return mcpServerRepo.DeleteBy(commonRepo.WithByID(id))
|
||||
}
|
||||
|
||||
func (m McpServerService) Operate(req request.McpServerOperate) error {
|
||||
mcpServer, err := mcpServerRepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
composePath := path.Join(mcpServer.Dir, "docker-compose.yml")
|
||||
var out string
|
||||
switch req.Operate {
|
||||
case "start":
|
||||
out, err = compose.Up(composePath)
|
||||
mcpServer.Status = constant.RuntimeRunning
|
||||
case "stop":
|
||||
out, err = compose.Down(composePath)
|
||||
mcpServer.Status = constant.RuntimeStopped
|
||||
case "restart":
|
||||
out, err = compose.Restart(composePath)
|
||||
mcpServer.Status = constant.RuntimeRunning
|
||||
}
|
||||
if err != nil {
|
||||
mcpServer.Status = constant.RuntimeError
|
||||
mcpServer.Message = out
|
||||
}
|
||||
return mcpServerRepo.Save(mcpServer)
|
||||
}
|
||||
|
||||
func (m McpServerService) GetBindDomain() (response.McpBindDomainRes, error) {
|
||||
var res response.McpBindDomainRes
|
||||
websiteID := GetWebsiteID()
|
||||
if websiteID == 0 {
|
||||
return res, nil
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
res.WebsiteID = website.ID
|
||||
res.Domain = website.PrimaryDomain
|
||||
if website.WebsiteSSLID > 0 {
|
||||
res.SSLID = website.WebsiteSSLID
|
||||
ssl, _ := websiteSSLRepo.GetFirst(commonRepo.WithByID(website.WebsiteSSLID))
|
||||
res.AcmeAccountID = ssl.AcmeAccountID
|
||||
}
|
||||
res.ConnUrl = fmt.Sprintf("%s://%s", strings.ToLower(website.Protocol), website.PrimaryDomain)
|
||||
res.AllowIPs = GetAllowIps(website)
|
||||
return res, nil
|
||||
|
||||
}
|
||||
|
||||
func (m McpServerService) BindDomain(req request.McpBindDomain) error {
|
||||
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
|
||||
if nginxInstall.ID == 0 {
|
||||
return buserr.New("ErrOpenrestyInstall")
|
||||
}
|
||||
var (
|
||||
ipList []string
|
||||
err error
|
||||
)
|
||||
if len(req.IPList) > 0 {
|
||||
ipList, err = common.HandleIPList(req.IPList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
ssl, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ssl.Pem == "" {
|
||||
return buserr.New("ErrSSL")
|
||||
}
|
||||
}
|
||||
group, _ := groupRepo.Get(groupRepo.WithByWebsiteDefault())
|
||||
createWebsiteReq := request.WebsiteCreate{
|
||||
PrimaryDomain: req.Domain,
|
||||
Alias: strings.ToLower(req.Domain),
|
||||
Type: constant.Static,
|
||||
WebsiteGroupID: group.ID,
|
||||
}
|
||||
websiteService := NewIWebsiteService()
|
||||
if err := websiteService.CreateWebsite(createWebsiteReq); err != nil {
|
||||
return err
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(strings.ToLower(req.Domain)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = settingRepo.UpdateOrCreate("MCP_WEBSITE_ID", fmt.Sprintf("%d", website.ID))
|
||||
if len(ipList) > 0 {
|
||||
if err = ConfigAllowIPs(ipList, website); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
sslReq := request.WebsiteHTTPSOp{
|
||||
WebsiteID: website.ID,
|
||||
Enable: true,
|
||||
Type: "existed",
|
||||
WebsiteSSLID: req.SSLID,
|
||||
HttpConfig: "HTTPSOnly",
|
||||
}
|
||||
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = addMCPProxy(website.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m McpServerService) UpdateBindDomain(req request.McpBindDomainUpdate) error {
|
||||
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
|
||||
if nginxInstall.ID == 0 {
|
||||
return buserr.New("ErrOpenrestyInstall")
|
||||
}
|
||||
var (
|
||||
ipList []string
|
||||
err error
|
||||
)
|
||||
if len(req.IPList) > 0 {
|
||||
ipList, err = common.HandleIPList(req.IPList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
ssl, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ssl.Pem == "" {
|
||||
return buserr.New("ErrSSL")
|
||||
}
|
||||
}
|
||||
websiteService := NewIWebsiteService()
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ConfigAllowIPs(ipList, website); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
sslReq := request.WebsiteHTTPSOp{
|
||||
WebsiteID: website.ID,
|
||||
Enable: true,
|
||||
Type: "existed",
|
||||
WebsiteSSLID: req.SSLID,
|
||||
HttpConfig: "HTTPSOnly",
|
||||
}
|
||||
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if website.WebsiteSSLID > 0 && req.SSLID == 0 {
|
||||
sslReq := request.WebsiteHTTPSOp{
|
||||
WebsiteID: website.ID,
|
||||
Enable: false,
|
||||
}
|
||||
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
go updateMcpConfig(website.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateMcpConfig(websiteID uint) {
|
||||
servers, _ := mcpServerRepo.List()
|
||||
if len(servers) == 0 {
|
||||
return
|
||||
}
|
||||
website, _ := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
|
||||
websiteDomain := website.Domains[0]
|
||||
var baseUrl string
|
||||
if website.Protocol == constant.ProtocolHTTP {
|
||||
baseUrl = fmt.Sprintf("http://%s", websiteDomain.Domain)
|
||||
} else {
|
||||
baseUrl = fmt.Sprintf("https://%s", websiteDomain.Domain)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for _, server := range servers {
|
||||
if server.BaseURL != baseUrl {
|
||||
server.BaseURL = baseUrl
|
||||
server.HostIP = "127.0.0.1"
|
||||
_ = updateMcpServer(&server)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func addProxy(server *model.McpServer) {
|
||||
websiteID := GetWebsiteID()
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("[mcp] add proxy failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("[mcp] add proxy failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
|
||||
if !fileOp.Stat(includeDir) {
|
||||
if err = fileOp.CreateDir(includeDir, 0644); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
config, err := parser.NewStringParser(string(nginx_conf.SSE)).Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
includePath := path.Join(includeDir, server.Name+".conf")
|
||||
config.FilePath = includePath
|
||||
directives := config.Directives
|
||||
location, ok := directives[0].(*components.Location)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.1:%d%s", server.Port, server.SsePath)})
|
||||
location.ChangePath("^~", server.SsePath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
global.LOG.Errorf("write config failed, err: %v", buserr.WithErr(constant.ErrUpdateBuWebsite, err))
|
||||
return
|
||||
}
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
|
||||
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
|
||||
global.LOG.Errorf("update nginx config failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func addMCPProxy(websiteID uint) error {
|
||||
servers, _ := mcpServerRepo.List()
|
||||
if len(servers) == 0 {
|
||||
return nil
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
|
||||
if !fileOp.Stat(includeDir) {
|
||||
if err = fileOp.CreateDir(includeDir, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
config, err := parser.NewStringParser(string(nginx_conf.SSE)).Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
websiteDomain := website.Domains[0]
|
||||
var baseUrl string
|
||||
if website.Protocol == constant.ProtocolHTTP {
|
||||
baseUrl = fmt.Sprintf("http://%s", websiteDomain.Domain)
|
||||
} else {
|
||||
baseUrl = fmt.Sprintf("https://%s", websiteDomain.Domain)
|
||||
}
|
||||
if websiteDomain.Port != 80 && websiteDomain.Port != 443 {
|
||||
baseUrl = fmt.Sprintf("%s:%d", baseUrl, websiteDomain.Port)
|
||||
}
|
||||
for _, server := range servers {
|
||||
includePath := path.Join(includeDir, server.Name+".conf")
|
||||
config.FilePath = includePath
|
||||
directives := config.Directives
|
||||
location, ok := directives[0].(*components.Location)
|
||||
if !ok {
|
||||
err = errors.New("error")
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.1:%d%s", server.Port, server.SsePath)})
|
||||
location.ChangePath("^~", server.SsePath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
|
||||
}
|
||||
server.BaseURL = baseUrl
|
||||
server.HostIP = "127.0.0.1"
|
||||
go updateMcpServer(&server)
|
||||
}
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
|
||||
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateMcpServer(mcpServer *model.McpServer) error {
|
||||
env := handleEnv(mcpServer)
|
||||
if err := gotenv.Write(env, path.Join(mcpServer.Dir, ".env")); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = mcpServerRepo.Save(mcpServer)
|
||||
composePath := path.Join(constant.McpDir, mcpServer.Name, "docker-compose.yml")
|
||||
_, _ = compose.Down(composePath)
|
||||
if _, err := compose.Up(composePath); err != nil {
|
||||
mcpServer.Status = constant.RuntimeError
|
||||
mcpServer.Message = err.Error()
|
||||
}
|
||||
return mcpServerRepo.Save(mcpServer)
|
||||
}
|
||||
|
||||
func handleEnv(mcpServer *model.McpServer) gotenv.Env {
|
||||
env := make(gotenv.Env)
|
||||
env["CONTAINER_NAME"] = mcpServer.ContainerName
|
||||
env["COMMAND"] = mcpServer.Command
|
||||
env["PANEL_APP_PORT_HTTP"] = strconv.Itoa(mcpServer.Port)
|
||||
env["BASE_URL"] = mcpServer.BaseURL
|
||||
env["SSE_PATH"] = mcpServer.SsePath
|
||||
env["HOST_IP"] = mcpServer.HostIP
|
||||
envStr, _ := gotenv.Marshal(env)
|
||||
mcpServer.Env = envStr
|
||||
return env
|
||||
}
|
||||
|
||||
func handleCreateParams(mcpServer *model.McpServer, environments []request.Environment, volumes []request.Volume) error {
|
||||
var composeContent []byte
|
||||
if mcpServer.ID == 0 {
|
||||
composeContent = mcp.DefaultMcpCompose
|
||||
} else {
|
||||
composeContent = []byte(mcpServer.DockerCompose)
|
||||
}
|
||||
composeMap := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal(composeContent, &composeMap); err != nil {
|
||||
return err
|
||||
}
|
||||
services, serviceValid := composeMap["services"].(map[string]interface{})
|
||||
if !serviceValid {
|
||||
return buserr.New(constant.ErrFileParse)
|
||||
}
|
||||
serviceName := ""
|
||||
serviceValue := make(map[string]interface{})
|
||||
|
||||
if mcpServer.ID > 0 {
|
||||
serviceName = mcpServer.Name
|
||||
serviceValue = services[serviceName].(map[string]interface{})
|
||||
} else {
|
||||
for name, service := range services {
|
||||
serviceName = name
|
||||
serviceValue = service.(map[string]interface{})
|
||||
break
|
||||
}
|
||||
delete(services, serviceName)
|
||||
}
|
||||
delete(serviceValue, "environment")
|
||||
if len(environments) > 0 {
|
||||
envMap := make(map[string]string)
|
||||
for _, env := range environments {
|
||||
envMap[env.Key] = env.Value
|
||||
}
|
||||
serviceValue["environment"] = envMap
|
||||
}
|
||||
delete(serviceValue, "volumes")
|
||||
if len(volumes) > 0 {
|
||||
volumeList := make([]string, 0)
|
||||
for _, volume := range volumes {
|
||||
volumeList = append(volumeList, fmt.Sprintf("%s:%s", volume.Source, volume.Target))
|
||||
}
|
||||
serviceValue["volumes"] = volumeList
|
||||
}
|
||||
|
||||
services[mcpServer.Name] = serviceValue
|
||||
composeByte, err := yaml.Marshal(composeMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mcpServer.DockerCompose = string(composeByte)
|
||||
return nil
|
||||
}
|
||||
|
||||
func startMcp(mcpServer *model.McpServer) {
|
||||
composePath := path.Join(constant.McpDir, mcpServer.Name, "docker-compose.yml")
|
||||
if mcpServer.Status != constant.RuntimeNormal {
|
||||
_, _ = compose.Down(composePath)
|
||||
}
|
||||
if out, err := compose.Up(composePath); err != nil {
|
||||
mcpServer.Status = constant.RuntimeError
|
||||
mcpServer.Message = out
|
||||
} else {
|
||||
mcpServer.Status = constant.RuntimeRunning
|
||||
mcpServer.Message = ""
|
||||
}
|
||||
_ = syncMcpServerContainerStatus(mcpServer)
|
||||
}
|
||||
|
||||
func syncMcpServerContainerStatus(mcpServer *model.McpServer) error {
|
||||
containerNames := []string{mcpServer.ContainerName}
|
||||
cli, err := docker.NewClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
containers, err := cli.ListContainersByName(containerNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
mcpServer.Status = constant.RuntimeStopped
|
||||
return mcpServerRepo.Save(mcpServer)
|
||||
}
|
||||
container := containers[0]
|
||||
switch container.State {
|
||||
case "exited":
|
||||
mcpServer.Status = constant.RuntimeError
|
||||
case "running":
|
||||
mcpServer.Status = constant.RuntimeRunning
|
||||
case "paused":
|
||||
mcpServer.Status = constant.RuntimeStopped
|
||||
case "restarting":
|
||||
mcpServer.Status = constant.RuntimeReStarting
|
||||
default:
|
||||
if mcpServer.Status != constant.RuntimeBuildIng {
|
||||
mcpServer.Status = constant.RuntimeStopped
|
||||
}
|
||||
}
|
||||
return mcpServerRepo.Save(mcpServer)
|
||||
}
|
||||
|
||||
func GetWebsiteID() uint {
|
||||
websiteID, _ := settingRepo.Get(settingRepo.WithByKey("MCP_WEBSITE_ID"))
|
||||
if websiteID.Value == "" {
|
||||
return 0
|
||||
}
|
||||
websiteIDUint, _ := strconv.ParseUint(websiteID.Value, 10, 64)
|
||||
return uint(websiteIDUint)
|
||||
}
|
||||
@@ -496,6 +496,10 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
|
||||
if err := websiteDomainRepo.DeleteBy(ctx, websiteDomainRepo.WithWebsiteId(req.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
websiteID := GetWebsiteID()
|
||||
if req.ID == websiteID {
|
||||
_ = settingRepo.Update("MCP_WEBSITE_ID", "0")
|
||||
}
|
||||
tx.Commit()
|
||||
|
||||
uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/website/%s", website.Alias))
|
||||
|
||||
@@ -35,6 +35,7 @@ var WebUrlMap = map[string]struct{}{
|
||||
"/ai": {},
|
||||
"/ai/model": {},
|
||||
"/ai/gpu": {},
|
||||
"/ai/mcp": {},
|
||||
|
||||
"/containers": {},
|
||||
"/containers/container": {},
|
||||
|
||||
@@ -17,4 +17,5 @@ var (
|
||||
RuntimeDir = path.Join(DataDir, "runtime")
|
||||
RecycleBinDir = "/.1panel_clash"
|
||||
SSLLogDir = path.Join(global.CONF.System.DataDir, "log", "ssl")
|
||||
McpDir = path.Join(DataDir, "mcp")
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
RuntimeStopped = "stopped"
|
||||
RuntimeUnhealthy = "unhealthy"
|
||||
RuntimeCreating = "creating"
|
||||
RuntimeReStarting = "restarting"
|
||||
|
||||
RuntimePHP = "php"
|
||||
RuntimeNode = "node"
|
||||
|
||||
@@ -288,6 +288,7 @@ SystemMode: "mode: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: 'Please install Openresty first'
|
||||
ErrSSL: "Certificate content is empty, please check the certificate!"
|
||||
ErrSsePath: "SSE path is duplicated"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: 'Token verification error, please reset and scan again.'
|
||||
|
||||
@@ -285,6 +285,7 @@ SystemMode: "モード: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: 'まず Openresty をインストールしてください'
|
||||
ErrSSL: "証明書の内容が空です。証明書を確認してください!"
|
||||
ErrSsePath: "SSE パスが重複しています"
|
||||
|
||||
|
||||
#mobile app
|
||||
|
||||
@@ -288,6 +288,7 @@ SystemMode: "모드: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: '먼저 Openresty를 설치하세요'
|
||||
ErrSSL: "인증서 내용이 비어 있습니다. 인증서를 확인하세요!"
|
||||
ErrSsePath: "SSE 경로가 중복되었습니다"
|
||||
|
||||
|
||||
#mobile app
|
||||
|
||||
@@ -287,7 +287,7 @@ SystemMode: "Mod: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: 'Sila pasang Openresty terlebih dahulu'
|
||||
ErrSSL: "Kandungan sijil kosong, sila periksa sijil!"
|
||||
|
||||
ErrSsePath: "Laluan SSE bertindan"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: 'Ralat pengesahan token, sila tetapkan semula dan imbas semula.'
|
||||
|
||||
@@ -285,6 +285,7 @@ SystemMode: "modo: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: 'Por favor, instale o Openresty primeiro'
|
||||
ErrSSL: "O conteúdo do certificado está vazio, por favor, verifique o certificado!"
|
||||
ErrSsePath: "Caminho SSE duplicado"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: 'Erro de verificação do token, por favor, reinicie e escaneie novamente.'
|
||||
|
||||
@@ -288,6 +288,7 @@ SystemMode: "режим: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: "Пожалуйста, установите Openresty сначала"
|
||||
ErrSSL: "Содержимое сертификата пустое, пожалуйста, проверьте сертификат!"
|
||||
ErrSsePath: "Путь SSE дублируется"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: 'шибка проверки токена, пожалуйста, сбросьте и отсканируйте снова.'
|
||||
|
||||
@@ -288,6 +288,7 @@ SystemMode: "模式: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: "請先安裝 Openresty"
|
||||
ErrSSL: "證書內容為空,請檢查證書!"
|
||||
ErrSsePath: "SSE 路徑重複"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: '令牌驗證錯誤,請重置後再次掃碼'
|
||||
|
||||
@@ -288,6 +288,7 @@ SystemMode: "模式: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: '请先安装 Openresty'
|
||||
ErrSSL: "证书内容为空,请检查证书!"
|
||||
ErrSsePath: "SSE 路径重复"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: '令牌验证错误,请重置后再次扫码'
|
||||
|
||||
@@ -23,8 +23,11 @@ func Init() {
|
||||
|
||||
constant.SSLLogDir = path.Join(global.CONF.System.DataDir, "log", "ssl")
|
||||
|
||||
constant.McpDir = path.Join(constant.DataDir, "mcp")
|
||||
|
||||
dirs := []string{constant.DataDir, constant.ResourceDir, constant.AppResourceDir, constant.AppInstallDir,
|
||||
global.CONF.System.Backup, constant.RuntimeDir, constant.LocalAppResourceDir, constant.RemoteAppResourceDir, constant.SSLLogDir}
|
||||
global.CONF.System.Backup, constant.RuntimeDir, constant.LocalAppResourceDir, constant.RemoteAppResourceDir,
|
||||
constant.SSLLogDir, constant.McpDir}
|
||||
|
||||
fileOp := files.NewFileOp()
|
||||
for _, dir := range dirs {
|
||||
|
||||
@@ -106,6 +106,8 @@ func Init() {
|
||||
migrations.AddAppMenu,
|
||||
migrations.AddAppPanelName,
|
||||
migrations.AddLicenseVerify,
|
||||
|
||||
migrations.AddMcpServer,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -459,3 +459,13 @@ var AddLicenseVerify = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddMcpServer = &gormigrate.Migration{
|
||||
ID: "20250401-add-mcp-server",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.McpServer{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -24,5 +24,6 @@ func commonGroups() []CommonRouter {
|
||||
&ProcessRouter{},
|
||||
&WebsiteCARouter{},
|
||||
&AIToolsRouter{},
|
||||
&McpServerRouter{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,5 +26,14 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter.POST("/domain/bind", baseApi.BindDomain)
|
||||
aiToolsRouter.POST("/domain/get", baseApi.GetBindDomain)
|
||||
aiToolsRouter.POST("/domain/update", baseApi.UpdateBindDomain)
|
||||
|
||||
aiToolsRouter.POST("/mcp/search", baseApi.PageMcpServers)
|
||||
aiToolsRouter.POST("/mcp/server", baseApi.CreateMcpServer)
|
||||
aiToolsRouter.POST("/mcp/server/update", baseApi.UpdateMcpServer)
|
||||
aiToolsRouter.POST("/mcp/server/del", baseApi.DeleteMcpServer)
|
||||
aiToolsRouter.POST("/mcp/server/op", baseApi.OperateMcpServer)
|
||||
aiToolsRouter.POST("/mcp/domain/bind", baseApi.BindMcpDomain)
|
||||
aiToolsRouter.GET("/mcp/domain/get", baseApi.GetMcpBindDomain)
|
||||
aiToolsRouter.POST("/mcp/domain/update", baseApi.UpdateMcpBindDomain)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type McpServerRouter struct {
|
||||
}
|
||||
|
||||
func (m *McpServerRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
mcpRouter := Router.Group("mcp")
|
||||
mcpRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
|
||||
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
mcpRouter.POST("/search", baseApi.PageMcpServers)
|
||||
mcpRouter.POST("/server", baseApi.CreateMcpServer)
|
||||
}
|
||||
}
|
||||
@@ -188,18 +188,19 @@ func (r *Local) ChangeAccess(info AccessChangeInfo) error {
|
||||
info.Name = "*"
|
||||
info.Password = r.Password
|
||||
}
|
||||
if info.Permission != info.OldPermission {
|
||||
if err := r.Delete(DeleteInfo{
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.OldPermission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300}); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Username == "root" {
|
||||
return nil
|
||||
}
|
||||
if info.Permission == info.OldPermission {
|
||||
return nil
|
||||
}
|
||||
if err := r.Delete(DeleteInfo{
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.OldPermission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300}); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Username == "root" {
|
||||
return nil
|
||||
}
|
||||
if err := r.CreateUser(CreateInfo{
|
||||
Name: info.Name,
|
||||
|
||||
@@ -199,18 +199,19 @@ func (r *Remote) ChangeAccess(info AccessChangeInfo) error {
|
||||
info.Name = "*"
|
||||
info.Password = r.Password
|
||||
}
|
||||
if info.Permission != info.OldPermission {
|
||||
if err := r.Delete(DeleteInfo{
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.OldPermission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300}); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Username == "root" {
|
||||
return nil
|
||||
}
|
||||
if info.Permission == info.OldPermission {
|
||||
return nil
|
||||
}
|
||||
if err := r.Delete(DeleteInfo{
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.OldPermission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300}); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Username == "root" {
|
||||
return nil
|
||||
}
|
||||
if err := r.CreateUser(CreateInfo{
|
||||
Name: info.Name,
|
||||
|
||||
@@ -46,7 +46,9 @@ func NewLocation(directive IDirective) *Location {
|
||||
location.Host = params[1]
|
||||
}
|
||||
case "proxy_cache":
|
||||
location.Cache = true
|
||||
if params[0] != "off" {
|
||||
location.Cache = true
|
||||
}
|
||||
case "if":
|
||||
if params[0] == "(" && params[1] == "$uri" && params[2] == "~*" {
|
||||
dirs := dir.GetBlock().GetDirectives()
|
||||
|
||||
+37
-12
@@ -3,6 +3,7 @@ package ssl
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"github.com/go-acme/lego/v4/providers/dns/westcn"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/clouddns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/dnspod"
|
||||
"github.com/go-acme/lego/v4/providers/dns/godaddy"
|
||||
"github.com/go-acme/lego/v4/providers/dns/huaweicloud"
|
||||
@@ -75,6 +77,7 @@ const (
|
||||
Volcengine DnsType = "Volcengine"
|
||||
CloudFlare DnsType = "CloudFlare"
|
||||
CloudDns DnsType = "CloudDns"
|
||||
ClouDNS DnsType = "ClouDNS"
|
||||
FreeMyIP DnsType = "FreeMyIP"
|
||||
NameSilo DnsType = "NameSilo"
|
||||
NameCheap DnsType = "NameCheap"
|
||||
@@ -83,21 +86,26 @@ const (
|
||||
TencentCloud DnsType = "TencentCloud"
|
||||
HuaweiCloud DnsType = "HuaweiCloud"
|
||||
RainYun DnsType = "RainYun"
|
||||
WestCN DnsType = "WestCN"
|
||||
)
|
||||
|
||||
type DNSParam struct {
|
||||
ID string `json:"id"`
|
||||
Token string `json:"token"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Email string `json:"email"`
|
||||
APIkey string `json:"apiKey"`
|
||||
APIUser string `json:"apiUser"`
|
||||
APISecret string `json:"apiSecret"`
|
||||
SecretID string `json:"secretID"`
|
||||
Region string `json:"region"`
|
||||
ClientID string `json:"clientID"`
|
||||
Password string `json:"password"`
|
||||
ID string `json:"id"`
|
||||
Token string `json:"token"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Email string `json:"email"`
|
||||
APIkey string `json:"apiKey"`
|
||||
APIUser string `json:"apiUser"`
|
||||
APISecret string `json:"apiSecret"`
|
||||
SecretID string `json:"secretID"`
|
||||
Region string `json:"region"`
|
||||
ClientID string `json:"clientID"`
|
||||
Password string `json:"password"`
|
||||
AuthID string `json:"authID"`
|
||||
SubAuthID string `json:"subAuthID"`
|
||||
AuthPassword string `json:"authPassword"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -158,6 +166,15 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
|
||||
clouddnsConfig.PollingInterval = pollingInterval
|
||||
clouddnsConfig.TTL = ttl
|
||||
p, err = clouddns.NewDNSProviderConfig(clouddnsConfig)
|
||||
case ClouDNS:
|
||||
cloudnsConfig := cloudns.NewDefaultConfig()
|
||||
cloudnsConfig.AuthID = param.AuthID
|
||||
cloudnsConfig.SubAuthID = param.SubAuthID
|
||||
cloudnsConfig.AuthPassword = param.AuthPassword
|
||||
cloudnsConfig.PropagationTimeout = propagationTimeout
|
||||
cloudnsConfig.PollingInterval = pollingInterval
|
||||
cloudnsConfig.TTL = ttl
|
||||
p, err = cloudns.NewDNSProviderConfig(cloudnsConfig)
|
||||
case FreeMyIP:
|
||||
freeMyIpConfig := freemyip.NewDefaultConfig()
|
||||
freeMyIpConfig.Token = param.Token
|
||||
@@ -219,6 +236,14 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
|
||||
rainyunConfig.PollingInterval = pollingInterval
|
||||
rainyunConfig.TTL = ttl
|
||||
p, err = rainyun.NewDNSProviderConfig(rainyunConfig)
|
||||
case WestCN:
|
||||
westcnConfig := westcn.NewDefaultConfig()
|
||||
westcnConfig.Username = param.Username
|
||||
westcnConfig.Password = param.Password
|
||||
westcnConfig.PropagationTimeout = propagationTimeout
|
||||
westcnConfig.PollingInterval = pollingInterval
|
||||
westcnConfig.TTL = ttl
|
||||
p, err = westcn.NewDNSProviderConfig(westcnConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -25,8 +25,8 @@ type LocalCommand struct {
|
||||
pty *os.File
|
||||
}
|
||||
|
||||
func NewCommand(initCmd string) (*LocalCommand, error) {
|
||||
cmd := exec.Command("bash")
|
||||
func NewCommand(initCmd []string) (*LocalCommand, error) {
|
||||
cmd := exec.Command("docker", initCmd...)
|
||||
if term := os.Getenv("TERM"); term != "" {
|
||||
cmd.Env = append(os.Environ(), "TERM="+term)
|
||||
} else {
|
||||
@@ -38,11 +38,6 @@ func NewCommand(initCmd string) (*LocalCommand, error) {
|
||||
return nil, errors.Wrapf(err, "failed to start command")
|
||||
}
|
||||
|
||||
if len(initCmd) != 0 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
_, _ = pty.Write([]byte(initCmd + "\n"))
|
||||
}
|
||||
|
||||
lcmd := &LocalCommand{
|
||||
closeSignal: DefaultCloseSignal,
|
||||
closeTimeout: DefaultCloseTimeout,
|
||||
@@ -100,4 +95,5 @@ func (lcmd *LocalCommand) Wait(quitChan chan bool) {
|
||||
global.LOG.Errorf("ssh session wait failed, err: %v", err)
|
||||
setQuit(quitChan)
|
||||
}
|
||||
setQuit(quitChan)
|
||||
}
|
||||
|
||||
+629
-626
File diff suppressed because it is too large
Load Diff
+629
-626
File diff suppressed because it is too large
Load Diff
+378
-384
@@ -3342,6 +3342,8 @@ definitions:
|
||||
type: string
|
||||
lastCleanTime:
|
||||
type: string
|
||||
licenseVerify:
|
||||
type: string
|
||||
localTime:
|
||||
type: string
|
||||
menuTabs:
|
||||
@@ -11237,381 +11239,6 @@ paths:
|
||||
formatEN: update group [name][type]
|
||||
formatZH: 更新组 [name][type]
|
||||
paramKeys: []
|
||||
/host/conffile/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHConf'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Update host SSH setting by file
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys: []
|
||||
formatEN: update SSH conf
|
||||
formatZH: 修改 SSH 配置文件
|
||||
paramKeys: []
|
||||
/host/ssh/conf:
|
||||
get:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH conf
|
||||
tags:
|
||||
- SSH
|
||||
/host/ssh/generate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.GenerateSSH'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Generate host SSH secret
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys: []
|
||||
formatEN: generate SSH secret
|
||||
formatZH: '生成 SSH 密钥 '
|
||||
paramKeys: []
|
||||
/host/ssh/log:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SearchSSHLog'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHLog'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH logs
|
||||
tags:
|
||||
- SSH
|
||||
/host/ssh/operate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.Operate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Operate SSH
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operation
|
||||
formatEN: '[operation] SSH'
|
||||
formatZH: '[operation] SSH '
|
||||
paramKeys: []
|
||||
/host/ssh/search:
|
||||
post:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHInfo'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH setting info
|
||||
tags:
|
||||
- SSH
|
||||
/host/ssh/secret:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.GenerateLoad'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH secret
|
||||
tags:
|
||||
- SSH
|
||||
/host/ssh/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHUpdate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Update host SSH setting
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- key
|
||||
- value
|
||||
formatEN: update SSH setting [key] => [value]
|
||||
formatZH: 修改 SSH 配置 [key] => [value]
|
||||
paramKeys: []
|
||||
/host/tool:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.HostToolRes'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get tool status
|
||||
tags:
|
||||
- Host tool
|
||||
/host/tool/config:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolConfig'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.HostToolConfig'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get tool config
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
formatEN: '[operate] tool config'
|
||||
formatZH: '[operate] 主机工具配置文件 '
|
||||
paramKeys: []
|
||||
/host/tool/create:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolCreate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Create Host tool Config
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- type
|
||||
formatEN: create [type] config
|
||||
formatZH: 创建 [type] 配置
|
||||
paramKeys: []
|
||||
/host/tool/log:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolLogReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get tool logs
|
||||
tags:
|
||||
- Host tool
|
||||
/host/tool/operate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Operate tool
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
- type
|
||||
formatEN: '[operate] [type]'
|
||||
formatZH: '[operate] [type] '
|
||||
paramKeys: []
|
||||
/host/tool/supervisor/process:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.SupervisorProcessConfig'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get Supervisor process config
|
||||
tags:
|
||||
- Host tool
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.SupervisorProcessConfig'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Create Supervisor process
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
formatEN: '[operate] process'
|
||||
formatZH: '[operate] 守护进程 '
|
||||
paramKeys: []
|
||||
/host/tool/supervisor/process/file:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.SupervisorProcessFileReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get Supervisor process config
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
formatEN: '[operate] Supervisor Process Config file'
|
||||
formatZH: '[operate] Supervisor 进程文件 '
|
||||
paramKeys: []
|
||||
/hosts:
|
||||
post:
|
||||
consumes:
|
||||
@@ -11878,6 +11505,32 @@ paths:
|
||||
formatEN: update quick command [name]
|
||||
formatZH: 更新快捷命令 [name]
|
||||
paramKeys: []
|
||||
/hosts/conffile/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHConf'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Update host SSH setting by file
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys: []
|
||||
formatEN: update SSH conf
|
||||
formatZH: 修改 SSH 配置文件
|
||||
paramKeys: []
|
||||
/hosts/del:
|
||||
post:
|
||||
consumes:
|
||||
@@ -12197,6 +11850,157 @@ paths:
|
||||
summary: Page host
|
||||
tags:
|
||||
- Host
|
||||
/hosts/ssh/conf:
|
||||
get:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH conf
|
||||
tags:
|
||||
- SSH
|
||||
/hosts/ssh/generate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.GenerateSSH'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Generate host SSH secret
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys: []
|
||||
formatEN: generate SSH secret
|
||||
formatZH: '生成 SSH 密钥 '
|
||||
paramKeys: []
|
||||
/hosts/ssh/log:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SearchSSHLog'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHLog'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH logs
|
||||
tags:
|
||||
- SSH
|
||||
/hosts/ssh/operate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.Operate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Operate SSH
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operation
|
||||
formatEN: '[operation] SSH'
|
||||
formatZH: '[operation] SSH '
|
||||
paramKeys: []
|
||||
/hosts/ssh/search:
|
||||
post:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHInfo'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH setting info
|
||||
tags:
|
||||
- SSH
|
||||
/hosts/ssh/secret:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.GenerateLoad'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH secret
|
||||
tags:
|
||||
- SSH
|
||||
/hosts/ssh/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHUpdate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Update host SSH setting
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- key
|
||||
- value
|
||||
formatEN: update SSH setting [key] => [value]
|
||||
formatZH: 修改 SSH 配置 [key] => [value]
|
||||
paramKeys: []
|
||||
/hosts/test/byid/{id}:
|
||||
post:
|
||||
consumes:
|
||||
@@ -12240,6 +12044,204 @@ paths:
|
||||
summary: Test host conn by info
|
||||
tags:
|
||||
- Host
|
||||
/hosts/tool:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.HostToolRes'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get tool status
|
||||
tags:
|
||||
- Host tool
|
||||
/hosts/tool/config:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolConfig'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.HostToolConfig'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get tool config
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
formatEN: '[operate] tool config'
|
||||
formatZH: '[operate] 主机工具配置文件 '
|
||||
paramKeys: []
|
||||
/hosts/tool/create:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolCreate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Create Host tool Config
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- type
|
||||
formatEN: create [type] config
|
||||
formatZH: 创建 [type] 配置
|
||||
paramKeys: []
|
||||
/hosts/tool/log:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolLogReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get tool logs
|
||||
tags:
|
||||
- Host tool
|
||||
/hosts/tool/operate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Operate tool
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
- type
|
||||
formatEN: '[operate] [type]'
|
||||
formatZH: '[operate] [type] '
|
||||
paramKeys: []
|
||||
/hosts/tool/supervisor/process:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.SupervisorProcessConfig'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get Supervisor process config
|
||||
tags:
|
||||
- Host tool
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.SupervisorProcessConfig'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Create Supervisor process
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
formatEN: '[operate] process'
|
||||
formatZH: '[operate] 守护进程 '
|
||||
paramKeys: []
|
||||
/hosts/tool/supervisor/process/file:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.SupervisorProcessFileReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get Supervisor process config
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
formatEN: '[operate] Supervisor Process Config file'
|
||||
formatZH: '[operate] Supervisor 进程文件 '
|
||||
paramKeys: []
|
||||
/hosts/tree:
|
||||
post:
|
||||
consumes:
|
||||
@@ -16766,15 +16768,7 @@ schemes:
|
||||
- https
|
||||
securityDefinitions:
|
||||
ApiKeyAuth:
|
||||
description: |-
|
||||
Custom Token Format, Format: md5('1panel' + API-Key + UnixTimestamp).
|
||||
```
|
||||
eg:
|
||||
curl -X GET "http://localhost:4004/api/v1/dashboard/current" \
|
||||
-H "1Panel-Token: <1panel_token>" \
|
||||
-H "1Panel-Timestamp: <current_unix_timestamp>"
|
||||
```
|
||||
- `1Panel-Token` is the key for the panel API Key.
|
||||
description: '- `1Panel-Token` is the key for the panel API Key.'
|
||||
in: header
|
||||
name: 1Panel-Token
|
||||
type: apiKey
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
services:
|
||||
mcp-server:
|
||||
image: supercorp/supergateway:latest
|
||||
container_name: ${CONTAINER_NAME}
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${PANEL_APP_PORT_HTTP}"
|
||||
command: [
|
||||
"--stdio", "${COMMAND}",
|
||||
"--port", "${PANEL_APP_PORT_HTTP}",
|
||||
"--baseUrl", "${BASE_URL}",
|
||||
"--ssePath", "${SSE_PATH}",
|
||||
"--messagePath", "${SSE_PATH}/messages"
|
||||
]
|
||||
networks:
|
||||
- 1panel-network
|
||||
networks:
|
||||
1panel-network:
|
||||
external: true
|
||||
@@ -0,0 +1,8 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed compose.yml
|
||||
var DefaultMcpCompose []byte
|
||||
@@ -37,3 +37,6 @@ var DomainNotFoundHTML []byte
|
||||
|
||||
//go:embed stop.html
|
||||
var StopHTML []byte
|
||||
|
||||
//go:embed sse.conf
|
||||
var SSE []byte
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
location ^~ /github {
|
||||
proxy_pass http://127.0.0.1:8001/github;
|
||||
proxy_buffering off;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection '';
|
||||
chunked_transfer_encoding off;
|
||||
}
|
||||
@@ -109,4 +109,75 @@ export namespace AI {
|
||||
websiteID?: number;
|
||||
connUrl: string;
|
||||
}
|
||||
|
||||
export interface Environment {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Volume {
|
||||
source: string;
|
||||
target: string;
|
||||
}
|
||||
|
||||
export interface McpServer {
|
||||
id: number;
|
||||
name: string;
|
||||
status: string;
|
||||
baseUrl: string;
|
||||
ssePath: string;
|
||||
command: string;
|
||||
port: number;
|
||||
message: string;
|
||||
createdAt?: string;
|
||||
containerName: string;
|
||||
environments: Environment[];
|
||||
volumes: Volume[];
|
||||
dir?: string;
|
||||
hostIP: string;
|
||||
protocol: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface McpServerSearch extends ReqPage {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface McpServerDelete {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface McpServerOperate {
|
||||
id: number;
|
||||
operate: string;
|
||||
}
|
||||
|
||||
export interface McpBindDomain {
|
||||
domain: string;
|
||||
sslID: number;
|
||||
ipList: string;
|
||||
}
|
||||
|
||||
export interface McpDomainRes {
|
||||
domain: string;
|
||||
sslID: number;
|
||||
acmeAccountID: number;
|
||||
allowIPs: string[];
|
||||
websiteID?: number;
|
||||
connUrl: string;
|
||||
}
|
||||
|
||||
export interface McpBindDomainUpdate {
|
||||
websiteID: number;
|
||||
sslID: number;
|
||||
ipList: string;
|
||||
}
|
||||
|
||||
export interface ImportMcpServer {
|
||||
name: string;
|
||||
command: string;
|
||||
ssePath: string;
|
||||
containerName: string;
|
||||
environments: Environment[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ export namespace Cronjob {
|
||||
export interface CronjobDelete {
|
||||
ids: Array<number>;
|
||||
cleanData: boolean;
|
||||
cleanRemoteData: boolean;
|
||||
}
|
||||
export interface UpdateStatus {
|
||||
id: number;
|
||||
|
||||
@@ -39,3 +39,35 @@ export const getBindDomain = (req: AI.BindDomainReq) => {
|
||||
export const updateBindDomain = (req: AI.BindDomain) => {
|
||||
return http.post(`/ai/domain/update`, req);
|
||||
};
|
||||
|
||||
export const pageMcpServer = (req: AI.McpServerSearch) => {
|
||||
return http.post<ResPage<AI.McpServer>>(`/ai/mcp/search`, req);
|
||||
};
|
||||
|
||||
export const createMcpServer = (req: AI.McpServer) => {
|
||||
return http.post(`/ai/mcp/server`, req);
|
||||
};
|
||||
|
||||
export const updateMcpServer = (req: AI.McpServer) => {
|
||||
return http.post(`/ai/mcp/server/update`, req);
|
||||
};
|
||||
|
||||
export const deleteMcpServer = (req: AI.McpServerDelete) => {
|
||||
return http.post(`/ai/mcp/server/del`, req);
|
||||
};
|
||||
|
||||
export const operateMcpServer = (req: AI.McpServerOperate) => {
|
||||
return http.post(`/ai/mcp/server/op`, req);
|
||||
};
|
||||
|
||||
export const bindMcpDomain = (req: AI.McpBindDomain) => {
|
||||
return http.post(`/ai/mcp/domain/bind`, req);
|
||||
};
|
||||
|
||||
export const getMcpDomain = () => {
|
||||
return http.get<AI.McpDomainRes>(`/ai/mcp/domain/get`);
|
||||
};
|
||||
|
||||
export const updateMcpDomain = (req: AI.McpBindDomainUpdate) => {
|
||||
return http.post(`/ai/mcp/domain/update`, req);
|
||||
};
|
||||
|
||||
@@ -27,8 +27,12 @@ export const searchRecords = (params: Cronjob.SearchRecord) => {
|
||||
return http.post<ResPage<Cronjob.Record>>(`cronjobs/search/records`, params);
|
||||
};
|
||||
|
||||
export const cleanRecords = (id: number, cleanData: boolean) => {
|
||||
return http.post(`cronjobs/records/clean`, { cronjobID: id, cleanData: cleanData });
|
||||
export const cleanRecords = (id: number, cleanData: boolean, cleanRemoteData: boolean) => {
|
||||
return http.post(`cronjobs/records/clean`, {
|
||||
cronjobID: id,
|
||||
cleanData: cleanData,
|
||||
cleanRemoteData: cleanRemoteData,
|
||||
});
|
||||
};
|
||||
|
||||
export const getRecordDetail = (params: string) => {
|
||||
|
||||
@@ -49,8 +49,8 @@ export const SaveFileContent = (params: File.FileEdit) => {
|
||||
return http.post<File.File>('files/save', params);
|
||||
};
|
||||
|
||||
export const CheckFile = (path: string) => {
|
||||
return http.post<boolean>('files/check', { path: path });
|
||||
export const CheckFile = (path: string, withInit: boolean) => {
|
||||
return http.post<boolean>('files/check', { path: path, withInit: withInit });
|
||||
};
|
||||
|
||||
export const BatchCheckFiles = (paths: string[]) => {
|
||||
|
||||
@@ -85,7 +85,9 @@ function onClose(isKeepShow: boolean = false) {
|
||||
term.value.dispose();
|
||||
} catch {}
|
||||
}
|
||||
terminalElement.value.innerHTML = '';
|
||||
if (terminalElement.value) {
|
||||
terminalElement.value.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
// terminal 相关代码 start
|
||||
@@ -202,10 +204,10 @@ const errorRealTerminal = (ex: any) => {
|
||||
|
||||
const closeRealTerminal = (ev: CloseEvent) => {
|
||||
if (heartbeatTimer.value) {
|
||||
clearInterval(heartbeatTimer.value);
|
||||
clearInterval(Number(heartbeatTimer.value));
|
||||
}
|
||||
term.value.write('The connection has been disconnected.');
|
||||
term.value.write(ev.reason);
|
||||
term.value?.write('The connection has been disconnected.');
|
||||
term.value?.write(ev.reason);
|
||||
};
|
||||
|
||||
const isWsOpen = () => {
|
||||
|
||||
@@ -308,7 +308,7 @@ const onSubmit = async () => {
|
||||
MsgError(i18n.global.t('commons.msg.fileNameErr'));
|
||||
return;
|
||||
}
|
||||
const res = await CheckFile(baseDir.value + file.raw.name);
|
||||
const res = await CheckFile(baseDir.value + file.raw.name, false);
|
||||
if (res.data) {
|
||||
MsgError(i18n.global.t('commons.msg.fileExist'));
|
||||
return;
|
||||
|
||||
@@ -121,7 +121,8 @@ const checkIllegal = (rule: any, value: any, callback: any) => {
|
||||
value.indexOf('`') !== -1 ||
|
||||
value.indexOf('(') !== -1 ||
|
||||
value.indexOf(')') !== -1 ||
|
||||
value.indexOf("'") !== -1
|
||||
value.indexOf('>') !== -1 ||
|
||||
value.indexOf('<') !== -1
|
||||
) {
|
||||
callback(new Error(i18n.global.t('commons.rule.illegalInput')));
|
||||
} else {
|
||||
|
||||
@@ -169,21 +169,13 @@ export const DNSTypes = [
|
||||
value: 'HuaweiCloud',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('website.volcengine'),
|
||||
value: 'Volcengine',
|
||||
},
|
||||
{
|
||||
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
|
||||
value: 'DnsPod',
|
||||
label: 'GoDaddy',
|
||||
value: 'Godaddy',
|
||||
},
|
||||
{
|
||||
label: 'Cloudflare',
|
||||
value: 'CloudFlare',
|
||||
},
|
||||
{
|
||||
label: 'CloudDNS',
|
||||
value: 'CloudDns',
|
||||
},
|
||||
{
|
||||
label: 'NameSilo',
|
||||
value: 'NameSilo',
|
||||
@@ -193,12 +185,16 @@ export const DNSTypes = [
|
||||
value: 'NameCheap',
|
||||
},
|
||||
{
|
||||
label: 'Name.com',
|
||||
value: 'NameCom',
|
||||
label: 'CloudDNS',
|
||||
value: 'CloudDns',
|
||||
},
|
||||
{
|
||||
label: 'GoDaddy',
|
||||
value: 'Godaddy',
|
||||
label: 'ClouDNS',
|
||||
value: 'ClouDNS',
|
||||
},
|
||||
{
|
||||
label: 'Name.com',
|
||||
value: 'NameCom',
|
||||
},
|
||||
{
|
||||
label: 'FreeMyIP',
|
||||
@@ -208,6 +204,18 @@ export const DNSTypes = [
|
||||
label: i18n.global.t('website.rainyun'),
|
||||
value: 'RainYun',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('website.westCN'),
|
||||
value: 'WestCN',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('website.volcengine'),
|
||||
value: 'Volcengine',
|
||||
},
|
||||
{
|
||||
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
|
||||
value: 'DnsPod',
|
||||
},
|
||||
];
|
||||
|
||||
export const Fields = [
|
||||
|
||||
@@ -191,6 +191,7 @@ const message = {
|
||||
rePassword: 'Confirm password is inconsistent with the password.',
|
||||
requiredInput: 'This field is required.',
|
||||
requiredSelect: 'Select an item in the list',
|
||||
illegalChar: 'Injection of characters & ; $ \' ` ( ) " > < | is currently not supported',
|
||||
illegalInput: `This field mustn't contains illegal characters.`,
|
||||
commonName:
|
||||
'This field must start with non-special characters and must consist of English, Chinese, numbers, ".", "-", and "_" characters with a length of 1-128.',
|
||||
@@ -427,6 +428,8 @@ const message = {
|
||||
permission: 'Permissions',
|
||||
permissionForIP: 'IP',
|
||||
permissionAll: 'All of them(%)',
|
||||
localhostHelper:
|
||||
'Configuring database permissions as "localhost" for container deployment will prevent external access to the container. Please choose carefully!',
|
||||
databaseConnInfo: 'Connection info',
|
||||
rootPassword: 'Root password',
|
||||
serviceName: 'Service Name',
|
||||
@@ -438,6 +441,7 @@ const message = {
|
||||
remoteConnHelper:
|
||||
'Remote connection to MySQL as user root may have security risks. Therefore, perform this operation with caution.',
|
||||
changePassword: 'Password',
|
||||
changeConnHelper: 'This operation will modify the current database {0}. Do you want to continue?',
|
||||
changePasswordHelper:
|
||||
'The database has been associated with an application. Changing the password will change the database password of the application at the same time. The change takes effect after the application restarts.',
|
||||
|
||||
@@ -1009,6 +1013,7 @@ const message = {
|
||||
errHandle: 'Cronjob execution failure',
|
||||
noRecord: 'Trigger the Cron Job, and you will see the records here.',
|
||||
cleanData: 'Clean data',
|
||||
cleanRemoteData: 'Delete remote data',
|
||||
cleanDataHelper: 'Delete the backup file generated during this task.',
|
||||
noLogs: 'No task output yet...',
|
||||
errPath: 'Backup path [{0}] error, cannot download!',
|
||||
@@ -1380,6 +1385,7 @@ const message = {
|
||||
existFileHelper: 'The uploaded file contains a file with the same name, do you want to overwrite it?',
|
||||
existFileSize: 'File size (new -> old)',
|
||||
existFileDirHelper: 'The selected file/folder has a duplicate name. Please proceed with caution!',
|
||||
noSuchFile: 'The file or directory was not found. Please check and try again.',
|
||||
},
|
||||
ssh: {
|
||||
setting: 'Setting',
|
||||
@@ -2256,6 +2262,7 @@ const message = {
|
||||
"When the reverse proxy backend is HTTPS, you might need to set the origin SNI. Please refer to the CDN service provider's documentation for details.",
|
||||
huaweicloud: 'Huawei Cloud',
|
||||
rainyun: 'Rain Yun',
|
||||
westCN: 'West CN',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Short tag support',
|
||||
@@ -2537,8 +2544,8 @@ const message = {
|
||||
env: 'Environments',
|
||||
noenv: 'None',
|
||||
net: 'Network connections',
|
||||
laddr: 'Source address/port',
|
||||
raddr: 'Destination address/port',
|
||||
laddr: 'Local address/port',
|
||||
raddr: 'Remote address/port',
|
||||
stopProcess: 'End',
|
||||
viewDetails: 'Details',
|
||||
stopProcessWarn: 'Are you sure you want to end this process (PID:{0})?',
|
||||
@@ -2597,6 +2604,29 @@ const message = {
|
||||
proxyHelper6: 'To disable proxy configuration, you can delete it from the website list.',
|
||||
whiteListHelper: 'Restrict access to only IPs in the whitelist',
|
||||
},
|
||||
mcp: {
|
||||
server: 'MCP Server',
|
||||
create: 'Add MCP Server',
|
||||
edit: 'Edit MCP Server',
|
||||
commandHelper: 'For example: npx -y {0}',
|
||||
baseUrl: 'External Access Path',
|
||||
baseUrlHelper: 'For example: http://192.168.1.2:8000',
|
||||
ssePath: 'SSE Path',
|
||||
ssePathHelper: 'For example: /sse, note not to duplicate with other servers',
|
||||
environment: 'Environment Variables',
|
||||
envKey: 'Variable Name',
|
||||
envValue: 'Variable Value',
|
||||
externalUrl: 'External Connection Address',
|
||||
operatorHelper: 'Will perform {1} operation on {0}, continue?',
|
||||
domain: 'Default Access Address',
|
||||
domainHelper: 'For example: 192.168.1.1 or example.com',
|
||||
bindDomain: 'Bind Website',
|
||||
commandPlaceHolder: 'Currently only supports npx startup commands',
|
||||
importMcpJson: 'Import MCP Server Configuration',
|
||||
importMcpJsonError: 'mcpServers structure is incorrect',
|
||||
bindDomainHelper:
|
||||
'After binding the website, it will modify the access address of all installed MCP Servers and close external access to the ports',
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -74,7 +74,7 @@ const message = {
|
||||
createNewFile: '新しいファイルを作成します',
|
||||
helpDoc: '文書をヘルプします',
|
||||
unbind: 'バインド',
|
||||
cover: '上書き',
|
||||
cover: 'に覆いを',
|
||||
skip: 'スキップ',
|
||||
},
|
||||
search: {
|
||||
@@ -190,6 +190,7 @@ const message = {
|
||||
rePassword: 'パスワードがパスワードと矛盾することを確認してください。',
|
||||
requiredInput: 'この項目は必須です。',
|
||||
requiredSelect: 'リスト内のアイテムを選択します',
|
||||
illegalChar: '現在、文字 & ; $ \' ` ( ) " > < | の注入はサポートされていません',
|
||||
illegalInput: `このフィールドには違法なキャラクターが含まれてはなりません。`,
|
||||
commonName:
|
||||
'このフィールドは、特別なキャラクターではなく、英語、中国語、数字で構成されている必要があります。「。」、「」、および「_」文字が1〜128の文字で構成されている必要があります。',
|
||||
@@ -427,6 +428,8 @@ const message = {
|
||||
permission: '権限',
|
||||
permissionForIP: 'ip',
|
||||
permissionAll: 'それらすべて(%)',
|
||||
localhostHelper:
|
||||
'コンテナ展開でデータベース権限を"localhost"に設定すると、コンテナ外部からのアクセスができなくなります。慎重に選択してください!',
|
||||
databaseConnInfo: '接続情報',
|
||||
rootPassword: 'ルートパスワード',
|
||||
serviceName: 'サービス名',
|
||||
@@ -438,6 +441,7 @@ const message = {
|
||||
remoteConnHelper:
|
||||
'ユーザールートとしてのMySQLへのリモート接続には、セキュリティリスクがある場合があります。したがって、この操作を慎重に実行します。',
|
||||
changePassword: 'パスワード',
|
||||
changeConnHelper: 'この操作は現在のデータベース {0} を変更します。続行しますか?',
|
||||
changePasswordHelper:
|
||||
'データベースはアプリケーションに関連付けられています。パスワードを変更すると、アプリケーションのデータベースパスワードが同時に変更されます。アプリケーションが再起動した後、変更は有効になります。',
|
||||
|
||||
@@ -994,6 +998,7 @@ const message = {
|
||||
errHandle: 'cronjob実行障害',
|
||||
noRecord: 'Cronジョブをトリガーすると、ここにレコードが表示されます。',
|
||||
cleanData: 'クリーンデータ',
|
||||
cleanRemoteData: 'リモートデータを削除',
|
||||
cleanDataHelper: 'このタスク中に生成されたバックアップファイルを削除します。',
|
||||
noLogs: 'タスク出力はまだありません...',
|
||||
errPath: 'バックアップパス[{0}]エラー、ダウンロードできません!',
|
||||
@@ -1358,6 +1363,7 @@ const message = {
|
||||
existFileHelper: 'アップロードしたファイルに同じ名前のファイルが含まれています。上書きしますか?',
|
||||
existFileSize: 'ファイルサイズ(新しい -> 古い)',
|
||||
existFileDirHelper: '選択したファイル/フォルダーには同じ名前のものが既に存在します。慎重に操作してください!',
|
||||
noSuchFile: 'ファイルまたはディレクトリが見つかりませんでした。確認して再試行してください。',
|
||||
},
|
||||
ssh: {
|
||||
setting: '設定',
|
||||
@@ -2228,6 +2234,8 @@ const message = {
|
||||
sniHelper:
|
||||
'逆プロキシバックエンドがHTTPSの場合、Origin SNIを設定する必要がある場合があります。詳細については、CDNサービスプロバイダーのドキュメントを参照してください。',
|
||||
huaweicloud: 'huaweiCloud',
|
||||
rainyun: 'Rain Yun',
|
||||
westCN: 'West CN',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '短いタグサポート',
|
||||
@@ -2566,6 +2574,29 @@ const message = {
|
||||
proxyHelper6: 'プロキシ設定を無効にするには、ウェブサイトリストから削除できます。',
|
||||
whiteListHelper: 'ホワイトリスト内のIPのみアクセスを許可する',
|
||||
},
|
||||
mcp: {
|
||||
server: 'MCP サーバー',
|
||||
create: 'サーバーを追加',
|
||||
edit: 'サーバーを編集',
|
||||
commandHelper: '例: npx -y {0}',
|
||||
baseUrl: '外部アクセスパス',
|
||||
baseUrlHelper: '例: http://192.168.1.2:8000',
|
||||
ssePath: 'SSE パス',
|
||||
ssePathHelper: '例: /sse, 他のサーバーと重複しないように注意してください',
|
||||
environment: '環境変数',
|
||||
envKey: '変数名',
|
||||
envValue: '変数値',
|
||||
externalUrl: '外部接続アドレス',
|
||||
operatorHelper: '{0} に {1} 操作を実行します、続行しますか?',
|
||||
domain: 'デフォルトアクセスアドレス',
|
||||
domainHelper: '例: 192.168.1.1 または example.com',
|
||||
bindDomain: 'ウェブサイトをバインド',
|
||||
commandPlaceHolder: '現在、npx スタートアップコマンドのみをサポートしています',
|
||||
importMcpJson: 'MCP サーバー設定をインポート',
|
||||
importMcpJsonError: 'mcpServers 構造が正しくありません',
|
||||
bindDomainHelper:
|
||||
'ウェブサイトをバインドした後、インストールされたすべての MCP サーバーのアクセスアドレスを変更し、ポートへの外部アクセスを閉じます',
|
||||
},
|
||||
};
|
||||
export default {
|
||||
...fit2cloudJaLocale,
|
||||
|
||||
@@ -190,6 +190,7 @@ const message = {
|
||||
rePassword: '확인 비밀번호가 비밀번호와 일치하지 않습니다.',
|
||||
requiredInput: '이 필드는 필수 항목입니다.',
|
||||
requiredSelect: '목록에서 항목을 선택하세요',
|
||||
illegalChar: '현재 & ; $ \' ` ( ) " > < | 문자 주입은 지원되지 않습니다',
|
||||
illegalInput: '이 필드에는 유효하지 않은 문자가 포함될 수 없습니다.',
|
||||
commonName:
|
||||
'이 필드는 특수 문자로 시작할 수 없으며, 영어, 한자, 숫자, ".", "-", "_" 문자로 구성되어야 하며 길이는 1-128자여야 합니다.',
|
||||
@@ -429,6 +430,8 @@ const message = {
|
||||
permission: '권한',
|
||||
permissionForIP: 'IP',
|
||||
permissionAll: '모두(%)',
|
||||
localhostHelper:
|
||||
'컨테이너 배포 시 데이터베이스 권한을 "localhost"로 설정하면 컨테이너 외부에서 접근할 수 없게 됩니다. 신중하게 선택하세요!',
|
||||
databaseConnInfo: '연결 정보',
|
||||
rootPassword: '루트 비밀번호',
|
||||
serviceName: '서비스 이름',
|
||||
@@ -440,6 +443,7 @@ const message = {
|
||||
remoteConnHelper:
|
||||
'MySQL 의 root 사용자로 원격 접속은 보안 위험을 초래할 수 있습니다. 따라서 이 작업은 신중히 수행해야 합니다.',
|
||||
changePassword: '비밀번호',
|
||||
changeConnHelper: '이 작업은 현재 데이터베이스 {0}을(를) 수정합니다. 계속하시겠습니까?',
|
||||
changePasswordHelper:
|
||||
'데이터베이스가 애플리케이션과 연결되어 있습니다. 비밀번호를 변경하면 애플리케이션의 데이터베이스 비밀번호도 변경됩니다. 변경 사항은 애플리케이션이 재시작된 후에 적용됩니다.',
|
||||
confChange: '설정',
|
||||
@@ -988,6 +992,7 @@ const message = {
|
||||
errHandle: '크론 작업 실행 실패',
|
||||
noRecord: '크론 작업을 트리거하고 나면 여기에 레코드가 표시됩니다.',
|
||||
cleanData: '데이터 정리',
|
||||
cleanRemoteData: '원격 데이터 삭제',
|
||||
cleanDataHelper: '이 작업에서 생성된 백업 파일을 삭제합니다.',
|
||||
noLogs: '작업 출력이 아직 없습니다...',
|
||||
errPath: '백업 경로 [{0}] 오류, 다운로드할 수 없습니다!',
|
||||
@@ -1344,6 +1349,7 @@ const message = {
|
||||
existFileHelper: '업로드한 파일에 동일한 이름의 파일이 포함되어 있습니다. 덮어쓰시겠습니까?',
|
||||
existFileSize: '파일 크기 (새로운 -> 오래된)',
|
||||
existFileDirHelper: '선택한 파일/폴더에 동일한 이름이 이미 존재합니다. 신중하게 작업하세요!',
|
||||
noSuchFile: '파일 또는 디렉터리를 찾을 수 없습니다. 확인 후 다시 시도하세요.',
|
||||
},
|
||||
ssh: {
|
||||
setting: '설정',
|
||||
@@ -2190,6 +2196,7 @@ const message = {
|
||||
'역방향 프록시 백엔드가 HTTPS 인 경우 원본 SNI 를 설정해야 할 수 있습니다. 자세한 내용은 CDN 서비스 제공자의 문서를 참조하세요.',
|
||||
huaweicloud: '화웨이 클라우드',
|
||||
rainyun: '레인윤',
|
||||
westCN: 'West CN',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '짧은 태그 지원',
|
||||
@@ -2526,6 +2533,29 @@ const message = {
|
||||
proxyHelper6: '프록시 구성을 비활성화하려면 웹사이트 목록에서 삭제할 수 있습니다.',
|
||||
whiteListHelper: '화이트리스트에 있는 IP만 접근 허용',
|
||||
},
|
||||
mcp: {
|
||||
server: 'MCP サーバー',
|
||||
create: 'サーバーを追加',
|
||||
edit: 'サーバーを編集',
|
||||
commandHelper: '例: npx -y {0}',
|
||||
baseUrl: '外部アクセスパス',
|
||||
baseUrlHelper: '例: http://192.168.1.2:8000',
|
||||
ssePath: 'SSE パス',
|
||||
ssePathHelper: '例: /sse, 他のサーバーと重複しないように注意してください',
|
||||
environment: '環境変数',
|
||||
envKey: '変数名',
|
||||
envValue: '変数値',
|
||||
externalUrl: '外部接続アドレス',
|
||||
operatorHelper: '{0} に {1} 操作を実行します、続行しますか?',
|
||||
domain: 'デフォルトアクセスアドレス',
|
||||
domainHelper: '例: 192.168.1.1 または example.com',
|
||||
bindDomain: 'ウェブサイトをバインド',
|
||||
commandPlaceHolder: '현재 npx 시작 명령만 지원합니다',
|
||||
importMcpJson: 'MCP サーバー設定をインポート',
|
||||
importMcpJsonError: 'mcpServers 構造が正しくありません',
|
||||
bindDomainHelper:
|
||||
'웹사이트를 바인딩한 후, 설치된 모든 MCP 서버의 접근 주소를 수정하고 포트의 외부 접근을 닫습니다',
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -193,6 +193,7 @@ const message = {
|
||||
rePassword: 'Pengesahan kata laluan tidak sepadan dengan kata laluan.',
|
||||
requiredInput: 'Ruangan ini wajib diisi.',
|
||||
requiredSelect: 'Pilih satu item dalam senarai',
|
||||
illegalChar: 'Suntikan aksara & ; $ \' ` ( ) " > < | tidak disokong buat masa ini',
|
||||
illegalInput: 'Ruangan ini tidak boleh mengandungi aksara tidak sah.',
|
||||
commonName:
|
||||
'Ruangan ini mesti bermula dengan aksara bukan khas dan mesti terdiri daripada aksara rumi, Cina, nombor, ".", "-", dan "_" dengan panjang 1-128 aksara.',
|
||||
@@ -435,6 +436,8 @@ const message = {
|
||||
permission: 'Kebenaran',
|
||||
permissionForIP: 'IP',
|
||||
permissionAll: 'Kesemuanya(%)',
|
||||
localhostHelper:
|
||||
'Mengkonfigurasi kebenaran pangkalan data sebagai "localhost" untuk penyebaran kontena akan menghalang akses luar ke kontena. Sila pilih dengan teliti!',
|
||||
databaseConnInfo: 'Maklumat sambungan',
|
||||
rootPassword: 'Kata laluan root',
|
||||
serviceName: 'Nama Perkhidmatan',
|
||||
@@ -446,6 +449,7 @@ const message = {
|
||||
remoteConnHelper:
|
||||
'Sambungan jauh ke MySQL sebagai pengguna root mungkin mempunyai risiko keselamatan. Oleh itu, lakukan operasi ini dengan berhati-hati.',
|
||||
changePassword: 'Kata laluan',
|
||||
changeConnHelper: 'Operasi ini akan mengubah pangkalan data semasa {0}. Adakah anda ingin meneruskan?',
|
||||
changePasswordHelper:
|
||||
'Pangkalan data telah dikaitkan dengan aplikasi. Menukar kata laluan akan menukar kata laluan pangkalan data aplikasi pada masa yang sama. Perubahan ini akan berkuat kuasa selepas aplikasi dimulakan semula.',
|
||||
|
||||
@@ -1023,6 +1027,7 @@ const message = {
|
||||
errHandle: 'Kegagalan pelaksanaan tugas cron',
|
||||
noRecord: 'Picu Tugas Cron, dan anda akan melihat rekod di sini.',
|
||||
cleanData: 'Bersihkan data',
|
||||
cleanRemoteData: 'Padam data jarak jauh',
|
||||
cleanDataHelper: 'Padam fail sandaran yang dijana semasa tugas ini.',
|
||||
noLogs: 'Tiada keluaran tugas lagi...',
|
||||
errPath: 'Laluan sandaran [{0}] salah, tidak boleh dimuat turun!',
|
||||
@@ -1401,6 +1406,7 @@ const message = {
|
||||
existFileHelper: 'Fail yang dimuat naik mengandungi fail dengan nama yang sama. Adakah anda mahu menimpanya?',
|
||||
existFileSize: 'Saiz fail (baru -> lama)',
|
||||
existFileDirHelper: 'Fail/folder yang dipilih mempunyai nama yang sama. Sila berhati-hati!',
|
||||
noSuchFile: 'Fail atau direktori tidak ditemui. Sila periksa dan cuba lagi.',
|
||||
},
|
||||
ssh: {
|
||||
setting: 'tetapan',
|
||||
@@ -2279,6 +2285,7 @@ const message = {
|
||||
'Apabila backend proksi terbalik adalah HTTPS, anda mungkin perlu menetapkan sumber SNI. Sila rujuk dokumentasi penyedia perkhidmatan CDN untuk butiran.',
|
||||
huaweicloud: 'Huawei Cloud',
|
||||
rainyun: 'Rain Yun',
|
||||
westCN: 'West CN',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Sokongan tag pendek',
|
||||
@@ -2625,6 +2632,29 @@ const message = {
|
||||
proxyHelper6: 'Untuk melumpuhkan konfigurasi proksi, anda boleh memadamnya dari senarai laman web.',
|
||||
whiteListHelper: 'Hadkan akses kepada hanya IP dalam senarai putih',
|
||||
},
|
||||
mcp: {
|
||||
server: 'Pelayan MCP',
|
||||
create: 'Tambah Pelayan',
|
||||
edit: 'Edit Pelayan',
|
||||
commandHelper: 'Contoh: npx -y {0}',
|
||||
baseUrl: 'Laluan Akses Luar',
|
||||
baseUrlHelper: 'Contoh: http://192.168.1.2:8000',
|
||||
ssePath: 'Laluan SSE',
|
||||
ssePathHelper: 'Contoh: /sse, berhati-hati jangan bertindan dengan pelayan lain',
|
||||
environment: 'Pemboleh Ubah Persekitaran',
|
||||
envKey: 'Nama Pemboleh Ubah',
|
||||
envValue: 'Nilai Pemboleh Ubah',
|
||||
externalUrl: 'Alamat Sambungan Luar',
|
||||
operatorHelper: 'Akan melakukan operasi {1} pada {0}, teruskan?',
|
||||
domain: 'Alamat Akses Lalai',
|
||||
domainHelper: 'Contoh: 192.168.1.1 atau example.com',
|
||||
bindDomain: 'Sematkan Laman Web',
|
||||
commandPlaceHolder: 'Kini hanya menyokong perintah pelancaran npx',
|
||||
importMcpJson: 'Import Konfigurasi Pelayan MCP',
|
||||
importMcpJsonError: 'Struktur mcpServers tidak betul',
|
||||
bindDomainHelper:
|
||||
'Setelah mengikat laman web, ia akan mengubah alamat akses semua Pelayan MCP yang dipasang dan menutup akses luaran ke pelabuhan',
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -192,6 +192,7 @@ const message = {
|
||||
rePassword: 'A confirmação da senha não corresponde à senha.',
|
||||
requiredInput: 'Este campo é obrigatório.',
|
||||
requiredSelect: 'Selecione um item na lista',
|
||||
illegalChar: 'Atualmente não há suporte para injeção dos caracteres & ; $ \' ` ( ) " > < |',
|
||||
illegalInput: 'Este campo não deve conter caracteres ilegais.',
|
||||
commonName:
|
||||
'Este campo deve começar com caracteres não especiais e consistir em letras, números, ".", "-", e "_" com comprimento de 1-128.',
|
||||
@@ -433,6 +434,8 @@ const message = {
|
||||
permission: 'Permissões',
|
||||
permissionForIP: 'IP',
|
||||
permissionAll: 'Todos (% de)',
|
||||
localhostHelper:
|
||||
'Configurar permissões de banco de dados como "localhost" para implantação em contêiner impedirá o acesso externo ao contêiner. Por favor, escolha com cuidado!',
|
||||
databaseConnInfo: 'Informações de conexão',
|
||||
rootPassword: 'Senha root',
|
||||
serviceName: 'Nome do serviço',
|
||||
@@ -444,6 +447,7 @@ const message = {
|
||||
remoteConnHelper:
|
||||
'Conectar-se ao MySQL como usuário root pode representar riscos de segurança. Realize esta operação com cautela.',
|
||||
changePassword: 'Senha',
|
||||
changeConnHelper: 'Esta operação modificará o banco de dados atual {0}. Deseja continuar?',
|
||||
changePasswordHelper:
|
||||
'O banco de dados está associado a um aplicativo. Alterar a senha alterará a senha do banco de dados do aplicativo ao mesmo tempo. A mudança surtirá efeito após a reinicialização do aplicativo.',
|
||||
|
||||
@@ -1014,6 +1018,7 @@ const message = {
|
||||
errHandle: 'Falha na execução do Cronjob',
|
||||
noRecord: 'Acione a tarefa Cron e você verá os registros aqui.',
|
||||
cleanData: 'Limpar dados',
|
||||
cleanRemoteData: 'Excluir dados remotos',
|
||||
cleanDataHelper: 'Excluir o arquivo de backup gerado durante esta tarefa.',
|
||||
noLogs: 'Ainda não há saída de tarefa...',
|
||||
errPath: 'Caminho de backup [{0}] com erro, não é possível fazer o download!',
|
||||
@@ -1388,6 +1393,7 @@ const message = {
|
||||
existFileHelper: 'O arquivo enviado contém um arquivo com o mesmo nome. Deseja substituí-lo?',
|
||||
existFileSize: 'Tamanho do arquivo (novo -> antigo)',
|
||||
existFileDirHelper: 'O arquivo/pasta selecionado tem um nome duplicado. Por favor, prossiga com cautela!',
|
||||
noSuchFile: 'O arquivo ou diretório não foi encontrado. Por favor, verifique e tente novamente.',
|
||||
},
|
||||
ssh: {
|
||||
setting: 'configuração',
|
||||
@@ -2275,6 +2281,8 @@ const message = {
|
||||
sniHelper:
|
||||
'Quando o proxy reverso de backend for HTTPS, você pode precisar configurar o SNI de origem. Consulte a documentação do provedor de serviços CDN para mais detalhes.',
|
||||
huaweicloud: 'Huawei Cloud',
|
||||
rainyun: 'Rain Yun',
|
||||
westCN: 'West CN',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Suporte para short tags',
|
||||
@@ -2622,6 +2630,29 @@ const message = {
|
||||
proxyHelper6: 'Para desativar a configuração de proxy, você pode excluí-la da lista de sites.',
|
||||
whiteListHelper: 'Restringir o acesso apenas aos IPs na lista branca',
|
||||
},
|
||||
mcp: {
|
||||
server: 'Servidor MCP',
|
||||
create: 'Adicionar Servidor',
|
||||
edit: 'Editar Servidor',
|
||||
commandHelper: 'Por exemplo: npx -y {0}',
|
||||
baseUrl: 'Caminho de Acesso Externo',
|
||||
baseUrlHelper: 'Por exemplo: http://192.168.1.2:8000',
|
||||
ssePath: 'Caminho SSE',
|
||||
ssePathHelper: 'Por exemplo: /sse, tome cuidado para não duplicar com outros servidores',
|
||||
environment: 'Variáveis de Ambiente',
|
||||
envKey: 'Nome da Variável',
|
||||
envValue: 'Valor da Variável',
|
||||
externalUrl: 'Endereço de Conexão Externo',
|
||||
operatorHelper: 'Será realizada a operação {1} no {0}, continuar?',
|
||||
domain: 'Endereço de Acesso Padrão',
|
||||
domainHelper: 'Por exemplo: 192.168.1.1 ou example.com',
|
||||
bindDomain: 'Vincular Site',
|
||||
commandPlaceHolder: 'Atualmente, apenas comandos de inicialização npx são suportados',
|
||||
importMcpJson: 'Importar Configuração do Servidor MCP',
|
||||
importMcpJsonError: 'A estrutura mcpServers está incorreta',
|
||||
bindDomainHelper:
|
||||
'Após vincular o site, ele modificará o endereço de acesso de todos os servidores MCP instalados e fechará o acesso externo às portas',
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -113,8 +113,7 @@ const message = {
|
||||
refreshRate: 'Частота обновления',
|
||||
refreshRateUnit: 'Без обновления | {n} секунда/раз | {n} секунд/раз',
|
||||
selectColumn: 'Выбрать столбец',
|
||||
cover: 'Заменить',
|
||||
skip: 'Пропустить',
|
||||
serialNumber: 'Серийный номер',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: 'Обновление системы, пожалуйста, подождите...',
|
||||
@@ -192,6 +191,7 @@ const message = {
|
||||
rePassword: 'Подтверждение пароля не совпадает с паролем.',
|
||||
requiredInput: 'Это поле обязательно для заполнения.',
|
||||
requiredSelect: 'Выберите элемент из списка',
|
||||
illegalChar: 'В настоящее время не поддерживается вставка символов & ; $ \' ` ( ) " > < |',
|
||||
illegalInput: 'Это поле не должно содержать недопустимых символов.',
|
||||
commonName:
|
||||
'Это поле должно начинаться с неспециальных символов и должно состоять из английских букв, китайских иероглифов, цифр, ".", "-" и "_" длиной 1-128.',
|
||||
@@ -430,6 +430,8 @@ const message = {
|
||||
permission: 'Разрешения',
|
||||
permissionForIP: 'IP',
|
||||
permissionAll: 'Все (%)',
|
||||
localhostHelper:
|
||||
'Настройка разрешений базы данных как "localhost" для развертывания контейнера предотвратит внешний доступ к контейнеру. Пожалуйста, выбирайте внимательно!',
|
||||
databaseConnInfo: 'Информация о подключении',
|
||||
rootPassword: 'Пароль root',
|
||||
serviceName: 'Имя сервиса',
|
||||
@@ -441,6 +443,7 @@ const message = {
|
||||
remoteConnHelper:
|
||||
'Удаленное подключение к MySQL как пользователь root может иметь риски безопасности. Поэтому выполняйте эту операцию с осторожностью.',
|
||||
changePassword: 'Пароль',
|
||||
changeConnHelper: 'Эта операция изменит текущую базу данных {0}. Продолжить?',
|
||||
changePasswordHelper:
|
||||
'База данных была связана с приложением. Изменение пароля изменит пароль базы данных приложения одновременно. Изменение вступит в силу после перезапуска приложения.',
|
||||
|
||||
@@ -1018,6 +1021,7 @@ const message = {
|
||||
errHandle: 'Сбой выполнения задачи Cron',
|
||||
noRecord: 'Запустите задачу Cron, и вы увидите записи здесь.',
|
||||
cleanData: 'Очистить данные',
|
||||
cleanRemoteData: 'Удалить удалённые данные',
|
||||
cleanDataHelper: 'Удалить файл резервной копии, созданный во время этой задачи.',
|
||||
noLogs: 'Пока нет вывода задачи...',
|
||||
errPath: 'Ошибка пути резервной копии [{0}], невозможно скачать!',
|
||||
@@ -1391,6 +1395,7 @@ const message = {
|
||||
existFileHelper: 'Загруженный файл содержит файл с таким же именем. Заменить его?',
|
||||
existFileSize: 'Размер файла (новый -> старый)',
|
||||
existFileDirHelper: 'Выбранный файл/папка имеет дублирующееся имя. Пожалуйста, действуйте осторожно!',
|
||||
noSuchFile: 'Файл или каталог не найдены. Пожалуйста, проверьте и повторите попытку.',
|
||||
},
|
||||
ssh: {
|
||||
setting: 'настройка',
|
||||
@@ -2277,6 +2282,7 @@ const message = {
|
||||
'Когда бэкенд обратного прокси использует HTTPS, может потребоваться установить origin SNI. Подробности см. в документации провайдера CDN.',
|
||||
huaweicloud: 'Huawei Cloud',
|
||||
rainyun: 'Rain Yun',
|
||||
westCN: 'West CN',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Поддержка коротких тегов',
|
||||
@@ -2620,6 +2626,29 @@ const message = {
|
||||
proxyHelper6: 'Чтобы отключить настройку прокси, вы можете удалить её из списка сайтов.',
|
||||
whiteListHelper: 'Ограничить доступ только для IP-адресов из белого списка',
|
||||
},
|
||||
mcp: {
|
||||
server: 'Сервер MCP',
|
||||
create: 'Добавить сервер',
|
||||
edit: 'Редактировать сервер',
|
||||
commandHelper: 'Например: npx -y {0}',
|
||||
baseUrl: 'Внешний путь доступа',
|
||||
baseUrlHelper: 'Например: http://192.168.1.2:8000',
|
||||
ssePath: 'Путь SSE',
|
||||
ssePathHelper: 'Например: /sse, будьте осторожны, чтобы не дублировать с другими серверами',
|
||||
environment: 'Переменные среды',
|
||||
envKey: 'Имя переменной',
|
||||
envValue: 'Значение переменной',
|
||||
externalUrl: 'Внешний адрес подключения',
|
||||
operatorHelper: 'Будет выполнена операция {1} на {0}, продолжить?',
|
||||
domain: 'Адрес доступа по умолчанию',
|
||||
domainHelper: 'Например: 192.168.1.1 или example.com',
|
||||
bindDomain: 'Привязать сайт',
|
||||
commandPlaceHolder: 'В настоящее время поддерживаются только команды запуска npx',
|
||||
importMcpJson: 'Импортировать конфигурацию сервера MCP',
|
||||
importMcpJsonError: 'Структура mcpServers некорректна',
|
||||
bindDomainHelper:
|
||||
'После привязки веб-сайта он изменит адрес доступа для всех установленных серверов MCP и закроет внешний доступ к портам',
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -192,6 +192,7 @@ const message = {
|
||||
rePassword: '密碼不一致,請檢查後重新輸入',
|
||||
requiredInput: '請填寫必填項',
|
||||
requiredSelect: '請選擇必選項',
|
||||
illegalChar: '暫不支援注入字元 & ; $ \' ` ( ) " > < |',
|
||||
illegalInput: '輸入框中存在不合法字元',
|
||||
commonName: '支援非特殊字元開頭,英文、中文、數字、.-和_,長度1-128',
|
||||
userName: '支援英文、中文、數字和_,長度3-30',
|
||||
@@ -374,7 +375,7 @@ const message = {
|
||||
|
||||
core: '物理核心',
|
||||
logicCore: '邏輯核心',
|
||||
loadAverage: '最近 {0} 分鐘平均負載',
|
||||
loadAverage: '最近 1 分鐘平均負載 | 最近 {n} 分鐘平均負載',
|
||||
load: '負載',
|
||||
mount: '掛載點',
|
||||
fileSystem: '檔案系統',
|
||||
@@ -418,6 +419,7 @@ const message = {
|
||||
permission: '權限',
|
||||
permissionForIP: '指定 IP',
|
||||
permissionAll: '所有人(%)',
|
||||
localhostHelper: '將容器部署的資料庫權限配置為"localhost"會導致容器外部無法存取,請謹慎選擇!',
|
||||
databaseConnInfo: '連接資訊',
|
||||
rootPassword: 'root 密碼',
|
||||
serviceName: '服務名稱',
|
||||
@@ -428,6 +430,7 @@ const message = {
|
||||
remoteHelper: '多個 ip 以逗號分隔,例:172.16.10.111,172.16.10.112',
|
||||
remoteConnHelper: 'root 帳號遠端連接 MySQL 有安全風險,開啟需謹慎!',
|
||||
changePassword: '改密',
|
||||
changeConnHelper: '此操作將修改當前資料庫 {0},是否繼續?',
|
||||
changePasswordHelper: '目前資料庫已經關聯應用,修改密碼將同步修改應用中資料庫密碼,修改後重啟生效。',
|
||||
|
||||
portHelper: '該埠為容器對外暴露埠,修改需要單獨儲存並且重啟容器!',
|
||||
@@ -962,6 +965,7 @@ const message = {
|
||||
errHandle: '任務執行失敗',
|
||||
noRecord: '目前計劃任務暫未產生記錄',
|
||||
cleanData: '刪除備份檔案',
|
||||
cleanRemoteData: '刪除遠端備份檔案',
|
||||
cleanDataHelper: '刪除該任務執行過程中產生的備份檔案',
|
||||
noLogs: '暫無任務輸出...',
|
||||
errPath: '備份路徑 [{0}] 錯誤,無法下載!',
|
||||
@@ -1315,6 +1319,7 @@ const message = {
|
||||
existFileHelper: '上傳的檔案存在同名檔案,是否覆蓋?',
|
||||
existFileSize: '文件大小(新->舊)',
|
||||
existFileDirHelper: '選擇的檔案/資料夾存在同名,請謹慎操作!',
|
||||
noSuchFile: '找不到該檔案或目錄,請檢查後重試。',
|
||||
},
|
||||
ssh: {
|
||||
setting: '設定',
|
||||
@@ -2112,6 +2117,7 @@ const message = {
|
||||
sniHelper: '反代後端為 https 的時候可能需要設定回源 SNI,具體需要看 CDN 服務商文件',
|
||||
huaweicloud: '華為雲',
|
||||
rainyun: '雨雲',
|
||||
westCN: '西部數據',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '短標籤支援',
|
||||
@@ -2375,8 +2381,8 @@ const message = {
|
||||
env: '環境變數',
|
||||
noenv: '無',
|
||||
net: '網路連線',
|
||||
laddr: '源地址/埠',
|
||||
raddr: '目標地址/埠',
|
||||
laddr: '本地地址/埠',
|
||||
raddr: '远程地址/埠',
|
||||
stopProcess: '結束',
|
||||
viewDetails: '查看詳情',
|
||||
stopProcessWarn: '是否確定結束此行程 (PID:{0})?',
|
||||
@@ -2433,6 +2439,28 @@ const message = {
|
||||
proxyHelper6: '如需關閉代理配置,可以在網站列表中刪除',
|
||||
whiteListHelper: '限制僅白名單中的 IP 可瀏覽',
|
||||
},
|
||||
mcp: {
|
||||
server: 'MCP Server',
|
||||
create: '创建 MCP Server',
|
||||
edit: '編輯 MCP Server',
|
||||
commandHelper: '例如:npx -y {0}',
|
||||
baseUrl: '外部訪問路徑',
|
||||
baseUrlHelper: '例如:http://192.168.1.2:8000',
|
||||
ssePath: 'SSE 路徑',
|
||||
ssePathHelper: '例如:/sse,注意不要與其他 Server 重複',
|
||||
environment: '環境變數',
|
||||
envKey: '變數名',
|
||||
envValue: '變數值',
|
||||
externalUrl: '外部連接地址',
|
||||
operatorHelper: '將對 {0} 進行 {1} 操作,是否繼續?',
|
||||
domain: '默認訪問地址',
|
||||
domainHelper: '例如:192.168.1.1 或者 example.com',
|
||||
bindDomain: '綁定網站',
|
||||
commandPlaceHolder: '當前僅支持 npx 啟動命令',
|
||||
importMcpJson: '導入 MCP Server配置',
|
||||
importMcpJsonError: 'mcpServers 結構不正確',
|
||||
bindDomainHelper: '綁定網站之後會修改所有已安裝 MCP Server 的訪問地址,並關閉端口的外部訪問',
|
||||
},
|
||||
};
|
||||
export default {
|
||||
...fit2cloudTwLocale,
|
||||
|
||||
@@ -191,6 +191,7 @@ const message = {
|
||||
rePassword: '密码不一致,请检查后重新输入',
|
||||
requiredInput: '请填写必填项',
|
||||
requiredSelect: '请选择必选项',
|
||||
illegalChar: '暂不支持注入字符 & ; $ \' ` ( ) " > < |',
|
||||
illegalInput: '输入框中存在不合法字符',
|
||||
commonName: '支持非特殊字符开头,英文、中文、数字、.-和_,长度1-128',
|
||||
userName: '支持英文、中文、数字和_,长度3-30',
|
||||
@@ -419,6 +420,7 @@ const message = {
|
||||
permission: '权限',
|
||||
permissionForIP: '指定 IP',
|
||||
permissionAll: '所有人(%)',
|
||||
localhostHelper: '将容器部署的数据库权限配置为 localhost 会导致容器外部无法访问,请谨慎选择!',
|
||||
databaseConnInfo: '连接信息',
|
||||
rootPassword: 'root 密码',
|
||||
serviceName: '服务名称',
|
||||
@@ -429,6 +431,7 @@ const message = {
|
||||
remoteHelper: '多个 ip 以逗号分隔,例:172.16.10.111,172.16.10.112',
|
||||
remoteConnHelper: 'root 帐号远程连接 MySQL 有安全风险,开启需谨慎!',
|
||||
changePassword: '改密',
|
||||
changeConnHelper: '此操作将修改当前数据库 {0},是否继续?',
|
||||
changePasswordHelper: '当前数据库已经关联应用,修改密码将同步修改应用中数据库密码,修改后重启生效。',
|
||||
|
||||
portHelper: '该端口为容器对外暴露端口,修改需要单独保存并且重启容器!',
|
||||
@@ -563,7 +566,7 @@ const message = {
|
||||
restartNowHelper: '修改配置后需要重启生效。',
|
||||
|
||||
persistence: '持久化',
|
||||
rdbHelper1: '秒內,插入',
|
||||
rdbHelper1: '秒内,插入',
|
||||
rdbHelper2: '条数据',
|
||||
rdbHelper3: '符合任意一个条件将会触发RDB持久化',
|
||||
rdbInfo: '请确认规则列表中值在 1-100000 之间',
|
||||
@@ -963,6 +966,7 @@ const message = {
|
||||
errHandle: '任务执行失败',
|
||||
noRecord: '当前计划任务暂未产生记录',
|
||||
cleanData: '删除备份文件',
|
||||
cleanRemoteData: '删除远程备份文件',
|
||||
cleanDataHelper: '删除该任务执行过程中产生的备份文件',
|
||||
noLogs: '暂无任务输出...',
|
||||
errPath: '备份路径 [{0}] 错误,无法下载!',
|
||||
@@ -1317,6 +1321,7 @@ const message = {
|
||||
existFileHelper: '上传的文件存在同名文件,是否覆盖?',
|
||||
existFileSize: '文件大小 (新 -> 旧)',
|
||||
existFileDirHelper: '选择的文件/文件夹存在同名,请谨慎操作!',
|
||||
noSuchFile: '未能找到该文件或目录,请检查后重试',
|
||||
},
|
||||
ssh: {
|
||||
setting: '配置',
|
||||
@@ -2113,6 +2118,7 @@ const message = {
|
||||
sniHelper: '反代后端为 https 的时候可能需要设置回源 SNI,具体需要看 CDN 服务商文档',
|
||||
huaweicloud: '华为云',
|
||||
rainyun: '雨云',
|
||||
westCN: '西部数码',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '短标签支持',
|
||||
@@ -2377,8 +2383,8 @@ const message = {
|
||||
env: '环境变量',
|
||||
noenv: '无',
|
||||
net: '网络连接',
|
||||
laddr: '源地址/端口',
|
||||
raddr: '目标地址/端口',
|
||||
laddr: '本地地址/端口',
|
||||
raddr: '远程地址/端口',
|
||||
stopProcess: '结束',
|
||||
viewDetails: '查看详情',
|
||||
stopProcessWarn: '是否确定结束此进程 (PID:{0})?',
|
||||
@@ -2435,6 +2441,28 @@ const message = {
|
||||
proxyHelper6: '如需关闭代理配置,可以在网站列表中删除',
|
||||
whiteListHelper: '限制仅白名单中的 IP 可访问',
|
||||
},
|
||||
mcp: {
|
||||
server: 'MCP Server',
|
||||
create: '创建 MCP Server',
|
||||
edit: '编辑 MCP Server',
|
||||
commandHelper: '例如:npx -y {0}',
|
||||
baseUrl: '外部访问路径',
|
||||
baseUrlHelper: '例如:http://192.168.1.1:8000',
|
||||
ssePath: 'SSE 路径',
|
||||
ssePathHelper: '例如:/sse,注意不要与其他 Server 重复',
|
||||
environment: '环境变量',
|
||||
envKey: '变量名',
|
||||
envValue: '变量值',
|
||||
externalUrl: '外部连接地址',
|
||||
operatorHelper: '将对 {0} 进行 {1} 操作,是否继续?',
|
||||
domain: '默认访问地址',
|
||||
domainHelper: '例如:192.168.1.1 或者 example.com',
|
||||
bindDomain: '绑定网站',
|
||||
commandPlaceHolder: '当前仅支持 npx 启动命令',
|
||||
importMcpJson: '导入 MCP Server 配置',
|
||||
importMcpJsonError: 'mcpServers 结构不正确',
|
||||
bindDomainHelper: '绑定网站之后会修改所有已安装 MCP Server 的访问地址,并关闭端口的外部访问',
|
||||
},
|
||||
};
|
||||
export default {
|
||||
...fit2cloudZhLocale,
|
||||
|
||||
@@ -19,6 +19,15 @@ const databaseRouter = {
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/ai/mcp',
|
||||
name: 'MCPServer',
|
||||
component: () => import('@/views/ai/mcp/server/index.vue'),
|
||||
meta: {
|
||||
title: 'MCP',
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/ai/gpu',
|
||||
name: 'GPU',
|
||||
|
||||
@@ -71,6 +71,9 @@ const GlobalStore = defineStore({
|
||||
this.csrfToken = token;
|
||||
},
|
||||
updateLanguage(language: any) {
|
||||
if (language === 'pt-BR') {
|
||||
language = 'pt-br';
|
||||
}
|
||||
this.language = language;
|
||||
localStorage.setItem('lang', language);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
<RouterButton :buttons="buttons" />
|
||||
<LayoutContent>
|
||||
<router-view></router-view>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const buttons = [
|
||||
{
|
||||
label: 'Servers',
|
||||
path: '/ai/mcp/servers',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="open"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
size="50%"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('mcp.bindDomain')" :back="handleClose" />
|
||||
</template>
|
||||
<div v-loading="loading">
|
||||
<el-form ref="formRef" label-position="top" @submit.prevent :model="req" :rules="rules">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-alert class="common-prompt" :closable="false" type="warning">
|
||||
<template #default>
|
||||
<ul>
|
||||
<li>{{ $t('aitool.proxyHelper1') }}</li>
|
||||
<li>{{ $t('aitool.proxyHelper2') }}</li>
|
||||
<li>{{ $t('aitool.proxyHelper3') }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-form-item :label="$t('website.domain')" prop="domain">
|
||||
<el-input v-model.trim="req.domain" :disabled="operate === 'update'" />
|
||||
<span class="input-help">
|
||||
{{ $t('aitool.proxyHelper4') }}
|
||||
</span>
|
||||
<span class="input-help">
|
||||
{{ $t('aitool.proxyHelper6') }}
|
||||
<el-link
|
||||
class="pageRoute"
|
||||
icon="Position"
|
||||
@click="toWebsite(req.websiteID)"
|
||||
type="primary"
|
||||
>
|
||||
{{ $t('firewall.quickJump') }}
|
||||
</el-link>
|
||||
</span>
|
||||
<el-text type="danger">{{ $t('mcp.bindDomainHelper') }}</el-text>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('xpack.waf.whiteList') + ' IP'" prop="ipList">
|
||||
<el-input
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
clearable
|
||||
v-model="req.ipList"
|
||||
:placeholder="$t('xpack.waf.ipGroupHelper')"
|
||||
/>
|
||||
<span class="input-help">
|
||||
{{ $t('aitool.whiteListHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="req.enableSSL" @change="changeSSL">
|
||||
{{ $t('website.enable') + ' ' + 'HTTPS' }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('website.acmeAccountManage')"
|
||||
prop="acmeAccountID"
|
||||
v-if="req.enableSSL"
|
||||
>
|
||||
<el-select
|
||||
v-model="req.acmeAccountID"
|
||||
:placeholder="$t('website.selectAcme')"
|
||||
@change="listSSL"
|
||||
>
|
||||
<el-option :key="0" :label="$t('website.imported')" :value="0"></el-option>
|
||||
<el-option
|
||||
v-for="(acme, index) in acmeAccounts"
|
||||
:key="index"
|
||||
:label="acme.email"
|
||||
:value="acme.id"
|
||||
>
|
||||
<span>
|
||||
{{ acme.email }}
|
||||
<el-tag class="ml-5">{{ getAccountName(acme.type) }}</el-tag>
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.ssl')" prop="sslID" v-if="req.enableSSL">
|
||||
<el-select
|
||||
v-model="req.sslID"
|
||||
:placeholder="$t('website.selectSSL')"
|
||||
@change="changeSSl(req.sslID)"
|
||||
>
|
||||
<el-option
|
||||
v-for="(ssl, index) in ssls"
|
||||
:key="index"
|
||||
:label="ssl.primaryDomain"
|
||||
:value="ssl.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { ListSSL, SearchAcmeAccount } from '@/api/modules/website';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { FormInstance, FormRules } from 'element-plus';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { getAccountName } from '@/utils/util';
|
||||
import { bindMcpDomain, getMcpDomain, updateMcpDomain } from '@/api/modules/ai';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const open = ref(false);
|
||||
const operate = ref('create');
|
||||
const loading = ref(false);
|
||||
const ssls = ref([]);
|
||||
const websiteSSL = ref<Website.SSL>();
|
||||
const acmeAccounts = ref();
|
||||
const formRef = ref();
|
||||
const req = ref({
|
||||
domain: '',
|
||||
sslID: undefined,
|
||||
ipList: '',
|
||||
acmeAccountID: 0,
|
||||
enableSSL: false,
|
||||
allowIPs: [],
|
||||
websiteID: 0,
|
||||
});
|
||||
const rules = reactive<FormRules>({
|
||||
domain: [Rules.domainWithPort],
|
||||
sslID: [Rules.requiredSelectBusiness],
|
||||
});
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close');
|
||||
open.value = false;
|
||||
};
|
||||
|
||||
const acceptParams = () => {
|
||||
search();
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const changeSSl = (sslid: number) => {
|
||||
const res = ssls.value.filter((element: Website.SSL) => {
|
||||
return element.id == sslid;
|
||||
});
|
||||
websiteSSL.value = res[0];
|
||||
};
|
||||
|
||||
const changeSSL = () => {
|
||||
if (!req.value.enableSSL) {
|
||||
req.value.sslID = undefined;
|
||||
} else {
|
||||
listAcmeAccount();
|
||||
}
|
||||
};
|
||||
|
||||
const listSSL = () => {
|
||||
const sslReq = {
|
||||
acmeAccountID: String(req.value.acmeAccountID),
|
||||
};
|
||||
ListSSL(sslReq).then((res) => {
|
||||
ssls.value = res.data || [];
|
||||
if (ssls.value.length > 0) {
|
||||
let exist = false;
|
||||
for (const ssl of ssls.value) {
|
||||
if (ssl.id === req.value.sslID) {
|
||||
exist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exist) {
|
||||
req.value.sslID = ssls.value[0].id;
|
||||
}
|
||||
changeSSl(req.value.sslID);
|
||||
} else {
|
||||
req.value.sslID = undefined;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const listAcmeAccount = () => {
|
||||
SearchAcmeAccount({ page: 1, pageSize: 100 }).then((res) => {
|
||||
acmeAccounts.value = res.data.items || [];
|
||||
listSSL();
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
if (operate.value === 'update') {
|
||||
await updateMcpDomain(req.value);
|
||||
} else {
|
||||
await bindMcpDomain(req.value);
|
||||
}
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
handleClose();
|
||||
});
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
try {
|
||||
const res = await getMcpDomain();
|
||||
if (res.data.websiteID > 0) {
|
||||
operate.value = 'update';
|
||||
req.value.domain = res.data.domain;
|
||||
req.value.websiteID = res.data.websiteID;
|
||||
if (res.data.allowIPs && res.data.allowIPs.length > 0) {
|
||||
req.value.ipList = res.data.allowIPs.join('\n');
|
||||
}
|
||||
if (res.data.sslID > 0) {
|
||||
req.value.enableSSL = true;
|
||||
req.value.sslID = res.data.sslID;
|
||||
req.value.acmeAccountID = res.data.acmeAccountID;
|
||||
listAcmeAccount();
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
const toWebsite = (websiteID: number) => {
|
||||
if (websiteID != undefined && websiteID > 0) {
|
||||
window.location.href = `/websites/${websiteID}/config/basic`;
|
||||
} else {
|
||||
window.location.href = '/websites';
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pageRoute {
|
||||
font-size: 12px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
v-model="open"
|
||||
size="50%"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('menu.config')" :back="handleClose" />
|
||||
</template>
|
||||
<codemirror
|
||||
:autofocus="true"
|
||||
:placeholder="$t('commons.msg.noneData')"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="height: 300px"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="prettyJson"
|
||||
:disabled="true"
|
||||
/>
|
||||
<CopyButton :content="prettyJson" class="mt-2" />
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { ref } from 'vue';
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
|
||||
const extensions = [javascript(), oneDark];
|
||||
|
||||
const open = ref(false);
|
||||
const jsonObj = ref({
|
||||
mcpServers: {},
|
||||
});
|
||||
const prettyJson = ref('');
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
};
|
||||
|
||||
const acceptParams = (mcpServer: AI.McpServer) => {
|
||||
jsonObj.value.mcpServers = {};
|
||||
jsonObj.value.mcpServers[mcpServer.name] = {
|
||||
url: mcpServer.baseUrl + mcpServer.ssePath,
|
||||
};
|
||||
prettyJson.value = JSON.stringify(jsonObj.value, null, 2);
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<el-dialog v-model="submitVisible" :destroy-on-close="true" :close-on-click-modal="false" width="40%">
|
||||
<template #header>
|
||||
{{ $t('mcp.importMcpJson') }}
|
||||
</template>
|
||||
<div>
|
||||
<el-input
|
||||
v-model="mcpServerJson"
|
||||
type="textarea"
|
||||
:rows="15"
|
||||
placeholder='{
|
||||
"mcpServers": {
|
||||
"postgres": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-postgres",
|
||||
"postgresql://localhost/mydb"
|
||||
]
|
||||
}
|
||||
}
|
||||
}'
|
||||
></el-input>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="onCancel">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="onConfirm">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import i18n from '@/lang';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const submitVisible = ref(false);
|
||||
const mcpServerJson = ref();
|
||||
const mcpServerConfig = ref();
|
||||
|
||||
const acceptParams = (): void => {
|
||||
mcpServerJson.value = '';
|
||||
submitVisible.value = true;
|
||||
};
|
||||
const emit = defineEmits(['confirm', 'cancel']);
|
||||
|
||||
const onConfirm = async () => {
|
||||
try {
|
||||
const data = JSON.parse(mcpServerJson.value);
|
||||
if (!data.mcpServers || typeof data.mcpServers !== 'object') {
|
||||
throw new Error(i18n.global.t('mcp.importMcpJsonError'));
|
||||
}
|
||||
mcpServerConfig.value = Object.entries(data.mcpServers).map(([name, config]: any) => ({
|
||||
name,
|
||||
command: [config.command, ...config.args].join(' '),
|
||||
environments: config.env ? Object.entries(config.env).map(([key, value]) => ({ key, value })) : [],
|
||||
ssePath: '/' + name,
|
||||
containerName: name,
|
||||
}));
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
emit('confirm', mcpServerConfig.value);
|
||||
submitVisible.value = false;
|
||||
};
|
||||
|
||||
const onCancel = async () => {
|
||||
emit('cancel');
|
||||
submitVisible.value = false;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<div>
|
||||
<RouterMenu />
|
||||
<LayoutContent :title="'Servers'" v-loading="loading">
|
||||
<template #toolbar>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="openCreate">
|
||||
{{ $t('mcp.create') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="openDomain">
|
||||
{{ $t('mcp.bindDomain') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()">
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
fix
|
||||
prop="name"
|
||||
width="200px"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-text type="primary" class="cursor-pointer" @click="openDetail(row)">
|
||||
{{ row.name }}
|
||||
</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('mcp.externalUrl')" prop="baseUrl" min-width="200px">
|
||||
<template #default="{ row }">
|
||||
{{ row.baseUrl + row.ssePath }}
|
||||
<CopyButton :content="row.baseUrl + row.ssePath" type="icon" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.status')" prop="status" width="120px">
|
||||
<template #default="{ row }">
|
||||
<el-popover
|
||||
v-if="row.status === 'error'"
|
||||
placement="bottom"
|
||||
:width="400"
|
||||
trigger="hover"
|
||||
:content="row.message"
|
||||
popper-class="max-h-[300px] overflow-auto"
|
||||
>
|
||||
<template #reference>
|
||||
<Status :key="row.status" :status="row.status"></Status>
|
||||
</template>
|
||||
</el-popover>
|
||||
<div v-else>
|
||||
<Status :key="row.status" :status="row.status"></Status>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.button.log')" prop="path" width="120px">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
@click="openLog(row)"
|
||||
link
|
||||
type="primary"
|
||||
:disabled="
|
||||
row.status !== 'running' && row.status !== 'error' && row.status !== 'restarting'
|
||||
"
|
||||
>
|
||||
{{ $t('website.check') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFormat"
|
||||
show-overflow-tooltip
|
||||
width="180"
|
||||
fix
|
||||
/>
|
||||
<fu-table-operations
|
||||
:ellipsis="mobile ? 0 : 2"
|
||||
:min-width="mobile ? 'auto' : 200"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
<McpServerOperate ref="createRef" @close="searchWithTimeOut" />
|
||||
<OpDialog ref="opRef" @search="search" />
|
||||
<ComposeLogs ref="composeLogRef" />
|
||||
<BindDomain ref="bindDomainRef" @close="searchWithTimeOut" />
|
||||
<Config ref="configRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { deleteMcpServer, operateMcpServer, pageMcpServer } from '@/api/modules/ai';
|
||||
import RouterMenu from '@/views/ai/mcp/index.vue';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import McpServerOperate from './operate/index.vue';
|
||||
import ComposeLogs from '@/components/compose-log/index.vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import BindDomain from './bind/index.vue';
|
||||
import Config from './config/index.vue';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const createRef = ref();
|
||||
const opRef = ref();
|
||||
const composeLogRef = ref();
|
||||
const bindDomainRef = ref();
|
||||
const configRef = ref();
|
||||
const items = ref<AI.McpServer[]>([]);
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'mcp-server-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
const mobile = computed(() => {
|
||||
return globalStore.isMobile();
|
||||
});
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('menu.config'),
|
||||
click: (row: AI.McpServer) => {
|
||||
openConfig(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
click: (row: AI.McpServer) => {
|
||||
openDetail(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.start'),
|
||||
click: (row: AI.McpServer) => {
|
||||
opServer(row, 'start');
|
||||
},
|
||||
disabled: (row: AI.McpServer) => {
|
||||
return row.status === 'running';
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.stop'),
|
||||
click: (row: AI.McpServer) => {
|
||||
opServer(row, 'stop');
|
||||
},
|
||||
disabled: (row: AI.McpServer) => {
|
||||
return row.status === 'stopped';
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.restart'),
|
||||
click: (row: AI.McpServer) => {
|
||||
opServer(row, 'restart');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: (row: AI.McpServer) => {
|
||||
deleteServer(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const searchWithTimeOut = () => {
|
||||
search();
|
||||
setTimeout(() => {
|
||||
search();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const search = () => {
|
||||
loading.value = true;
|
||||
pageMcpServer({
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
name: '',
|
||||
}).then((res) => {
|
||||
items.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const openDetail = (row: AI.McpServer) => {
|
||||
createRef.value.acceptParams(row);
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
let maxPort = 7999;
|
||||
if (items.value && items.value.length > 0) {
|
||||
maxPort = Math.max(...items.value.map((item) => item.port));
|
||||
}
|
||||
createRef.value.acceptParams({ port: maxPort + 1 });
|
||||
};
|
||||
|
||||
const openLog = (row: AI.McpServer) => {
|
||||
composeLogRef.value.acceptParams({ compose: row.dir + '/docker-compose.yml', resource: row.name });
|
||||
};
|
||||
|
||||
const deleteServer = async (row: AI.McpServer) => {
|
||||
try {
|
||||
opRef.value.acceptParams({
|
||||
title: i18n.global.t('commons.button.delete'),
|
||||
names: [row.name],
|
||||
msg: i18n.global.t('commons.msg.operatorHelper', [
|
||||
i18n.global.t('mcp.server'),
|
||||
i18n.global.t('commons.button.delete'),
|
||||
]),
|
||||
api: deleteMcpServer,
|
||||
params: { id: row.id },
|
||||
});
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const opServer = async (row: AI.McpServer, operate: string) => {
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('mcp.operatorHelper', [i18n.global.t('mcp.server'), i18n.global.t('commons.button.' + operate)]),
|
||||
i18n.global.t('commons.button.' + operate),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
},
|
||||
).then(async () => {
|
||||
try {
|
||||
await operateMcpServer({ id: row.id, operate: operate });
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
} catch (error) {}
|
||||
});
|
||||
};
|
||||
|
||||
const openDomain = () => {
|
||||
bindDomainRef.value.acceptParams();
|
||||
};
|
||||
|
||||
const openConfig = (row: AI.McpServer) => {
|
||||
configRef.value.acceptParams(row);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
v-model="open"
|
||||
size="50%"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader
|
||||
:header="$t('mcp.' + mode)"
|
||||
:hideResource="mode == 'create'"
|
||||
:resource="mcpServer.name"
|
||||
:back="handleClose"
|
||||
/>
|
||||
</template>
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="22" :offset="1">
|
||||
<el-form ref="mcpServerForm" label-position="top" :model="mcpServer" label-width="125px" :rules="rules">
|
||||
<el-form-item>
|
||||
<el-button @click="importRef.acceptParams()" type="primary" plain>
|
||||
{{ $t('mcp.importMcpJson') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model="mcpServer.name" :disabled="mode == 'edit'" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('runtime.runScript')" prop="command">
|
||||
<el-input
|
||||
v-model="mcpServer.command"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
:placeholder="$t('mcp.commandPlaceHolder')"
|
||||
></el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('mcp.commandHelper', ['@modelcontextprotocol/server-github']) }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<div>
|
||||
<el-text>{{ $t('mcp.environment') }}</el-text>
|
||||
<div class="mt-1">
|
||||
<el-row :gutter="20" v-for="(env, index) in mcpServer.environments" :key="index">
|
||||
<el-col :span="8">
|
||||
<el-form-item :prop="`environments.${index}.key`" :rules="rules.key">
|
||||
<el-input v-model="env.key" :placeholder="$t('mcp.envKey')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item :prop="`environments.${index}.value`" :rules="rules.value">
|
||||
<el-input v-model="env.value" :placeholder="$t('mcp.envValue')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="removeEnv(index)" link class="mt-1">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4">
|
||||
<el-button class="mb-2" @click="addEnv">{{ $t('commons.button.add') }}</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
<Volumes :volumes="mcpServer.volumes" class="mb-2" />
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item :label="$t('commons.table.port')" prop="port">
|
||||
<el-input v-model.number="mcpServer.port" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item :label="$t('app.allowPort')" prop="hostIP">
|
||||
<el-switch
|
||||
v-model="mcpServer.hostIP"
|
||||
:active-value="'0.0.0.0'"
|
||||
:inactive-value="'127.0.0.1'"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item :label="$t('mcp.baseUrl')" prop="url">
|
||||
<el-input v-model.trim="mcpServer.url">
|
||||
<template #prepend>
|
||||
<el-select v-model="mcpServer.protocol" class="pre-select">
|
||||
<el-option label="http" value="http://" />
|
||||
<el-option label="https" value="https://" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('mcp.baseUrlHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('app.containerName')" prop="containerName">
|
||||
<el-input v-model.trim="mcpServer.containerName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('mcp.ssePath')" prop="ssePath">
|
||||
<el-input v-model.trim="mcpServer.ssePath"></el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('mcp.ssePathHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<span>
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit(mcpServerForm)" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
<Import ref="importRef" @confirm="getImport" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { createMcpServer, getMcpDomain, updateMcpServer } from '@/api/modules/ai';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { ref, watch } from 'vue';
|
||||
import Volumes from '../volume/index.vue';
|
||||
import Import from '../import/index.vue';
|
||||
|
||||
const open = ref(false);
|
||||
const mode = ref('create');
|
||||
const loading = ref(false);
|
||||
const mcpServerForm = ref();
|
||||
const importRef = ref();
|
||||
const newMcpServer = () => {
|
||||
return {
|
||||
id: 0,
|
||||
name: '',
|
||||
port: 8000,
|
||||
status: '',
|
||||
message: '',
|
||||
baseUrl: '',
|
||||
ssePath: '',
|
||||
command: '',
|
||||
containerName: '',
|
||||
environments: [],
|
||||
volumes: [],
|
||||
hostIP: '127.0.0.1',
|
||||
protocol: 'http://',
|
||||
url: '',
|
||||
};
|
||||
};
|
||||
const em = defineEmits(['close']);
|
||||
const mcpServer = ref(newMcpServer());
|
||||
const rules = ref({
|
||||
name: [Rules.requiredInput, Rules.appName],
|
||||
command: [Rules.requiredInput],
|
||||
port: [Rules.requiredInput, Rules.port],
|
||||
containerName: [Rules.requiredInput],
|
||||
url: [Rules.requiredInput],
|
||||
ssePath: [Rules.requiredInput],
|
||||
key: [Rules.requiredInput],
|
||||
value: [Rules.requiredInput],
|
||||
});
|
||||
const hasWebsite = ref(false);
|
||||
|
||||
const acceptParams = async (params: AI.McpServer) => {
|
||||
hasWebsite.value = false;
|
||||
mode.value = params.id ? 'edit' : 'create';
|
||||
let mcpDomainRes;
|
||||
try {
|
||||
mcpDomainRes = await getMcpDomain();
|
||||
if (mcpDomainRes.data.connUrl != '') {
|
||||
hasWebsite.value = true;
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
if (mode.value == 'edit') {
|
||||
mcpServer.value = params;
|
||||
if (!mcpServer.value.environments) {
|
||||
mcpServer.value.environments = [];
|
||||
}
|
||||
if (!mcpServer.value.volumes) {
|
||||
mcpServer.value.volumes = [];
|
||||
}
|
||||
const parts = mcpServer.value.baseUrl.split(/(https?:\/\/)/).filter(Boolean);
|
||||
mcpServer.value.protocol = parts[0];
|
||||
mcpServer.value.url = parts[1];
|
||||
} else {
|
||||
mcpServer.value = newMcpServer();
|
||||
if (params.port) {
|
||||
mcpServer.value.port = params.port;
|
||||
}
|
||||
if (mcpDomainRes.data && mcpDomainRes.data.connUrl != '') {
|
||||
const parts = mcpDomainRes.data.connUrl.split(/(https?:\/\/)/).filter(Boolean);
|
||||
mcpServer.value.protocol = parts[0];
|
||||
mcpServer.value.url = parts[1];
|
||||
mcpServer.value.baseUrl = mcpDomainRes.data.connUrl;
|
||||
}
|
||||
}
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => mcpServer.value.name,
|
||||
(newVal) => {
|
||||
if (newVal && mode.value == 'create') {
|
||||
mcpServer.value.containerName = newVal;
|
||||
mcpServer.value.ssePath = '/' + newVal;
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
const addEnv = () => {
|
||||
mcpServer.value.environments.push({
|
||||
key: '',
|
||||
value: '',
|
||||
});
|
||||
};
|
||||
|
||||
const removeEnv = (index: number) => {
|
||||
mcpServer.value.environments.splice(index, 1);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const getImport = async (data: AI.ImportMcpServer[]) => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
const importServer = data[0];
|
||||
mcpServer.value.name = importServer.name;
|
||||
mcpServer.value.containerName = importServer.containerName;
|
||||
mcpServer.value.ssePath = importServer.ssePath;
|
||||
mcpServer.value.command = importServer.command;
|
||||
mcpServer.value.environments = importServer.environments || [];
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
let request = true;
|
||||
if (mcpServer.value.hostIP != '0.0.0.0' && !hasWebsite.value) {
|
||||
await ElMessageBox.confirm(i18n.global.t('app.installWarn'), i18n.global.t('app.checkTitle'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).catch(() => {
|
||||
request = false;
|
||||
});
|
||||
}
|
||||
if (!request) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
loading.value = true;
|
||||
mcpServer.value.baseUrl = mcpServer.value.protocol + mcpServer.value.url;
|
||||
if (mode.value == 'create') {
|
||||
await createMcpServer(mcpServer.value);
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
} else {
|
||||
await updateMcpServer(mcpServer.value);
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
}
|
||||
handleClose();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="mt-2">
|
||||
<el-text>{{ $t('container.mount') }}</el-text>
|
||||
<div class="mt-2">
|
||||
<el-row :gutter="20" v-for="(volume, index) in volumes" :key="index">
|
||||
<el-col :span="8">
|
||||
<el-form-item :prop="`volumes.${index}.source`" :rules="rules.value">
|
||||
<el-input v-model="volume.source" :placeholder="$t('container.hostOption')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item :prop="`volumes.${index}.target`" :rules="rules.value">
|
||||
<el-input v-model="volume.target" :placeholder="$t('container.containerDir')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="removeEnv(index)" link class="mt-1">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4">
|
||||
<el-button @click="addEnv">{{ $t('commons.button.add') }}</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, reactive } from 'vue';
|
||||
import { FormRules } from 'element-plus';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
|
||||
const props = defineProps({
|
||||
volumes: {
|
||||
type: Array<AI.Volume>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
value: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
const addEnv = () => {
|
||||
props.volumes.push({
|
||||
source: '',
|
||||
target: '',
|
||||
});
|
||||
};
|
||||
|
||||
const removeEnv = (index: number) => {
|
||||
props.volumes.splice(index, 1);
|
||||
};
|
||||
</script>
|
||||
@@ -546,6 +546,7 @@ const onOpenDialog = async (
|
||||
rowData: Partial<Container.ContainerHelper> = {
|
||||
cmd: [],
|
||||
cmdStr: '',
|
||||
network: 'bridge',
|
||||
publishAllPorts: false,
|
||||
exposedPorts: [],
|
||||
cpuShares: 1024,
|
||||
|
||||
@@ -163,6 +163,11 @@
|
||||
<el-form class="mt-4 mb-1" v-if="showClean" ref="deleteForm" label-position="left">
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="cleanData" :label="$t('cronjob.cleanData')" />
|
||||
<el-checkbox
|
||||
v-if="cleanData"
|
||||
v-model="cleanRemoteData"
|
||||
:label="$t('cronjob.cleanRemoteData')"
|
||||
/>
|
||||
<span class="input-help">
|
||||
{{ $t('cronjob.cleanDataHelper') }}
|
||||
</span>
|
||||
@@ -197,6 +202,7 @@ const operateIDs = ref();
|
||||
const opRef = ref();
|
||||
const showClean = ref();
|
||||
const cleanData = ref();
|
||||
const cleanRemoteData = ref(true);
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
@@ -282,6 +288,7 @@ const onDelete = async (row: Cronjob.CronjobInfo | null) => {
|
||||
let ids = [];
|
||||
showClean.value = false;
|
||||
cleanData.value = false;
|
||||
cleanRemoteData.value = true;
|
||||
if (row) {
|
||||
ids = [row.id];
|
||||
names = [row.name];
|
||||
@@ -312,7 +319,7 @@ const onDelete = async (row: Cronjob.CronjobInfo | null) => {
|
||||
|
||||
const onSubmitDelete = async () => {
|
||||
loading.value = true;
|
||||
await deleteCronjob({ ids: operateIDs.value, cleanData: cleanData.value })
|
||||
await deleteCronjob({ ids: operateIDs.value, cleanData: cleanData.value, cleanRemoteData: cleanRemoteData.value })
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
|
||||
@@ -223,6 +223,7 @@
|
||||
<el-form ref="deleteForm" label-position="left" v-loading="delLoading">
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="cleanData" :label="$t('cronjob.cleanData')" />
|
||||
<el-checkbox v-if="cleanData" v-model="cleanRemoteData" :label="$t('cronjob.cleanRemoteData')" />
|
||||
<span class="input-help">
|
||||
{{ $t('cronjob.cleanDataHelper') }}
|
||||
</span>
|
||||
@@ -282,6 +283,7 @@ const currentRecordDetail = ref<string>('');
|
||||
const deleteVisible = ref();
|
||||
const delLoading = ref();
|
||||
const cleanData = ref();
|
||||
const cleanRemoteData = ref(true);
|
||||
|
||||
const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
let itemSize = Number(localStorage.getItem(searchInfo.cacheSizeKey));
|
||||
@@ -433,26 +435,30 @@ const loadRecord = async (row: Cronjob.Record) => {
|
||||
};
|
||||
|
||||
const onClean = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('commons.msg.clean'), i18n.global.t('commons.msg.deleteTitle'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'warning',
|
||||
}).then(async () => {
|
||||
await cleanRecords(dialogData.value.rowData.id, cleanData.value)
|
||||
.then(() => {
|
||||
delLoading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
delLoading.value = false;
|
||||
});
|
||||
});
|
||||
if (!isBackup()) {
|
||||
ElMessageBox.confirm(i18n.global.t('commons.msg.clean'), i18n.global.t('commons.msg.deleteTitle'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'warning',
|
||||
}).then(async () => {
|
||||
await cleanRecords(dialogData.value.rowData.id, cleanData.value, cleanRemoteData.value)
|
||||
.then(() => {
|
||||
delLoading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
delLoading.value = false;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
deleteVisible.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const cleanRecord = async () => {
|
||||
delLoading.value = true;
|
||||
await cleanRecords(dialogData.value.rowData.id, cleanData.value)
|
||||
await cleanRecords(dialogData.value.rowData.id, cleanData.value, cleanRemoteData.value)
|
||||
.then(() => {
|
||||
delLoading.value = false;
|
||||
deleteVisible.value = false;
|
||||
@@ -464,6 +470,17 @@ const cleanRecord = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
function isBackup() {
|
||||
return (
|
||||
dialogData.value.rowData!.type === 'app' ||
|
||||
dialogData.value.rowData!.type === 'website' ||
|
||||
dialogData.value.rowData!.type === 'database' ||
|
||||
dialogData.value.rowData!.type === 'directory' ||
|
||||
dialogData.value.rowData!.type === 'snapshot' ||
|
||||
dialogData.value.rowData!.type === 'log'
|
||||
);
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.password')" prop="password">
|
||||
<el-input type="password" clearable show-password v-model="form.password"></el-input>
|
||||
<span class="input-help">{{ $t('commons.rule.illegalChar') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('database.permission')" prop="permission">
|
||||
<el-select v-model="form.permission">
|
||||
@@ -25,10 +26,13 @@
|
||||
<el-option
|
||||
v-if="form.from !== 'local'"
|
||||
value="localhost"
|
||||
:label="$t('terminal.localhost')"
|
||||
:label="$t('terminal.localhost') + '(localhost)'"
|
||||
/>
|
||||
<el-option value="ip" :label="$t('database.permissionForIP')" />
|
||||
</el-select>
|
||||
<span v-if="form.from !== 'local'" class="input-help">
|
||||
{{ $t('database.localhostHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.permission === 'ip'" prop="permissionIPs">
|
||||
<el-input clearable :rows="3" type="textarea" v-model="form.permissionIPs" />
|
||||
@@ -60,7 +64,6 @@ import { bindUser } from '@/api/modules/database';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { checkIp } from '@/utils/util';
|
||||
|
||||
const loading = ref();
|
||||
const bindVisible = ref(false);
|
||||
@@ -79,21 +82,11 @@ const confirmDialogRef = ref();
|
||||
|
||||
const rules = reactive({
|
||||
username: [Rules.requiredInput, Rules.name],
|
||||
password: [Rules.paramComplexity],
|
||||
password: [Rules.requiredInput, Rules.noSpace, Rules.illegal],
|
||||
permission: [Rules.requiredSelect],
|
||||
permissionIPs: [{ validator: checkIPs, trigger: 'blur', required: true }],
|
||||
permissionIPs: [Rules.requiredInput, Rules.noSpace, Rules.illegal],
|
||||
});
|
||||
|
||||
function checkIPs(rule: any, value: any, callback: any) {
|
||||
let ips = form.permissionIPs.split(',');
|
||||
for (const item of ips) {
|
||||
if (checkIp(item)) {
|
||||
return callback(new Error(i18n.global.t('commons.rule.ip')));
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
interface DialogProps {
|
||||
from: string;
|
||||
database: string;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('database.databaseConnInfo')" :back="handleClose" />
|
||||
</template>
|
||||
<el-form @submit.prevent v-loading="loading" ref="formRef" :model="form" label-position="top">
|
||||
<el-form @submit.prevent v-loading="loading" ref="formRef" :rules="rules" :model="form" label-position="top">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('database.containerConn')" v-if="form.from === 'local'">
|
||||
@@ -75,11 +75,7 @@
|
||||
/>
|
||||
<span class="input-help">{{ $t('database.remoteConnHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('database.rootPassword')"
|
||||
:rules="Rules.paramComplexity"
|
||||
prop="password"
|
||||
>
|
||||
<el-form-item :label="$t('database.rootPassword')" prop="password">
|
||||
<el-input
|
||||
style="width: calc(100% - 205px)"
|
||||
type="password"
|
||||
@@ -93,6 +89,7 @@
|
||||
{{ $t('commons.button.random') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
<span class="input-help">{{ $t('commons.rule.illegalChar') }}</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
@@ -110,9 +107,6 @@
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<ConfirmDialog ref="confirmDialogRef" @confirm="onSubmit" @cancel="loadPassword"></ConfirmDialog>
|
||||
<ConfirmDialog ref="confirmAccessDialogRef" @confirm="onSubmitAccess" @cancel="loadAccess"></ConfirmDialog>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="loading" @click="dialogVisible = false">
|
||||
@@ -132,7 +126,6 @@ import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { getDatabase, loadRemoteAccess, updateMysqlAccess, updateMysqlPassword } from '@/api/modules/database';
|
||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||
import { GetAppConnInfo } from '@/api/modules/app';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
@@ -148,6 +141,7 @@ const form = reactive({
|
||||
password: '',
|
||||
serviceName: '',
|
||||
containerName: '',
|
||||
oldPrivilege: false,
|
||||
privilege: false,
|
||||
port: 0,
|
||||
|
||||
@@ -158,8 +152,9 @@ const form = reactive({
|
||||
remoteIP: '',
|
||||
});
|
||||
|
||||
const confirmDialogRef = ref();
|
||||
const confirmAccessDialogRef = ref();
|
||||
const rules = reactive({
|
||||
password: [Rules.requiredInput, Rules.noSpace, Rules.illegal],
|
||||
});
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
@@ -200,6 +195,7 @@ const loadAccess = async () => {
|
||||
if (form.from === 'local') {
|
||||
const res = await loadRemoteAccess(form.type, form.database);
|
||||
form.privilege = res.data;
|
||||
form.oldPrivilege = res.data;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -227,68 +223,72 @@ const loadPassword = async () => {
|
||||
form.remoteIP = res.data.address;
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
let param = {
|
||||
id: 0,
|
||||
from: form.from,
|
||||
type: form.type,
|
||||
database: form.database,
|
||||
value: form.password,
|
||||
};
|
||||
loading.value = true;
|
||||
await updateMysqlPassword(param)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
dialogVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const onSave = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
let params = {
|
||||
header: i18n.global.t('database.confChange'),
|
||||
operationInfo: i18n.global.t('database.restartNowHelper'),
|
||||
submitInputInfo: i18n.global.t('database.restartNow'),
|
||||
};
|
||||
confirmDialogRef.value!.acceptParams(params);
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('database.changeConnHelper', [i18n.global.t('commons.login.password')]),
|
||||
i18n.global.t('commons.msg.infoTitle'),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
},
|
||||
).then(async () => {
|
||||
let param = {
|
||||
id: 0,
|
||||
from: form.from,
|
||||
type: form.type,
|
||||
database: form.database,
|
||||
value: form.password,
|
||||
};
|
||||
loading.value = true;
|
||||
await updateMysqlPassword(param)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
dialogVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmitAccess = async () => {
|
||||
let param = {
|
||||
id: 0,
|
||||
from: form.from,
|
||||
type: form.type,
|
||||
database: form.database,
|
||||
value: form.privilege ? '%' : 'localhost',
|
||||
};
|
||||
loading.value = true;
|
||||
await updateMysqlAccess(param)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
dialogVisible.value = false;
|
||||
const onSaveAccess = async () => {
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('database.changeConnHelper', [i18n.global.t('database.remoteAccess')]),
|
||||
i18n.global.t('commons.msg.infoTitle'),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
},
|
||||
)
|
||||
.then(async () => {
|
||||
let param = {
|
||||
id: 0,
|
||||
from: form.from,
|
||||
type: form.type,
|
||||
database: form.database,
|
||||
value: form.privilege ? '%' : 'localhost',
|
||||
};
|
||||
loading.value = true;
|
||||
await updateMysqlAccess(param)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
dialogVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
form.privilege = form.oldPrivilege;
|
||||
});
|
||||
};
|
||||
|
||||
const onSaveAccess = () => {
|
||||
let params = {
|
||||
header: i18n.global.t('database.confChange'),
|
||||
operationInfo: i18n.global.t('database.restartNowHelper'),
|
||||
submitInputInfo: i18n.global.t('database.restartNow'),
|
||||
};
|
||||
confirmAccessDialogRef.value!.acceptParams(params);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
<el-button @click="random">{{ $t('commons.button.random') }}</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="input-help">{{ $t('commons.rule.illegalChar') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('database.permission')" prop="permission">
|
||||
@@ -42,10 +43,13 @@
|
||||
<el-option
|
||||
v-if="form.from !== 'local'"
|
||||
value="localhost"
|
||||
:label="$t('terminal.localhost')"
|
||||
:label="$t('terminal.localhost') + '(localhost)'"
|
||||
/>
|
||||
<el-option value="ip" :label="$t('database.permissionForIP')" />
|
||||
</el-select>
|
||||
<span v-if="form.from !== 'local'" class="input-help">
|
||||
{{ $t('database.localhostHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.permission === 'ip'" prop="permissionIPs">
|
||||
<el-input clearable :rows="3" type="textarea" v-model="form.permissionIPs" />
|
||||
@@ -85,7 +89,7 @@ import { ElForm } from 'element-plus';
|
||||
import { addMysqlDB } from '@/api/modules/database';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { checkIp, getRandomStr } from '@/utils/util';
|
||||
import { getRandomStr } from '@/utils/util';
|
||||
|
||||
const loading = ref();
|
||||
const createVisible = ref(false);
|
||||
@@ -104,19 +108,10 @@ const form = reactive({
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput, Rules.dbName],
|
||||
username: [Rules.requiredInput, Rules.name],
|
||||
password: [Rules.paramComplexity],
|
||||
password: [Rules.requiredInput, Rules.noSpace, Rules.illegal],
|
||||
permission: [Rules.requiredSelect],
|
||||
permissionIPs: [{ validator: checkIPs, trigger: 'blur', required: true }],
|
||||
permissionIPs: [Rules.requiredInput, Rules.noSpace, Rules.illegal],
|
||||
});
|
||||
function checkIPs(rule: any, value: any, callback: any) {
|
||||
let ips = form.permissionIPs.split(',');
|
||||
for (const item of ips) {
|
||||
if (checkIp(item)) {
|
||||
return callback(new Error(i18n.global.t('commons.rule.ip')));
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
@@ -18,12 +18,8 @@
|
||||
<el-input disabled v-model="changeForm.userName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.password')" prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
clearable
|
||||
show-password
|
||||
v-model="changeForm.password"
|
||||
></el-input>
|
||||
<el-input type="password" clearable show-password v-model="changeForm.password" />
|
||||
<span class="input-help">{{ $t('commons.rule.illegalChar') }}</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="changeForm.operation === 'privilege'">
|
||||
@@ -33,10 +29,13 @@
|
||||
<el-option
|
||||
v-if="changeForm.from !== 'local'"
|
||||
value="localhost"
|
||||
:label="$t('terminal.localhost')"
|
||||
:label="$t('terminal.localhost') + '(localhost)'"
|
||||
/>
|
||||
<el-option value="ip" :label="$t('database.permissionForIP')" />
|
||||
</el-select>
|
||||
<span v-if="changeForm.from !== 'local'" class="input-help">
|
||||
{{ $t('database.localhostHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="changeForm.privilege === 'ip'" prop="privilegeIPs">
|
||||
<el-input clearable :rows="3" type="textarea" v-model="changeForm.privilegeIPs" />
|
||||
@@ -69,7 +68,6 @@ import { deleteCheckMysqlDB, updateMysqlAccess, updateMysqlPassword } from '@/ap
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { checkIp } from '@/utils/util';
|
||||
|
||||
const loading = ref();
|
||||
const changeVisible = ref(false);
|
||||
@@ -92,20 +90,10 @@ const changeForm = reactive({
|
||||
const confirmDialogRef = ref();
|
||||
|
||||
const rules = reactive({
|
||||
password: [Rules.paramComplexity],
|
||||
privilegeIPs: [{ validator: checkIPs, trigger: 'blur', required: true }],
|
||||
password: [Rules.requiredInput, Rules.noSpace, Rules.illegal],
|
||||
privilegeIPs: [Rules.requiredInput, Rules.noSpace, Rules.illegal],
|
||||
});
|
||||
|
||||
function checkIPs(rule: any, value: any, callback: any) {
|
||||
let ips = changeForm.privilegeIPs.split(',');
|
||||
for (const item of ips) {
|
||||
if (checkIp(item)) {
|
||||
return callback(new Error(i18n.global.t('commons.rule.ip')));
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
interface DialogProps {
|
||||
id: number;
|
||||
from: string;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.password')" prop="password">
|
||||
<el-input type="password" clearable show-password v-model="form.password" />
|
||||
<span class="input-help">{{ $t('commons.rule.illegalChar') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('database.permission')" prop="superUser">
|
||||
<el-checkbox v-model="form.superUser">{{ $t('database.pgSuperUser') }}</el-checkbox>
|
||||
@@ -65,7 +66,7 @@ const confirmDialogRef = ref();
|
||||
|
||||
const rules = reactive({
|
||||
username: [Rules.requiredInput, Rules.name],
|
||||
password: [Rules.paramComplexity],
|
||||
password: [Rules.requiredInput, Rules.noSpace, Rules.illegal],
|
||||
});
|
||||
|
||||
interface DialogProps {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('database.databaseConnInfo')" :back="handleClose" />
|
||||
</template>
|
||||
<el-form @submit.prevent v-loading="loading" ref="formRef" :model="form" label-position="top">
|
||||
<el-form @submit.prevent v-loading="loading" ref="formRef" :rules="rules" :model="form" label-position="top">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('database.containerConn')" v-if="form.from === 'local'">
|
||||
@@ -68,7 +68,13 @@
|
||||
<el-divider border-style="dashed" />
|
||||
<div v-if="form.from === 'local'">
|
||||
<el-form-item :label="$t('commons.login.username')" prop="username">
|
||||
<el-input type="text" readonly disabled v-model="form.username">
|
||||
<el-input
|
||||
type="text"
|
||||
style="width: calc(100% - 60px)"
|
||||
readonly
|
||||
disabled
|
||||
v-model="form.username"
|
||||
>
|
||||
<template #append>
|
||||
<el-button-group>
|
||||
<CopyButton :content="form.username" />
|
||||
@@ -76,11 +82,7 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('commons.login.password')"
|
||||
:rules="Rules.paramComplexity"
|
||||
prop="password"
|
||||
>
|
||||
<el-form-item :label="$t('commons.login.password')" prop="password">
|
||||
<el-input
|
||||
style="width: calc(100% - 205px)"
|
||||
type="password"
|
||||
@@ -94,6 +96,7 @@
|
||||
{{ $t('commons.button.random') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
<span class="input-help">{{ $t('commons.rule.illegalChar') }}</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="form.from !== 'local'">
|
||||
@@ -110,8 +113,6 @@
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<ConfirmDialog ref="confirmDialogRef" @confirm="onSubmit" @cancel="loadPassword"></ConfirmDialog>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="loading" @click="dialogVisible = false">
|
||||
@@ -131,7 +132,6 @@ import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { getDatabase, updatePostgresqlPassword } from '@/api/modules/database';
|
||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||
import { GetAppConnInfo } from '@/api/modules/app';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
@@ -157,7 +157,9 @@ const form = reactive({
|
||||
remoteIP: '',
|
||||
});
|
||||
|
||||
const confirmDialogRef = ref();
|
||||
const rules = reactive({
|
||||
password: [Rules.requiredInput, Rules.noSpace, Rules.illegal],
|
||||
});
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
@@ -225,36 +227,36 @@ const loadPassword = async () => {
|
||||
form.remoteIP = res.data.address;
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
let param = {
|
||||
id: 0,
|
||||
from: form.from,
|
||||
type: form.type,
|
||||
database: form.database,
|
||||
value: form.password,
|
||||
};
|
||||
loading.value = true;
|
||||
await updatePostgresqlPassword(param)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
dialogVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const onSave = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
let params = {
|
||||
header: i18n.global.t('database.confChange'),
|
||||
operationInfo: i18n.global.t('database.restartNowHelper'),
|
||||
submitInputInfo: i18n.global.t('database.restartNow'),
|
||||
};
|
||||
confirmDialogRef.value!.acceptParams(params);
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('database.changeConnHelper', [i18n.global.t('commons.login.password')]),
|
||||
i18n.global.t('commons.msg.infoTitle'),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
},
|
||||
).then(async () => {
|
||||
let param = {
|
||||
id: 0,
|
||||
from: form.from,
|
||||
type: form.type,
|
||||
database: form.database,
|
||||
value: form.password,
|
||||
};
|
||||
loading.value = true;
|
||||
await updatePostgresqlPassword(param)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
dialogVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<el-button @click="random">{{ $t('commons.button.random') }}</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="input-help">{{ $t('commons.rule.illegalChar') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('database.permission')" prop="superUser">
|
||||
<el-checkbox v-model="form.superUser">{{ $t('database.pgSuperUser') }}</el-checkbox>
|
||||
@@ -81,7 +82,7 @@ const form = reactive({
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput, Rules.dbName],
|
||||
username: [Rules.requiredInput, Rules.name],
|
||||
password: [Rules.paramComplexity],
|
||||
password: [Rules.requiredInput, Rules.noSpace, Rules.illegal],
|
||||
});
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
show-password
|
||||
v-model="changeForm.password"
|
||||
></el-input>
|
||||
<span class="input-help">{{ $t('commons.rule.illegalChar') }}</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
@@ -71,7 +72,7 @@ const changeForm = reactive({
|
||||
});
|
||||
const confirmDialogRef = ref();
|
||||
const rules = reactive({
|
||||
password: [Rules.paramComplexity],
|
||||
password: [Rules.requiredInput, Rules.noSpace, Rules.illegal],
|
||||
});
|
||||
|
||||
interface DialogProps {
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
{{ $t('commons.button.random') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
<span class="input-help">{{ $t('commons.rule.illegalChar') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="form.from !== 'local'">
|
||||
@@ -118,6 +119,7 @@ import { MsgSuccess } from '@/utils/message';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { getRandomStr } from '@/utils/util';
|
||||
import { getSettingInfo } from '@/api/modules/setting';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -135,21 +137,9 @@ const form = reactive({
|
||||
remoteIP: '',
|
||||
});
|
||||
const rules = reactive({
|
||||
password: [{ validator: checkPassword, trigger: 'blur' }],
|
||||
password: [Rules.requiredInput, Rules.noSpace, Rules.illegal],
|
||||
});
|
||||
|
||||
function checkPassword(rule: any, value: any, callback: any) {
|
||||
if (form.password !== '') {
|
||||
const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9.%@!~_-]{4,126}[a-zA-Z0-9]{1}$/;
|
||||
if (!reg.test(value) && value !== '') {
|
||||
callback(new Error(i18n.global.t('commons.rule.paramComplexity', ['.%@!~_-'])));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
const confirmDialogRef = ref();
|
||||
|
||||
const emit = defineEmits(['checkExist', 'closeTerminal']);
|
||||
|
||||
@@ -234,7 +234,7 @@ const acceptParams = async (props: MoveProps) => {
|
||||
type.value = props.type;
|
||||
if (props.name && props.name != '') {
|
||||
oldName.value = props.name;
|
||||
const res = await CheckFile(props.path + '/' + props.name);
|
||||
const res = await CheckFile(props.path + '/' + props.name, false);
|
||||
if (res.data) {
|
||||
changeName.value = true;
|
||||
addForm.cover = false;
|
||||
|
||||
@@ -59,12 +59,8 @@ let isComplexity = ref(false);
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const passFormRef = ref<FormInstance>();
|
||||
const passRules = reactive({
|
||||
oldPass: [Rules.noSpace, Rules.requiredInput],
|
||||
newPass: [
|
||||
Rules.requiredInput,
|
||||
Rules.noSpace,
|
||||
{ min: 6, message: i18n.global.t('commons.rule.commonPassword'), trigger: 'blur' },
|
||||
],
|
||||
oldPass: [Rules.requiredInput, Rules.noSpace],
|
||||
newPass: [Rules.requiredInput, Rules.noSpace],
|
||||
newPassComplexity: [Rules.requiredInput, Rules.noSpace, Rules.password],
|
||||
rePass: [Rules.requiredInput, Rules.noSpace, { validator: checkPasswordSame, trigger: 'blur' }],
|
||||
});
|
||||
|
||||
@@ -70,12 +70,8 @@ import { logOutApi } from '@/api/modules/auth';
|
||||
const globalStore = GlobalStore();
|
||||
const passFormRef = ref<FormInstance>();
|
||||
const passRules = reactive({
|
||||
oldPassword: [Rules.noSpace, Rules.requiredInput],
|
||||
newPassword: [
|
||||
Rules.requiredInput,
|
||||
Rules.noSpace,
|
||||
{ min: 6, message: i18n.global.t('commons.rule.commonPassword'), trigger: 'blur' },
|
||||
],
|
||||
oldPassword: [Rules.requiredInput, Rules.noSpace],
|
||||
newPassword: [Rules.requiredInput, Rules.noSpace],
|
||||
newPasswordComplexity: [Rules.requiredInput, Rules.noSpace, Rules.password],
|
||||
retryPassword: [Rules.requiredInput, Rules.noSpace, { validator: checkPassword, trigger: 'blur' }],
|
||||
});
|
||||
|
||||
@@ -60,8 +60,9 @@ import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { snapshotImport } from '@/api/modules/setting';
|
||||
import { getBackupList, getFilesFromBackup } from '@/api/modules/setting';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import router from '@/routers';
|
||||
import { CheckFile } from '@/api/modules/files';
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const loading = ref();
|
||||
@@ -109,7 +110,12 @@ const checkDisable = (val: string) => {
|
||||
return false;
|
||||
};
|
||||
const toFolder = async () => {
|
||||
router.push({ path: '/hosts/files', query: { path: backupPath.value } });
|
||||
const res = await CheckFile(backupPath.value, true);
|
||||
if (res.data) {
|
||||
router.push({ path: '/hosts/files', query: { path: backupPath.value } });
|
||||
} else {
|
||||
MsgError(i18n.global.t('file.noSuchFile'));
|
||||
}
|
||||
};
|
||||
|
||||
const submitImport = async (formEl: FormInstance | undefined) => {
|
||||
|
||||
@@ -97,6 +97,17 @@
|
||||
<el-input v-model.trim="account.authorization['password']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'ClouDNS'">
|
||||
<el-form-item label="Auth ID" prop="authorization.authID">
|
||||
<el-input v-model.trim="account.authorization['authID']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Sub Auth ID" prop="authorization.subAuthID">
|
||||
<el-input v-model.trim="account.authorization['subAuthID']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Auth Password" prop="authorization.authPassword">
|
||||
<el-input v-model.trim="account.authorization['authPassword']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item
|
||||
label="API Key"
|
||||
prop="authorization.apiKey"
|
||||
@@ -119,6 +130,14 @@
|
||||
<el-input v-model.trim="account.authorization['token']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'WestCN'">
|
||||
<el-form-item label="Username" prop="authorization.username">
|
||||
<el-input v-model.trim="account.authorization['username']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Password" prop="authorization.password">
|
||||
<el-input v-model.trim="account.authorization['password']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -137,7 +156,7 @@
|
||||
import { CreateDnsAccount, UpdateDnsAccount } from '@/api/modules/website';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { MsgSuccess, MsgError } from '@/utils/message';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
import { DNSTypes } from '@/global/mimetype';
|
||||
@@ -169,6 +188,8 @@ const rules = ref<any>({
|
||||
clientID: [Rules.requiredInput],
|
||||
email: [Rules.email],
|
||||
password: [Rules.requiredInput],
|
||||
authPassword: [Rules.requiredInput],
|
||||
username: [Rules.requiredInput],
|
||||
},
|
||||
});
|
||||
const account = ref({
|
||||
@@ -217,6 +238,12 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
if (account.value.type === 'ClouDNS') {
|
||||
if (!account.value.authorization['authID'] && !account.value.authorization['subAuthID']) {
|
||||
MsgError('Please input Auth ID or Sub Auth ID');
|
||||
return;
|
||||
}
|
||||
}
|
||||
loading.value = true;
|
||||
|
||||
if (accountData.value.mode === 'edit') {
|
||||
|
||||
@@ -507,7 +507,7 @@ const searchAppInstalled = () => {
|
||||
GetAppInstalled({ type: 'website', unused: true, all: true, page: 1, pageSize: 100 }).then((res) => {
|
||||
appInstalls.value = res.data.items;
|
||||
if (res.data.items && res.data.items.length > 0) {
|
||||
website.value.appInstallId = res.data[0].id;
|
||||
website.value.appInstallId = res.data.items[0].id;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module github.com/1Panel-dev/1Panel
|
||||
|
||||
go 1.22.3
|
||||
go 1.23
|
||||
|
||||
toolchain go1.23.7
|
||||
|
||||
require (
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
@@ -21,7 +23,7 @@ require (
|
||||
github.com/go-redis/redis v6.15.9+incompatible
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/goh-chunlin/go-onedrive v1.1.1
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/jackc/pgx/v5 v5.5.4
|
||||
@@ -53,7 +55,7 @@ require (
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.46
|
||||
github.com/xlzd/gotp v0.1.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/net v0.33.0
|
||||
golang.org/x/net v0.37.0
|
||||
golang.org/x/oauth2 v0.24.0
|
||||
golang.org/x/sys v0.29.0
|
||||
golang.org/x/term v0.28.0
|
||||
@@ -97,7 +99,7 @@ require (
|
||||
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.112.0 // indirect
|
||||
github.com/containerd/console v1.0.4 // indirect
|
||||
github.com/containerd/containerd v1.7.13 // indirect
|
||||
github.com/containerd/containerd v1.7.27 // indirect
|
||||
github.com/containerd/continuity v0.4.3 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/ttrpc v1.2.3 // indirect
|
||||
@@ -105,7 +107,7 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/docker/buildx v0.13.1 // indirect
|
||||
github.com/docker/buildx v0.21.3 // indirect
|
||||
github.com/docker/cli v26.0.0+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.1 // indirect
|
||||
@@ -163,13 +165,14 @@ require (
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mark3labs/mcp-go v0.14.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/miekg/dns v1.1.62 // indirect
|
||||
@@ -195,6 +198,7 @@ require (
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||
github.com/nrdcg/freemyip v0.3.0 // indirect
|
||||
github.com/nrdcg/mailinabox v0.2.0 // indirect
|
||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
@@ -270,10 +274,10 @@ require (
|
||||
k8s.io/client-go v0.29.2 // indirect
|
||||
k8s.io/klog/v2 v2.120.1 // indirect
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect
|
||||
modernc.org/libc v1.41.0 // indirect
|
||||
modernc.org/libc v1.49.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/sqlite v1.29.1 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/sqlite v1.29.7 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
|
||||
@@ -392,8 +392,8 @@ github.com/goh-chunlin/go-onedrive v1.1.1 h1:HGtHk5iG0MZ92zYUtaY04czfZPBIJUr12Uu
|
||||
github.com/goh-chunlin/go-onedrive v1.1.1/go.mod h1:N8qIGHD7tryO734epiBKk5oXcpGwxKET/u3LuBHciTs=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
@@ -474,8 +474,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
@@ -617,8 +617,8 @@ github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6K
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
@@ -652,6 +652,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mark3labs/mcp-go v0.14.1 h1:NsieyFbuWQaeZSWSHPvJ5TwJdQwu+1jmivAIVljeouY=
|
||||
github.com/mark3labs/mcp-go v0.14.1/go.mod h1:xBB350hekQsJAK7gJAii8bcEoWemboLm2mRm5/+KBaU=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
@@ -668,8 +670,8 @@ github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebG
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
|
||||
@@ -758,6 +760,8 @@ github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U
|
||||
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
||||
github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc=
|
||||
github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM=
|
||||
github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk=
|
||||
github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc=
|
||||
github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
|
||||
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
|
||||
@@ -1509,14 +1513,30 @@ k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/A
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ=
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
|
||||
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
|
||||
modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
|
||||
modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA=
|
||||
modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg=
|
||||
modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/sqlite v1.29.1 h1:19GY2qvWB4VPw0HppFlZCPAbmxFU41r+qjKZQdQ1ryA=
|
||||
modernc.org/sqlite v1.29.1/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.29.7 h1:Puwf5TIYuOipbcRnpFnLlGlR03DKenw8ggf3ijnuNQ0=
|
||||
modernc.org/sqlite v1.29.7/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
Reference in New Issue
Block a user