From e5bd5d17ba1a1eba37ace77ecebe3b3046a6f07a Mon Sep 17 00:00:00 2001 From: ChengPlay <31820853+zhengkunwang223@users.noreply.github.com> Date: Mon, 28 Apr 2025 15:21:47 +0800 Subject: [PATCH] feat: merge mcp server from dev (#8499) --- agent/app/api/v2/entry.go | 3 +- agent/app/api/v2/mcp_server.go | 166 +++++ agent/app/dto/request/mcp_server.go | 47 ++ agent/app/dto/request/website.go | 5 + agent/app/dto/response/mcp_server.go | 26 + agent/app/model/mcp_server.go | 18 + agent/app/repo/group.go | 7 + agent/app/repo/mcp_server.go | 56 ++ agent/app/service/app.go | 3 + agent/app/service/entry.go | 3 +- agent/app/service/mcp_server.go | 641 ++++++++++++++++++ agent/app/service/website.go | 24 + agent/cmd/server/mcp/compose.yml | 19 + agent/cmd/server/mcp/mcp.go | 8 + agent/cmd/server/nginx_conf/nginx_conf.go | 3 + agent/cmd/server/nginx_conf/sse.conf | 7 + agent/global/config.go | 1 + agent/i18n/lang/en.yaml | 20 +- agent/i18n/lang/ja.yaml | 20 +- agent/i18n/lang/ko.yaml | 20 +- agent/i18n/lang/ms.yaml | 20 +- agent/i18n/lang/pt-BR.yaml | 20 +- agent/i18n/lang/ru.yaml | 20 +- agent/i18n/lang/zh-Hant.yaml | 20 +- agent/i18n/lang/zh.yaml | 13 +- agent/init/dir/dir.go | 1 + agent/init/migration/migrate.go | 1 + agent/init/migration/migrations/init.go | 14 + agent/router/{ai.go => ro_ai.go} | 9 + core/constant/common.go | 1 + core/init/migration/helper/menu.go | 1 + core/init/migration/migrations/init.go | 2 +- frontend/src/api/interface/ai.ts | 71 ++ frontend/src/api/modules/ai.ts | 32 + frontend/src/lang/modules/en.ts | 23 + frontend/src/lang/modules/ja.ts | 23 + frontend/src/lang/modules/ko.ts | 23 + frontend/src/lang/modules/ms.ts | 23 + frontend/src/lang/modules/pt-br.ts | 23 + frontend/src/lang/modules/ru.ts | 23 + frontend/src/lang/modules/zh-Hant.ts | 22 + frontend/src/lang/modules/zh.ts | 22 + frontend/src/routers/modules/ai.ts | 9 + frontend/src/views/ai/mcp/index.vue | 17 + .../src/views/ai/mcp/server/bind/index.vue | 228 +++++++ .../src/views/ai/mcp/server/config/index.vue | 54 ++ .../src/views/ai/mcp/server/import/index.vue | 80 +++ frontend/src/views/ai/mcp/server/index.vue | 256 +++++++ .../src/views/ai/mcp/server/operate/index.vue | 275 ++++++++ .../src/views/ai/mcp/server/volume/index.vue | 60 ++ .../views/website/website/create/index.vue | 2 +- 51 files changed, 2378 insertions(+), 107 deletions(-) create mode 100644 agent/app/api/v2/mcp_server.go create mode 100644 agent/app/dto/request/mcp_server.go create mode 100644 agent/app/dto/response/mcp_server.go create mode 100644 agent/app/model/mcp_server.go create mode 100644 agent/app/repo/mcp_server.go create mode 100644 agent/app/service/mcp_server.go create mode 100644 agent/cmd/server/mcp/compose.yml create mode 100644 agent/cmd/server/mcp/mcp.go create mode 100644 agent/cmd/server/nginx_conf/sse.conf rename agent/router/{ai.go => ro_ai.go} (65%) create mode 100644 frontend/src/views/ai/mcp/index.vue create mode 100644 frontend/src/views/ai/mcp/server/bind/index.vue create mode 100644 frontend/src/views/ai/mcp/server/config/index.vue create mode 100644 frontend/src/views/ai/mcp/server/import/index.vue create mode 100644 frontend/src/views/ai/mcp/server/index.vue create mode 100644 frontend/src/views/ai/mcp/server/operate/index.vue create mode 100644 frontend/src/views/ai/mcp/server/volume/index.vue diff --git a/agent/app/api/v2/entry.go b/agent/app/api/v2/entry.go index 03aeb34eb..3dec8c181 100644 --- a/agent/app/api/v2/entry.go +++ b/agent/app/api/v2/entry.go @@ -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() diff --git a/agent/app/api/v2/mcp_server.go b/agent/app/api/v2/mcp_server.go new file mode 100644 index 000000000..3b96e290a --- /dev/null +++ b/agent/app/api/v2/mcp_server.go @@ -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) +} diff --git a/agent/app/dto/request/mcp_server.go b/agent/app/dto/request/mcp_server.go new file mode 100644 index 000000000..984c287db --- /dev/null +++ b/agent/app/dto/request/mcp_server.go @@ -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"` +} diff --git a/agent/app/dto/request/website.go b/agent/app/dto/request/website.go index 35cb71882..d65e67593 100644 --- a/agent/app/dto/request/website.go +++ b/agent/app/dto/request/website.go @@ -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"` +} diff --git a/agent/app/dto/response/mcp_server.go b/agent/app/dto/response/mcp_server.go new file mode 100644 index 000000000..29cf71f7a --- /dev/null +++ b/agent/app/dto/response/mcp_server.go @@ -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"` +} diff --git a/agent/app/model/mcp_server.go b/agent/app/model/mcp_server.go new file mode 100644 index 000000000..61ce66b65 --- /dev/null +++ b/agent/app/model/mcp_server.go @@ -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"` +} diff --git a/agent/app/repo/group.go b/agent/app/repo/group.go index 294826b47..34486edff 100644 --- a/agent/app/repo/group.go +++ b/agent/app/repo/group.go @@ -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") + } +} diff --git a/agent/app/repo/mcp_server.go b/agent/app/repo/mcp_server.go new file mode 100644 index 000000000..775efa6fe --- /dev/null +++ b/agent/app/repo/mcp_server.go @@ -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 +} diff --git a/agent/app/service/app.go b/agent/app/service/app.go index 059edfa9a..10b01e854 100644 --- a/agent/app/service/app.go +++ b/agent/app/service/app.go @@ -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) diff --git a/agent/app/service/entry.go b/agent/app/service/entry.go index 3ee6709c9..046f127e5 100644 --- a/agent/app/service/entry.go +++ b/agent/app/service/entry.go @@ -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() diff --git a/agent/app/service/mcp_server.go b/agent/app/service/mcp_server.go new file mode 100644 index 000000000..d8a662bf9 --- /dev/null +++ b/agent/app/service/mcp_server.go @@ -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) +} diff --git a/agent/app/service/website.go b/agent/app/service/website.go index c4d5f447f..1304d9a52 100644 --- a/agent/app/service/website.go +++ b/agent/app/service/website.go @@ -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 diff --git a/agent/cmd/server/mcp/compose.yml b/agent/cmd/server/mcp/compose.yml new file mode 100644 index 000000000..9aef87157 --- /dev/null +++ b/agent/cmd/server/mcp/compose.yml @@ -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 \ No newline at end of file diff --git a/agent/cmd/server/mcp/mcp.go b/agent/cmd/server/mcp/mcp.go new file mode 100644 index 000000000..0e87d1335 --- /dev/null +++ b/agent/cmd/server/mcp/mcp.go @@ -0,0 +1,8 @@ +package mcp + +import ( + _ "embed" +) + +//go:embed compose.yml +var DefaultMcpCompose []byte diff --git a/agent/cmd/server/nginx_conf/nginx_conf.go b/agent/cmd/server/nginx_conf/nginx_conf.go index 144e94ee8..555a499bb 100644 --- a/agent/cmd/server/nginx_conf/nginx_conf.go +++ b/agent/cmd/server/nginx_conf/nginx_conf.go @@ -46,3 +46,6 @@ var Upstream []byte //go:embed php_extensions.json var PHPExtensionsJson []byte + +//go:embed sse.conf +var SSE []byte diff --git a/agent/cmd/server/nginx_conf/sse.conf b/agent/cmd/server/nginx_conf/sse.conf new file mode 100644 index 000000000..1a03ba96d --- /dev/null +++ b/agent/cmd/server/nginx_conf/sse.conf @@ -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; +} \ No newline at end of file diff --git a/agent/global/config.go b/agent/global/config.go index c4e39167e..8830dfa0f 100644 --- a/agent/global/config.go +++ b/agent/global/config.go @@ -41,6 +41,7 @@ type SystemDir struct { RuntimeDir string RecycleBinDir string SSLLogDir string + McpDir string } type LogConfig struct { diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index bbbaf2cd6..ef22b7328 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -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!' \ No newline at end of file +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" \ No newline at end of file diff --git a/agent/i18n/lang/ja.yaml b/agent/i18n/lang/ja.yaml index 848140caf..253adaaef 100644 --- a/agent/i18n/lang/ja.yaml +++ b/agent/i18n/lang/ja.yaml @@ -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: 'アラーム メッセージのリモート エラーです。確認してもう一度お試しください。' \ No newline at end of file +ErrAlertRemote: 'アラーム メッセージのリモート エラーです。確認してもう一度お試しください。' + +#task - runtime +ErrInstallExtension: "インストールタスクが進行中です、タスクが終了するのを待ってください" \ No newline at end of file diff --git a/agent/i18n/lang/ko.yaml b/agent/i18n/lang/ko.yaml index 2c4eb696c..14ec137e6 100644 --- a/agent/i18n/lang/ko.yaml +++ b/agent/i18n/lang/ko.yaml @@ -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: '알람 메시지 원격 오류, 확인하고 다시 시도하세요!' \ No newline at end of file +ErrAlertRemote: '알람 메시지 원격 오류, 확인하고 다시 시도하세요!' + +#task - runtime +ErrInstallExtension: "이미 설치 작업이 진행 중입니다. 작업이 완료될 때까지 기다려 주세요." \ No newline at end of file diff --git a/agent/i18n/lang/ms.yaml b/agent/i18n/lang/ms.yaml index 8a32ade0c..fda8f0777 100644 --- a/agent/i18n/lang/ms.yaml +++ b/agent/i18n/lang/ms.yaml @@ -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!' \ No newline at end of file +ErrAlertRemote: 'Ralat jauh mesej penggera, sila semak dan cuba lagi!' + +#task - runtime +ErrInstallExtension: "Tugas pemasangan sudah sedang berjalan, silakan tunggu tugas selesai" \ No newline at end of file diff --git a/agent/i18n/lang/pt-BR.yaml b/agent/i18n/lang/pt-BR.yaml index 864967e96..6ccf39038 100644 --- a/agent/i18n/lang/pt-BR.yaml +++ b/agent/i18n/lang/pt-BR.yaml @@ -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!' \ No newline at end of file +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" \ No newline at end of file diff --git a/agent/i18n/lang/ru.yaml b/agent/i18n/lang/ru.yaml index 7514c25cb..2375eb818 100644 --- a/agent/i18n/lang/ru.yaml +++ b/agent/i18n/lang/ru.yaml @@ -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: 'Ошибка удаленного сообщения об ошибке, проверьте и повторите попытку!' \ No newline at end of file +ErrAlertRemote: 'Ошибка удаленного сообщения об ошибке, проверьте и повторите попытку!' + +#task - runtime +ErrInstallExtension: "Уже выполняется задача установки, подождите, пока задача завершится" \ No newline at end of file diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index 1d241d1fd..36783d4f2 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -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: '警告訊息遠端錯誤,請檢查後重試!' \ No newline at end of file +ErrAlertRemote: '警告訊息遠端錯誤,請檢查後重試!' + +#task - runtime +ErrInstallExtension: "已有安裝任務正在進行,請等待任務結束" \ No newline at end of file diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index 59a2ce1b2..b8a22a5d0 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -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 }}" diff --git a/agent/init/dir/dir.go b/agent/init/dir/dir.go index 7e8ddb1cd..af40a8ba6 100644 --- a/agent/init/dir/dir.go +++ b/agent/init/dir/dir.go @@ -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")) } diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index 8ea780603..524f644b3 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -33,6 +33,7 @@ func InitAgentDB() { migrations.UpdateWebsite, migrations.UpdateWebsiteAcmeAccount, migrations.UpdateAppInstall, + migrations.AddMcpServer, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index 0bda2c9fc..937ff6724 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -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 + }, +} diff --git a/agent/router/ai.go b/agent/router/ro_ai.go similarity index 65% rename from agent/router/ai.go rename to agent/router/ro_ai.go index 703739294..1c590c556 100644 --- a/agent/router/ai.go +++ b/agent/router/ro_ai.go @@ -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) } } diff --git a/core/constant/common.go b/core/constant/common.go index 638140607..40de90f3e 100644 --- a/core/constant/common.go +++ b/core/constant/common.go @@ -58,6 +58,7 @@ var WebUrlMap = map[string]struct{}{ "/ai": {}, "/ai/model": {}, "/ai/gpu": {}, + "/ai/mcp": {}, "/containers": {}, "/containers/container/operate": {}, diff --git a/core/init/migration/helper/menu.go b/core/init/migration/helper/menu.go index 1d8eb8f37..d47c8d39c 100644 --- a/core/init/migration/helper/menu.go +++ b/core/init/migration/helper/menu.go @@ -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"}, diff --git a/core/init/migration/migrations/init.go b/core/init/migration/migrations/init.go index 1b925da01..a7a783f02 100644 --- a/core/init/migration/migrations/init.go +++ b/core/init/migration/migrations/init.go @@ -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 diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts index c1ebbdb4e..037ad7a63 100644 --- a/frontend/src/api/interface/ai.ts +++ b/frontend/src/api/interface/ai.ts @@ -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[]; + } } diff --git a/frontend/src/api/modules/ai.ts b/frontend/src/api/modules/ai.ts index 77cbba0a2..11c6eca2f 100644 --- a/frontend/src/api/modules/ai.ts +++ b/frontend/src/api/modules/ai.ts @@ -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>(`/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/mcp/domain/get`); +}; + +export const updateMcpDomain = (req: AI.McpBindDomainUpdate) => { + return http.post(`/ai/mcp/domain/update`, req); +}; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index f60fe944a..662b1646e 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -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', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index e3100e529..3b7c3239f 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -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: 'コンテナを作成します', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index b0e3c0b38..a89e6cc0b 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -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: '컨테이너 만들기', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 910bd6aeb..93ca55bb2 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -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', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index be399d9e0..e38b26cf3 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -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', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 66fb44558..2a81ff924 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -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: 'Создать контейнер', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index d160d07dc..bfc6fdbfa 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -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: '創建容器', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index ad585e27b..f93b7cde7 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -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: '创建容器', diff --git a/frontend/src/routers/modules/ai.ts b/frontend/src/routers/modules/ai.ts index a6bebc325..68a69387a 100644 --- a/frontend/src/routers/modules/ai.ts +++ b/frontend/src/routers/modules/ai.ts @@ -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', diff --git a/frontend/src/views/ai/mcp/index.vue b/frontend/src/views/ai/mcp/index.vue new file mode 100644 index 000000000..712f61483 --- /dev/null +++ b/frontend/src/views/ai/mcp/index.vue @@ -0,0 +1,17 @@ + + + diff --git a/frontend/src/views/ai/mcp/server/bind/index.vue b/frontend/src/views/ai/mcp/server/bind/index.vue new file mode 100644 index 000000000..91d88e202 --- /dev/null +++ b/frontend/src/views/ai/mcp/server/bind/index.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/frontend/src/views/ai/mcp/server/config/index.vue b/frontend/src/views/ai/mcp/server/config/index.vue new file mode 100644 index 000000000..a9d75c5c0 --- /dev/null +++ b/frontend/src/views/ai/mcp/server/config/index.vue @@ -0,0 +1,54 @@ + + + diff --git a/frontend/src/views/ai/mcp/server/import/index.vue b/frontend/src/views/ai/mcp/server/import/index.vue new file mode 100644 index 000000000..ced6c3cfc --- /dev/null +++ b/frontend/src/views/ai/mcp/server/import/index.vue @@ -0,0 +1,80 @@ + + + diff --git a/frontend/src/views/ai/mcp/server/index.vue b/frontend/src/views/ai/mcp/server/index.vue new file mode 100644 index 000000000..2e641fb84 --- /dev/null +++ b/frontend/src/views/ai/mcp/server/index.vue @@ -0,0 +1,256 @@ + + + diff --git a/frontend/src/views/ai/mcp/server/operate/index.vue b/frontend/src/views/ai/mcp/server/operate/index.vue new file mode 100644 index 000000000..edb8dbc05 --- /dev/null +++ b/frontend/src/views/ai/mcp/server/operate/index.vue @@ -0,0 +1,275 @@ + + + diff --git a/frontend/src/views/ai/mcp/server/volume/index.vue b/frontend/src/views/ai/mcp/server/volume/index.vue new file mode 100644 index 000000000..3661506bb --- /dev/null +++ b/frontend/src/views/ai/mcp/server/volume/index.vue @@ -0,0 +1,60 @@ + + + diff --git a/frontend/src/views/website/website/create/index.vue b/frontend/src/views/website/website/create/index.vue index 50b9de2e7..863ce4d5e 100644 --- a/frontend/src/views/website/website/create/index.vue +++ b/frontend/src/views/website/website/create/index.vue @@ -1,5 +1,5 @@