mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 08:00:53 +00:00
feat: merge mcp server from dev (#8499)
This commit is contained in:
@@ -17,7 +17,8 @@ var (
|
||||
appInstallService = service.NewIAppInstalledService()
|
||||
appIgnoreUpgradeService = service.NewIAppIgnoreUpgradeService()
|
||||
|
||||
aiToolService = service.NewIAIToolService()
|
||||
aiToolService = service.NewIAIToolService()
|
||||
mcpServerService = service.NewIMcpServerService()
|
||||
|
||||
containerService = service.NewIContainerService()
|
||||
composeTemplateService = service.NewIComposeTemplateService()
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"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.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(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.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(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.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(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.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(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.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(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.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(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.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package request
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/agent/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 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"`
|
||||
}
|
||||
@@ -284,3 +284,8 @@ type ChangeDatabase struct {
|
||||
DatabaseID uint `json:"databaseID" validate:"required"`
|
||||
DatabaseType string `json:"databaseType" validate:"required"`
|
||||
}
|
||||
|
||||
type WebsiteProxyDel struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/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"`
|
||||
}
|
||||
@@ -15,6 +15,7 @@ type IGroupRepo interface {
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
WithByDefault(isDefault bool) DBOption
|
||||
WithByWebsiteDefault() DBOption
|
||||
}
|
||||
|
||||
func NewIGroupRepo() IGroupRepo {
|
||||
@@ -62,3 +63,9 @@ func (g *GroupRepo) Delete(opts ...DBOption) error {
|
||||
}
|
||||
return db.Delete(&model.Group{}).Error
|
||||
}
|
||||
|
||||
func (g *GroupRepo) WithByWebsiteDefault() DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("is_default = ? AND type = ?", 1, "website")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package repo
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/agent/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
|
||||
}
|
||||
@@ -198,6 +198,9 @@ func (a AppService) GetApp(ctx *gin.Context, key string) (*response.AppDTO, erro
|
||||
latestVersion = detail.Version
|
||||
continue
|
||||
}
|
||||
if key == "openresty" && !common.CompareAppVersion(detail.Version, "1.27") {
|
||||
continue
|
||||
}
|
||||
versionsRaw = append(versionsRaw, detail.Version)
|
||||
}
|
||||
appDTO.Versions = common.GetSortedVersions(versionsRaw)
|
||||
|
||||
@@ -12,7 +12,8 @@ var (
|
||||
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
|
||||
appIgnoreUpgradeRepo = repo.NewIAppIgnoreUpgradeRepo()
|
||||
|
||||
aiRepo = repo.NewIAiRepo()
|
||||
aiRepo = repo.NewIAiRepo()
|
||||
mcpServerRepo = repo.NewIMcpServerRepo()
|
||||
|
||||
mysqlRepo = repo.NewIMysqlRepo()
|
||||
postgresqlRepo = repo.NewIPostgresqlRepo()
|
||||
|
||||
@@ -0,0 +1,641 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/cmd/server/mcp"
|
||||
"github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
||||
"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(global.Dir.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(repo.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(global.Dir.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.StatusStarting
|
||||
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("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(global.Dir.McpDir, create.Name)
|
||||
mcpServer := &model.McpServer{
|
||||
Name: create.Name,
|
||||
ContainerName: create.ContainerName,
|
||||
Port: create.Port,
|
||||
Command: create.Command,
|
||||
Status: constant.StatusStarting,
|
||||
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(repo.WithByID(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
composePath := path.Join(global.Dir.McpDir, mcpServer.Name, "docker-compose.yml")
|
||||
_, _ = compose.Down(composePath)
|
||||
_ = files.NewFileOp().DeleteDir(path.Join(global.Dir.McpDir, mcpServer.Name))
|
||||
|
||||
websiteID := GetWebsiteID()
|
||||
if websiteID > 0 {
|
||||
websiteService := NewIWebsiteService()
|
||||
delProxyReq := request.WebsiteProxyDel{
|
||||
ID: websiteID,
|
||||
Name: mcpServer.Name,
|
||||
}
|
||||
_ = websiteService.DeleteProxy(delProxyReq)
|
||||
}
|
||||
return mcpServerRepo.DeleteBy(repo.WithByID(id))
|
||||
}
|
||||
|
||||
func (m McpServerService) Operate(req request.McpServerOperate) error {
|
||||
mcpServer, err := mcpServerRepo.GetFirst(repo.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.StatusRunning
|
||||
case "stop":
|
||||
out, err = compose.Down(composePath)
|
||||
mcpServer.Status = constant.StatusStopped
|
||||
case "restart":
|
||||
out, err = compose.Restart(composePath)
|
||||
mcpServer.Status = constant.StatusRunning
|
||||
}
|
||||
if err != nil {
|
||||
mcpServer.Status = constant.StatusError
|
||||
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(repo.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(repo.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(repo.WithByID(req.SSLID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ssl.Pem == "" {
|
||||
return buserr.New("ErrSSL")
|
||||
}
|
||||
}
|
||||
group, _ := groupRepo.Get(groupRepo.WithByWebsiteDefault())
|
||||
createWebsiteReq := request.WebsiteCreate{
|
||||
Domains: []request.WebsiteDomain{
|
||||
{
|
||||
Domain: req.Domain,
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
Alias: strings.ToLower(req.Domain),
|
||||
Type: constant.Static,
|
||||
WebsiteGroupID: group.ID,
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
createWebsiteReq.WebsiteSSLID = req.SSLID
|
||||
createWebsiteReq.EnableSSL = true
|
||||
}
|
||||
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 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(repo.WithByID(req.SSLID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ssl.Pem == "" {
|
||||
return buserr.New("ErrSSL")
|
||||
}
|
||||
}
|
||||
websiteService := NewIWebsiteService()
|
||||
website, err := websiteRepo.GetFirst(repo.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(repo.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(repo.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.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("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(repo.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.0.1:%d%s", server.Port, server.SsePath)})
|
||||
location.ChangePath("^~", server.SsePath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr("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(global.Dir.McpDir, mcpServer.Name, "docker-compose.yml")
|
||||
_, _ = compose.Down(composePath)
|
||||
if _, err := compose.Up(composePath); err != nil {
|
||||
mcpServer.Status = constant.StatusError
|
||||
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("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(global.Dir.McpDir, mcpServer.Name, "docker-compose.yml")
|
||||
if mcpServer.Status != constant.StatusNormal {
|
||||
_, _ = compose.Down(composePath)
|
||||
}
|
||||
if out, err := compose.Up(composePath); err != nil {
|
||||
mcpServer.Status = constant.StatusError
|
||||
mcpServer.Message = out
|
||||
} else {
|
||||
mcpServer.Status = constant.StatusRunning
|
||||
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.StatusStopped
|
||||
return mcpServerRepo.Save(mcpServer)
|
||||
}
|
||||
container := containers[0]
|
||||
switch container.State {
|
||||
case "exited":
|
||||
mcpServer.Status = constant.StatusError
|
||||
case "running":
|
||||
mcpServer.Status = constant.StatusRunning
|
||||
case "paused":
|
||||
mcpServer.Status = constant.StatusStopped
|
||||
case "restarting":
|
||||
mcpServer.Status = constant.StatusRestarting
|
||||
default:
|
||||
if mcpServer.Status != constant.StatusBuilding {
|
||||
mcpServer.Status = constant.StatusStopped
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -93,6 +93,7 @@ type IWebsiteService interface {
|
||||
UpdateProxyCache(req request.NginxProxyCacheUpdate) (err error)
|
||||
GetProxyCache(id uint) (res response.NginxProxyCache, err error)
|
||||
ClearProxyCache(req request.NginxCommonReq) error
|
||||
DeleteProxy(req request.WebsiteProxyDel) (err error)
|
||||
|
||||
GetAntiLeech(id uint) (*response.NginxAntiLeechRes, error)
|
||||
UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err error)
|
||||
@@ -1856,6 +1857,29 @@ func (w WebsiteService) ClearProxyCache(req request.NginxCommonReq) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) DeleteProxy(req request.WebsiteProxyDel) (err error) {
|
||||
fileOp := files.NewFileOp()
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
|
||||
if !fileOp.Stat(includeDir) {
|
||||
_ = fileOp.CreateDir(includeDir, 0755)
|
||||
}
|
||||
fileName := fmt.Sprintf("%s.conf", req.Name)
|
||||
includePath := path.Join(includeDir, fileName)
|
||||
backName := fmt.Sprintf("%s.bak", req.Name)
|
||||
backPath := path.Join(includeDir, backName)
|
||||
_ = fileOp.DeleteFile(includePath)
|
||||
_ = fileOp.DeleteFile(backPath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
}
|
||||
|
||||
func (w WebsiteService) GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error) {
|
||||
var (
|
||||
website model.Website
|
||||
|
||||
@@ -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
|
||||
@@ -46,3 +46,6 @@ var Upstream []byte
|
||||
|
||||
//go:embed php_extensions.json
|
||||
var PHPExtensionsJson []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;
|
||||
}
|
||||
@@ -41,6 +41,7 @@ type SystemDir struct {
|
||||
RuntimeDir string
|
||||
RecycleBinDir string
|
||||
SSLLogDir string
|
||||
McpDir string
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
|
||||
+7
-13
@@ -42,11 +42,9 @@ ErrBackupLocalCreate: 'Creating local server backup accounts is not supported ye
|
||||
#app
|
||||
ErrPortInUsed: '{{ .detail }} port is already occupied!'
|
||||
ErrAppLimit: 'The number of applications installed has exceeded the limit'
|
||||
ErrAppRequired: 'Please install the {{ .detail }} application first'
|
||||
ErrNotInstall: 'Application not installed'
|
||||
ErrPortInOtherApp: '{{ .port }} port is already occupied by application {{ .apps }}!'
|
||||
ErrDbUserNotValid: 'Existing database, username and password do not match!'
|
||||
ErrDockerComposeNotValid: 'The docker-compose file format is incorrect'
|
||||
ErrUpdateBuWebsite: 'The application was updated successfully, but the website configuration file modification failed. Please check the configuration! '
|
||||
Err1PanelNetworkFailed: 'Default container network creation failed! {{ .detail }}'
|
||||
ErrFileParse: 'Application docker-compose file parsing failed!'
|
||||
@@ -59,7 +57,6 @@ ErrFileParseApp: '{{ .name }} file parsing failed {{ .err }}'
|
||||
ErrAppDirNull: 'The version folder does not exist'
|
||||
LocalAppErr: 'Application {{ .name }} sync failed! {{ .err }}'
|
||||
ErrContainerName: 'Container name already exists'
|
||||
ErrAppSystemRestart: '1Panel restart caused the task to terminate'
|
||||
ErrCreateHttpClient: 'Failed to create request {{ .err }}'
|
||||
ErrHttpReqTimeOut: 'Request timed out {{ .err }}'
|
||||
ErrHttpReqFailed: 'Request failed {{ .err }}'
|
||||
@@ -69,7 +66,6 @@ ErrImagePullTimeOut: 'Image pull timeout'
|
||||
ErrContainerNotFound: '{{ .name }} container does not exist'
|
||||
ErrContainerMsg: '{{ .name }} container is abnormal. Please check the log on the container page for details'
|
||||
ErrAppBackup: '{{ .name }} application backup failed {{ .err }}'
|
||||
ErrImagePull: 'Image pull failed {{ .err }}'
|
||||
ErrVersionTooLow: 'The current 1Panel version is too low to update the App Store. Please upgrade the version before operating.'
|
||||
ErrAppNameExist: 'The application name already exists'
|
||||
AppStoreIsSyncing: 'The App Store is syncing, please try again later'
|
||||
@@ -80,8 +76,6 @@ ErrAppUpgrade: 'Application {{ .name }} upgrade failed {{ .err }}'
|
||||
AppRecover: 'Rollback application {{ .name }}'
|
||||
PullImageStart: 'Start pulling image {{ .name }}'
|
||||
PullImageSuccess: 'Image pull successful'
|
||||
UpgradeAppStart: 'Start upgrading application {{ .name }}'
|
||||
UpgradeAppSuccess: 'Application {{ .name }} upgraded successfully'
|
||||
AppStoreIsLastVersion: 'The App Store is already the latest version'
|
||||
AppStoreSyncSuccess: 'App Store synchronization successful'
|
||||
SyncAppDetail: 'Synchronize application configuration'
|
||||
@@ -91,8 +85,6 @@ MoveSiteToDir: 'Migrate site directory to {{ .name }}'
|
||||
ErrMoveSiteDir: 'Failed to migrate site directory'
|
||||
MoveSiteDirSuccess: 'Successful migration of website directory'
|
||||
DeleteRuntimePHP: 'Delete PHP runtime'
|
||||
CustomAppStoreNotConfig: 'Please set the offline package address in the app store'
|
||||
CustomAppStoreNotFound: 'Failed to obtain the app store package, please check whether it exists'
|
||||
CustomAppStoreFileValid: 'App store packages need to be in .tar.gz format'
|
||||
PullImageTimeout: 'Pull image timeout, please try to increase the image acceleration or change to another image acceleration'
|
||||
ErrAppIsDown: '{{ .name }} application status is abnormal, please check'
|
||||
@@ -115,9 +107,7 @@ ErrInvalidChar: 'Illegal characters are not allowed'
|
||||
ErrPathNotDelete: 'The selected directory cannot be deleted'
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: 'Domain name already exists'
|
||||
ErrAliasIsExist: 'Alias already exists'
|
||||
ErrAppDelete: 'Other websites use this app and cannot delete it'
|
||||
ErrBackupMatch: 'The backup file does not match some of the current website data {{ .detail }}'
|
||||
ErrBackupExist: 'The corresponding part of the source data in the backup file does not exist {{ .detail }}'
|
||||
ErrPHPResource: 'The local operating environment does not support switching! '
|
||||
@@ -179,7 +169,6 @@ ErrPortRules: 'Port number does not match, please re-enter!'
|
||||
ErrPgImagePull: 'Image pull timed out, please configure image acceleration or manually pull the {{ .name }} image and try again'
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: 'The build folder does not exist! Please check the file integrity!'
|
||||
ErrFileNotExist: '{{ .detail }} file does not exist! Please check the integrity of the source file!'
|
||||
ErrImageBuildErr: 'Image build failed'
|
||||
ErrImageExist: 'Image already exists!'
|
||||
@@ -219,6 +208,7 @@ ErrRuleNotExist: 'Rule does not exist'
|
||||
ErrParseIP: 'Wrong IP format'
|
||||
ErrDefaultIP: 'default is a reserved name, please change it to another name'
|
||||
ErrGroupInUse: 'IP group is used by blacklist/whitelist and cannot be deleted'
|
||||
ErrIPGroupAclUse: "IP group is used by custom rules of website {{ .name }}, cannot be deleted"
|
||||
ErrGroupExist: 'IP group name already exists'
|
||||
ErrIPRange: 'Wrong IP range'
|
||||
ErrIPExist: 'IP already exists'
|
||||
@@ -262,6 +252,7 @@ ErrDBNotExist: 'Database does not exist'
|
||||
allow: 'allow'
|
||||
deny: 'deny'
|
||||
OpenrestyNotFound: 'Openresty is not installed'
|
||||
remoteIpIsNull: "IP list is empty"
|
||||
|
||||
#task
|
||||
TaskStart: '{{ .name }} task starts [START]'
|
||||
@@ -387,7 +378,7 @@ ContainerStartCheck: 'Check if the container has been started'
|
||||
# task - image
|
||||
ImageBuild: 'Image Build'
|
||||
ImageBuildStdoutCheck: 'Parse image output content'
|
||||
ImaegBuildRes: 'Image build output: {{ .name }}'
|
||||
ImageBuildRes: 'Image build output: {{ .name }}'
|
||||
ImagePull: 'Pull image'
|
||||
ImageRepoAuthFromDB: 'Get repository authentication information from the database'
|
||||
ImaegPullRes: 'Image pull output: {{ .name }}'
|
||||
@@ -415,4 +406,7 @@ ErrAlert: 'The format of the warning message is incorrect, please check and try
|
||||
ErrAlertPush: 'Error in pushing alert information, please check and try again!'
|
||||
ErrAlertSave: 'Error saving the alarm information, please check and try again!'
|
||||
ErrAlertSync: 'Alarm information synchronization error, please check and try again!'
|
||||
ErrAlertRemote: 'Alarm message remote error, please check and try again!'
|
||||
ErrAlertRemote: 'Alarm message remote error, please check and try again!'
|
||||
|
||||
#task - runtime
|
||||
ErrInstallExtension: "An installation task is already in progress, please wait for the task to finish"
|
||||
+7
-13
@@ -42,11 +42,9 @@ ErrBackupLocalCreate: 'ローカル サーバーのバックアップ アカウ
|
||||
#app
|
||||
ErrPortInUsed: '{{ .detail }} ポートはすでに使用されています!'
|
||||
ErrAppLimit: 'インストールされているアプリケーションの数が制限を超えました'
|
||||
ErrAppRequired: 'まず {{ .detail }} アプリケーションをインストールしてください'
|
||||
ErrNotInstall: 'アプリケーションがインストールされていません'
|
||||
ErrPortInOtherApp: '{{ .port }} ポートは既にアプリケーション {{ .apps }} によって使用されています!'
|
||||
ErrDbUserNotValid: '既存のデータベース、ユーザー名、およびパスワードが一致しません!'
|
||||
ErrDockerComposeNotValid: 'docker-compose ファイルの形式が正しくありません'
|
||||
ErrUpdateBuWebsite: 'アプリケーションは正常に更新されましたが、Web サイト構成ファイルの変更に失敗しました。設定を確認してください! '
|
||||
Err1PanelNetworkFailed: 'デフォルトのコンテナ ネットワークの作成に失敗しました。 {{ .detail }}'
|
||||
ErrFileParse: 'アプリケーションの docker-compose ファイルの解析に失敗しました!'
|
||||
@@ -59,7 +57,6 @@ ErrFileParseApp: '{{ .name }} ファイルの解析に失敗しました {{ .err
|
||||
ErrAppDirNull: 'バージョン フォルダーが存在しません'
|
||||
LocalAppErr: 'アプリケーション {{ .name }} の同期に失敗しました! {{ .err }}'
|
||||
ErrContainerName: 'コンテナ名が既に存在します'
|
||||
ErrAppSystemRestart: '1Panel の再起動によりタスクが終了しました'
|
||||
ErrCreateHttpClient: 'リクエスト {{ .err }} の作成に失敗しました'
|
||||
ErrHttpReqTimeOut: 'リクエストがタイムアウトしました {{ .err }}'
|
||||
ErrHttpReqFailed: 'リクエストが失敗しました {{ .err }}'
|
||||
@@ -69,7 +66,6 @@ ErrImagePullTimeOut: 'イメージ プル タイムアウト'
|
||||
ErrContainerNotFound: '{{ .name }} コンテナが存在しません'
|
||||
ErrContainerMsg: '{{ .name }} コンテナが異常です。詳細についてはコンテナページのログを確認してください。'
|
||||
ErrAppBackup: '{{ .name }} アプリケーションのバックアップに失敗しました。エラー {{ .err }}'
|
||||
ErrImagePull: 'イメージのプルに失敗しました {{ .err }}'
|
||||
ErrVersionTooLow: '現在の 1Panel のバージョンが低すぎるため、App Store を更新できません。操作する前にバージョンをアップグレードしてください。'
|
||||
ErrAppNameExist: 'アプリケーション名がすでに存在します'
|
||||
AppStoreIsSyncing: 'App Store が同期中です。しばらくしてからもう一度お試しください'
|
||||
@@ -80,8 +76,6 @@ ErrAppUpgrade: 'アプリケーション {{ .name }} のアップグレードに
|
||||
AppRecover: 'アプリケーション {{ .name }} をロールバックします'
|
||||
PullImageStart: "イメージ {{ .name }} のプルを開始します"
|
||||
PullImageSuccess: 'イメージのプルが成功しました'
|
||||
UpgradeAppStart: 'アプリケーション {{ .name }} のアップグレードを開始します'
|
||||
UpgradeAppSuccess: 'アプリケーション {{ .name }} が正常にアップグレードされました'
|
||||
AppStoreIsLastVersion: 'App Store はすでに最新バージョンです'
|
||||
AppStoreSyncSuccess: 'App Store の同期が成功しました'
|
||||
SyncAppDetail: 'アプリケーション構成を同期する'
|
||||
@@ -91,8 +85,6 @@ MoveSiteToDir: 'サイト ディレクトリを {{ .name }} に移行します'
|
||||
ErrMoveSiteDir: 'サイト ディレクトリの移行に失敗しました'
|
||||
MoveSiteDirSuccess: 'Web サイト ディレクトリの移行に成功しました'
|
||||
DeleteRuntimePHP: 'PHP ランタイムを削除する'
|
||||
CustomAppStoreNotConfig: 'アプリストアでオフライン パッケージ アドレスを設定してください'
|
||||
CustomAppStoreNotFound: 'アプリストア パッケージを取得できませんでした。存在するかどうかを確認してください'
|
||||
CustomAppStoreFileValid: 'App Store パッケージは .tar.gz 形式である必要があります'
|
||||
PullImageTimeout: 'プル イメージのタイムアウトです。イメージのアクセラレーションを増やすか、別のイメージのアクセラレーションに変更してください'
|
||||
ErrAppIsDown: '{{ .name }} アプリケーションの状態が異常です。確認してください'
|
||||
@@ -115,9 +107,7 @@ ErrInvalidChar: '不正な文字は許可されません'
|
||||
ErrPathNotDelete: '選択されたディレクトリは削除できません'
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: 'ドメイン名は既に存在します'
|
||||
ErrAliasIsExist: 'エイリアスがすでに存在します'
|
||||
ErrAppDelete: '他のウェブサイトがこのアプリを使用しているため、削除できません'
|
||||
ErrBackupMatch: 'バックアップ ファイルは、現在の Web サイト データ {{ .detail }} の一部と一致しません'
|
||||
ErrBackupExist: 'バックアップ ファイル内のソース データの対応する部分が存在しません {{ .detail }}'
|
||||
ErrPHPResource: 'ローカルオペレーティング環境は切り替えをサポートしていません! '
|
||||
@@ -179,7 +169,6 @@ ErrPortRules: 'ポート番号が一致しません。再入力してくださ
|
||||
ErrPgImagePull: 'イメージのプルがタイムアウトしました。イメージのアクセラレーションを設定するか、{{ .name }} イメージを手動でプルして再試行してください'
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: 'ビルド フォルダーが存在しません。ファイルの整合性を確認してください。'
|
||||
ErrFileNotExist: '{{ .detail }} ファイルが存在しません。ソース ファイルの整合性を確認してください。'
|
||||
ErrImageBuildErr: 'イメージのビルドに失敗しました'
|
||||
ErrImageExist: '画像がすでに存在します!'
|
||||
@@ -219,6 +208,7 @@ ErrRuleNotExist: 'ルールが存在しません'
|
||||
ErrParseIP: 'IP 形式が間違っています'
|
||||
ErrDefaultIP: 'デフォルトは予約名です。別の名前に変更してください'
|
||||
ErrGroupInUse: 'IP グループはブラックリスト/ホワイトリストで使用されており、削除できません'
|
||||
ErrIPGroupAclUse: "IPグループはウェブサイト {{ .name }} のカスタムルールで使用されているため、削除できません"
|
||||
ErrGroupExist: 'IP グループ名がすでに存在します'
|
||||
ErrIPRange: 'IP 範囲が間違っています'
|
||||
ErrIPExist: 'IP がすでに存在します'
|
||||
@@ -262,6 +252,7 @@ ErrDBNotExist: 'データベースが存在しません'
|
||||
allow: '許可'
|
||||
deny: '拒否'
|
||||
OpenrestyNotFound: 'Openresty がインストールされていません'
|
||||
remoteIpIsNull: "IPリストが空です"
|
||||
|
||||
#task
|
||||
TaskStart: '{{ .name }} タスクが開始されました [START]'
|
||||
@@ -387,7 +378,7 @@ ContainerStartCheck: 'コンテナが起動されているかどうかを確認
|
||||
#task - image
|
||||
ImageBuild: 'イメージビルド'
|
||||
ImageBuildStdoutCheck: '画像出力コンテンツを解析する'
|
||||
ImaegBuildRes: 'イメージビルド出力: {{ .name }}'
|
||||
ImageBuildRes: 'イメージビルド出力: {{ .name }}'
|
||||
ImagePull: '画像をプル'
|
||||
ImageRepoAuthFromDB: 'データベースからリポジトリ認証情報を取得する'
|
||||
ImaegPullRes: "画像プル出力: {{ .name }}"
|
||||
@@ -415,4 +406,7 @@ ErrAlert: '警告メッセージの形式が正しくありません。確認し
|
||||
ErrAlertPush: 'アラート情報のプッシュ中にエラーが発生しました。確認してもう一度お試しください。'
|
||||
ErrAlertSave: 'アラーム情報の保存中にエラーが発生しました。確認してもう一度お試しください。'
|
||||
ErrAlertSync: 'アラーム情報の同期エラーです。確認してもう一度お試しください。'
|
||||
ErrAlertRemote: 'アラーム メッセージのリモート エラーです。確認してもう一度お試しください。'
|
||||
ErrAlertRemote: 'アラーム メッセージのリモート エラーです。確認してもう一度お試しください。'
|
||||
|
||||
#task - runtime
|
||||
ErrInstallExtension: "インストールタスクが進行中です、タスクが終了するのを待ってください"
|
||||
+7
-13
@@ -42,11 +42,9 @@ ErrBackupLocalCreate: '로컬 서버 백업 계정 생성은 아직 지원되지
|
||||
#앱
|
||||
ErrPortInUsed: '{{ .detail }} 포트가 이미 사용 중입니다!'
|
||||
ErrAppLimit: '설치된 애플리케이션 수가 한도를 초과했습니다'
|
||||
ErrAppRequired: '먼저 {{ .detail }} 애플리케이션을 설치하세요'
|
||||
ErrNotInstall: '응용 프로그램이 설치되지 않았습니다'
|
||||
ErrPortInOtherApp: '{{ .port }} 포트는 이미 {{ .apps }} 애플리케이션에 의해 사용되고 있습니다!'
|
||||
ErrDbUserNotValid: '기존 데이터베이스, 사용자 이름 및 비밀번호가 일치하지 않습니다!'
|
||||
ErrDockerComposeNotValid: 'docker-compose 파일 형식이 올바르지 않습니다'
|
||||
ErrUpdateBuWebsite: '응용 프로그램이 성공적으로 업데이트되었지만, 웹사이트 구성 파일 수정에 실패했습니다. 구성을 확인하세요! '
|
||||
Err1PanelNetworkFailed: '기본 컨테이너 네트워크 생성에 실패했습니다! {{ .세부 사항 }}'
|
||||
ErrFileParse: '응용 프로그램 docker-compose 파일 구문 분석에 실패했습니다!'
|
||||
@@ -59,7 +57,6 @@ ErrFileParseApp: '{{ .name }} 파일 구문 분석에 실패했습니다 {{ .err
|
||||
ErrAppDirNull: '버전 폴더가 존재하지 않습니다'
|
||||
LocalAppErr: '애플리케이션 {{ .name }} 동기화에 실패했습니다! {{ .err }}'
|
||||
ErrContainerName: '컨테이너 이름이 이미 존재합니다'
|
||||
ErrAppSystemRestart: '1패널 재시작으로 인해 작업이 종료되었습니다'
|
||||
ErrCreateHttpClient: '요청 {{ .err }}을(를) 생성하지 못했습니다.'
|
||||
ErrHttpReqTimeOut: '요청 시간이 초과되었습니다 {{ .err }}'
|
||||
ErrHttpReqFailed: '요청이 실패했습니다 {{ .err }}'
|
||||
@@ -69,7 +66,6 @@ ErrImagePullTimeOut: '이미지 풀링 시간 초과'
|
||||
ErrContainerNotFound: '{{ .name }} 컨테이너가 존재하지 않습니다'
|
||||
ErrContainerMsg: '{{ .name }} 컨테이너가 비정상입니다. 자세한 내용은 컨테이너 페이지의 로그를 확인하세요.'
|
||||
ErrAppBackup: '{{ .name }} 애플리케이션 백업에 실패했습니다 {{ .err }}'
|
||||
ErrImagePull: '이미지 가져오기에 실패했습니다 {{ .err }}'
|
||||
ErrVersionTooLow: '현재 1Panel 버전이 너무 낮아 App Store를 업데이트할 수 없습니다. 작동하시기 전에 버전을 업그레이드하세요.'
|
||||
ErrAppNameExist: '응용 프로그램 이름이 이미 존재합니다'
|
||||
AppStoreIsSyncing: 'App Store가 동기화 중입니다. 나중에 다시 시도하세요.'
|
||||
@@ -80,8 +76,6 @@ ErrAppUpgrade: '애플리케이션 {{ .name }} 업그레이드에 실패했습
|
||||
AppRecover: '롤백 애플리케이션 {{ .name }}'
|
||||
PullImageStart: '이미지 {{ .name }} 가져오기 시작'
|
||||
PullImageSuccess: '이미지 가져오기 성공'
|
||||
UpgradeAppStart: '애플리케이션 {{ .name }} 업그레이드 시작'
|
||||
UpgradeAppSuccess: '애플리케이션 {{ .name }}이 성공적으로 업그레이드되었습니다.'
|
||||
AppStoreIsLastVersion: '앱스토어가 이미 최신 버전입니다'
|
||||
AppStoreSyncSuccess: '앱스토어 동기화 성공'
|
||||
SyncAppDetail: '애플리케이션 구성 동기화'
|
||||
@@ -91,8 +85,6 @@ MoveSiteToDir: '사이트 디렉토리를 {{ .name }}로 마이그레이션'
|
||||
ErrMoveSiteDir: '사이트 디렉토리를 마이그레이션하지 못했습니다'
|
||||
MoveSiteDirSuccess: '웹사이트 디렉토리 마이그레이션 성공'
|
||||
DeleteRuntimePHP: 'PHP 런타임 삭제'
|
||||
CustomAppStoreNotConfig: '앱 스토어에서 오프라인 패키지 주소를 설정해주세요'
|
||||
CustomAppStoreNotFound: '앱 스토어 패키지를 가져오지 못했습니다. 해당 패키지가 있는지 확인하세요'
|
||||
CustomAppStoreFileValid: '앱 스토어 패키지는 .tar.gz 형식이어야 합니다.'
|
||||
PullImageTimeout: '이미지 가져오기 시간 초과, 이미지 가속을 높이거나 다른 이미지 가속으로 변경해 보세요.'
|
||||
ErrAppIsDown: '{{ .name }} 애플리케이션 상태가 비정상적입니다. 확인해 주세요'
|
||||
@@ -115,9 +107,7 @@ ErrInvalidChar: '불법 문자는 허용되지 않습니다'
|
||||
ErrPathNotDelete: '선택한 디렉토리를 삭제할 수 없습니다'
|
||||
|
||||
#웹사이트
|
||||
ErrDomainIsExist: '도메인 이름이 이미 존재합니다'
|
||||
ErrAliasIsExist: '별칭이 이미 존재합니다'
|
||||
ErrAppDelete: '다른 웹사이트에서 이 앱을 사용하고 있어 삭제할 수 없습니다'
|
||||
ErrBackupMatch: '백업 파일이 현재 웹사이트 데이터 중 일부 {{ .detail }}와 일치하지 않습니다.'
|
||||
ErrBackupExist: '백업 파일에 있는 소스 데이터의 해당 부분이 존재하지 않습니다 {{ .detail }}'
|
||||
ErrPHPResource: '로컬 운영 환경이 전환을 지원하지 않습니다! '
|
||||
@@ -179,7 +169,6 @@ ErrPortRules: '포트 번호가 일치하지 않습니다. 다시 입력하세
|
||||
ErrPgImagePull: '이미지 풀링 시간이 초과되었습니다. 이미지 가속을 구성하거나 {{ .name }} 이미지를 수동으로 풀링한 다음 다시 시도하세요.'
|
||||
|
||||
#실행 시간
|
||||
ErrDirNotFound: '빌드 폴더가 존재하지 않습니다! 파일 무결성을 확인하세요!'
|
||||
ErrFileNotExist: '{{ .detail }} 파일이 존재하지 않습니다! 소스 파일의 무결성을 확인하세요!'
|
||||
ErrImageBuildErr: '이미지 빌드 실패'
|
||||
ErrImageExist: '이미지가 이미 존재합니다!'
|
||||
@@ -219,6 +208,7 @@ ErrRuleNotExist: '규칙이 존재하지 않습니다'
|
||||
ErrParseIP: '잘못된 IP 형식'
|
||||
ErrDefaultIP: '기본값은 예약된 이름입니다. 다른 이름으로 변경해 주세요'
|
||||
ErrGroupInUse: 'IP 그룹이 블랙리스트/화이트리스트에 사용 중이므로 삭제할 수 없습니다.'
|
||||
ErrIPGroupAclUse: "IP 그룹은 웹사이트 {{ .name }} 의 사용자 정의 규칙에 사용되므로 삭제할 수 없습니다"
|
||||
ErrGroupExist: 'IP 그룹 이름이 이미 존재합니다'
|
||||
ErrIPRange: '잘못된 IP 범위'
|
||||
ErrIPExist: 'IP가 이미 존재합니다'
|
||||
@@ -262,6 +252,7 @@ ErrDBNotExist: '데이터베이스가 존재하지 않습니다'
|
||||
allow: '허용하다'
|
||||
deny: '거부하다'
|
||||
OpenrestyNotFound: 'Openresty가 설치되지 않았습니다'
|
||||
remoteIpIsNull: "IP 목록이 비어 있습니다"
|
||||
|
||||
#일
|
||||
TaskStart: '[START] {{ .name }} 작업이 시작됩니다.'
|
||||
@@ -387,7 +378,7 @@ ContainerStartCheck: '컨테이너가 시작되었는지 확인'
|
||||
# 작업 - 이미지
|
||||
ImageBuild: '이미지 빌드'
|
||||
ImageBuildStdoutCheck: '이미지 출력 콘텐츠 구문 분석'
|
||||
ImaegBuildRes: '이미지 빌드 출력: {{ .name }}'
|
||||
ImageBuildRes: '이미지 빌드 출력: {{ .name }}'
|
||||
ImagePull: '이미지 가져오기'
|
||||
ImageRepoAuthFromDB: '데이터베이스에서 저장소 인증 정보 가져오기'
|
||||
ImaegPullRes: '이미지 풀 출력: {{ .name }}'
|
||||
@@ -415,4 +406,7 @@ ErrAlert: '경고 메시지의 형식이 올바르지 않습니다. 확인하고
|
||||
ErrAlertPush: '알림 정보를 푸시하는 중 오류가 발생했습니다. 확인하고 다시 시도하세요!'
|
||||
ErrAlertSave: '알람 정보를 저장하는 중 오류가 발생했습니다. 확인하고 다시 시도하세요!'
|
||||
ErrAlertSync: '알람 정보 동기화 오류입니다. 확인하고 다시 시도하세요!'
|
||||
ErrAlertRemote: '알람 메시지 원격 오류, 확인하고 다시 시도하세요!'
|
||||
ErrAlertRemote: '알람 메시지 원격 오류, 확인하고 다시 시도하세요!'
|
||||
|
||||
#task - runtime
|
||||
ErrInstallExtension: "이미 설치 작업이 진행 중입니다. 작업이 완료될 때까지 기다려 주세요."
|
||||
+7
-13
@@ -42,11 +42,9 @@ ErrBackupLocalCreate: 'Membuat akaun sandaran pelayan tempatan belum disokong la
|
||||
#app
|
||||
ErrPortInUsed: 'Port {{ .detail }} sudah diduduki!'
|
||||
ErrAppLimit: 'Bilangan aplikasi yang dipasang telah melebihi had'
|
||||
ErrAppRequired: 'Sila pasang aplikasi {{ .detail }} dahulu'
|
||||
ErrNotInstall: 'Aplikasi tidak dipasang'
|
||||
ErrPortInOtherApp: 'Port {{ .port }} sudah diduduki oleh aplikasi {{ .apps }}!'
|
||||
ErrDbUserNotValid: 'Pangkalan data sedia ada, nama pengguna dan kata laluan tidak sepadan!'
|
||||
ErrDockerComposeNotValid: 'Format fail karang docker tidak betul'
|
||||
ErrUpdateBuWebsite: 'Aplikasi telah berjaya dikemas kini, tetapi pengubahsuaian fail konfigurasi tapak web gagal. Sila semak konfigurasi! '
|
||||
Err1PanelNetworkFailed: 'Pembuatan rangkaian kontena lalai gagal! {{ .detail }}'
|
||||
ErrFileParse: 'Penghuraian fail karang docker aplikasi gagal!'
|
||||
@@ -59,7 +57,6 @@ ErrFileParseApp: '{{ .name }} penghuraian fail gagal {{ .err }}'
|
||||
ErrAppDirNull: 'Folder versi tidak wujud'
|
||||
LocalAppErr: 'Penyegerakan {{ .name }} aplikasi gagal! {{ .err }}'
|
||||
ErrContainerName: 'Nama kontena sudah wujud'
|
||||
ErrAppSystemRestart: '1Panel restart menyebabkan tugas ditamatkan'
|
||||
ErrCreateHttpClient: 'Gagal membuat permintaan {{ .err }}'
|
||||
ErrHttpReqTimeOut: 'Permintaan tamat masa {{ .err }}'
|
||||
ErrHttpReqFailed: 'Permintaan gagal {{ .err }}'
|
||||
@@ -69,7 +66,6 @@ ErrImagePullTimeOut: 'Tamat masa tarik imej'
|
||||
ErrContainerNotFound: 'Bekas {{ .name }} tidak wujud'
|
||||
ErrContainerMsg: Bekas '{{ .name }} adalah tidak normal. Sila semak log pada halaman kontena untuk butiran'
|
||||
ErrAppBackup: '{{ .name }} sandaran aplikasi gagal {{ .err }}'
|
||||
ErrImagePull: 'Tarik imej gagal {{ .err }}'
|
||||
ErrVersionTooLow: 'Versi 1Panel semasa terlalu rendah untuk mengemas kini App Store. Sila tingkatkan versi sebelum beroperasi.'
|
||||
ErrAppNameExist: 'Nama aplikasi sudah wujud'
|
||||
AppStoreIsSyncing: 'App Store sedang menyegerak, sila cuba sebentar lagi'
|
||||
@@ -80,8 +76,6 @@ ErrAppUpgrade: 'Peningkatan {{ .name }} aplikasi gagal {{ .err }}'
|
||||
AppRecover: 'Aplikasi tarik balik {{ .name }}'
|
||||
PullImageStart: 'Mula tarik imej {{ .name }}'
|
||||
PullImageSuccess: 'Tarik imej berjaya'
|
||||
UpgradeAppStart: 'Mulakan naik taraf aplikasi {{ .name }}'
|
||||
UpgradeAppSuccess: 'Aplikasi {{ .name }} berjaya dinaik taraf'
|
||||
AppStoreIsLastVersion: 'App Store sudah pun versi terkini'
|
||||
AppStoreSyncSuccess: 'Penyegerakan App Store berjaya'
|
||||
SyncAppDetail: 'Segerakkan konfigurasi aplikasi'
|
||||
@@ -91,8 +85,6 @@ MoveSiteToDir: 'Pindahkan direktori tapak ke {{ .name }}'
|
||||
ErrMoveSiteDir: 'Gagal memindahkan direktori tapak'
|
||||
MoveSiteDirSuccess: 'Penghijrahan direktori tapak web yang berjaya'
|
||||
DeleteRuntimePHP: 'Padam masa jalan PHP'
|
||||
CustomAppStoreNotConfig: 'Sila tetapkan alamat pakej luar talian dalam gedung aplikasi'
|
||||
CustomAppStoreNotFound: 'Gagal mendapatkan pakej kedai aplikasi, sila semak sama ada ia wujud'
|
||||
CustomAppStoreFileValid: 'Pakej gedung apl perlu dalam format .tar.gz'
|
||||
PullImageTimeout: 'Tarik tamat masa imej, sila cuba tingkatkan pecutan imej atau tukar kepada pecutan imej lain'
|
||||
ErrAppIsDown: 'Status permohonan {{ .name }} tidak normal, sila semak'
|
||||
@@ -115,9 +107,7 @@ ErrInvalidChar: 'Aksara haram tidak dibenarkan'
|
||||
ErrPathNotDelete: 'Direktori yang dipilih tidak boleh dipadamkan'
|
||||
|
||||
#laman web
|
||||
ErrDomainIsExist: 'Nama domain sudah wujud'
|
||||
ErrAliasIsExist: 'Alias sudah wujud'
|
||||
ErrAppDelete: 'Laman web lain menggunakan aplikasi ini dan tidak boleh memadamkannya'
|
||||
ErrBackupMatch: 'Fail sandaran tidak sepadan dengan beberapa data tapak web semasa {{ .detail }}'
|
||||
ErrBackupExist: 'Bahagian sepadan data sumber dalam fail sandaran tidak wujud {{ .detail }}'
|
||||
ErrPHPResource: 'Persekitaran operasi tempatan tidak menyokong penukaran! '
|
||||
@@ -179,7 +169,6 @@ ErrPortRules: 'Nombor port tidak sepadan, sila masukkan semula!'
|
||||
ErrPgImagePull: 'Tarikh imej tamat masa, sila konfigurasikan pecutan imej atau tarik imej {{ .name }} secara manual dan cuba lagi'
|
||||
|
||||
#masa berjalan
|
||||
ErrDirNotFound: 'Folder binaan tidak wujud! Sila semak integriti fail!'
|
||||
ErrFileNotExist: 'Fail {{ .detail }} tidak wujud! Sila semak integriti fail sumber!'
|
||||
ErrImageBuildErr: 'Pembinaan imej gagal'
|
||||
ErrImageExist: 'Imej sudah wujud!'
|
||||
@@ -219,6 +208,7 @@ ErrRuleNotExist: 'Peraturan tidak wujud'
|
||||
ErrParseIP: 'Format IP salah'
|
||||
ErrDefaultIP: 'default ialah nama simpanan, sila tukar kepada nama lain'
|
||||
ErrGroupInUse: 'Kumpulan IP digunakan oleh senarai hitam/senarai putih dan tidak boleh dipadamkan'
|
||||
ErrIPGroupAclUse: "Kumpulan IP digunakan oleh peraturan tersuai tapak web {{ .name }}, tidak boleh dipadamkan"
|
||||
ErrGroupExist: 'Nama kumpulan IP sudah wujud'
|
||||
ErrIPRange: 'Julat IP salah'
|
||||
ErrIPExist: 'IP sudah wujud'
|
||||
@@ -262,6 +252,7 @@ ErrDBNotExist: 'Pangkalan data tidak wujud'
|
||||
allow: 'membenarkan'
|
||||
deny: 'menafikan'
|
||||
OpenrestyNotFound: 'Openresty tidak dipasang'
|
||||
remoteIpIsNull: "Senarai IP kosong"
|
||||
|
||||
#tugas
|
||||
TaskStart: '{{ .name }} Task mula [MULA]'
|
||||
@@ -387,7 +378,7 @@ ContainerStartCheck: 'Periksa sama ada bekas telah dimulakan'
|
||||
# tugas - imej
|
||||
ImageBuild: 'Membina Imej'
|
||||
ImageBuildStdoutCheck: 'Menghuraikan kandungan output imej'
|
||||
ImaegBuildRes: 'Output binaan imej: {{ .name }}'
|
||||
ImageBuildRes: 'Output binaan imej: {{ .name }}'
|
||||
ImagePull: 'Tarik imej'
|
||||
ImageRepoAuthFromDB: 'Dapatkan maklumat pengesahan repositori daripada pangkalan data'
|
||||
ImaegPullRes: 'Output tarik imej: {{ .name }}'
|
||||
@@ -415,4 +406,7 @@ ErrAlert: 'Format mesej amaran tidak betul, sila semak dan cuba lagi!'
|
||||
ErrAlertPush: 'Ralat dalam menolak maklumat amaran, sila semak dan cuba lagi!'
|
||||
ErrAlertSave: 'Ralat menyimpan maklumat penggera, sila semak dan cuba lagi!'
|
||||
ErrAlertSync: 'Ralat penyegerakan maklumat penggera, sila semak dan cuba lagi!'
|
||||
ErrAlertRemote: 'Ralat jauh mesej penggera, sila semak dan cuba lagi!'
|
||||
ErrAlertRemote: 'Ralat jauh mesej penggera, sila semak dan cuba lagi!'
|
||||
|
||||
#task - runtime
|
||||
ErrInstallExtension: "Tugas pemasangan sudah sedang berjalan, silakan tunggu tugas selesai"
|
||||
@@ -42,11 +42,9 @@ ErrBackupLocalCreate: 'A criação de contas de backup do servidor local ainda n
|
||||
#aplicativo
|
||||
ErrPortInUsed: 'A porta {{ .detail }} já está ocupada!'
|
||||
ErrAppLimit: 'O número de aplicativos instalados excedeu o limite'
|
||||
ErrAppRequired: 'Instale primeiro o aplicativo {{ .detail }}'
|
||||
ErrNotInstall: 'Aplicativo não instalado'
|
||||
ErrPortInOtherApp: 'A porta {{ .port }} já está ocupada pelo aplicativo {{ .apps }}!'
|
||||
ErrDbUserNotValid: 'Banco de dados existente, nome de usuário e senha não correspondem!'
|
||||
ErrDockerComposeNotValid: 'O formato do arquivo docker-compose está incorreto'
|
||||
ErrUpdateBuWebsite: 'O aplicativo foi atualizado com sucesso, mas a modificação do arquivo de configuração do site falhou. Por favor, verifique a configuração! '
|
||||
Err1PanelNetworkFailed: 'Falha na criação da rede de contêiner padrão! {{ .detalhe }}'
|
||||
ErrFileParse: 'Falha na análise do arquivo docker-compose do aplicativo!'
|
||||
@@ -59,7 +57,6 @@ ErrFileParseApp: 'Falha na análise do arquivo {{ .name }} {{ .err }}'
|
||||
ErrAppDirNull: 'A pasta da versão não existe'
|
||||
LocalAppErr: 'Falha na sincronização do aplicativo {{ .name }}! {{ .err }}'
|
||||
ErrContainerName: 'O nome do contêiner já existe'
|
||||
ErrAppSystemRestart: '1A reinicialização do painel causou o encerramento da tarefa'
|
||||
ErrCreateHttpClient: 'Falha ao criar solicitação {{ .err }}'
|
||||
ErrHttpReqTimeOut: 'Tempo limite da solicitação esgotado {{ .err }}'
|
||||
ErrHttpReqFailed: 'Falha na solicitação {{ .err }}'
|
||||
@@ -69,7 +66,6 @@ ErrImagePullTimeOut: 'Tempo limite de extração de imagem'
|
||||
ErrContainerNotFound: '{{ .name }} container não existe'
|
||||
ErrContainerMsg: 'O contêiner {{ .name }} está anormal. Por favor, verifique o log na página do contêiner para obter detalhes.'
|
||||
ErrAppBackup: '{{ .name }} falha no backup do aplicativo {{ .err }}'
|
||||
ErrImagePull: 'Falha na extração de imagem {{ .err }}'
|
||||
ErrVersionTooLow: 'A versão atual do 1Panel é muito antiga para atualizar a App Store. Por favor, atualize a versão antes de operar.'
|
||||
ErrAppNameExist: 'O nome do aplicativo já existe'
|
||||
AppStoreIsSyncing: 'A App Store está sincronizando, tente novamente mais tarde'
|
||||
@@ -80,8 +76,6 @@ ErrAppUpgrade: 'Falha na atualização do aplicativo {{ .name }} {{ .err }}'
|
||||
AppRecover: 'Reverter aplicativo {{ .name }}'
|
||||
PullImageStart: 'Comece a extrair a imagem {{ .name }}'
|
||||
PullImageSuccess: 'Imagem retirada com sucesso'
|
||||
UpgradeAppStart: 'Iniciar atualização do aplicativo {{ .name }}'
|
||||
UpgradeAppSuccess: 'Aplicativo {{ .name }} atualizado com sucesso'
|
||||
AppStoreIsLastVersion: 'A App Store já é a versão mais recente'
|
||||
AppStoreSyncSuccess: 'Sincronização da App Store bem-sucedida'
|
||||
SyncAppDetail: 'Sincronizar configuração do aplicativo'
|
||||
@@ -91,8 +85,6 @@ MoveSiteToDir: 'Migrar diretório do site para {{ .name }}'
|
||||
ErrMoveSiteDir: 'Falha ao migrar o diretório do site'
|
||||
MoveSiteDirSuccess: 'Migração bem-sucedida do diretório do site'
|
||||
DeleteRuntimePHP: 'Excluir tempo de execução do PHP'
|
||||
CustomAppStoreNotConfig: 'Defina o endereço do pacote offline na app store'
|
||||
CustomAppStoreNotFound: 'Falha ao obter o pacote da app store, verifique se ele existe'
|
||||
CustomAppStoreFileValid: 'Os pacotes da App Store precisam estar no formato .tar.gz'
|
||||
PullImageTimeout: 'Tempo limite para puxar imagem, tente aumentar a aceleração da imagem ou altere para outra aceleração de imagem'
|
||||
ErrAppIsDown: 'O status do aplicativo {{ .name }} é anormal, verifique'
|
||||
@@ -115,9 +107,7 @@ ErrInvalidChar: 'Caracteres ilegais não são permitidos'
|
||||
ErrPathNotDelete: 'O diretório selecionado não pode ser excluído'
|
||||
|
||||
#site
|
||||
ErrDomainIsExist: 'Nome de domínio já existe'
|
||||
ErrAliasIsExist: 'Alias já existe'
|
||||
ErrAppDelete: 'Outros sites usam este aplicativo e não podem excluí-lo'
|
||||
ErrBackupMatch: 'O arquivo de backup não corresponde a alguns dados atuais do site {{ .detail }}'
|
||||
ErrBackupExist: 'A parte correspondente dos dados de origem no arquivo de backup não existe {{ .detail }}'
|
||||
ErrPHPResource: 'O ambiente operacional local não suporta comutação! '
|
||||
@@ -179,7 +169,6 @@ ErrPortRules: 'O número da porta não corresponde, digite novamente!'
|
||||
ErrPgImagePull: 'Tempo limite para extração de imagem. Configure a aceleração de imagem ou extraia manualmente a imagem {{ .name }} e tente novamente'
|
||||
|
||||
#tempo de execução
|
||||
ErrDirNotFound: 'A pasta de compilação não existe! Verifique a integridade do arquivo!'
|
||||
ErrFileNotExist: 'O arquivo {{ .detail }} não existe! Verifique a integridade do arquivo de origem!'
|
||||
ErrImageBuildErr: 'Falha na criação da imagem'
|
||||
ErrImageExist: 'A imagem já existe!'
|
||||
@@ -219,6 +208,7 @@ ErrRuleNotExist: 'A regra não existe'
|
||||
ErrParseIP: 'Formato de IP errado'
|
||||
ErrDefaultIP: 'padrão é um nome reservado, altere-o para outro nome'
|
||||
ErrGroupInUse: 'O grupo de IP é usado pela lista negra/lista branca e não pode ser excluído'
|
||||
ErrIPGroupAclUse: "O grupo de IP está sendo usado por regras personalizadas do site {{ .name }}, não pode ser excluído"
|
||||
ErrGroupExist: 'O nome do grupo IP já existe'
|
||||
ErrIPRange: 'Intervalo de IP errado'
|
||||
ErrIPExist: 'IP já existe'
|
||||
@@ -262,6 +252,7 @@ ErrDBNotExist: 'O banco de dados não existe'
|
||||
allow: 'permitir'
|
||||
deny: 'negar'
|
||||
OpenrestyNotFound: 'Openresty não está instalado'
|
||||
remoteIpIsNull: "A lista de IP está vazia"
|
||||
|
||||
#tarefa
|
||||
TaskStart: '{{ .name }} A tarefa inicia [START]'
|
||||
@@ -387,7 +378,7 @@ ContainerStartCheck: 'Verifique se o contêiner foi iniciado'
|
||||
# tarefa - imagem
|
||||
ImageBuild: 'Construção de imagem'
|
||||
ImageBuildStdoutCheck: 'Analisar conteúdo de saída da imagem'
|
||||
ImaegBuildRes: 'Saída da criação da imagem: {{ .name }}'
|
||||
ImageBuildRes: 'Saída da criação da imagem: {{ .name }}'
|
||||
ImagePull: 'Puxar imagem'
|
||||
ImageRepoAuthFromDB: 'Obter informações de autenticação do repositório do banco de dados'
|
||||
ImaegPullRes: 'Saída de extração de imagem: {{ .name }}'
|
||||
@@ -415,4 +406,7 @@ ErrAlert: 'O formato da mensagem de aviso está incorreto, verifique e tente nov
|
||||
ErrAlertPush: 'Erro ao enviar informações de alerta, verifique e tente novamente!'
|
||||
ErrAlertSave: 'Erro ao salvar as informações do alarme. Verifique e tente novamente!'
|
||||
ErrAlertSync: 'Erro de sincronização de informações de alarme, verifique e tente novamente!'
|
||||
ErrAlertRemote: 'Erro remoto na mensagem de alarme, verifique e tente novamente!'
|
||||
ErrAlertRemote: 'Erro remoto na mensagem de alarme, verifique e tente novamente!'
|
||||
|
||||
#task - runtime
|
||||
ErrInstallExtension: "Já existe uma tarefa de instalação em andamento, aguarde a conclusão da tarefa"
|
||||
+7
-13
@@ -42,11 +42,9 @@ ErrBackupLocalCreate: 'Создание учетных записей резер
|
||||
#приложение
|
||||
ErrPortInUsed: '{{ .detail }} порт уже занят!'
|
||||
ErrAppLimit: 'Количество установленных приложений превысило лимит'
|
||||
ErrAppRequired: 'Сначала установите приложение {{ .detail }}'
|
||||
ErrNotInstall: 'Приложение не установлено'
|
||||
ErrPortInOtherApp: 'Порт {{ .port }} уже занят приложением {{ .apps }}!'
|
||||
ErrDbUserNotValid: 'Существующая база данных, имя пользователя и пароль не совпадают!'
|
||||
ErrDockerComposeNotValid: 'Неверный формат файла docker-compose'
|
||||
ErrUpdateBuWebsite: 'Приложение успешно обновлено, но изменение файла конфигурации веб-сайта не удалось. Пожалуйста, проверьте конфигурацию! '
|
||||
Err1PanelNetworkFailed: 'Создание сети контейнеров по умолчанию не удалось! {{ .detail }}'
|
||||
ErrFileParse: 'Ошибка анализа файла docker-compose приложения!'
|
||||
@@ -59,7 +57,6 @@ ErrFileParseApp: '{{ .name }} анализ файла не удался {{ .err
|
||||
ErrAppDirNull: 'Папка версии не существует'
|
||||
LocalAppErr: 'Синхронизация приложения {{ .name }} не удалась! {{ .err }}'
|
||||
ErrContainerName: 'Имя контейнера уже существует'
|
||||
ErrAppSystemRestart: '1Перезапуск панели привел к завершению задачи'
|
||||
ErrCreateHttpClient: 'Не удалось создать запрос {{ .err }}'
|
||||
ErrHttpReqTimeOut: 'Истекло время ожидания запроса {{ .err }}'
|
||||
ErrHttpReqFailed: 'Запрос не выполнен {{ .err }}'
|
||||
@@ -69,7 +66,6 @@ ErrImagePullTimeOut: 'Истекло время ожидания извлече
|
||||
ErrContainerNotFound: 'Контейнер {{ .name }} не существует'
|
||||
ErrContainerMsg: 'Контейнер {{ .name }} ненормален. Подробности смотрите в журнале на странице контейнера.'
|
||||
ErrAppBackup: '{{ .name }} резервное копирование приложения не удалось {{ .err }}'
|
||||
ErrImagePull: 'Не удалось извлечь изображение {{ .err }}'
|
||||
ErrVersionTooLow: 'Текущая версия 1Panel слишком низкая для обновления App Store. Пожалуйста, обновите версию перед началом работы.'
|
||||
ErrAppNameExist: 'Имя приложения уже существует'
|
||||
AppStoreIsSyncing: 'App Store синхронизируется, повторите попытку позже'
|
||||
@@ -80,8 +76,6 @@ ErrAppUpgrade: 'Обновление приложения {{ .name }} не уд
|
||||
AppRecover: 'Откатить приложение {{ .name }}'
|
||||
PullImageStart: 'Начать извлечение изображения {{ .name }}'
|
||||
PullImageSuccess: 'Изображение извлечено успешно'
|
||||
UpgradeAppStart: 'Начать обновление приложения {{ .name }}'
|
||||
UpgradeAppSuccess: 'Приложение {{ .name }} успешно обновлено'
|
||||
AppStoreIsLastVersion: 'В App Store уже установлена последняя версия'
|
||||
AppStoreSyncSuccess: 'Синхронизация с App Store прошла успешно'
|
||||
SyncAppDetail: 'Синхронизировать конфигурацию приложения'
|
||||
@@ -91,8 +85,6 @@ MoveSiteToDir: 'Перенести каталог сайта в {{ .name }}'
|
||||
ErrMoveSiteDir: 'Не удалось перенести каталог сайта'
|
||||
MoveSiteDirSuccess: 'Успешная миграция каталога веб-сайта'
|
||||
DeleteRuntimePHP: 'Удалить среду выполнения PHP'
|
||||
CustomAppStoreNotConfig: 'Укажите адрес автономного пакета в магазине приложений'
|
||||
CustomAppStoreNotFound: 'Не удалось получить пакет магазина приложений. Проверьте, существует ли он'
|
||||
CustomAppStoreFileValid: 'Пакеты магазина приложений должны быть в формате .tar.gz'
|
||||
PullImageTimeout: 'Истекло время ожидания извлечения изображения. Попробуйте увеличить ускорение изображения или выбрать другое ускорение изображения'
|
||||
ErrAppIsDown: '{{ .name }} статус приложения ненормальный, проверьте'
|
||||
@@ -115,9 +107,7 @@ ErrInvalidChar: 'Недопустимые символы не допускают
|
||||
ErrPathNotDelete: 'Выбранный каталог не может быть удален'
|
||||
|
||||
#веб-сайт
|
||||
ErrDomainIsExist: 'Доменное имя уже существует'
|
||||
ErrAliasIsExist: 'Псевдоним уже существует'
|
||||
ErrAppDelete: 'Другие веб-сайты используют это приложение и не могут его удалить'
|
||||
ErrBackupMatch: 'Файл резервной копии не соответствует некоторым текущим данным веб-сайта {{ .detail }}'
|
||||
ErrBackupExist: 'Соответствующая часть исходных данных в файле резервной копии не существует {{ .detail }}'
|
||||
ErrPHPResource: 'Локальная операционная среда не поддерживает переключение!'
|
||||
@@ -179,7 +169,6 @@ ErrPortRules: 'Номер порта не совпадает, введите з
|
||||
ErrPgImagePull: 'Время извлечения изображения истекло. Настройте ускорение изображения или вручную извлеките изображение {{ .name }} и повторите попытку'
|
||||
|
||||
#время выполнения
|
||||
ErrDirNotFound: 'Папка сборки не существует! Проверьте целостность файла!'
|
||||
ErrFileNotExist: 'Файл {{ .detail }} не существует! Проверьте целостность исходного файла!'
|
||||
ErrImageBuildErr: 'Сборка образа не удалась'
|
||||
ErrImageExist: 'Изображение уже существует!'
|
||||
@@ -219,6 +208,7 @@ ErrRuleNotExist: 'Правило не существует'
|
||||
ErrParseIP: 'Неправильный формат IP'
|
||||
ErrDefaultIP: 'по умолчанию это зарезервированное имя, пожалуйста, измените его на другое имя'
|
||||
ErrGroupInUse: 'IP-группа используется черным/белым списком и не может быть удалена'
|
||||
ErrIPGroupAclUse: "Группа IP используется пользовательскими правилами сайта {{ .name }}, невозможно удалить"
|
||||
ErrGroupExist: 'Имя группы IP уже существует'
|
||||
ErrIPRange: 'Неверный диапазон IP-адресов'
|
||||
ErrIPExist: 'IP-адрес уже существует'
|
||||
@@ -262,6 +252,7 @@ ErrDBNotExist: 'База данных не существует'
|
||||
allow: 'разрешить'
|
||||
deny: 'отрицать'
|
||||
OpenrestyNotFound: 'Openresty не установлен'
|
||||
remoteIpIsNull: "Список IP пуст"
|
||||
|
||||
#задача
|
||||
TaskStart: '{{ .name }} Задача начинается [START]'
|
||||
@@ -387,7 +378,7 @@ ContainerStartCheck: 'Проверить, запущен ли контейнер
|
||||
# задача - изображение
|
||||
ImageBuild: 'Создание изображения'
|
||||
ImageBuildStdoutCheck: 'Анализ содержимого выходного изображения'
|
||||
ImaegBuildRes: 'Выходные данные сборки образа: {{ .name }}'
|
||||
ImageBuildRes: 'Выходные данные сборки образа: {{ .name }}'
|
||||
ImagePull: 'Вытащить изображение'
|
||||
ImageRepoAuthFromDB: 'Получить информацию об аутентификации репозитория из базы данных'
|
||||
ImaegPullRes: 'Выход извлечения изображения: {{ .name }}'
|
||||
@@ -415,4 +406,7 @@ ErrAlert: 'Формат предупреждающего сообщения не
|
||||
ErrAlertPush: 'Ошибка при отправке оповещения. Проверьте и повторите попытку!'
|
||||
ErrAlertSave: 'Ошибка сохранения информации о тревоге, проверьте и повторите попытку!'
|
||||
ErrAlertSync: 'Ошибка синхронизации информации о тревоге, проверьте и повторите попытку!'
|
||||
ErrAlertRemote: 'Ошибка удаленного сообщения об ошибке, проверьте и повторите попытку!'
|
||||
ErrAlertRemote: 'Ошибка удаленного сообщения об ошибке, проверьте и повторите попытку!'
|
||||
|
||||
#task - runtime
|
||||
ErrInstallExtension: "Уже выполняется задача установки, подождите, пока задача завершится"
|
||||
@@ -42,11 +42,9 @@ ErrBackupLocalCreate: '暫時不支援建立本機伺服器備份帳號'
|
||||
#app
|
||||
ErrPortInUsed: '{{ .detail }} 連接埠已被佔用!'
|
||||
ErrAppLimit: '應用程式超出安裝數量限制'
|
||||
ErrAppRequired: '請先安裝{{ .detail }} 應用'
|
||||
ErrNotInstall: '應用程式未安裝'
|
||||
ErrPortInOtherApp: '{{ .port }} 連接埠已被應用程式{{ .apps }} 佔用!'
|
||||
ErrDbUserNotValid: '存量資料庫,使用者名稱密碼不符!'
|
||||
ErrDockerComposeNotValid: 'docker-compose 檔案格式錯誤'
|
||||
ErrUpdateBuWebsite: '應用程式更新成功,但網站設定檔修改失敗,請檢查設定! '
|
||||
Err1PanelNetworkFailed: '預設容器網路建立失敗! {{ .detail }}'
|
||||
ErrFileParse: '應用docker-compose 檔案解析失敗!'
|
||||
@@ -59,7 +57,6 @@ ErrFileParseApp: '{{ .name }} 檔案解析失敗{{ .err }}'
|
||||
ErrAppDirNull: '版本資料夾不存在'
|
||||
LocalAppErr: '應用程式{{ .name }} 同步失敗!{{ .err }}'
|
||||
ErrContainerName: '容器名稱已存在'
|
||||
ErrAppSystemRestart: '1Panel 重新啟動導致任務終止'
|
||||
ErrCreateHttpClient: '建立請求失敗{{ .err }}'
|
||||
ErrHttpReqTimeOut: '請求逾時{{ .err }}'
|
||||
ErrHttpReqFailed: '請求失敗{{ .err }}'
|
||||
@@ -69,7 +66,6 @@ ErrImagePullTimeOut: '鏡像拉取逾時'
|
||||
ErrContainerNotFound: '{{ .name }} 容器不存在'
|
||||
ErrContainerMsg: '{{ .name }} 容器異常,請在容器頁面上查看日誌'
|
||||
ErrAppBackup: '{{ .name }} 應用備份失敗 {{ .err }}'
|
||||
ErrImagePull: '鏡像拉取失敗{{ .err }}'
|
||||
ErrVersionTooLow: '目前1Panel 版本過低,無法更新應用程式商店,請升級版本之後操作'
|
||||
ErrAppNameExist: '應用程式名稱已存在'
|
||||
AppStoreIsSyncing: '應用程式商店正在同步中,請稍後再試'
|
||||
@@ -80,8 +76,6 @@ ErrAppUpgrade: '應用程式{{ .name }} 升級失敗{{ .err }}'
|
||||
AppRecover: '應用程式{{ .name }} 回滾'
|
||||
PullImageStart: '開始拉取鏡像{{ .name }}'
|
||||
PullImageSuccess: '鏡像拉取成功'
|
||||
UpgradeAppStart: '開始升級應用程式{{ .name }}'
|
||||
UpgradeAppSuccess: '應用程式{{ .name }} 升級成功'
|
||||
AppStoreIsLastVersion: '應用程式商店已經是最新版本'
|
||||
AppStoreSyncSuccess: '應用程式商店同步成功'
|
||||
SyncAppDetail: '同步應用程式設定'
|
||||
@@ -91,8 +85,6 @@ MoveSiteToDir: '遷移網站目錄到{{ .name }}'
|
||||
ErrMoveSiteDir: '遷移網站目錄失敗'
|
||||
MoveSiteDirSuccess: '遷移網站目錄成功'
|
||||
DeleteRuntimePHP: '刪除PHP 運行環境'
|
||||
CustomAppStoreNotConfig: '請在應用程式商店設定離線套件位址'
|
||||
CustomAppStoreNotFound: '應用程式商店套件取得失敗,請檢查是否存在'
|
||||
CustomAppStoreFileValid: '應用程式商店包需要.tar.gz 格式'
|
||||
PullImageTimeout: '拉取鏡像逾時,請嘗試增加鏡像加速或更換其他鏡像加速'
|
||||
ErrAppIsDown: '{{ .name }} 應用程式狀態異常,請檢查'
|
||||
@@ -115,9 +107,7 @@ ErrInvalidChar: '禁止使用非法字元'
|
||||
ErrPathNotDelete: '所選目錄不可刪除'
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: '網域已存在'
|
||||
ErrAliasIsExist: '代號已存在'
|
||||
ErrAppDelete: '其他網站使用此應用程式,無法刪除'
|
||||
ErrBackupMatch: '該備份檔案與目前網站部分資料不符{{ .detail }}'
|
||||
ErrBackupExist: '該備份檔案對應部分來源資料不存在{{ .detail }}'
|
||||
ErrPHPResource: '本地運行環境不支援切換! '
|
||||
@@ -179,7 +169,6 @@ ErrPortRules: '連接埠數目不匹配,請重新輸入!'
|
||||
ErrPgImagePull: '鏡像拉取逾時,請配置鏡像加速或手動拉取{{ .name }} 鏡像後重試'
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: 'build 資料夾不存在!請檢查檔案完整性!'
|
||||
ErrFileNotExist: '{{ .detail }} 檔案不存在!請檢查來源檔案完整性!'
|
||||
ErrImageBuildErr: '鏡像build 失敗'
|
||||
ErrImageExist: '鏡像已存在!'
|
||||
@@ -219,6 +208,7 @@ ErrRuleNotExist: '規則不存在'
|
||||
ErrParseIP: 'IP 格式錯誤'
|
||||
ErrDefaultIP: 'default 為保留名稱,請更換其他名稱'
|
||||
ErrGroupInUse: 'IP 群組被駭/白名單使用,無法刪除'
|
||||
ErrIPGroupAclUse: "IP 群組被網站 {{ .name }} 自定義規則使用,無法刪除"
|
||||
ErrGroupExist: 'IP 群組名稱已存在'
|
||||
ErrIPRange: 'IP 範圍錯誤'
|
||||
ErrIPExist: 'IP 已存在'
|
||||
@@ -262,6 +252,7 @@ ErrDBNotExist: '資料庫不存在'
|
||||
allow: '允許'
|
||||
deny: '禁止'
|
||||
OpenrestyNotFound: 'Openresty 未安裝'
|
||||
remoteIpIsNull: "IP 列表為空"
|
||||
|
||||
#task
|
||||
TaskStart: '{{ .name }} 任務開始[START]'
|
||||
@@ -387,7 +378,7 @@ ContainerStartCheck: '檢查容器是否已啟動'
|
||||
# task - image
|
||||
ImageBuild: '鏡像建置'
|
||||
ImageBuildStdoutCheck: '解析鏡像輸出內容'
|
||||
ImaegBuildRes: '鏡像建置輸出:{{ .name }}'
|
||||
ImageBuildRes: '鏡像建置輸出:{{ .name }}'
|
||||
ImagePull: '拉取鏡像'
|
||||
ImageRepoAuthFromDB: '從資料庫取得倉庫認證資訊'
|
||||
ImaegPullRes: '鏡像拉取輸出:{{ .name }}'
|
||||
@@ -415,4 +406,7 @@ ErrAlert: '警告訊息格式錯誤,請檢查後重試!'
|
||||
ErrAlertPush: '警告訊息推送錯誤,請檢查後重試!'
|
||||
ErrAlertSave: '警告訊息儲存錯誤,請檢查後重試!'
|
||||
ErrAlertSync: '警告訊息同步錯誤,請檢查後重試!'
|
||||
ErrAlertRemote: '警告訊息遠端錯誤,請檢查後重試!'
|
||||
ErrAlertRemote: '警告訊息遠端錯誤,請檢查後重試!'
|
||||
|
||||
#task - runtime
|
||||
ErrInstallExtension: "已有安裝任務正在進行,請等待任務結束"
|
||||
+1
-12
@@ -42,11 +42,9 @@ ErrBackupLocalCreate: "暂不支持创建本地服务器备份账号"
|
||||
#app
|
||||
ErrPortInUsed: "{{ .detail }} 端口已被占用!"
|
||||
ErrAppLimit: "应用超出安装数量限制"
|
||||
ErrAppRequired: "请先安装 {{ .detail }} 应用"
|
||||
ErrNotInstall: "应用未安装"
|
||||
ErrPortInOtherApp: "{{ .port }} 端口已被应用 {{ .apps }} 占用!"
|
||||
ErrDbUserNotValid: "存量数据库,用户名密码不匹配!"
|
||||
ErrDockerComposeNotValid: "docker-compose 文件格式错误"
|
||||
ErrUpdateBuWebsite: '应用更新成功,但是网站配置文件修改失败,请检查配置!'
|
||||
Err1PanelNetworkFailed: '默认容器网络创建失败!{{ .detail }}'
|
||||
ErrFileParse: '应用 docker-compose 文件解析失败!'
|
||||
@@ -59,7 +57,6 @@ ErrFileParseApp: '{{ .name }} 文件解析失败 {{ .err }}'
|
||||
ErrAppDirNull: '版本文件夹不存在'
|
||||
LocalAppErr: "应用 {{ .name }} 同步失败!{{ .err }}"
|
||||
ErrContainerName: "容器名称已存在"
|
||||
ErrAppSystemRestart: "1Panel 重启导致任务终止"
|
||||
ErrCreateHttpClient: "创建请求失败 {{ .err }}"
|
||||
ErrHttpReqTimeOut: "请求超时 {{ .err }}"
|
||||
ErrHttpReqFailed: "请求失败 {{ .err }}"
|
||||
@@ -69,7 +66,6 @@ ErrImagePullTimeOut: '镜像拉取超时'
|
||||
ErrContainerNotFound: '{{ .name }} 容器不存在'
|
||||
ErrContainerMsg: '{{ .name }} 容器异常,具体请在容器页面查看日志'
|
||||
ErrAppBackup: '{{ .name }} 应用备份失败 {{ .err }}'
|
||||
ErrImagePull: '镜像拉取失败 {{ .err }}'
|
||||
ErrVersionTooLow: '当前 1Panel 版本过低,无法更新应用商店,请升级版本之后操作'
|
||||
ErrAppNameExist: '应用名称已存在'
|
||||
AppStoreIsSyncing: '应用商店正在同步中,请稍后再试'
|
||||
@@ -80,8 +76,6 @@ ErrAppUpgrade: "应用 {{ .name }} 升级失败 {{ .err }}"
|
||||
AppRecover: "应用 {{ .name }} 回滚 "
|
||||
PullImageStart: "开始拉取镜像 {{ .name }}"
|
||||
PullImageSuccess: "镜像拉取成功"
|
||||
UpgradeAppStart: "开始升级应用 {{ .name }}"
|
||||
UpgradeAppSuccess: "应用 {{ .name }} 升级成功"
|
||||
AppStoreSyncSuccess: "应用商店同步成功"
|
||||
SyncAppDetail: "同步应用配置"
|
||||
AppVersionNotMatch: "{{ .name }} 应用需要更高的 1Panel 版本,跳过同步"
|
||||
@@ -90,8 +84,6 @@ MoveSiteToDir: "迁移网站目录到 {{ .name }}"
|
||||
ErrMoveSiteDir: "迁移网站目录失败"
|
||||
MoveSiteDirSuccess: "迁移网站目录成功"
|
||||
DeleteRuntimePHP: "删除 PHP 运行环境"
|
||||
CustomAppStoreNotConfig: "请在应用商店设置离线包地址"
|
||||
CustomAppStoreNotFound: "应用商店包获取失败,请检查是否存在"
|
||||
CustomAppStoreFileValid: "应用商店包需要 .tar.gz 格式"
|
||||
PullImageTimeout: "拉取镜像超时,请尝试增加镜像加速或者更换其他镜像加速"
|
||||
ErrAppIsDown: "{{ .name }} 应用状态异常,请检查"
|
||||
@@ -114,9 +106,7 @@ ErrInvalidChar: "禁止使用非法字符"
|
||||
ErrPathNotDelete: "所选目录不可删除"
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "域名已存在"
|
||||
ErrAliasIsExist: "代号已存在"
|
||||
ErrAppDelete: '其他网站使用此应用,无法删除'
|
||||
ErrBackupMatch: '该备份文件与当前网站部分数据不匹配 {{ .detail }}'
|
||||
ErrBackupExist: '该备份文件对应部分源数据不存在 {{ .detail }}'
|
||||
ErrPHPResource: '本地运行环境不支持切换!'
|
||||
@@ -179,7 +169,6 @@ ErrPortRules: "端口数目不匹配,请重新输入!"
|
||||
ErrPgImagePull: "镜像拉取超时,请配置镜像加速或手动拉取 {{ .name }} 镜像后重试"
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"
|
||||
ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!"
|
||||
ErrImageBuildErr: "镜像 build 失败"
|
||||
ErrImageExist: "镜像已存在!"
|
||||
@@ -389,7 +378,7 @@ ContainerStartCheck: "检查容器是否已启动"
|
||||
# task - image
|
||||
ImageBuild: "镜像构建"
|
||||
ImageBuildStdoutCheck: "解析镜像输出内容"
|
||||
ImaegBuildRes: "镜像构建输出:{{ .name }}"
|
||||
ImageBuildRes: "镜像构建输出:{{ .name }}"
|
||||
ImagePull: "拉取镜像"
|
||||
ImageRepoAuthFromDB: "从数据库获取仓库认证信息"
|
||||
ImaegPullRes: "镜像拉取输出:{{ .name }}"
|
||||
|
||||
@@ -30,4 +30,5 @@ func Init() {
|
||||
global.Dir.RuntimeDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/runtime"))
|
||||
global.Dir.RecycleBinDir, _ = fileOp.CreateDirWithPath(true, "/.1panel_clash")
|
||||
global.Dir.SSLLogDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/log/ssl"))
|
||||
global.Dir.McpDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/mcp"))
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ func InitAgentDB() {
|
||||
migrations.UpdateWebsite,
|
||||
migrations.UpdateWebsiteAcmeAccount,
|
||||
migrations.UpdateAppInstall,
|
||||
migrations.AddMcpServer,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -60,6 +60,10 @@ var AddTable = &gormigrate.Migration{
|
||||
&model.WebsiteSSL{},
|
||||
&model.Group{},
|
||||
&model.AppIgnoreUpgrade{},
|
||||
&model.McpServer{},
|
||||
&model.MonitorBase{},
|
||||
&model.MonitorIO{},
|
||||
&model.MonitorNetwork{},
|
||||
)
|
||||
},
|
||||
}
|
||||
@@ -362,3 +366,13 @@ var UpdateAppInstall = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddMcpServer = &gormigrate.Migration{
|
||||
ID: "20250428-add-mcpServer",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.McpServer{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -24,5 +24,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)
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ var WebUrlMap = map[string]struct{}{
|
||||
"/ai": {},
|
||||
"/ai/model": {},
|
||||
"/ai/gpu": {},
|
||||
"/ai/mcp": {},
|
||||
|
||||
"/containers": {},
|
||||
"/containers/container/operate": {},
|
||||
|
||||
@@ -20,6 +20,7 @@ func LoadMenus() string {
|
||||
Children: []dto.ShowMenu{
|
||||
{ID: "41", Disabled: false, Title: "aiTools.model.model", IsShow: true, Label: "OllamaModel", Path: "/ai/model"},
|
||||
{ID: "42", Disabled: false, Title: "aiTools.gpu.gpu", IsShow: true, Label: "GPU", Path: "/ai/gpu"},
|
||||
{ID: "43", Disabled: false, Title: "aiTools.mcp.server", IsShow: true, Label: "MCPServer", Path: "/ai/mcp"},
|
||||
}},
|
||||
{ID: "5", Disabled: false, Title: "menu.database", IsShow: true, Label: "Database-Menu", Path: "/databases"},
|
||||
{ID: "6", Disabled: false, Title: "menu.container", IsShow: true, Label: "Container-Menu", Path: "/containers"},
|
||||
|
||||
@@ -296,7 +296,7 @@ var AddMFAInterval = &gormigrate.Migration{
|
||||
}
|
||||
|
||||
var UpdateXpackHideMemu = &gormigrate.Migration{
|
||||
ID: "20250414-update-xpack-hide-menu",
|
||||
ID: "20250429-update-xpack-hide-menu",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Updates(map[string]interface{}{"key": "HideMenu", "value": helper.LoadMenus()}).Error; err != nil {
|
||||
return err
|
||||
|
||||
@@ -109,4 +109,75 @@ export namespace AI {
|
||||
connUrl: string;
|
||||
acmeAccountID: number;
|
||||
}
|
||||
|
||||
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[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -664,6 +664,29 @@ const message = {
|
||||
migModeHelper: 'Used to create MIG instances for physical isolation of the GPU at the user level.',
|
||||
migModeNA: 'Not Supported',
|
||||
},
|
||||
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 and binary 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',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
create: 'Create',
|
||||
|
||||
@@ -653,6 +653,29 @@ const message = {
|
||||
migModeHelper: 'ユーザーレベルでGPUの物理的分離を行うためのMIGインスタンスを作成するために使用されます。',
|
||||
migModeNA: 'サポートされていません',
|
||||
},
|
||||
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 サーバーのアクセスアドレスを変更し、ポートへの外部アクセスを閉じます',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
create: 'コンテナを作成します',
|
||||
|
||||
@@ -649,6 +649,29 @@ const message = {
|
||||
migModeHelper: '사용자 수준에서 GPU 를 물리적으로 분리하는 MIG 인스턴스를 생성하는 데 사용됩니다.',
|
||||
migModeNA: '지원되지 않음',
|
||||
},
|
||||
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 서버의 접근 주소를 수정하고 포트의 외부 접근을 닫습니다',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
create: '컨테이너 만들기',
|
||||
|
||||
@@ -666,6 +666,29 @@ const message = {
|
||||
migModeHelper: 'Digunakan untuk membuat contoh MIG bagi pengasingan fizikal GPU pada tahap pengguna.',
|
||||
migModeNA: 'Tidak Disokong',
|
||||
},
|
||||
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 dan binari',
|
||||
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',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
create: 'Cipta kontena',
|
||||
|
||||
@@ -662,6 +662,29 @@ const message = {
|
||||
migModeHelper: 'Usado para criar instâncias MIG para isolamento físico da GPU no nível do usuário.',
|
||||
migModeNA: 'Não Suportado',
|
||||
},
|
||||
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 e binários 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',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
create: 'Criar contêiner',
|
||||
|
||||
@@ -662,6 +662,29 @@ const message = {
|
||||
'Используется для создания MIG-инстансов для физической изоляции GPU на уровне пользователя.',
|
||||
migModeNA: 'Не поддерживается',
|
||||
},
|
||||
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 и закроет внешний доступ к портам',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
create: 'Создать контейнер',
|
||||
|
||||
@@ -642,6 +642,28 @@ const message = {
|
||||
migModeHelper: '用於建立 MIG 實例,在用戶層實現 GPU 的物理隔離。',
|
||||
migModeNA: '不支援',
|
||||
},
|
||||
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 的訪問地址,並關閉端口的外部訪問',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
create: '創建容器',
|
||||
|
||||
@@ -641,6 +641,28 @@ const message = {
|
||||
migModeNA: '不支持',
|
||||
shr: '共享显存',
|
||||
},
|
||||
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 的访问地址,并关闭端口的外部访问',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
create: '创建容器',
|
||||
|
||||
@@ -20,6 +20,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',
|
||||
|
||||
@@ -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,228 @@
|
||||
<template>
|
||||
<DrawerPro v-model="open" :header="$t('aiTools.mcp.bindDomain')" @close="handleClose" size="normal">
|
||||
<div v-loading="loading">
|
||||
<el-form ref="formRef" label-position="top" @submit.prevent :model="req" :rules="rules">
|
||||
<el-alert class="common-prompt" :closable="false" type="warning">
|
||||
<template #default>
|
||||
<ul>
|
||||
<li>{{ $t('aiTools.proxy.proxyHelper1') }}</li>
|
||||
<li>{{ $t('aiTools.proxy.proxyHelper2') }}</li>
|
||||
<li>{{ $t('aiTools.proxy.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('aiTools.proxy.proxyHelper4') }}
|
||||
</span>
|
||||
<span class="input-help">
|
||||
{{ $t('aiTools.proxy.proxyHelper6') }}
|
||||
<el-link class="pageRoute" icon="Position" @click="toWebsite(req.websiteID)" type="primary">
|
||||
{{ $t('firewall.quickJump') }}
|
||||
</el-link>
|
||||
</span>
|
||||
<el-text type="danger">{{ $t('aiTools.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('aiTools.proxy.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-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>
|
||||
</DrawerPro>
|
||||
</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 listSSLs = () => {
|
||||
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 || [];
|
||||
listSSLs();
|
||||
});
|
||||
};
|
||||
|
||||
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,54 @@
|
||||
<template>
|
||||
<DrawerPro v-model="open" :header="$t('menu.config')" @close="handleClose" size="normal">
|
||||
<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>
|
||||
</DrawerPro>
|
||||
</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('aiTools.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,256 @@
|
||||
<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('aiTools.mcp.create') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="openDomain">
|
||||
{{ $t('aiTools.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('aiTools.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 !== 'Rrror' && 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/log/compose/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('aiTools.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('aiTools.mcp.operatorHelper', [
|
||||
i18n.global.t('aiTools.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,275 @@
|
||||
<template>
|
||||
<DrawerPro
|
||||
v-model="open"
|
||||
:header="$t('aiTools.mcp.' + mode)"
|
||||
:resource="mcpServer.name"
|
||||
@close="handleClose"
|
||||
size="large"
|
||||
>
|
||||
<el-form
|
||||
ref="mcpServerForm"
|
||||
label-position="top"
|
||||
:model="mcpServer"
|
||||
label-width="125px"
|
||||
:rules="rules"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-button @click="importRef.acceptParams()" type="primary" plain>
|
||||
{{ $t('aiTools.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('aiTools.mcp.commandPlaceHolder')"
|
||||
></el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('aiTools.mcp.commandHelper', ['@modelcontextprotocol/server-github']) }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<div>
|
||||
<el-text>{{ $t('aiTools.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('aiTools.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('aiTools.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('aiTools.mcp.ssePath')" prop="ssePath">
|
||||
<el-input v-model.trim="mcpServer.ssePath"></el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('aiTools.mcp.ssePathHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<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>
|
||||
</DrawerPro>
|
||||
<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>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<DrawerPro v-model="open" :header="$t('website.create')" size="large" @close="handleClose">
|
||||
<DrawerPro v-model="open" :header="$t('website.create')" size="60%" @close="handleClose">
|
||||
<template #buttons>
|
||||
<span class="drawer-header-button">
|
||||
<template v-for="item in WebsiteTypes" :key="item.value">
|
||||
|
||||
Reference in New Issue
Block a user