Compare commits

..
76 Commits
Author SHA1 Message Date
ssongliu b8a4e8e9e0 chore: update xpack menu migration (#13207) 2026-07-06 11:14:29 +08:00
ssongliu 4a462aecc7 fix: reset firewall import dialogs on open (#13198) 2026-07-03 16:51:08 +08:00
ssongliu 74f221b895 fix: use svg renderer for container monitor charts (#13197) 2026-07-03 16:50:47 +08:00
ssongliu cbcb628bff fix: normalize firewall import addresses (#13196) 2026-07-03 16:37:03 +08:00
蘭 f602645e31 feat: Implement code editor layout with splitter for improved UI (#13195) 2026-07-03 16:01:44 +08:00
CityFun ec9e609f10 feat: add some translate (#13194) 2026-07-03 15:59:35 +08:00
ssongliu 1c1f3c7761 feat: support importing all firewall rules (#13192)
* feat: support importing all firewall rules

* fix: preserve selected firewall import action
2026-07-03 15:59:22 +08:00
ssongliu fae49284b7 fix: support mariadb manual backup args (#13193) 2026-07-03 15:58:15 +08:00
蘭 072a608a63 feat: File editor supports right-click operation (#13191) 2026-07-03 15:04:04 +08:00
蘭 1e6fd9c3a0 ref: Optimize menu settings (#13190) 2026-07-03 12:10:47 +08:00
ssongliu 23200cbe85 fix: support mariadb cronjob backup args (#13188) 2026-07-03 11:41:52 +08:00
ssongliu 08604e8c02 fix: fix dashboard running time calculation (#13187) 2026-07-03 11:41:37 +08:00
蘭 62a472d2d1 ref: refactor code editor theme management (#13186) 2026-07-03 11:41:22 +08:00
ssongliu 2c84665e8b chore: organize i18n language files (#13183) 2026-07-02 21:13:22 +08:00
蘭 616bd6830c feat: add menu accordion setting and update related components (#13182) 2026-07-02 21:13:07 +08:00
ssongliu e55ea96f00 feat: support virtual machine (#13171)
* feat: support virtual machine

* feat: add vm operation logs
2026-07-02 15:07:48 +08:00
蘭 aa676e139c feat: implement code editor theme management and synchronization (#13169) 2026-07-02 14:49:52 +08:00
ssongliu 7708fea58d feat: update operation log content (#13166) 2026-07-01 18:14:33 +08:00
ssongliu 34bdff01ad fix: allow ssl push on selected node (#13165) 2026-07-01 17:04:00 +08:00
CityFun 5a49579ddf feat: Optimize error messages for HTTP certificate requests (#13164) 2026-07-01 16:19:57 +08:00
CityFun b98a2b06c9 feat: Add multiple dimensions to website search (#13163) 2026-07-01 16:19:43 +08:00
ssongliu 9073b16baa feat: support compose rebuild operation (#13162) 2026-07-01 16:19:25 +08:00
ssongliu 2ec4db8d7e feat: adjust batch application installation API (#13161) 2026-07-01 16:19:12 +08:00
CityFun fe9628f91c feat: Add TaskLog for MCP Servers (#13160) 2026-07-01 16:18:59 +08:00
蘭 18a469e4be fix: Enhance Python language support in Monaco editor (#13158) 2026-07-01 16:18:42 +08:00
蘭 8c691cc074 fix: Resolve the issue of inability to access mobile web login (#13157) 2026-07-01 16:18:26 +08:00
ssongliu 2cd31a47f8 fix: complete operation logs (#13155) 2026-06-30 22:22:20 +08:00
CityFun d97aa9c2a0 feat: Optimize SSH session performance on Ubuntu 25. (#13152)
* feat: Optimize SSH session performance on Ubuntu 25.

* feat: Optimize SSH session performance on Ubuntu 25.

* feat: Optimize SSH session performance on Ubuntu 25.

* feat: Optimize SSH session performance on Ubuntu 25.
2026-06-30 18:36:28 +08:00
ssongliu f31f0a89ff feat: support certificate synchronization (#13153) 2026-06-30 18:31:45 +08:00
CityFun 0a1621166e feat: Unify the default website and default site under the same button. (#13149) 2026-06-30 15:56:25 +08:00
蘭 8623aa5318 feat: Enhance API authentication with HMAC-SHA256 support and user role management (#13148) 2026-06-30 14:16:36 +08:00
CityFun ec50599526 feat: Node runtime environment supports configuring whether to install node_modules. (#13147) 2026-06-30 14:16:22 +08:00
CityFun 458a30c520 feat: Add a description for syncing WAF IP groups. (#13143) 2026-06-29 18:42:02 +08:00
Andrea Leone 9a660db4c3 feat: add Ionos DNS Provider (#13024)
* feat: add Ionos DNS Provider

* Fix typo string formatting for APIKey in Ionos config
2026-06-29 16:11:33 +08:00
Reza Alipour Kondori f4fc71d32b add some translate and update Persian (#13132)
* Add Persian (fa) translations

* Update index.ts

Add fa to LOCALE_LOADERS

* Update fu.ts

Add fa item

* Update index.vue

enable Persian (fa) in language selector

* Update login-form.vue

enable Persian (fa) in language selector

* Create fa.yaml

add fa backend translations

* Update i18n.go

Add fa to langFiles variable

* Add Persian (fa) Translation to agent

* Add Persian README file

Add Persian (fa) README.fa.md to project

* fix(i18n): add Persian (fa) to backend validation and login dropdown

* Update README.fa.md

* Upade link in readme
2026-06-29 15:25:56 +08:00
王贺 e45a8185e3 fix container upgrade network IP handling (#13126) 2026-06-26 18:38:11 +08:00
CityFun 7be9189b3c fix: Fixed issue with reload ssl failed (#13129) 2026-06-26 18:08:38 +08:00
蘭 bdf13c01f6 ref: Improve support for Persian (fa) language (#13128) 2026-06-26 18:08:22 +08:00
王贺 902bc90aef fix: Fix updating the application host binding IP (#13127) 2026-06-26 18:08:08 +08:00
Octopusandocto-patch 27ba50d8be feat: add MiniMax M3 model to provider catalog (#13119)
Add the latest MiniMax-M3 flagship model to the MiniMax provider model
list and set it as the default. M3 offers a 1M context window, 128K max
output, reasoning, and image input. Drop the older M2.5 entries while
keeping M2.7 as an alternative, and update the account verification probe
to use M3.

Co-authored-by: octo-patch <266937838+octo-patch@users.noreply.github.com>
2026-06-26 17:55:04 +08:00
Reza Alipour Kondori fb483ecb90 Add Persian (fa) translations (#13100)
* Add Persian (fa) translations

* Update index.ts

Add fa to LOCALE_LOADERS

* Update fu.ts

Add fa item

* Update index.vue

enable Persian (fa) in language selector

* Update login-form.vue

enable Persian (fa) in language selector

* Create fa.yaml

add fa backend translations

* Update i18n.go

Add fa to langFiles variable

* Add Persian (fa) Translation to agent
2026-06-26 17:17:32 +08:00
蘭 2de7079dd2 feat: add new localization strings for skill management features (#13124) 2026-06-26 16:17:57 +08:00
CityFun 2e4ac604a8 fix: Fix incomplete log download issue (#13123) 2026-06-26 16:17:40 +08:00
CityFun 46af80142e feat: add username/password support for hermes-agent (#13121)
* feat: add username/password support for hermes-agent

* feat: add username/password support for hermes-agent

* feat: add username/password support for hermes-agent
2026-06-26 16:17:23 +08:00
CityFun 677b47dcdb fix: Fixed issue with openclaw config telegram failed (#13114) 2026-06-25 18:40:34 +08:00
王贺 56cccdf7c0 refactor: remove skills hub server leftovers (#13115) 2026-06-25 15:09:21 +08:00
蘭 3e0d0bb809 feat: add quick toggle comment functionality in the editor (#13107) 2026-06-24 18:23:51 +08:00
ssongliu 86aa6c09c6 fix: resolve snapshot extraction directory missing issue (#13104) 2026-06-24 18:23:37 +08:00
蘭 f67e1d897c ref: add some translate (#13102) 2026-06-24 18:23:23 +08:00
蘭 9e518daa62 ref: add some translate (#13093) 2026-06-23 14:05:48 +08:00
CityFun 2e5455e655 feat: Add “Remove old images during app upgrade” configuration to the App Store. (#13092) 2026-06-23 14:05:33 +08:00
CityFun 868f1db4c5 feat: Create and delete runtime environments, and add task logs. (#13081) 2026-06-18 18:46:32 +08:00
王贺 efd84b1e1b feat: support skills hub server frontend (#13074) 2026-06-18 16:57:53 +08:00
CityFun 5591fe50f0 chore: optimize runtime environment container configuration (#13071)
* chore: optimize runtime environment container configuration

* chore: optimize runtime environment container configuration
2026-06-17 19:09:31 +08:00
王贺 5e0a598799 feat: update waf prompt translations (#13070) 2026-06-17 17:00:28 +08:00
CityFun 45d7b30abc feat: Add support for syncing self-signed certificates and manually u… (#13068)
* feat: Add support for syncing self-signed certificates and manually uploaded certificates to other nodes.

* feat: Add support for syncing self-signed certificates and manually
2026-06-17 17:00:15 +08:00
ssongliu 46af88e1d0 feat: support app and image synchronization (#13067) 2026-06-17 15:41:09 +08:00
CityFun 7f331f5503 fix: 解决证书推送到其他节点报错的问题 (#13065) 2026-06-17 15:31:51 +08:00
igophper 7b74e09d30 fix: manage expanded node states in the tree view component (#13064) 2026-06-17 12:36:23 +08:00
蘭 01e4455110 refactor: Remove error logging for missing alert config in alert handling (#13060) 2026-06-17 11:08:45 +08:00
CityFun 922a2d21c8 feat: add protocolVersion param for MCP Server (#13057)
* feat: add protocolVersion param for MCP Server

* feat: add protocolVersion param for MCP Server
2026-06-16 16:47:27 +08:00
CityFun 439c4794b7 feat: Optimize the vLLM display (#13056) 2026-06-15 16:42:00 +08:00
ssongliu 5ac7f16b67 feat: optimize image deletion logs (#13053) 2026-06-15 15:41:46 +08:00
蘭 bfb79066b4 fix: Adjust the default values of alert attributes (#13052) 2026-06-15 15:41:33 +08:00
蘭 19602b42fb feat: add QR code verification endpoint to password expiration checks (#13049) 2026-06-15 15:15:09 +08:00
ssongliu f987af264f feat: disable script library auto-sync on startup (#13047) 2026-06-15 13:46:24 +08:00
ssongliu a6c76dffc3 feat: add snapshot rollback prompt (#13045) 2026-06-15 13:46:13 +08:00
CityFun a2eed66bc4 fix: fixed issue with refresh agent page failed (#13035) 2026-06-12 18:14:35 +08:00
CityFun a49b9b132d feat: update app upgrade logic (#13032) 2026-06-12 17:44:34 +08:00
ssongliu e8505a249a fix: resolve password expiration issue in multi-user mode (#13030) 2026-06-12 14:16:16 +08:00
王贺 dbff9c02e9 fix: improve login page compatibility (#13029) 2026-06-12 11:45:14 +08:00
ssongliu 45c5c5b40f fix: resolve SSH key generation issue (#13027) 2026-06-12 11:17:08 +08:00
CityFun aea71664ee feat: add some translate (#13019) 2026-06-12 11:13:49 +08:00
ssongliu fc65fb9323 fix: fix monitoring tooltip styles (#13017) 2026-06-11 17:26:00 +08:00
ssongliu f476823531 fix: correct node administrator permissions (#13016) 2026-06-11 17:25:47 +08:00
CityFun 8222579d99 feat: update website ssl apply logic (#13015) 2026-06-11 17:25:34 +08:00
311 changed files with 19784 additions and 62418 deletions
+2 -1
View File
@@ -25,7 +25,7 @@
<a href="/docs/README.pt-br.md"><img alt="Português (Brasil)" src="https://img.shields.io/badge/Português (Brasil)-d9d9d9"></a>
<a href="/docs/README.ar.md"><img alt="العربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="/docs/README.de.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-d9d9d9"></a>
<a href="/docs/README.es.md"><img alt="Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="/docs/README.es-es.md"><img alt="Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="/docs/README.fr.md"><img alt="français" src="https://img.shields.io/badge/français-d9d9d9"></a>
<a href="/docs/README.ko.md"><img alt="한국어" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="/docs/README.id.md"><img alt="Bahasa Indonesia" src="https://img.shields.io/badge/Bahasa Indonesia-d9d9d9"></a>
@@ -33,6 +33,7 @@
<a href="/docs/README.tr.md"><img alt="Türkçe" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="/docs/README.ru.md"><img alt="Русский" src="https://img.shields.io/badge/Русский-d9d9d9"></a>
<a href="/docs/README.ms.md"><img alt="Bahasa Melayu" src="https://img.shields.io/badge/Bahasa Melayu-d9d9d9"></a>
<a href="/docs/README.fa.md"><img alt="Persian" src="https://img.shields.io/badge/%D9%81%D8%A7%D8%B1%D8%B3%DB%8C-d9d9d9"></a>
</p>
---
+6 -1
View File
@@ -413,10 +413,15 @@ func (b *BaseApi) Backup(c *gin.Context) {
switch req.Type {
case "app":
if _, err := backupService.AppBackup(req); err != nil {
record, err := backupService.AppBackup(req)
if err != nil {
helper.InternalServer(c, err)
return
}
if req.IsImmediate {
helper.SuccessWithData(c, record)
return
}
case "mysql", "mariadb", constant.AppMysqlCluster:
if err := backupService.MysqlBackup(req); err != nil {
helper.InternalServer(c, err)
+63
View File
@@ -23,6 +23,27 @@ func (b *BaseApi) PageMcpServers(c *gin.Context) {
helper.SuccessWithData(c, list)
}
// @Tags McpServer
// @Summary Load mcp server detail
// @Accept json
// @Param request body request.McpServerDetail true "request"
// @Success 200 {object} response.McpServerDTO
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/mcp/server/detail [post]
func (b *BaseApi) LoadMcpServerDetail(c *gin.Context) {
var req request.McpServerDetail
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
res, err := mcpServerService.Detail(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags McpServer
// @Summary Create mcp server
// @Accept json
@@ -107,6 +128,48 @@ func (b *BaseApi) OperateMcpServer(c *gin.Context) {
helper.Success(c)
}
// @Tags McpServer
// @Summary Sync mcp server status
// @Accept json
// @Param request body request.McpServerStatusSync true "request"
// @Success 200 {array} response.McpServerStatusDTO
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/mcp/server/status/sync [post]
func (b *BaseApi) SyncMcpServerStatus(c *gin.Context) {
var req request.McpServerStatusSync
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
res, err := mcpServerService.SyncStatus(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags McpServer
// @Summary Test mcp server connection
// @Accept json
// @Param request body request.McpServerConnectionTest true "request"
// @Success 200 {object} response.McpServerConnectionTestRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/mcp/server/connection/test [post]
func (b *BaseApi) TestMcpServerConnection(c *gin.Context) {
var req request.McpServerConnectionTest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
res, err := mcpServerService.TestConnection(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags McpServer
// @Summary Bind Domain for mcp server
// @Accept json
+25
View File
@@ -210,6 +210,27 @@ func (b *BaseApi) UpdateWebsiteSSL(c *gin.Context) {
helper.Success(c)
}
// @Tags Website SSL
// @Summary Push ssl to nodes
// @Accept json
// @Param request body request.WebsiteSSLPush true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/ssl/push [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"推送证书到节点 [domain]","formatEN":"Push ssl to nodes [domain]"}
func (b *BaseApi) PushWebsiteSSLToNode(c *gin.Context) {
var req request.WebsiteSSLPush
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteSSLService.PushToNode(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Website SSL
// @Summary Upload ssl
// @Accept json
@@ -248,6 +269,8 @@ func (b *BaseApi) UploadSSLFile(c *gin.Context) {
var req request.WebsiteSSLFileUpload
req.Description = c.PostForm("description")
req.Nodes = c.PostForm("nodes")
req.PushNode, _ = strconv.ParseBool(c.PostForm("pushNode"))
sslID := c.PostForm("sslID")
if sslID != "" {
req.SSLID, _ = strconv.ParseUint(sslID, 10, 64)
@@ -283,6 +306,8 @@ func (b *BaseApi) UploadSSLFile(c *gin.Context) {
Certificate: string(certificateContent),
Description: req.Description,
SSLID: uint(req.SSLID),
PushNode: req.PushNode,
Nodes: req.Nodes,
}
if err := websiteSSLService.Upload(uploadReq); err != nil {
+39 -34
View File
@@ -3,28 +3,30 @@ package dto
import "time"
type AgentCreateReq struct {
Name string `json:"name" validate:"required"`
Remark string `json:"remark"`
AppVersion string `json:"appVersion" validate:"required"`
WebUIPort int `json:"webUIPort" validate:"required,min=1,max=65535"`
BridgePort int `json:"bridgePort"`
AllowedOrigins []string `json:"allowedOrigins"`
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw hermes-agent"`
Model string `json:"model"`
AccountID uint `json:"accountId"`
Token string `json:"token"`
TaskID string `json:"taskID"`
Advanced bool `json:"advanced"`
ContainerName string `json:"containerName"`
AllowPort bool `json:"allowPort"`
SpecifyIP string `json:"specifyIP"`
RestartPolicy string `json:"restartPolicy"`
CpuQuota float64 `json:"cpuQuota"`
MemoryLimit float64 `json:"memoryLimit"`
MemoryUnit string `json:"memoryUnit"`
PullImage bool `json:"pullImage"`
EditCompose bool `json:"editCompose"`
DockerCompose string `json:"dockerCompose"`
Name string `json:"name" validate:"required"`
Remark string `json:"remark"`
AppVersion string `json:"appVersion" validate:"required"`
WebUIPort int `json:"webUIPort" validate:"required,min=1,max=65535"`
BridgePort int `json:"bridgePort"`
AllowedOrigins []string `json:"allowedOrigins"`
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw hermes-agent"`
Model string `json:"model"`
AccountID uint `json:"accountId"`
Token string `json:"token"`
DashboardUsername string `json:"dashboardUsername"`
DashboardPassword string `json:"dashboardPassword"`
TaskID string `json:"taskID"`
Advanced bool `json:"advanced"`
ContainerName string `json:"containerName"`
AllowPort bool `json:"allowPort"`
SpecifyIP string `json:"specifyIP"`
RestartPolicy string `json:"restartPolicy"`
CpuQuota float64 `json:"cpuQuota"`
MemoryLimit float64 `json:"memoryLimit"`
MemoryUnit string `json:"memoryUnit"`
PullImage bool `json:"pullImage"`
EditCompose bool `json:"editCompose"`
DockerCompose string `json:"dockerCompose"`
}
type AgentBatchInstallReq struct {
@@ -38,6 +40,8 @@ type AgentBatchInstallReq struct {
Model string `json:"model"`
AccountID uint `json:"accountId"`
Token string `json:"token"`
DashboardUsername string `json:"dashboardUsername"`
DashboardPassword string `json:"dashboardPassword"`
TaskID string `json:"taskID"`
Advanced bool `json:"advanced"`
ContainerName string `json:"containerName"`
@@ -119,6 +123,8 @@ type AgentItem struct {
BaseURL string `json:"baseUrl"`
APIKey string `json:"apiKey"`
Token string `json:"token"`
DashboardUsername string `json:"dashboardUsername"`
DashboardPassword string `json:"dashboardPassword"`
Status string `json:"status"`
Message string `json:"message"`
AppInstallID uint `json:"appInstallId"`
@@ -407,11 +413,6 @@ type AgentFeishuConfigUpdateReq struct {
Bots []AgentFeishuBot `json:"bots" validate:"required,min=1"`
}
type AgentFeishuPairingApproveReq struct {
AgentID uint `json:"agentId" validate:"required"`
PairingCode string `json:"pairingCode" validate:"required"`
}
type AgentFeishuConfig struct {
Enabled bool `json:"enabled"`
ThreadSession bool `json:"threadSession"`
@@ -660,16 +661,20 @@ type AgentSecurityConfig struct {
}
type AgentOtherConfigUpdateReq struct {
AgentID uint `json:"agentId" validate:"required"`
UserTimezone string `json:"userTimezone" validate:"required"`
BrowserEnabled bool `json:"browserEnabled"`
NPMRegistry string `json:"npmRegistry" validate:"required"`
AgentID uint `json:"agentId" validate:"required"`
UserTimezone string `json:"userTimezone" validate:"required"`
BrowserEnabled bool `json:"browserEnabled"`
NPMRegistry string `json:"npmRegistry" validate:"required"`
DashboardUsername string `json:"dashboardUsername"`
DashboardPassword string `json:"dashboardPassword"`
}
type AgentOtherConfig struct {
UserTimezone string `json:"userTimezone"`
BrowserEnabled bool `json:"browserEnabled"`
NPMRegistry string `json:"npmRegistry"`
UserTimezone string `json:"userTimezone"`
BrowserEnabled bool `json:"browserEnabled"`
NPMRegistry string `json:"npmRegistry"`
DashboardUsername string `json:"dashboardUsername"`
DashboardPassword string `json:"dashboardPassword"`
}
type AgentConfigFileReq struct {
+1
View File
@@ -132,6 +132,7 @@ type Locale struct {
Ko string `json:"ko"`
Tr string `json:"tr"`
Es string `json:"es-es" yaml:"es-es"`
Fa string `json:"fa"`
}
type AppForm struct {
+9 -8
View File
@@ -65,14 +65,15 @@ type UploadForRecover struct {
}
type CommonBackup struct {
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mongodb mysql-cluster postgresql-cluster redis-cluster container compose"`
Name string `json:"name"`
DetailName string `json:"detailName"`
Secret string `json:"secret"`
StopBefore bool `json:"stopBefore"`
TaskID string `json:"taskID"`
FileName string `json:"fileName"`
Args []string `json:"args"`
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mongodb mysql-cluster postgresql-cluster redis-cluster container compose"`
Name string `json:"name"`
DetailName string `json:"detailName"`
Secret string `json:"secret"`
IsImmediate bool `json:"isImmediate"`
StopBefore bool `json:"stopBefore"`
TaskID string `json:"taskID"`
FileName string `json:"fileName"`
Args []string `json:"args"`
Description string `json:"description"`
}
+1 -1
View File
@@ -319,7 +319,7 @@ type ComposeCreate struct {
type ComposeOperation struct {
Name string `json:"name" validate:"required"`
Path string `json:"path"`
Operation string `json:"operation" validate:"required,oneof=up start restart stop down delete"`
Operation string `json:"operation" validate:"required,oneof=up start restart stop down delete rebuild"`
WithFile bool `json:"withFile"`
Force bool `json:"force"`
}
+10 -2
View File
@@ -79,8 +79,9 @@ type NodeCurrent struct {
}
type DashboardCurrent struct {
Uptime uint64 `json:"uptime"`
TimeSinceUptime string `json:"timeSinceUptime"`
Uptime uint64 `json:"uptime"`
TimeSinceUptime string `json:"timeSinceUptime"`
RunningTime RunningTime `json:"runningTime"`
Procs uint64 `json:"procs"`
@@ -128,6 +129,13 @@ type DashboardCurrent struct {
ShotTime time.Time `json:"shotTime"`
}
type RunningTime struct {
Days uint64 `json:"days"`
Hours uint64 `json:"hours"`
Minutes uint64 `json:"minutes"`
Seconds uint64 `json:"seconds"`
}
type AppLauncherSync struct {
Keys []string `json:"keys"`
}
+1
View File
@@ -99,6 +99,7 @@ type AppInstallUpgrade struct {
DetailID uint `json:"detailId"`
Backup bool `json:"backup"`
PullImage bool `json:"pullImage"`
DeleteImage bool `json:"deleteImage"`
DockerCompose string `json:"dockerCompose"`
TaskID string `json:"taskID"`
}
+15
View File
@@ -21,6 +21,9 @@ type McpServerCreate struct {
StreamableHttpPath string `json:"streamableHttpPath"`
OutputTransport string `json:"outputTransport" validate:"required"`
Type string `json:"type" validate:"required"`
GatewayImage string `json:"gatewayImage"`
ProtocolVersion string `json:"protocolVersion"`
TaskID string `json:"taskID"`
}
type McpServerUpdate struct {
@@ -32,11 +35,23 @@ type McpServerDelete struct {
ID uint `json:"id" validate:"required"`
}
type McpServerDetail struct {
ID uint `json:"id" validate:"required"`
}
type McpServerStatusSync struct {
IDs []uint `json:"ids"`
}
type McpServerOperate struct {
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
}
type McpServerConnectionTest struct {
ID uint `json:"id" validate:"required"`
}
type McpBindDomain struct {
Domain string `json:"domain" validate:"required"`
SSLID uint `json:"sslID"`
+8 -3
View File
@@ -21,13 +21,14 @@ type RuntimeCreate struct {
Source string `json:"source"`
CodeDir string `json:"codeDir"`
Remark string `json:"remark"`
TaskID string `json:"taskID"`
Params map[string]interface{} `json:"params"`
NodeConfig
}
type NodeConfig struct {
Install bool `json:"install"`
Install *bool `json:"install"`
Clean bool `json:"clean"`
ExposedPorts []ExposedPort `json:"exposedPorts"`
Environments []Environment `json:"environments"`
@@ -42,12 +43,14 @@ type Environment struct {
type Volume struct {
Source string `json:"source"`
Target string `json:"target"`
Mode string `json:"mode"`
}
type ExposedPort struct {
HostPort int `json:"hostPort"`
ContainerPort int `json:"containerPort"`
HostIP string `json:"hostIP"`
Protocol string `json:"protocol"`
}
type ExtraHost struct {
@@ -56,8 +59,10 @@ type ExtraHost struct {
}
type RuntimeDelete struct {
ID uint `json:"id"`
ForceDelete bool `json:"forceDelete"`
ID uint `json:"id"`
ForceDelete bool `json:"forceDelete"`
DeleteImage bool `json:"deleteImage"`
TaskID string `json:"taskID"`
}
type RuntimeUpdate struct {
+15 -1
View File
@@ -6,7 +6,7 @@ type WebsiteSSLSearch struct {
dto.PageInfo
AcmeAccountID string `json:"acmeAccountID"`
Domain string `json:"domain"`
OrderBy string `json:"orderBy" validate:"omitempty,oneof=created_at expire_date"`
OrderBy string `json:"orderBy" validate:"omitempty,oneof=created_at updated_at expire_date"`
Order string `json:"order" validate:"omitempty,oneof=null ascending descending"`
}
@@ -126,6 +126,16 @@ type WebsiteSSLUpload struct {
Type string `json:"type" validate:"required,oneof=paste local"`
SSLID uint `json:"sslID"`
Description string `json:"description"`
PushNode bool `json:"pushNode"`
Nodes string `json:"nodes"`
}
type WebsiteSSLPush struct {
ID uint `json:"id" validate:"required"`
PushNode bool `json:"pushNode"`
Nodes string `json:"nodes"`
TaskID string `json:"taskID" validate:"required"`
Sync bool `json:"sync"`
}
type WebsiteCASearch struct {
@@ -157,6 +167,8 @@ type WebsiteCAObtain struct {
Description string `json:"description"`
ExecShell bool `json:"execShell"`
Shell string `json:"shell"`
PushNode bool `json:"pushNode"`
Nodes string `json:"nodes"`
}
type WebsiteCARenew struct {
@@ -167,4 +179,6 @@ type WebsiteSSLFileUpload struct {
Type string `json:"type"`
Description string `json:"description"`
SSLID uint64 `json:"sslID"`
PushNode bool `json:"pushNode"`
Nodes string `json:"nodes"`
}
+30 -28
View File
@@ -104,34 +104,36 @@ type AppDetail struct {
}
type AppInstallDTO struct {
ID uint `json:"id"`
Name string `json:"name"`
AppID uint `json:"appID"`
AppDetailID uint `json:"appDetailID"`
Version string `json:"version"`
Status string `json:"status"`
Message string `json:"message"`
HttpPort int `json:"httpPort"`
HttpsPort int `json:"httpsPort"`
Path string `json:"path"`
CanUpdate bool `json:"canUpdate"`
Icon string `json:"icon"`
AppName string `json:"appName"`
Ready int `json:"ready"`
Total int `json:"total"`
AppKey string `json:"appKey"`
AppType string `json:"appType"`
AppStatus string `json:"appStatus"`
DockerCompose string `json:"dockerCompose"`
WebUI string `json:"webUI"`
CreatedAt time.Time `json:"createdAt"`
Favorite bool `json:"favorite"`
SortOrder int `json:"sortOrder"`
App AppDetail `json:"app"`
Container string `json:"container"`
IsEdit bool `json:"isEdit"`
LinkDB bool `json:"linkDB"`
ServiceName string `json:"serviceName"`
ID uint `json:"id"`
Name string `json:"name"`
AppID uint `json:"appID"`
AppDetailID uint `json:"appDetailID"`
Version string `json:"version"`
Status string `json:"status"`
Message string `json:"message"`
HttpPort int `json:"httpPort"`
HttpsPort int `json:"httpsPort"`
Path string `json:"path"`
CanUpdate bool `json:"canUpdate"`
Icon string `json:"icon"`
AppName string `json:"appName"`
Ready int `json:"ready"`
Total int `json:"total"`
AppKey string `json:"appKey"`
AppType string `json:"appType"`
AppStatus string `json:"appStatus"`
DockerCompose string `json:"dockerCompose"`
WebUI string `json:"webUI"`
CreatedAt time.Time `json:"createdAt"`
Favorite bool `json:"favorite"`
SortOrder int `json:"sortOrder"`
App AppDetail `json:"app"`
Container string `json:"container"`
IsEdit bool `json:"isEdit"`
LinkDB bool `json:"linkDB"`
ResourceKeys []string `json:"resourceKeys"`
ServiceName string `json:"serviceName"`
Env map[string]interface{} `json:"env"`
}
type AppInstallInfo struct {
+14
View File
@@ -16,6 +16,12 @@ type McpServerDTO struct {
Volumes []request.Volume `json:"volumes"`
}
type McpServerStatusDTO struct {
ID uint `json:"id"`
Status string `json:"status"`
Message string `json:"message"`
}
type McpBindDomainRes struct {
Domain string `json:"domain"`
SSLID uint `json:"sslID"`
@@ -24,3 +30,11 @@ type McpBindDomainRes struct {
WebsiteID uint `json:"websiteID"`
ConnUrl string `json:"connUrl"`
}
type McpServerConnectionTestRes struct {
Success bool `json:"success"`
Endpoint string `json:"endpoint"`
OutputTransport string `json:"outputTransport"`
ProtocolVersion string `json:"protocolVersion,omitempty"`
Message string `json:"message"`
}
+2
View File
@@ -18,4 +18,6 @@ type McpServer struct {
StreamableHttpPath string `json:"streamableHttpPath"`
OutputTransport string `json:"outputTransport"`
Type string `json:"type"`
GatewayImage string `json:"gatewayImage"`
ProtocolVersion string `json:"protocolVersion"`
}
+1 -2
View File
@@ -163,10 +163,9 @@ var catalog = map[string]Meta{
Input: []string{"text"},
},
Models: []Model{
{ID: "minimax/MiniMax-M3", Name: "MiniMax M3", ContextWindow: 1000000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}},
{ID: "minimax/MiniMax-M2.7", Name: "MiniMax M2.7"},
{ID: "minimax/MiniMax-M2.7-highspeed", Name: "MiniMax M2.7 highspeed"},
{ID: "minimax/MiniMax-M2.5", Name: "MiniMax M2.5", Reasoning: true},
{ID: "minimax/MiniMax-M2.5-highspeed", Name: "MiniMax M2.5 highspeed"},
},
},
"xiaomi": {
+1 -1
View File
@@ -131,7 +131,7 @@ func BuildVerifyRequest(provider, baseURL, apiKey string) VerifyRequest {
request.URL = base + "/v1/messages"
}
request.Body = mustJSON(map[string]interface{}{
"model": "MiniMax-M2.5",
"model": "MiniMax-M3",
"max_tokens": 1,
"messages": []map[string]interface{}{{
"role": "user",
+14
View File
@@ -17,6 +17,7 @@ type IWebsiteRepo interface {
WithGroupID(groupId uint) DBOption
WithDefaultServer() DBOption
WithDomainLike(domain string) DBOption
WithSearchKeyword(keyword string, ids []uint) DBOption
WithRuntimeID(runtimeID uint) DBOption
WithParentID(websiteID uint) DBOption
WithType(websiteType string) DBOption
@@ -76,6 +77,19 @@ func (w *WebsiteRepo) WithDomainLike(domain string) DBOption {
}
}
func (w *WebsiteRepo) WithSearchKeyword(keyword string, ids []uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
if keyword == "" {
return db
}
keyword = "%" + keyword + "%"
if len(ids) == 0 {
return db.Where("(primary_domain like ? OR alias like ?)", keyword, keyword)
}
return db.Where("(primary_domain like ? OR alias like ? OR id in (?))", keyword, keyword, ids)
}
}
func (w *WebsiteRepo) WithAlias(alias string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("alias = ?", alias)
+20 -6
View File
@@ -2,8 +2,10 @@ package repo
import (
"context"
"time"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant"
"gorm.io/gorm"
)
@@ -21,6 +23,7 @@ type ISSLRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error)
GetFirst(opts ...DBOption) (*model.WebsiteSSL, error)
List(opts ...DBOption) ([]model.WebsiteSSL, error)
TryMarkApplying(id uint) (bool, error)
Create(ctx context.Context, ssl *model.WebsiteSSL) error
Save(ssl *model.WebsiteSSL) error
DeleteBy(opts ...DBOption) error
@@ -76,12 +79,12 @@ func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.W
}
func (w WebsiteSSLRepo) GetFirst(opts ...DBOption) (*model.WebsiteSSL, error) {
var website *model.WebsiteSSL
var website model.WebsiteSSL
db := getDb(opts...).Model(&model.WebsiteSSL{})
if err := db.Preload("AcmeAccount").Preload("DnsAccount").First(&website).Error; err != nil {
return website, err
return nil, err
}
return website, nil
return &website, nil
}
func (w WebsiteSSLRepo) List(opts ...DBOption) ([]model.WebsiteSSL, error) {
@@ -93,14 +96,25 @@ func (w WebsiteSSLRepo) List(opts ...DBOption) ([]model.WebsiteSSL, error) {
return websites, nil
}
func (w WebsiteSSLRepo) TryMarkApplying(id uint) (bool, error) {
db := getDb().Model(&model.WebsiteSSL{}).
Where("id = ? AND status <> ?", id, constant.SSLApply).
Updates(map[string]interface{}{
"status": constant.SSLApply,
"updated_at": time.Now(),
})
if db.Error != nil {
return false, db.Error
}
return db.RowsAffected > 0, nil
}
func (w WebsiteSSLRepo) Create(ctx context.Context, ssl *model.WebsiteSSL) error {
return getTx(ctx).Create(ssl).Error
}
func (w WebsiteSSLRepo) Save(ssl *model.WebsiteSSL) error {
return getDb().Model(&model.WebsiteSSL{BaseModel: model.BaseModel{
ID: ssl.ID,
}}).Save(&ssl).Error
return getDb().Save(ssl).Error
}
func (w WebsiteSSLRepo) SaveByMap(ssl *model.WebsiteSSL, params map[string]interface{}) error {
+50 -27
View File
@@ -176,6 +176,7 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
var allowedOrigins []string
var account *model.AgentAccount
var installHooks *appInstallHooks
var hermesAuth hermesDashboardAuth
if agentType == constant.AppOpenclaw || agentType == constant.AppHermesAgent {
if req.AccountID == 0 {
@@ -222,9 +223,13 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
},
}
} else if agentType == constant.AppHermesAgent {
hermesAuth = normalizeHermesDashboardAuth(req.DashboardUsername, req.DashboardPassword)
installHooks = &appInstallHooks{
AfterCopyData: func(appInstall *model.AppInstall) error {
return prepareHermesInstallFiles(appInstall, account, storedModel)
if err := prepareHermesInstallFiles(appInstall, account, storedModel); err != nil {
return err
}
return writeHermesDashboardAuthEnv(path.Join(appInstall.GetPath(), ".env"), hermesAuth, false)
},
}
}
@@ -248,6 +253,10 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
params["API_KEY"] = apiKey
params["OPENCLAW_GATEWAY_TOKEN"] = token
}
if agentType == constant.AppHermesAgent {
params[hermesDashboardUsernameEnvKey] = hermesAuth.Username
params[hermesDashboardPasswordEnvKey] = hermesAuth.Password
}
if req.EditCompose && strings.TrimSpace(req.DockerCompose) == "" {
return nil, buserr.New("ErrAgentComposeRequired")
@@ -604,28 +613,30 @@ func batchOperateSkipMessage(operate constant.AppOperate, status string) string
func buildCreateReqFromBatchInstallReq(req dto.AgentBatchInstallReq) dto.AgentCreateReq {
return dto.AgentCreateReq{
Name: req.Name,
Remark: req.Remark,
AppVersion: req.AppVersion,
WebUIPort: req.WebUIPort,
BridgePort: req.BridgePort,
AllowedOrigins: req.AllowedOrigins,
AgentType: req.AgentType,
Model: req.Model,
AccountID: req.AccountID,
Token: req.Token,
TaskID: req.TaskID,
Advanced: req.Advanced,
ContainerName: req.ContainerName,
AllowPort: req.AllowPort,
SpecifyIP: req.SpecifyIP,
RestartPolicy: req.RestartPolicy,
CpuQuota: req.CpuQuota,
MemoryLimit: req.MemoryLimit,
MemoryUnit: req.MemoryUnit,
PullImage: req.PullImage,
EditCompose: req.EditCompose,
DockerCompose: req.DockerCompose,
Name: req.Name,
Remark: req.Remark,
AppVersion: req.AppVersion,
WebUIPort: req.WebUIPort,
BridgePort: req.BridgePort,
AllowedOrigins: req.AllowedOrigins,
AgentType: req.AgentType,
Model: req.Model,
AccountID: req.AccountID,
Token: req.Token,
DashboardUsername: req.DashboardUsername,
DashboardPassword: req.DashboardPassword,
TaskID: req.TaskID,
Advanced: req.Advanced,
ContainerName: req.ContainerName,
AllowPort: req.AllowPort,
SpecifyIP: req.SpecifyIP,
RestartPolicy: req.RestartPolicy,
CpuQuota: req.CpuQuota,
MemoryLimit: req.MemoryLimit,
MemoryUnit: req.MemoryUnit,
PullImage: req.PullImage,
EditCompose: req.EditCompose,
DockerCompose: req.DockerCompose,
}
}
@@ -1327,10 +1338,13 @@ func (a AgentService) GetOtherConfig(req dto.AgentIDReq) (*dto.AgentOtherConfig,
if err != nil {
return nil, err
}
auth := readHermesDashboardAuthFromInstall(install)
return &dto.AgentOtherConfig{
UserTimezone: cfg.Timezone,
BrowserEnabled: true,
NPMRegistry: "https://registry.npmjs.org/",
UserTimezone: cfg.Timezone,
BrowserEnabled: true,
NPMRegistry: "https://registry.npmjs.org/",
DashboardUsername: auth.Username,
DashboardPassword: auth.Password,
}, nil
}
conf, err := readOpenclawConfig(agent.ConfigPath)
@@ -1355,12 +1369,21 @@ func (a AgentService) UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error
if err != nil {
return err
}
previousAuth := readHermesDashboardAuthFromInstall(install)
nextAuth := normalizeHermesDashboardAuth(req.DashboardUsername, req.DashboardPassword)
if err := writeHermesConfig(path.Dir(agent.ConfigPath), account, agent.Model, strings.TrimSpace(req.UserTimezone)); err != nil {
return err
}
if err := writeHermesDashboardAuthEnv(path.Join(install.GetPath(), ".env"), nextAuth, true); err != nil {
return err
}
operate := constant.Restart
if previousAuth.Username != nextAuth.Username || previousAuth.Password != nextAuth.Password {
operate = constant.Rebuild
}
return NewIAppInstalledService().Operate(request.AppInstalledOperate{
InstallId: install.ID,
Operate: constant.Restart,
Operate: operate,
})
}
if err := ensureContainerRunning(install.ContainerName); err != nil {
+27 -6
View File
@@ -811,9 +811,7 @@ func extractTelegramConfig(conf map[string]interface{}) dto.AgentTelegramConfig
result.RequireMention = result.GroupPolicy == "allowlist"
result.GroupAllowFrom = extractStringList(telegram["groupAllowFrom"])
result.Proxy = extractStringValue(telegram["proxy"])
if streaming := extractStringValue(telegram["streaming"]); streaming != "" {
result.Streaming = streaming
}
result.Streaming = normalizeTelegramStreamingMode(telegram["streaming"], result.Streaming)
accounts := childMap(telegram, "accounts")
if len(accounts) == 0 {
botToken := extractStringValue(telegram["botToken"])
@@ -839,7 +837,7 @@ func extractTelegramConfig(conf map[string]interface{}) dto.AgentTelegramConfig
BotToken: extractStringValue(account["botToken"]),
DmPolicy: extractStringValue(account["dmPolicy"]),
GroupPolicy: extractStringValue(account["groupPolicy"]),
Streaming: extractStringValue(account["streaming"]),
Streaming: normalizeTelegramStreamingMode(account["streaming"], result.Streaming),
})
}
result.DefaultAccount = normalizeDefaultAccount(extractStringValue(telegram["defaultAccount"]), getTelegramBotAccountIDs(bots))
@@ -879,7 +877,7 @@ func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConf
} else {
delete(telegram, "proxy")
}
telegram["streaming"] = config.Streaming
telegram["streaming"] = buildTelegramStreamingConfig(config.Streaming)
accounts := make(map[string]interface{}, len(config.Bots))
for _, bot := range config.Bots {
account := map[string]interface{}{
@@ -888,7 +886,7 @@ func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConf
"botToken": bot.BotToken,
"dmPolicy": bot.DmPolicy,
"groupPolicy": bot.GroupPolicy,
"streaming": bot.Streaming,
"streaming": buildTelegramStreamingConfig(bot.Streaming),
}
if bot.DmPolicy == "open" {
account["allowFrom"] = []string{"*"}
@@ -899,6 +897,29 @@ func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConf
delete(telegram, "botToken")
}
func normalizeTelegramStreamingMode(value interface{}, defaultMode string) string {
mode := defaultMode
switch typed := value.(type) {
case string:
mode = typed
case map[string]interface{}:
mode = extractStringValue(typed["mode"])
}
mode = strings.ToLower(strings.TrimSpace(mode))
switch mode {
case "off", "partial", "block", "progress":
return mode
default:
return "partial"
}
}
func buildTelegramStreamingConfig(mode string) map[string]interface{} {
return map[string]interface{}{
"mode": normalizeTelegramStreamingMode(mode, "partial"),
}
}
func extractDiscordConfig(conf map[string]interface{}) dto.AgentDiscordConfig {
result := dto.AgentDiscordConfig{Enabled: true, DmPolicy: "pairing", AllowFrom: []string{}, RequireMention: false, GroupPolicy: "open"}
discord := getChannelConfig(conf, "discord")
+63 -52
View File
@@ -13,14 +13,19 @@ import (
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/utils/common"
agentenv "github.com/1Panel-dev/1Panel/agent/utils/env"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/joho/godotenv"
"gopkg.in/yaml.v3"
)
const hermesWorkspaceDir = "/opt/data/workspace"
const hermesExecutablePath = "/opt/hermes/.venv/bin/hermes"
const hermesDashboardUsernameEnvKey = "HERMES_DASHBOARD_USERNAME"
const hermesDashboardPasswordEnvKey = "HERMES_DASHBOARD_PASSWORD"
type hermesDashboardAuth struct {
Username string
Password string
}
type hermesConfig struct {
Model hermesModelConfig `yaml:"model"`
@@ -112,6 +117,52 @@ func prepareHermesInstallFiles(appInstall *model.AppInstall, account *model.Agen
return files.NewFileOp().ChownR(dataDir, "1000", "1000", true)
}
func normalizeHermesDashboardAuth(username, password string) hermesDashboardAuth {
auth := hermesDashboardAuth{
Username: strings.TrimSpace(username),
Password: strings.TrimSpace(password),
}
if auth.Username == "" {
auth.Username = "admin"
}
if auth.Password == "" {
auth.Password = common.RandStr(8)
}
return auth
}
func writeHermesDashboardAuthEnv(envPath string, auth hermesDashboardAuth, overwrite bool) error {
return upsertAgentEnv(envPath, map[string]string{
hermesDashboardUsernameEnvKey: auth.Username,
hermesDashboardPasswordEnvKey: auth.Password,
}, []string{
hermesDashboardUsernameEnvKey,
hermesDashboardPasswordEnvKey,
}, overwrite)
}
func readHermesDashboardAuthEnv(envPath string) (hermesDashboardAuth, error) {
envMap, err := readAgentEnvMap(envPath)
if err != nil {
return hermesDashboardAuth{}, err
}
return hermesDashboardAuth{
Username: strings.TrimSpace(envMap[hermesDashboardUsernameEnvKey]),
Password: strings.TrimSpace(envMap[hermesDashboardPasswordEnvKey]),
}, nil
}
func readHermesDashboardAuthFromInstall(appInstall *model.AppInstall) hermesDashboardAuth {
if appInstall == nil || appInstall.ID == 0 {
return hermesDashboardAuth{}
}
auth, err := readHermesDashboardAuthEnv(path.Join(appInstall.GetPath(), ".env"))
if err != nil {
return hermesDashboardAuth{}
}
return auth
}
func readHermesConfig(configPath string) (*hermesConfig, error) {
content, err := files.NewFileOp().GetContent(configPath)
if err != nil {
@@ -152,7 +203,7 @@ func writeHermesConfigMap(configPath string, cfg map[string]interface{}) error {
}
func readHermesTelegramChannelConfig(confDir string) (*dto.AgentTelegramConfig, error) {
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
if err != nil {
return nil, err
}
@@ -209,7 +260,7 @@ func readHermesTelegramChannelConfig(confDir string) (*dto.AgentTelegramConfig,
func writeHermesTelegramChannelConfig(confDir string, config dto.AgentTelegramConfig) error {
envPath := path.Join(confDir, ".env")
envMap, err := readHermesEnvMap(envPath)
envMap, err := readAgentEnvMap(envPath)
if err != nil {
return err
}
@@ -229,7 +280,7 @@ func writeHermesTelegramChannelConfig(confDir string, config dto.AgentTelegramCo
envMap["TELEGRAM_ALLOWED_USERS"] = allow
}
}
if err := writeHermesEnvMap(envPath, envMap, []string{
if err := writeAgentEnvMap(envPath, envMap, []string{
"TELEGRAM_BOT_TOKEN",
"TELEGRAM_ALLOWED_USERS",
"TELEGRAM_ALLOW_ALL_USERS",
@@ -248,7 +299,7 @@ func writeHermesTelegramChannelConfig(confDir string, config dto.AgentTelegramCo
}
func readHermesDiscordChannelConfig(confDir string) (*dto.AgentDiscordConfig, error) {
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
if err != nil {
return nil, err
}
@@ -300,7 +351,7 @@ func readHermesDiscordChannelConfig(confDir string) (*dto.AgentDiscordConfig, er
func writeHermesDiscordChannelConfig(confDir string, config dto.AgentDiscordConfig) error {
envPath := path.Join(confDir, ".env")
envMap, err := readHermesEnvMap(envPath)
envMap, err := readAgentEnvMap(envPath)
if err != nil {
return err
}
@@ -320,7 +371,7 @@ func writeHermesDiscordChannelConfig(confDir string, config dto.AgentDiscordConf
envMap["DISCORD_ALLOWED_USERS"] = allow
}
}
if err := writeHermesEnvMap(envPath, envMap, []string{
if err := writeAgentEnvMap(envPath, envMap, []string{
"DISCORD_BOT_TOKEN",
"DISCORD_ALLOWED_USERS",
"DISCORD_ALLOW_ALL_USERS",
@@ -340,14 +391,14 @@ func writeHermesDiscordChannelConfig(confDir string, config dto.AgentDiscordConf
func deleteHermesEnvKeys(confDir string, keys ...string) error {
envPath := path.Join(confDir, ".env")
envMap, err := readHermesEnvMap(envPath)
envMap, err := readAgentEnvMap(envPath)
if err != nil {
return err
}
for _, key := range keys {
delete(envMap, key)
}
return writeHermesEnvMap(envPath, envMap, keys)
return writeAgentEnvMap(envPath, envMap, keys)
}
func deleteHermesConfigSections(confDir string, topLevelKeys []string, platformKeys []string) error {
@@ -532,7 +583,7 @@ func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
}
func writeHermesModelEnv(envPath string, account *model.AgentAccount) error {
envMap, err := readHermesEnvMap(envPath)
envMap, err := readAgentEnvMap(envPath)
if err != nil {
return err
}
@@ -548,7 +599,7 @@ func writeHermesModelEnv(envPath string, account *model.AgentAccount) error {
envMap[entry.Key] = entry.Value
order = append(order, entry.Key)
}
return writeHermesEnvMap(envPath, envMap, order)
return writeAgentEnvMap(envPath, envMap, order)
}
func hermesManagedModelEnvKeys() []string {
@@ -598,46 +649,6 @@ func hermesManagedModelEnvKeys() []string {
return result
}
func writeHermesEnv(envPath string, entries []hermesEnvEntry) error {
envMap, err := readHermesEnvMap(envPath)
if err != nil {
return err
}
for _, entry := range entries {
if entry.Key == "" || entry.Value == "" {
continue
}
envMap[entry.Key] = entry.Value
}
order := make([]string, 0, len(entries))
for _, entry := range entries {
if entry.Key == "" {
continue
}
order = append(order, entry.Key)
}
return writeHermesEnvMap(envPath, envMap, order)
}
func readHermesEnvMap(envPath string) (map[string]string, error) {
fileOp := files.NewFileOp()
if !fileOp.Stat(envPath) {
return map[string]string{}, nil
}
envMap, err := godotenv.Read(envPath)
if err != nil {
return nil, err
}
return envMap, nil
}
func writeHermesEnvMap(envPath string, envMap map[string]string, order []string) error {
if len(envMap) == 0 {
return files.NewFileOp().SaveFile(envPath, "", 0600)
}
return agentenv.WriteWithOrder(envMap, envPath, order)
}
func splitHermesEnvList(value string) []string {
if value == "" {
return []string{}
+13 -13
View File
@@ -34,7 +34,7 @@ print('Restart the Hermes-Agent container to apply the new Weixin settings.')
`
func readHermesQQBotChannelConfig(confDir string) (*dto.AgentQQBotConfig, error) {
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
if err != nil {
return nil, err
}
@@ -78,7 +78,7 @@ func readHermesQQBotChannelConfig(confDir string) (*dto.AgentQQBotConfig, error)
func writeHermesQQBotChannelConfig(confDir string, config dto.AgentQQBotConfig) error {
envPath := path.Join(confDir, ".env")
envMap, err := readHermesEnvMap(envPath)
envMap, err := readAgentEnvMap(envPath)
if err != nil {
return err
}
@@ -102,7 +102,7 @@ func writeHermesQQBotChannelConfig(confDir string, config dto.AgentQQBotConfig)
envMap["QQ_GROUP_ALLOWED_USERS"] = allow
}
}
if err := writeHermesEnvMap(envPath, envMap, []string{
if err := writeAgentEnvMap(envPath, envMap, []string{
"QQ_APP_ID",
"QQ_CLIENT_SECRET",
"QQ_ALLOW_ALL_USERS",
@@ -163,7 +163,7 @@ func deleteHermesQQBotChannelConfig(confDir string) error {
}
func readHermesWecomChannelConfig(confDir string) (*dto.AgentWecomConfig, error) {
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
if err != nil {
return nil, err
}
@@ -194,7 +194,7 @@ func readHermesWecomChannelConfig(confDir string) (*dto.AgentWecomConfig, error)
func writeHermesWecomChannelConfig(confDir string, config dto.AgentWecomConfig) error {
envPath := path.Join(confDir, ".env")
envMap, err := readHermesEnvMap(envPath)
envMap, err := readAgentEnvMap(envPath)
if err != nil {
return err
}
@@ -229,7 +229,7 @@ func writeHermesWecomChannelConfig(confDir string, config dto.AgentWecomConfig)
envMap["WECOM_GROUP_ALLOWED_USERS"] = allow
}
}
if err := writeHermesEnvMap(envPath, envMap, []string{
if err := writeAgentEnvMap(envPath, envMap, []string{
"WECOM_BOT_ID",
"WECOM_SECRET",
"WECOM_DM_POLICY",
@@ -282,7 +282,7 @@ func deleteHermesWecomChannelConfig(confDir string) error {
}
func readHermesDingTalkChannelConfig(confDir string) (*dto.AgentDingTalkConfig, error) {
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
if err != nil {
return nil, err
}
@@ -332,7 +332,7 @@ func readHermesDingTalkChannelConfig(confDir string) (*dto.AgentDingTalkConfig,
func writeHermesDingTalkChannelConfig(confDir string, config dto.AgentDingTalkConfig) error {
envPath := path.Join(confDir, ".env")
envMap, err := readHermesEnvMap(envPath)
envMap, err := readAgentEnvMap(envPath)
if err != nil {
return err
}
@@ -356,7 +356,7 @@ func writeHermesDingTalkChannelConfig(confDir string, config dto.AgentDingTalkCo
} else if config.DmPolicy == "open" {
envMap["DINGTALK_ALLOW_ALL_USERS"] = "true"
}
if err := writeHermesEnvMap(envPath, envMap, []string{
if err := writeAgentEnvMap(envPath, envMap, []string{
"DINGTALK_CLIENT_ID",
"DINGTALK_CLIENT_SECRET",
"DINGTALK_ALLOW_ALL_USERS",
@@ -398,7 +398,7 @@ func deleteHermesDingTalkChannelConfig(confDir string) error {
}
func readHermesFeishuChannelConfig(confDir string) (*dto.AgentFeishuConfig, error) {
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
if err != nil {
return nil, err
}
@@ -452,7 +452,7 @@ func readHermesFeishuChannelConfig(confDir string) (*dto.AgentFeishuConfig, erro
func writeHermesFeishuChannelConfig(confDir string, config dto.AgentFeishuConfig) error {
envPath := path.Join(confDir, ".env")
envMap, err := readHermesEnvMap(envPath)
envMap, err := readAgentEnvMap(envPath)
if err != nil {
return err
}
@@ -488,7 +488,7 @@ func writeHermesFeishuChannelConfig(confDir string, config dto.AgentFeishuConfig
envMap["FEISHU_ALLOWED_USERS"] = allow
}
}
if err := writeHermesEnvMap(envPath, envMap, []string{
if err := writeAgentEnvMap(envPath, envMap, []string{
"FEISHU_APP_ID",
"FEISHU_APP_SECRET",
"FEISHU_DOMAIN",
@@ -529,7 +529,7 @@ func deleteHermesFeishuChannelConfig(confDir string) error {
}
func readHermesWeixinChannelConfig(confDir string) (*dto.AgentWeixinConfig, error) {
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
if err != nil {
return nil, err
}
+50 -6
View File
@@ -20,8 +20,10 @@ import (
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/common"
agentenv "github.com/1Panel-dev/1Panel/agent/utils/env"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
"github.com/joho/godotenv"
"gorm.io/gorm"
)
@@ -376,6 +378,11 @@ func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map
item.BridgePort = toInt(bridge)
}
}
if agentType == constant.AppHermesAgent {
auth := readHermesDashboardAuthFromInstall(appInstall)
item.DashboardUsername = auth.Username
item.DashboardPassword = auth.Password
}
}
return item
}
@@ -860,13 +867,15 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
return err
}
envPath := path.Join(confDir, ".env")
lines := []string{fmt.Sprintf("OPENCLAW_GATEWAY_TOKEN=%s", token)}
if envKey := providercatalog.EnvKey(account.Provider); envKey != "" && account.APIKey != "" {
lines = append(lines, fmt.Sprintf("%s=%s", envKey, account.APIKey))
envMap := map[string]string{
"OPENCLAW_GATEWAY_TOKEN": token,
}
content := strings.Join(lines, "\n") + "\n"
return fileOp.SaveFile(envPath, content, 0600)
order := []string{"OPENCLAW_GATEWAY_TOKEN"}
if envKey := providercatalog.EnvKey(account.Provider); envKey != "" && account.APIKey != "" {
envMap[envKey] = account.APIKey
order = append(order, envKey)
}
return writeAgentEnvMap(path.Join(confDir, ".env"), envMap, order)
}
func resolveOpenclawFallbackModels(account *model.AgentAccount, primaryModel string, fallbackIDs []string) ([]string, error) {
@@ -1524,6 +1533,41 @@ func readInstallEnv(envStr string) map[string]interface{} {
return data
}
func readAgentEnvMap(envPath string) (map[string]string, error) {
fileOp := files.NewFileOp()
if !fileOp.Stat(envPath) {
return map[string]string{}, nil
}
envMap, err := godotenv.Read(envPath)
if err != nil {
return nil, err
}
return envMap, nil
}
func writeAgentEnvMap(envPath string, envMap map[string]string, order []string) error {
if len(envMap) == 0 {
return files.NewFileOp().SaveFile(envPath, "", 0600)
}
return agentenv.WriteWithOrder(envMap, envPath, order)
}
func upsertAgentEnv(envPath string, values map[string]string, order []string, overwrite bool) error {
envMap, err := readAgentEnvMap(envPath)
if err != nil {
return err
}
for key, value := range values {
if key == "" {
continue
}
if overwrite || strings.TrimSpace(envMap[key]) == "" {
envMap[key] = value
}
}
return writeAgentEnvMap(envPath, envMap, order)
}
func maskKey(value string) string {
trim := strings.TrimSpace(value)
if len(trim) <= 6 {
-1
View File
@@ -705,7 +705,6 @@ func sendAlertsByLegacyMethod(alert dto.AlertDTO, alertType, quota, quotaType st
}
config, err := alertRepo.GetConfig(alertRepo.WithByType(configType))
if err != nil {
global.LOG.Errorf("alert config not found for type %s: %v", configType, err)
return
}
doSendAlert(alert, alertType, quota, quotaType, params, config)
-1
View File
@@ -93,7 +93,6 @@ func (s *AlertSender) sendByLegacyMethod(method string, quota string, params []d
}
config, err := alertRepo.GetConfig(alertRepo.WithByType(configType))
if err != nil {
global.LOG.Errorf("alert config not found for type %s: %v", configType, err)
return
}
if !alertUtil.IsAlertConfigEnabled(config) {
+1
View File
@@ -294,6 +294,7 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
DetailID: req.DetailId,
Backup: req.Backup,
PullImage: req.PullImage,
DeleteImage: req.DeleteImage,
DockerCompose: req.DockerCompose,
TaskID: req.TaskID,
}
+170 -63
View File
@@ -89,7 +89,15 @@ func checkPort(key string, params map[string]interface{}) (int, error) {
return 0, nil
}
func isPortInUse(port int, protocol string) bool {
return common.ScanPortWithProto(port, normalizeComposeProtocol(protocol))
}
func checkPortExist(port int) error {
return checkPortExistWithProtocol(port, "")
}
func checkPortExistWithProtocol(port int, protocol string) error {
errMap := make(map[string]interface{})
errMap["port"] = port
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port))
@@ -110,7 +118,7 @@ func checkPortExist(port int) error {
errMap["name"] = domain.Domain
return buserr.WithMap("ErrPortExist", errMap, nil)
}
if common.ScanPort(port) {
if isPortInUse(port, protocol) {
return buserr.WithDetail("ErrPortInUsed", port, nil)
}
return nil
@@ -357,33 +365,13 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
return err
}
if deleteReq.DeleteImage {
delImageStr := i18n.GetMsgByKey("TaskDelete") + i18n.GetMsgByKey("Image")
content, err := op.GetContent(install.GetEnvPath())
if err != nil {
return err
}
images, err := docker.GetImagesFromDockerCompose(content, []byte(install.DockerCompose))
if err != nil {
if err = deleteAppImagesByCompose(t, content, []byte(install.DockerCompose), nil); err != nil {
return err
}
client, err := docker.NewClient()
if err != nil {
return err
}
defer client.Close()
for _, image := range images {
imageID, err := client.GetImageIDByName(image)
if err == nil {
imgStr := delImageStr + image
t.Log(imgStr)
if err = client.DeleteImage(imageID); err != nil {
t.LogFailedWithErr(imgStr, err)
continue
}
t.LogSuccess(delImageStr + image)
}
}
}
}
tx, ctx := helper.GetTxAndContext()
@@ -468,6 +456,69 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
return nil
}
type appImageID struct {
name string
id string
}
func getAppImageIDsByCompose(client docker.Client, envContent, composeContent []byte) ([]appImageID, error) {
images, err := docker.GetImagesFromDockerCompose(envContent, composeContent)
if err != nil {
return nil, err
}
imageIDs := make([]appImageID, 0, len(images))
for _, image := range images {
imageID, err := client.GetImageIDByName(image)
if err == nil && imageID != "" {
imageIDs = append(imageIDs, appImageID{name: image, id: imageID})
}
}
return imageIDs, nil
}
func deleteAppImagesByCompose(t *task.Task, envContent, composeContent []byte, excludeImages []string) error {
client, err := docker.NewClient()
if err != nil {
return err
}
defer client.Close()
imageIDs, err := getAppImageIDsByCompose(client, envContent, composeContent)
if err != nil {
return err
}
return deleteAppImagesByIDs(t, client, imageIDs, excludeImages)
}
func deleteAppImagesByIDs(t *task.Task, client docker.Client, imageIDs []appImageID, excludeImages []string) error {
delImageStr := i18n.GetMsgByKey("TaskDelete") + i18n.GetMsgByKey("Image")
excludeImageIDs := make(map[string]struct{}, len(excludeImages))
for _, image := range excludeImages {
imageID, err := client.GetImageIDByName(image)
if err == nil && imageID != "" {
excludeImageIDs[imageID] = struct{}{}
}
}
deletedImageIDs := make(map[string]struct{}, len(imageIDs))
for _, image := range imageIDs {
if _, ok := excludeImageIDs[image.id]; ok {
continue
}
if _, ok := deletedImageIDs[image.id]; ok {
continue
}
deletedImageIDs[image.id] = struct{}{}
imgStr := delImageStr + image.name
t.Log(imgStr)
if err := client.DeleteImage(image.id); err != nil {
t.LogFailedWithErr(imgStr, err)
continue
}
t.LogSuccess(imgStr)
}
return nil
}
func deleteLink(del dto.DelAppLink) error {
install := del.Install
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
@@ -751,6 +802,8 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
if err != nil {
return err
}
oldEnvContent := append([]byte(nil), content...)
oldDockerCompose := install.DockerCompose
if install.App.Key == vllmAppKeyForUpgrade {
envs := make(map[string]interface{})
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
@@ -765,33 +818,6 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
install.Env = string(paramByte)
content = setVllmImageInEnvContent(content, image)
}
if req.PullImage {
composeContent := []byte(detail.DockerCompose)
if install.App.Key == vllmAppKeyForUpgrade {
composeContent = []byte(install.DockerCompose)
}
if req.DockerCompose != "" {
composeContent = []byte(req.DockerCompose)
}
images, err := docker.GetImagesFromDockerCompose(content, composeContent)
if err != nil {
return err
}
dockerCLi, err := docker.NewClient()
if err != nil {
return err
}
defer dockerCLi.Close()
for _, image := range images {
t.Log(i18n.GetWithName("PullImageStart", image))
if err = dockerCLi.PullImageWithProcess(t, image); err != nil {
err = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
return err
}
t.LogSuccess(i18n.GetMsgByKey("PullImage"))
}
}
_ = copyAppDetailMissing(fileOp, detailDir, install.GetPath())
if install.App.Key == constant.AppOpenresty {
installBuildDir := path.Join(install.GetPath(), "build")
@@ -847,6 +873,46 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
install.Version = detail.Version
install.AppDetailId = req.DetailID
var oldImageIDs []appImageID
if req.DeleteImage {
dockerCLi, err := docker.NewClient()
if err != nil {
return err
}
oldImageIDs, err = getAppImageIDsByCompose(dockerCLi, oldEnvContent, []byte(oldDockerCompose))
dockerCLi.Close()
if err != nil {
return err
}
}
if req.PullImage {
images, err := docker.GetImagesFromDockerCompose(content, []byte(install.DockerCompose))
if err != nil {
return err
}
dockerCLi, err := docker.NewClient()
if err != nil {
return err
}
defer dockerCLi.Close()
for _, image := range images {
t.Log(i18n.GetWithName("PullImageStart", image))
if pullErr := dockerCLi.PullImageWithProcess(t, image); pullErr != nil {
if exist, _ := dockerCLi.ImageExists(image); exist {
t.Log(i18n.GetMsgByKey("UseExistImage"))
continue
}
return buserr.WithNameAndErr("ErrDockerPullImage", "", pullErr)
}
exist, err := dockerCLi.ImageExists(image)
if err != nil || !exist {
return buserr.WithNameAndErr("ErrDockerPullImage", "", fmt.Errorf("image %s does not exist after pull: %v", image, err))
}
t.LogSuccess(i18n.GetMsgByKey("PullImage"))
}
}
if out, err := compose.Down(install.GetComposePath()); err != nil {
if out != "" {
upErr = errors.New(out)
@@ -897,7 +963,31 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
}
t.LogSuccess(logStr)
install.Status = constant.StatusRunning
return appInstallRepo.Save(context.Background(), &install)
if err = appInstallRepo.Save(context.Background(), &install); err != nil {
return err
}
if req.DeleteImage {
newEnvContent, err := fileOp.GetContent(install.GetEnvPath())
if err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
return nil
}
excludeImages, err := docker.GetImagesFromDockerCompose(newEnvContent, []byte(install.DockerCompose))
if err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
return nil
}
dockerCLi, err := docker.NewClient()
if err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
return nil
}
defer dockerCLi.Close()
if err = deleteAppImagesByIDs(t, dockerCLi, oldImageIDs, excludeImages); err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
}
}
return nil
}
rollBackApp := func(t *task.Task) {
@@ -1624,6 +1714,9 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
synAppInstall(containersMap, &installed, false)
}
resourceKeys := getAppInstallResourceKeys(installed.ID)
envMap := make(map[string]interface{})
_ = json.Unmarshal([]byte(installed.Env), &envMap)
installDTO := response.AppInstallDTO{
ID: installed.ID,
Name: installed.Name,
@@ -1645,14 +1738,16 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
Website: installed.App.Website,
Document: installed.App.Document,
},
Favorite: installed.Favorite,
SortOrder: installed.SortOrder,
Container: installed.ContainerName,
ServiceName: strings.ToLower(installed.ServiceName),
Favorite: installed.Favorite,
SortOrder: installed.SortOrder,
Container: installed.ContainerName,
ServiceName: strings.ToLower(installed.ServiceName),
ResourceKeys: resourceKeys,
Env: envMap,
}
if !updated && !checkUpdate {
installDTO.LinkDB = hasLinkDB(installed.ID)
installDTO.LinkDB = hasLinkDBFromKeys(resourceKeys)
res = append(res, installDTO)
continue
}
@@ -1660,7 +1755,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
if installed.Version == "latest" {
if checkUpdate {
installDTO.CanUpdate = false
installDTO.LinkDB = hasLinkDB(installed.ID)
installDTO.LinkDB = hasLinkDBFromKeys(resourceKeys)
res = append(res, installDTO)
}
continue
@@ -1689,7 +1784,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
if len(versions) == 0 {
if checkUpdate {
installDTO.CanUpdate = false
installDTO.LinkDB = hasLinkDB(installed.ID)
installDTO.LinkDB = hasLinkDBFromKeys(resourceKeys)
res = append(res, installDTO)
}
continue
@@ -1721,7 +1816,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
res = append(res, installDTO)
}
} else if checkUpdate {
installDTO.LinkDB = hasLinkDB(installed.ID)
installDTO.LinkDB = hasLinkDBFromKeys(resourceKeys)
res = append(res, installDTO)
}
}
@@ -2288,13 +2383,25 @@ func needsUpdate(localTag *model.Tag, remoteTag dto.Tag, translations string) bo
}
func hasLinkDB(installID uint) bool {
return hasLinkDBFromKeys(getAppInstallResourceKeys(installID))
}
func getAppInstallResourceKeys(installID uint) []string {
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(installID))
keys := make([]string, 0, len(resources))
for _, resource := range resources {
keys = append(keys, resource.Key)
}
return keys
}
func hasLinkDBFromKeys(resourceKeys []string) bool {
hasDB := false
if len(resources) > 0 {
for _, resource := range resources {
if resource.Key == constant.AppPostgres || resource.Key == constant.AppMysql ||
resource.Key == constant.AppMariaDB || resource.Key == constant.AppMysqlCluster ||
resource.Key == constant.AppPostgresql || resource.Key == constant.AppPostgresqlCluster {
if len(resourceKeys) > 0 {
for _, resourceKey := range resourceKeys {
if resourceKey == constant.AppPostgres || resourceKey == constant.AppMysql ||
resourceKey == constant.AppMariaDB || resourceKey == constant.AppMysqlCluster ||
resourceKey == constant.AppPostgresql || resourceKey == constant.AppPostgresqlCluster {
hasDB = true
break
}
+22 -3
View File
@@ -63,11 +63,30 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er
return nil, err
}
if err = handleAppBackup(&install, nil, record.ID, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {
global.LOG.Errorf("backup app %s failed, err: %v", req.DetailName, err)
return nil, err
if !req.IsImmediate {
if err = handleAppBackup(&install, nil, record.ID, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
global.LOG.Errorf("backup app %s failed, err: %v", req.DetailName, err)
return nil, err
}
return record, nil
}
backupTask, err := task.NewTaskWithOps(install.Name, task.TaskBackup, task.TaskScopeBackup, req.TaskID, install.ID)
if err != nil {
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
record.Status = constant.StatusFailed
record.Message = err.Error()
return nil, err
}
if err = doAppBackup(&install, backupTask, backupDir, fileName, "", req.Secret); err != nil {
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
record.Status = constant.StatusFailed
record.Message = err.Error()
return nil, err
}
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusSuccess})
record.Status = constant.StatusSuccess
return record, nil
}
-5
View File
@@ -782,11 +782,6 @@ func buildContainerRecoverNetworkConfig(networkSettings *container.NetworkSettin
IPv4Address: endpoint.IPAMConfig.IPv4Address,
IPv6Address: endpoint.IPAMConfig.IPv6Address,
}
} else if endpoint.IPAddress != "" || endpoint.GlobalIPv6Address != "" {
endpointSetting.IPAMConfig = &network.EndpointIPAMConfig{
IPv4Address: endpoint.IPAddress,
IPv6Address: endpoint.GlobalIPv6Address,
}
}
if name == primaryName {
config.EndpointsConfig[name] = endpointSetting
+58 -31
View File
@@ -15,6 +15,7 @@ import (
"os/exec"
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
@@ -32,7 +33,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/re"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/build"
"github.com/docker/docker/api/types/container"
@@ -53,6 +53,8 @@ import (
type ContainerService struct{}
var containerLogAnsiRegex = regexp.MustCompile("\x1b\\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>")
type IContainerService interface {
Page(req dto.PageContainer) (int64, interface{}, error)
List() []dto.ContainerOptions
@@ -1036,6 +1038,7 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
if cmd.CheckIllegal(container, since, tail) {
return buserr.New("ErrCmdIllegal")
}
ctx := c.Request.Context()
commandArg := []string{"logs", container}
dockerCommand := global.CONF.DockerConfig.Command
if containerType == "compose" {
@@ -1065,10 +1068,11 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
}
var dockerCmd *exec.Cmd
if containerType == "compose" && dockerCommand == "docker-compose" {
dockerCmd = exec.Command("docker-compose", commandArg...)
dockerCmd = exec.CommandContext(ctx, "docker-compose", commandArg...)
} else {
dockerCmd = exec.Command("docker", commandArg...)
dockerCmd = exec.CommandContext(ctx, "docker", commandArg...)
}
dockerCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
stdout, err := dockerCmd.StdoutPipe()
if err != nil {
return err
@@ -1077,15 +1081,20 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
if err := dockerCmd.Start(); err != nil {
return err
}
defer func() {
if dockerCmd.Process != nil {
_ = dockerCmd.Process.Kill()
_ = dockerCmd.Wait()
done := make(chan struct{})
go func() {
select {
case <-ctx.Done():
killContainerLogProcess(dockerCmd)
case <-done:
}
}()
defer close(done)
tempFile, err := os.CreateTemp("", "cmd_output_*.txt")
if err != nil {
killContainerLogProcess(dockerCmd)
_ = dockerCmd.Wait()
return err
}
defer tempFile.Close()
@@ -1094,31 +1103,19 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
global.LOG.Errorf("os.Remove() failed: %v", err)
}
}()
errCh := make(chan error, 1)
go func() {
scanner := bufio.NewScanner(stdout)
var ansiRegex = re.GetRegex(re.AnsiEscapePattern)
for scanner.Scan() {
line := scanner.Text()
cleanLine := ansiRegex.ReplaceAllString(line, "")
if _, err := tempFile.WriteString(cleanLine + "\n"); err != nil {
errCh <- err
return
}
copyErr := copyContainerLogOutput(tempFile, stdout)
waitErr := dockerCmd.Wait()
if copyErr != nil {
return copyErr
}
if waitErr != nil {
if ctx.Err() != nil {
return ctx.Err()
}
if err := scanner.Err(); err != nil {
errCh <- err
return
}
errCh <- nil
}()
select {
case err := <-errCh:
if err != nil {
global.LOG.Errorf("Error: %v", err)
}
case <-time.After(40 * time.Second):
global.LOG.Errorf("Download container logs timeout reached")
return waitErr
}
if _, err := tempFile.Seek(0, io.SeekStart); err != nil {
return err
}
info, _ := tempFile.Stat()
@@ -1128,6 +1125,36 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
return nil
}
func copyContainerLogOutput(dst io.Writer, src io.Reader) error {
reader := bufio.NewReader(src)
for {
line, err := reader.ReadString('\n')
if len(line) > 0 {
cleanLine := containerLogAnsiRegex.ReplaceAllString(line, "")
if _, writeErr := io.WriteString(dst, cleanLine); writeErr != nil {
return writeErr
}
}
if err != nil {
if err == io.EOF {
return nil
}
return err
}
}
}
func killContainerLogProcess(command *exec.Cmd) {
if command == nil || command.Process == nil {
return
}
if pgid, err := syscall.Getpgid(command.Process.Pid); err == nil {
_ = syscall.Kill(-pgid, syscall.SIGKILL)
return
}
_ = command.Process.Kill()
}
func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) {
client, err := docker.NewDockerClient()
if err != nil {
+4
View File
@@ -270,6 +270,10 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
if stdout, err := compose.Up(req.Path); err != nil {
return fmt.Errorf("docker-compose up failed, std: %s, err: %v", stdout, err)
}
} else if req.Operation == "rebuild" {
if stdout, err := compose.DownAndUp(req.Path); err != nil {
return fmt.Errorf("docker-compose rebuild failed, std: %s, err: %v", stdout, err)
}
} else {
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
return fmt.Errorf("docker-compose %s failed, std: %s, err: %v", req.Operation, stdout, err)
+10
View File
@@ -187,6 +187,7 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
hostInfo, _ := psutil.HOST.GetHostInfo(false)
currentInfo.Uptime = hostInfo.Uptime
currentInfo.TimeSinceUptime = time.Unix(int64(hostInfo.BootTime), 0).Format(constant.DateTimeLayout)
currentInfo.RunningTime = loadRunningTime(hostInfo.Uptime)
currentInfo.Procs = hostInfo.Procs
currentInfo.CPUTotal, _ = psutil.CPUInfo.GetLogicalCores(false)
@@ -267,6 +268,15 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
return &currentInfo
}
func loadRunningTime(uptime uint64) dto.RunningTime {
return dto.RunningTime{
Days: uptime / 86400,
Hours: (uptime % 86400) / 3600,
Minutes: (uptime % 3600) / 60,
Seconds: uptime % 60,
}
}
func (u *DashboardService) LoadTopCPU() []dto.Process {
return loadTopCPU()
}
@@ -0,0 +1,84 @@
package service
import (
"fmt"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
)
func composePortEnvKeys(index int) (containerPort, hostPort, hostIP, protocol string) {
return fmt.Sprintf("CONTAINER_PORT_%d", index),
fmt.Sprintf("HOST_PORT_%d", index),
fmt.Sprintf("HOST_IP_%d", index),
fmt.Sprintf("PORT_PROTOCOL_%d", index)
}
func isComposePortEnvKey(key string) bool {
return strings.HasPrefix(key, "CONTAINER_PORT_") ||
strings.HasPrefix(key, "HOST_PORT_") ||
strings.HasPrefix(key, "HOST_IP_") ||
strings.HasPrefix(key, "PORT_PROTOCOL_")
}
func formatComposePortMapping(hostIP, hostPort, containerPort, protocol string) string {
return fmt.Sprintf("${%s}:${%s}:${%s}/%s", hostIP, hostPort, containerPort, normalizeComposeProtocol(protocol))
}
func normalizeComposeProtocol(protocol string) string {
switch strings.ToLower(strings.TrimSpace(protocol)) {
case "udp":
return "udp"
default:
return "tcp"
}
}
func formatComposeVolume(source, target, mode string) string {
return fmt.Sprintf("%s:%s:%s", source, target, normalizeComposeVolumeMode(mode))
}
func normalizeComposeVolumeMode(mode string) string {
switch strings.ToLower(strings.TrimSpace(mode)) {
case "ro":
return "ro"
default:
return "rw"
}
}
func loadComposeExposedPortsFromEnv(envs map[string]string, defaultHostIP string, strict bool) ([]request.ExposedPort, error) {
var ports []request.ExposedPort
for key, value := range envs {
if !strings.HasPrefix(key, "CONTAINER_PORT_") {
continue
}
index := strings.TrimPrefix(key, "CONTAINER_PORT_")
containerPort, err := strconv.Atoi(value)
if err != nil {
if strict {
return nil, err
}
continue
}
hostPort, err := strconv.Atoi(envs["HOST_PORT_"+index])
if err != nil {
if strict {
return nil, err
}
continue
}
hostIP := envs["HOST_IP_"+index]
if hostIP == "" {
hostIP = defaultHostIP
}
ports = append(ports, request.ExposedPort{
ContainerPort: containerPort,
HostPort: hostPort,
HostIP: hostIP,
Protocol: normalizeComposeProtocol(envs["PORT_PROTOCOL_"+index]),
})
}
return ports, nil
}
+1 -1
View File
@@ -1116,7 +1116,7 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
}
logFilePath = path.Join(global.Dir.LogDir, "ai", safeName)
default:
return nil, buserr.New("ErrNotSupportType")
return nil, buserr.WithName("ErrNotSupportType", req.Type)
}
file, err := os.Open(logFilePath)
+22 -1
View File
@@ -222,7 +222,7 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
req.Chain = iptables.Chain1PanelBasic
}
protos := strings.Split(req.Protocol, "/")
itemAddress := strings.Split(strings.TrimSuffix(req.Address, ","), ",")
itemAddress := splitFirewallRuleAddresses(req.Address)
if client.Name() == "ufw" {
if strings.Contains(req.Port, ",") || strings.Contains(req.Port, "-") {
@@ -515,6 +515,7 @@ func (u *FirewallService) operatePort(client firewall.FirewallClient, req dto.Po
if err := copier.Copy(&fireInfo, &req); err != nil {
return err
}
fireInfo.Address = normalizeFirewallRuleAddress(fireInfo.Address)
if client.Name() == "ufw" {
if len(fireInfo.Address) != 0 && !strings.EqualFold(fireInfo.Address, "Anywhere") {
@@ -529,6 +530,26 @@ func (u *FirewallService) operatePort(client firewall.FirewallClient, req dto.Po
return client.Port(fireInfo, req.Operation)
}
func splitFirewallRuleAddresses(address string) []string {
parts := strings.Split(strings.TrimSuffix(address, ","), ",")
addresses := make([]string, 0, len(parts))
for _, part := range parts {
addresses = append(addresses, normalizeFirewallRuleAddress(part))
}
if len(addresses) == 0 {
return []string{""}
}
return addresses
}
func normalizeFirewallRuleAddress(address string) string {
address = strings.TrimSpace(address)
if strings.EqualFold(address, "Anywhere") {
return ""
}
return address
}
type portOfApp struct {
AppName string
HttpPort string
+1 -1
View File
@@ -502,7 +502,7 @@ func (u *ImageService) ImageRemove(req dto.BatchDelete) error {
return err
}
defer client.Close()
taskItem, err := task.NewTaskWithOps(task.TaskScopeImage, task.TaskDelete, task.TaskScopeContainer, req.TaskID, 1)
taskItem, err := task.NewTaskWithOps(strings.Join(req.Names, ","), task.TaskDelete, task.TaskScopeImage, req.TaskID, 1)
if err != nil {
global.LOG.Errorf("new task for create container failed, err: %v", err)
return err
+367 -66
View File
@@ -1,18 +1,24 @@
package service
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"path"
"strconv"
"strings"
"time"
"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/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/cmd/server/ai"
"github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
@@ -25,18 +31,30 @@ import (
"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/docker/docker/api/types/container"
"github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
)
type McpServerService struct{}
const (
defaultMcpGatewayImageNpx = "supercorp/supergateway:3.4.3"
defaultMcpGatewayImageUvx = "supercorp/supergateway:uvx"
defaultMcpProtocolVersion = "2025-06-18"
mcpOutputTransportSSE = "sse"
mcpOutputTransportStreamableHTTP = "streamableHttp"
)
type IMcpServerService interface {
Page(req request.McpServerSearch) response.McpServersRes
Detail(req request.McpServerDetail) (response.McpServerDTO, error)
Create(create request.McpServerCreate) error
Update(req request.McpServerUpdate) error
Delete(id uint) error
Operate(req request.McpServerOperate) error
SyncStatus(req request.McpServerStatusSync) ([]response.McpServerStatusDTO, error)
TestConnection(req request.McpServerConnectionTest) (response.McpServerConnectionTestRes, error)
GetBindDomain() (response.McpBindDomainRes, error)
BindDomain(req request.McpBindDomain) error
UpdateBindDomain(req request.McpBindDomainUpdate) error
@@ -54,44 +72,36 @@ func (m McpServerService) Page(req request.McpServerSearch) response.McpServersR
total, data, _ := mcpServerRepo.Page(req.PageInfo.Page, req.PageInfo.PageSize)
for _, item := range data {
_ = syncMcpServerContainerStatus(&item)
serverDTO := response.McpServerDTO{
normalizeMcpServerGateway(&item)
items = append(items, response.McpServerDTO{
McpServer: item,
Environments: make([]request.Environment, 0),
Volumes: make([]request.Volume, 0),
}
project, err := docker.GetComposeProject(item.Name, path.Join(global.Dir.McpDir, item.Name), []byte(item.DockerCompose), []byte(item.Env), true)
if err != nil {
global.LOG.Errorf("get mcp compose project error: %s", err.Error())
continue
}
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) Detail(req request.McpServerDetail) (response.McpServerDTO, error) {
mcpServer, err := mcpServerRepo.GetFirst(repo.WithByID(req.ID))
if err != nil {
return response.McpServerDTO{}, err
}
normalizeMcpServerGateway(mcpServer)
serverDTO := response.McpServerDTO{
McpServer: *mcpServer,
Environments: make([]request.Environment, 0),
Volumes: make([]request.Volume, 0),
}
if err := loadMcpServerComposeConfig(&serverDTO); err != nil {
return response.McpServerDTO{}, err
}
return serverDTO, nil
}
func (m McpServerService) Update(req request.McpServerUpdate) error {
go pullImage(req.Type)
mcpServer, err := mcpServerRepo.GetFirst(repo.WithByID(req.ID))
if err != nil {
return err
@@ -115,7 +125,10 @@ func (m McpServerService) Update(req request.McpServerUpdate) error {
mcpServer.HostIP = req.HostIP
mcpServer.OutputTransport = req.OutputTransport
mcpServer.Type = req.Type
if req.OutputTransport == "sse" {
mcpServer.GatewayImage = req.GatewayImage
mcpServer.ProtocolVersion = req.ProtocolVersion
normalizeMcpServerGateway(mcpServer)
if req.OutputTransport == mcpOutputTransportSSE {
mcpServer.SsePath = req.SsePath
} else {
mcpServer.StreamableHttpPath = req.StreamableHttpPath
@@ -137,12 +150,11 @@ func (m McpServerService) Update(req request.McpServerUpdate) error {
if err := mcpServerRepo.Save(mcpServer); err != nil {
return err
}
go startMcp(mcpServer)
go runMcpServerTask(mcpServer, req.TaskID, task.TaskUpdate)
return nil
}
func (m McpServerService) Create(create request.McpServerCreate) error {
go pullImage(create.Type)
servers, _ := mcpServerRepo.List()
for _, server := range servers {
if server.Port == create.Port {
@@ -177,8 +189,11 @@ func (m McpServerService) Create(create request.McpServerCreate) error {
HostIP: create.HostIP,
OutputTransport: create.OutputTransport,
Type: create.Type,
GatewayImage: create.GatewayImage,
ProtocolVersion: create.ProtocolVersion,
}
if create.OutputTransport == "sse" {
normalizeMcpServerGateway(mcpServer)
if create.OutputTransport == mcpOutputTransportSSE {
mcpServer.SsePath = create.SsePath
} else {
mcpServer.StreamableHttpPath = create.StreamableHttpPath
@@ -205,7 +220,7 @@ func (m McpServerService) Create(create request.McpServerCreate) error {
return err
}
addProxy(mcpServer)
go startMcp(mcpServer)
go runMcpServerTask(mcpServer, create.TaskID, task.TaskCreate)
return nil
}
@@ -235,6 +250,9 @@ func (m McpServerService) Operate(req request.McpServerOperate) error {
if err != nil {
return err
}
if err := refreshMcpServerFiles(mcpServer); err != nil {
return err
}
composePath := path.Join(mcpServer.Dir, "docker-compose.yml")
var out string
switch req.Operate {
@@ -255,6 +273,148 @@ func (m McpServerService) Operate(req request.McpServerOperate) error {
return mcpServerRepo.Save(mcpServer)
}
func (m McpServerService) SyncStatus(req request.McpServerStatusSync) ([]response.McpServerStatusDTO, error) {
var (
servers []model.McpServer
err error
)
if len(req.IDs) > 0 {
servers, err = mcpServerRepo.List(repo.WithByIDs(req.IDs))
} else {
servers, err = mcpServerRepo.List()
}
if err != nil {
return nil, err
}
if len(servers) == 0 {
return []response.McpServerStatusDTO{}, nil
}
containerNames := make([]string, 0, len(servers))
for _, server := range servers {
if server.ContainerName != "" {
containerNames = append(containerNames, server.ContainerName)
}
}
containerMap := make(map[string]*container.Summary)
if len(containerNames) > 0 {
cli, err := docker.NewClient()
if err != nil {
return nil, err
}
defer cli.Close()
containers, err := cli.ListContainersByName(containerNames)
if err != nil {
return nil, err
}
for i := range containers {
for _, name := range containers[i].Names {
containerMap[strings.TrimPrefix(name, "/")] = &containers[i]
}
}
}
res := make([]response.McpServerStatusDTO, 0, len(servers))
for i := range servers {
oldStatus := servers[i].Status
applyMcpContainerStatus(&servers[i], containerMap[servers[i].ContainerName])
if servers[i].Status != oldStatus {
if err := mcpServerRepo.Save(&servers[i]); err != nil {
return nil, err
}
}
res = append(res, response.McpServerStatusDTO{
ID: servers[i].ID,
Status: servers[i].Status,
Message: servers[i].Message,
})
}
return res, nil
}
func (m McpServerService) TestConnection(req request.McpServerConnectionTest) (response.McpServerConnectionTestRes, error) {
mcpServer, err := mcpServerRepo.GetFirst(repo.WithByID(req.ID))
if err != nil {
return response.McpServerConnectionTestRes{}, err
}
normalizeMcpServerGateway(mcpServer)
endpoint := buildMcpEndpoint(mcpServer)
res := response.McpServerConnectionTestRes{
Endpoint: endpoint,
OutputTransport: mcpServer.OutputTransport,
ProtocolVersion: mcpServer.ProtocolVersion,
}
if endpoint == "" {
res.Message = "empty endpoint"
return res, nil
}
client := &http.Client{Timeout: 10 * time.Second}
if mcpServer.OutputTransport == mcpOutputTransportSSE {
httpReq, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
res.Message = err.Error()
return res, nil
}
httpRes, err := client.Do(httpReq)
if err != nil {
res.Message = err.Error()
return res, nil
}
defer httpRes.Body.Close()
contentType := httpRes.Header.Get("Content-Type")
if httpRes.StatusCode >= 200 && httpRes.StatusCode < 300 && strings.Contains(contentType, "text/event-stream") {
res.Success = true
res.Message = "ok"
return res, nil
}
res.Message = fmt.Sprintf("unexpected response: status=%d content-type=%s", httpRes.StatusCode, contentType)
return res, nil
}
payload := map[string]interface{}{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": map[string]interface{}{
"protocolVersion": mcpServer.ProtocolVersion,
"capabilities": map[string]interface{}{},
"clientInfo": map[string]string{
"name": "1Panel",
"version": "1.0.0",
},
},
}
body, _ := json.Marshal(payload)
httpReq, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(body))
if err != nil {
res.Message = err.Error()
return res, nil
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Accept", "application/json, text/event-stream")
httpRes, err := client.Do(httpReq)
if err != nil {
res.Message = err.Error()
return res, nil
}
defer httpRes.Body.Close()
res.Success, res.Message = handleMcpConnectionHTTPResponse(httpRes)
return res, nil
}
func handleMcpConnectionHTTPResponse(httpRes *http.Response) (bool, string) {
if httpRes.StatusCode >= 200 && httpRes.StatusCode < 300 {
return true, "ok"
}
respBody, _ := io.ReadAll(httpRes.Body)
message := strings.TrimSpace(string(respBody))
if message == "" {
message = httpRes.Status
}
return false, message
}
func (m McpServerService) GetBindDomain() (response.McpBindDomainRes, error) {
var res response.McpBindDomainRes
websiteID := GetWebsiteID()
@@ -534,11 +694,9 @@ func addMCPProxy(websiteID uint) error {
}
func updateMcpServer(mcpServer *model.McpServer) error {
env := handleEnv(mcpServer)
if err := gotenv.Write(env, path.Join(mcpServer.Dir, ".env")); err != nil {
if err := refreshMcpServerFiles(mcpServer); 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 {
@@ -548,6 +706,106 @@ func updateMcpServer(mcpServer *model.McpServer) error {
return mcpServerRepo.Save(mcpServer)
}
func refreshMcpServerFiles(mcpServer *model.McpServer) error {
normalizeMcpServerGateway(mcpServer)
if err := handleCreateParams(mcpServer, nil, nil); err != nil {
return err
}
env := handleEnv(mcpServer)
if err := gotenv.Write(env, path.Join(mcpServer.Dir, ".env")); err != nil {
return err
}
composePath := path.Join(mcpServer.Dir, "docker-compose.yml")
if err := files.NewFileOp().SaveFile(composePath, mcpServer.DockerCompose, 0644); err != nil {
return err
}
return mcpServerRepo.Save(mcpServer)
}
func normalizeMcpServerGateway(mcpServer *model.McpServer) {
if mcpServer.Type == "" {
mcpServer.Type = "npx"
}
if strings.TrimSpace(mcpServer.GatewayImage) == "" {
if mcpServer.Type == "uvx" {
mcpServer.GatewayImage = defaultMcpGatewayImageUvx
} else {
mcpServer.GatewayImage = defaultMcpGatewayImageNpx
}
}
if strings.TrimSpace(mcpServer.ProtocolVersion) == "" {
mcpServer.ProtocolVersion = defaultMcpProtocolVersion
}
}
func buildSupergatewayCommand(mcpServer *model.McpServer) []string {
normalizeMcpServerGateway(mcpServer)
command := []string{
"--stdio", mcpServer.Command,
"--outputTransport", mcpServer.OutputTransport,
"--port", strconv.Itoa(mcpServer.Port),
}
if mcpServer.OutputTransport == mcpOutputTransportSSE {
return append(command,
"--baseUrl", mcpServer.BaseURL,
"--ssePath", mcpServer.SsePath,
"--messagePath", fmt.Sprintf("%s/messages", mcpServer.SsePath),
)
}
command = append(command, "--streamableHttpPath", mcpServer.StreamableHttpPath)
return append(command, "--protocolVersion", mcpServer.ProtocolVersion)
}
func buildMcpEndpoint(mcpServer *model.McpServer) string {
if mcpServer.BaseURL == "" {
return ""
}
if mcpServer.OutputTransport == mcpOutputTransportSSE {
return strings.TrimRight(mcpServer.BaseURL, "/") + "/" + strings.TrimLeft(mcpServer.SsePath, "/")
}
return strings.TrimRight(mcpServer.BaseURL, "/") + "/" + strings.TrimLeft(mcpServer.StreamableHttpPath, "/")
}
func loadMcpServerComposeConfig(serverDTO *response.McpServerDTO) error {
serverDTO.Environments = make([]request.Environment, 0)
serverDTO.Volumes = make([]request.Volume, 0)
dir := serverDTO.Dir
if dir == "" {
dir = path.Join(global.Dir.McpDir, serverDTO.Name)
}
project, err := docker.GetComposeProject(serverDTO.Name, dir, []byte(serverDTO.DockerCompose), []byte(serverDTO.Env), true)
if err != nil {
return err
}
for _, service := range project.Services {
if service.Environment != nil {
for key, value := range service.Environment {
if value == nil {
continue
}
serverDTO.Environments = append(serverDTO.Environments, request.Environment{
Key: key,
Value: *value,
})
}
}
if service.Volumes != nil {
for _, volume := range service.Volumes {
mode := "rw"
if volume.ReadOnly {
mode = "ro"
}
serverDTO.Volumes = append(serverDTO.Volumes, request.Volume{
Source: volume.Source,
Target: volume.Target,
Mode: mode,
})
}
}
}
return nil
}
func handleEnv(mcpServer *model.McpServer) gotenv.Env {
env := make(gotenv.Env)
env["CONTAINER_NAME"] = mcpServer.ContainerName
@@ -558,6 +816,7 @@ func handleEnv(mcpServer *model.McpServer) gotenv.Env {
env["HOST_IP"] = mcpServer.HostIP
env["STREAMABLE_HTTP_PATH"] = mcpServer.StreamableHttpPath
env["OUTPUT_TRANSPORT"] = mcpServer.OutputTransport
env["GATEWAY_IMAGE"] = mcpServer.GatewayImage
envStr, _ := gotenv.Marshal(env)
mcpServer.Env = envStr
return env
@@ -592,26 +851,39 @@ func handleCreateParams(mcpServer *model.McpServer, environments []request.Envir
}
delete(services, serviceName)
}
normalizeMcpServerGateway(mcpServer)
serviceValue["command"] = buildSupergatewayCommand(mcpServer)
serviceValue["image"] = mcpServer.GatewayImage
serviceValue["ports"] = []string{
formatComposePortMapping("HOST_IP", "PANEL_APP_PORT_HTTP", "PANEL_APP_PORT_HTTP", ""),
}
oldEnv, hasOldEnv := serviceValue["environment"]
delete(serviceValue, "environment")
if len(environments) > 0 {
if environments != nil && len(environments) > 0 {
envMap := make(map[string]string)
for _, env := range environments {
envMap[env.Key] = env.Value
}
serviceValue["environment"] = envMap
} else if environments == nil {
// Preserve existing custom environment when regenerating compose during start/restart.
if mcpServer.ID > 0 && hasOldEnv {
serviceValue["environment"] = oldEnv
}
}
oldVolumes, hasOldVolumes := serviceValue["volumes"]
delete(serviceValue, "volumes")
if len(volumes) > 0 {
if volumes != nil && len(volumes) > 0 {
volumeList := make([]string, 0)
for _, volume := range volumes {
volumeList = append(volumeList, fmt.Sprintf("%s:%s", volume.Source, volume.Target))
volumeList = append(volumeList, formatComposeVolume(volume.Source, volume.Target, volume.Mode))
}
serviceValue["volumes"] = volumeList
}
if mcpServer.Type == "npx" {
serviceValue["image"] = "supercorp/supergateway:latest"
} else {
serviceValue["image"] = "supercorp/supergateway:uvx"
} else if volumes == nil {
// Preserve existing custom volumes when regenerating compose during start/restart.
if mcpServer.ID > 0 && hasOldVolumes {
serviceValue["volumes"] = oldVolumes
}
}
services[mcpServer.Name] = serviceValue
@@ -623,19 +895,56 @@ func handleCreateParams(mcpServer *model.McpServer, environments []request.Envir
return nil
}
func runMcpServerTask(mcpServer *model.McpServer, taskID, operate string) {
mcpTask, err := task.NewTaskWithOps(mcpServer.Name, operate, task.TaskScopeAI, taskID, mcpServer.ID)
if err != nil {
mcpServer.Status = constant.StatusError
mcpServer.Message = err.Error()
_ = mcpServerRepo.Save(mcpServer)
return
}
mcpTask.AddSubTask(task.GetTaskName(mcpServer.Name, operate, task.TaskScopeAI), func(t *task.Task) error {
return startMcpWithTask(mcpServer, t)
}, nil)
_ = mcpTask.Execute()
}
func startMcp(mcpServer *model.McpServer) {
_ = startMcpWithTask(mcpServer, nil)
}
func startMcpWithTask(mcpServer *model.McpServer, taskItem *task.Task) error {
if err := refreshMcpServerFiles(mcpServer); err != nil {
mcpServer.Status = constant.StatusError
mcpServer.Message = err.Error()
_ = mcpServerRepo.Save(mcpServer)
return err
}
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 {
var err error
var out string
if taskItem != nil {
err = compose.UpWithTask(composePath, taskItem, false)
} else {
out, err = compose.Up(composePath)
}
if err != nil {
mcpServer.Status = constant.StatusError
mcpServer.Message = out
if out != "" {
mcpServer.Message = out
} else {
mcpServer.Message = err.Error()
}
_ = mcpServerRepo.Save(mcpServer)
return err
} else {
mcpServer.Status = constant.StatusRunning
mcpServer.Message = ""
}
_ = syncMcpServerContainerStatus(mcpServer)
return syncMcpServerContainerStatus(mcpServer)
}
func syncMcpServerContainerStatus(mcpServer *model.McpServer) error {
@@ -650,11 +959,19 @@ func syncMcpServerContainerStatus(mcpServer *model.McpServer) error {
return err
}
if len(containers) == 0 {
mcpServer.Status = constant.StatusStopped
applyMcpContainerStatus(mcpServer, nil)
return mcpServerRepo.Save(mcpServer)
}
container := containers[0]
switch container.State {
applyMcpContainerStatus(mcpServer, &containers[0])
return mcpServerRepo.Save(mcpServer)
}
func applyMcpContainerStatus(mcpServer *model.McpServer, containerSummary *container.Summary) {
if containerSummary == nil {
mcpServer.Status = constant.StatusStopped
return
}
switch containerSummary.State {
case "exited":
mcpServer.Status = constant.StatusError
case "running":
@@ -668,7 +985,6 @@ func syncMcpServerContainerStatus(mcpServer *model.McpServer) error {
mcpServer.Status = constant.StatusStopped
}
}
return mcpServerRepo.Save(mcpServer)
}
func GetWebsiteID() uint {
@@ -679,18 +995,3 @@ func GetWebsiteID() uint {
websiteIDUint, _ := strconv.ParseUint(websiteID.Value, 10, 64)
return uint(websiteIDUint)
}
func pullImage(imageType string) {
if global.CONF.Base.IsOffline {
return
}
if imageType == "npx" {
if err := docker.PullImage("supercorp/supergateway:latest"); err != nil {
global.LOG.Errorf("docker pull mcp image error: %s", err.Error())
}
} else {
if err := docker.PullImage("supercorp/supergateway:uvx"); err != nil {
global.LOG.Errorf("docker pull mcp image error: %s", err.Error())
}
}
}
+294 -74
View File
@@ -29,6 +29,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
@@ -82,6 +83,47 @@ func NewRuntimeService() IRuntimeService {
return &RuntimeService{}
}
var pullRuntimeComposeImages = compose.PullComposeImages
var downRuntimeCompose = compose.Down
type runtimeTaskMeta struct {
resourceName string
operate string
scope string
taskID string
resourceID uint
}
func buildRuntimeCreateTaskMeta(create request.RuntimeCreate, runtimeID uint) runtimeTaskMeta {
return runtimeTaskMeta{
resourceName: create.Name,
operate: task.TaskCreate,
scope: task.TaskScopeRuntime,
taskID: create.TaskID,
resourceID: runtimeID,
}
}
func buildRuntimeDeleteTaskMeta(deleteReq request.RuntimeDelete, runtime *model.Runtime) runtimeTaskMeta {
return runtimeTaskMeta{
resourceName: runtime.Name,
operate: task.TaskDelete,
scope: task.TaskScopeRuntime,
taskID: deleteReq.TaskID,
resourceID: deleteReq.ID,
}
}
func runtimeCreateInitialStatus(create request.RuntimeCreate) string {
if create.Type == constant.RuntimePHP {
if create.Resource == constant.ResourceLocal {
return constant.StatusNormal
}
return constant.StatusBuilding
}
return constant.StatusCreating
}
func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, error) {
var (
opts []repo.DBOption
@@ -104,20 +146,29 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
return nil, err
}
}
if create.Type == constant.RuntimePHP && create.Resource == constant.ResourceLocal {
runtime := &model.Runtime{
Name: create.Name,
Resource: create.Resource,
Type: create.Type,
Version: create.Version,
Status: runtimeCreateInitialStatus(create),
Remark: create.Remark,
}
if err := runtimeRepo.Create(context.Background(), runtime); err != nil {
return nil, err
}
if err := startRuntimeCreateTask(create, runtime, model.App{}, model.AppDetail{}, ""); err != nil {
runtime.Status = constant.StatusError
runtime.Message = err.Error()
_ = runtimeRepo.Save(runtime)
return nil, err
}
return runtime, nil
}
var hostPorts []string
switch create.Type {
case constant.RuntimePHP:
if create.Resource == constant.ResourceLocal {
runtime := &model.Runtime{
Name: create.Name,
Resource: create.Resource,
Type: create.Type,
Version: create.Version,
Status: constant.StatusNormal,
Remark: create.Remark,
}
return nil, runtimeRepo.Create(context.Background(), runtime)
}
exist, _ = runtimeRepo.GetFirst(context.Background(), runtimeRepo.WithImage(create.Image))
if exist != nil {
return nil, buserr.New("ErrImageExist")
@@ -134,10 +185,9 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
if !fileOp.Stat(create.CodeDir) {
return nil, buserr.New("ErrPathNotFound")
}
create.Install = true
for _, export := range create.ExposedPorts {
hostPorts = append(hostPorts, strconv.Itoa(export.HostPort))
if err := checkPortExist(export.HostPort); err != nil {
if err := checkPortExistWithProtocol(export.HostPort, export.Protocol); err != nil {
return nil, err
}
}
@@ -160,11 +210,6 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
}
appVersionDir := filepath.Join(app.GetAppResourcePath(), appDetail.Version)
if !fileOp.Stat(appVersionDir) {
if err = downloadApp(app, appDetail, nil, nil); err != nil {
return nil, err
}
}
runtime := &model.Runtime{
Name: create.Name,
@@ -176,24 +221,92 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
ContainerName: containerName.(string),
Port: strings.Join(hostPorts, ","),
Remark: create.Remark,
}
switch create.Type {
case constant.RuntimePHP:
if err = handlePHP(create, runtime, fileOp, appVersionDir); err != nil {
return nil, err
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
if err = handleRuntime(create, runtime, fileOp, appVersionDir); err != nil {
return nil, err
}
Status: runtimeCreateInitialStatus(create),
}
if err := runtimeRepo.Create(context.Background(), runtime); err != nil {
return nil, err
}
if err := startRuntimeCreateTask(create, runtime, app, appDetail, appVersionDir); err != nil {
runtime.Status = constant.StatusError
runtime.Message = err.Error()
_ = runtimeRepo.Save(runtime)
return nil, err
}
return runtime, nil
}
func startRuntimeCreateTask(create request.RuntimeCreate, runtime *model.Runtime, app model.App, appDetail model.AppDetail, appVersionDir string) error {
meta := buildRuntimeCreateTaskMeta(create, runtime.ID)
createTask, err := task.NewTaskWithOps(meta.resourceName, meta.operate, meta.scope, meta.taskID, meta.resourceID)
if err != nil {
return err
}
createTask.AddSubTask(task.GetTaskName(create.Name, task.TaskCreate, task.TaskScopeRuntime), func(t *task.Task) error {
return executeRuntimeCreateTask(create, runtime, app, appDetail, appVersionDir, t)
}, nil)
go func() {
_ = createTask.Execute()
}()
return nil
}
func executeRuntimeCreateTask(create request.RuntimeCreate, runtime *model.Runtime, app model.App, appDetail model.AppDetail, appVersionDir string, taskItem *task.Task) error {
if create.Type == constant.RuntimePHP && create.Resource == constant.ResourceLocal {
runtime.Status = constant.StatusNormal
runtime.Message = ""
return runtimeRepo.Save(runtime)
}
if err := downloadApp(app, appDetail, nil, taskItem.Logger); err != nil {
return markRuntimeCreateFailed(runtime, err)
}
go func() {
RequestDownloadCallBack(appDetail.DownloadCallBackUrl)
}()
fileOp := files.NewFileOp()
var err error
switch create.Type {
case constant.RuntimePHP:
err = handlePHP(create, runtime, fileOp, appVersionDir)
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
err = handleRuntime(create, runtime, fileOp, appVersionDir)
}
if err != nil {
return markRuntimeCreateFailed(runtime, err)
}
if err = runtimeRepo.Save(runtime); err != nil {
return err
}
switch create.Type {
case constant.RuntimePHP:
return buildRuntimeWithResult(runtime, "", "", false)
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
if err := pullRuntimeImagesBeforeStart(create, runtime, taskItem); err != nil {
return markRuntimeCreateFailed(runtime, err)
}
return startRuntimeWithResult(runtime)
}
return nil
}
func pullRuntimeImagesBeforeStart(create request.RuntimeCreate, runtime *model.Runtime, taskItem *task.Task) error {
switch create.Type {
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
return pullRuntimeComposeImages(runtime.GetComposePath(), false, taskItem)
default:
return nil
}
}
func markRuntimeCreateFailed(runtime *model.Runtime, err error) error {
runtime.Status = constant.StatusError
runtime.Message = err.Error()
_ = runtimeRepo.Save(runtime)
return err
}
func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.RuntimeDTO, error) {
var (
opts []repo.DBOption
@@ -241,30 +354,11 @@ func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.Runt
runtimeDTO.AppID = detail.AppId
}
for k, v := range envs {
runtimeDTO.Params[k] = v
if strings.Contains(k, "CONTAINER_PORT") || strings.Contains(k, "HOST_PORT") {
if strings.Contains(k, "CONTAINER_PORT") {
matches := re.GetRegex(re.TrailingDigitsPattern).FindStringSubmatch(k)
if len(matches) < 2 {
continue
}
containerPort, err := strconv.Atoi(v)
if err != nil {
continue
}
hostPort, err := strconv.Atoi(envs[fmt.Sprintf("HOST_PORT_%s", matches[1])])
if err != nil {
continue
}
hostIP := envs[fmt.Sprintf("HOST_IP_%s", matches[1])]
runtimeDTO.ExposedPorts = append(runtimeDTO.ExposedPorts, request.ExposedPort{
ContainerPort: containerPort,
HostPort: hostPort,
HostIP: hostIP,
})
}
if !isComposePortEnvKey(k) {
runtimeDTO.Params[k] = v
}
}
runtimeDTO.ExposedPorts, _ = loadComposeExposedPortsFromEnv(envs, "", false)
res = append(res, runtimeDTO)
}
return total, res, nil
@@ -291,31 +385,37 @@ func (r *RuntimeService) Delete(runtimeDelete request.RuntimeDelete) error {
if website.ID > 0 {
return buserr.New("ErrDelWithWebsite")
}
return startRuntimeDeleteTask(runtimeDelete, runtime)
}
func startRuntimeDeleteTask(runtimeDelete request.RuntimeDelete, runtime *model.Runtime) error {
meta := buildRuntimeDeleteTaskMeta(runtimeDelete, runtime)
deleteTask, err := task.NewTaskWithOps(meta.resourceName, meta.operate, meta.scope, meta.taskID, meta.resourceID)
if err != nil {
return err
}
deleteTask.AddSubTask(task.GetTaskName(runtime.Name, task.TaskDelete, task.TaskScopeRuntime), func(t *task.Task) error {
return executeRuntimeDeleteTask(runtimeDelete, runtime, t)
}, nil)
go func() {
_ = deleteTask.Execute()
}()
return nil
}
func executeRuntimeDeleteTask(runtimeDelete request.RuntimeDelete, runtime *model.Runtime, taskItem *task.Task) error {
if runtime.Resource != constant.ResourceAppstore {
if runtimeDelete.DeleteImage {
deleteRuntimeImages(runtime, taskItem)
}
return runtimeRepo.DeleteBy(repo.WithByID(runtimeDelete.ID))
}
projectDir := runtime.GetPath()
if out, err := compose.Down(runtime.GetComposePath()); err != nil && !runtimeDelete.ForceDelete {
if out != "" {
return errors.New(out)
}
if err := stopRuntimeBeforeDelete(runtime, runtimeDelete.ForceDelete, taskItem); err != nil {
return err
}
if runtime.Type == constant.RuntimePHP {
client, err := docker.NewClient()
if err != nil {
return err
}
defer client.Close()
imageID, err := client.GetImageIDByName(runtime.Image)
if err != nil {
return err
}
if imageID != "" {
if err := client.DeleteImage(imageID); err != nil {
global.LOG.Errorf("delete image id [%s] error %v", imageID, err)
}
}
if runtimeDelete.DeleteImage {
deleteRuntimeImages(runtime, taskItem)
}
if err := files.NewFileOp().DeleteDir(projectDir); err != nil && !runtimeDelete.ForceDelete {
return err
@@ -323,6 +423,121 @@ func (r *RuntimeService) Delete(runtimeDelete request.RuntimeDelete) error {
return runtimeRepo.DeleteBy(repo.WithByID(runtimeDelete.ID))
}
func getRuntimeStopLog() string {
logStr := i18n.GetMsgByKey("Stop") + i18n.GetMsgByKey("Runtime")
if strings.TrimSpace(logStr) == "" {
return "StopRuntime"
}
return logStr
}
func stopRuntimeBeforeDelete(runtime *model.Runtime, forceDelete bool, taskItem *task.Task) error {
logStr := getRuntimeStopLog()
if taskItem != nil {
taskItem.Log(logStr)
}
out, err := downRuntimeCompose(runtime.GetComposePath())
if err != nil && !forceDelete {
if out != "" {
err = errors.New(out)
}
if taskItem != nil {
taskItem.LogFailedWithErr(logStr, err)
}
return err
}
if taskItem != nil {
taskItem.LogSuccess(logStr)
}
return nil
}
func getRuntimeDeleteImages(runtime *model.Runtime) ([]string, error) {
var images []string
imageMap := make(map[string]struct{})
appendImage := func(image string) {
image = strings.TrimSpace(image)
if image == "" {
return
}
if _, ok := imageMap[image]; ok {
return
}
imageMap[image] = struct{}{}
images = append(images, image)
}
appendImage(runtime.Image)
if runtime.DockerCompose != "" {
composeImages, err := docker.GetImagesFromDockerCompose([]byte(runtime.Env), []byte(runtime.DockerCompose))
if err != nil {
return nil, err
}
for _, image := range composeImages {
appendImage(image)
}
}
return images, nil
}
func deleteRuntimeImages(runtime *model.Runtime, taskItem *task.Task) {
logPrefix := i18n.GetMsgByKey("TaskDelete") + i18n.GetMsgByKey("Image")
images, err := getRuntimeDeleteImages(runtime)
if err != nil {
global.LOG.Errorf("get runtime [%s] delete images error %v", runtime.Name, err)
if taskItem != nil {
taskItem.LogFailedWithErr(logPrefix, err)
}
return
}
if len(images) == 0 {
if taskItem != nil {
taskItem.LogFailedWithErr(logPrefix, errors.New(i18n.GetWithName("ErrImageNotExist", runtime.Name)))
}
return
}
client, err := docker.NewClient()
if err != nil {
global.LOG.Errorf("delete runtime images [%s] error %v", runtime.Name, err)
if taskItem != nil {
taskItem.LogFailedWithErr(logPrefix, err)
}
return
}
defer client.Close()
for _, imageName := range images {
logStr := logPrefix + imageName
if taskItem != nil {
taskItem.Log(logStr)
}
imageID, err := client.GetImageIDByName(imageName)
if err != nil {
global.LOG.Errorf("get runtime image [%s] error %v", imageName, err)
if taskItem != nil {
taskItem.LogFailedWithErr(logStr, err)
}
continue
}
if imageID == "" {
if taskItem != nil {
taskItem.LogFailedWithErr(logStr, errors.New(i18n.GetWithName("ErrImageNotExist", imageName)))
}
continue
}
if err := client.DeleteImage(imageID); err != nil {
global.LOG.Errorf("delete image id [%s] error %v", imageID, err)
if taskItem != nil {
taskItem.LogFailedWithErr(logStr, err)
}
continue
}
if taskItem != nil {
taskItem.LogSuccess(logStr)
}
}
}
func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) {
runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(id))
if err != nil {
@@ -435,9 +650,11 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
return buserr.New("ErrImageExist")
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
ownedPortKeys := runtimeOwnedPortKeys(runtime.Env)
for _, export := range req.ExposedPorts {
hostPorts = append(hostPorts, strconv.Itoa(export.HostPort))
if err = checkRuntimePortExist(export.HostPort, false, runtime.ID); err != nil {
_, owned := ownedPortKeys[composePortCheckKey(export.HostPort, export.Protocol)]
if err = checkRuntimePortExistWithProtocol(export.HostPort, export.Protocol, !owned, runtime.ID); err != nil {
return err
}
}
@@ -478,7 +695,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
Version: req.Version,
Remark: req.Remark,
NodeConfig: request.NodeConfig{
Install: true,
Install: req.Install,
ExposedPorts: req.ExposedPorts,
Environments: req.Environments,
Volumes: req.Volumes,
@@ -1058,6 +1275,7 @@ func (r *RuntimeService) UpdatePHPContainer(req request.PHPContainerConfig) erro
var (
composeContent []byte
)
ownedPortKeys := runtimeOwnedPortKeys(runtime.Env)
for _, export := range req.ExposedPorts {
if strconv.Itoa(export.HostPort) == runtime.Port {
return buserr.WithName("ErrPHPRuntimePortFailed", strconv.Itoa(export.HostPort))
@@ -1065,7 +1283,8 @@ func (r *RuntimeService) UpdatePHPContainer(req request.PHPContainerConfig) erro
if export.ContainerPort == 9000 {
return buserr.New("ErrPHPPortIsDefault")
}
if err = checkRuntimePortExist(export.HostPort, false, runtime.ID); err != nil {
_, owned := ownedPortKeys[composePortCheckKey(export.HostPort, export.Protocol)]
if err = checkRuntimePortExistWithProtocol(export.HostPort, export.Protocol, !owned, runtime.ID); err != nil {
return err
}
}
@@ -1090,7 +1309,7 @@ func (r *RuntimeService) UpdatePHPContainer(req request.PHPContainerConfig) erro
return err
}
for k := range envs {
if strings.HasPrefix(k, "CONTAINER_PORT_") || strings.HasPrefix(k, "HOST_PORT_") || strings.HasPrefix(k, "HOST_IP_") || strings.Contains(k, "APP_PORT") {
if isComposePortEnvKey(k) || strings.Contains(k, "APP_PORT") {
delete(envs, k)
}
}
@@ -1102,6 +1321,7 @@ func (r *RuntimeService) UpdatePHPContainer(req request.PHPContainerConfig) erro
ExposedPorts: req.ExposedPorts,
Environments: req.Environments,
Volumes: req.Volumes,
ExtraHosts: req.ExtraHosts,
},
}
composeContent, err = handleCompose(envs, composeContent, create, projectDir)
+79 -68
View File
@@ -19,7 +19,6 @@ import (
cmd2 "github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
@@ -33,7 +32,6 @@ import (
"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/re"
"github.com/pkg/errors"
"github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
@@ -67,20 +65,13 @@ func handleRuntime(create request.RuntimeCreate, runtime *model.Runtime, fileOp
runtime.Env = string(envContent)
runtime.Status = constant.StatusCreating
runtime.CodeDir = create.CodeDir
nodeDetail, err := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID))
if err != nil {
return err
}
go func() {
RequestDownloadCallBack(nodeDetail.DownloadCallBackUrl)
}()
go startRuntime(runtime)
return
}
func runtimeNodeShouldInstallDependencies(create request.RuntimeCreate) bool {
return create.NodeConfig.Install == nil || *create.NodeConfig.Install
}
func isPathInsideOrEqual(baseDir, targetDir string) bool {
baseAbs := resolveRuntimePath(baseDir)
targetAbs := resolveRuntimePath(targetDir)
@@ -131,32 +122,28 @@ func handlePHP(create request.RuntimeCreate, runtime *model.Runtime, fileOp file
runtime.Env = string(envContent)
runtime.Params = string(forms)
runtime.Status = constant.StatusBuilding
go func() {
appDetail, err := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID))
if err == nil {
RequestDownloadCallBack(appDetail.DownloadCallBackUrl)
}
}()
go buildRuntime(runtime, "", "", false)
return
}
func startRuntime(runtime *model.Runtime) {
_ = startRuntimeWithResult(runtime)
}
func startRuntimeWithResult(runtime *model.Runtime) error {
if err := runComposeCmdWithLog("up", runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
runtime.Status = constant.StatusError
runtime.Message = err.Error()
_ = runtimeRepo.Save(runtime)
return
return err
}
if err := SyncRuntimeContainerStatus(runtime); err != nil {
runtime.Status = constant.StatusError
runtime.Message = err.Error()
_ = runtimeRepo.Save(runtime)
return
return err
}
return nil
}
func reCreateRuntime(runtime *model.Runtime) {
@@ -328,6 +315,10 @@ func deleteImageByID(oldImageID, imageName string, client docker.Client) {
}
func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebuild bool) {
_ = buildRuntimeWithResult(runtime, oldImageID, oldEnv, rebuild)
}
func buildRuntimeWithResult(runtime *model.Runtime, oldImageID string, oldEnv string, rebuild bool) error {
runtimePath := runtime.GetPath()
composePath := runtime.GetComposePath()
logPath := path.Join(runtimePath, "build.log")
@@ -335,11 +326,17 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebu
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.FilePerm)
if err != nil {
global.LOG.Errorf("failed to open log file: %v", err)
return
runtime.Status = constant.StatusError
runtime.Message = err.Error()
_ = runtimeRepo.Save(runtime)
return err
}
defer func() {
_ = logFile.Close()
}()
defer func() {
_ = runtimeRepo.Save(runtime)
}()
newPHPVersion := getRuntimeEnv(runtime.Env, "PHP_VERSION")
oldPHPVersion := getRuntimeEnv(oldEnv, "PHP_VERSION")
@@ -371,16 +368,19 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebu
} else {
runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + stderrBuf.String()
}
_ = runtimeRepo.Save(runtime)
return
return err
}
if err = runComposeCmdWithLog(constant.RuntimeDown, runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
return
runtime.Status = constant.StatusError
runtime.Message = err.Error()
return err
}
client, err := docker.NewClient()
if err != nil {
_, _ = logFile.WriteString(fmt.Sprintf("failed to connect to docker client: %v", err))
return
runtime.Status = constant.StatusError
runtime.Message = err.Error()
return err
}
runtime.Message = ""
if rebuild && runtime.ID > 0 {
@@ -406,14 +406,10 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebu
}
}
defer func() {
_ = runtimeRepo.Save(runtime)
}()
if out, err := compose.Up(composePath); err != nil {
runtime.Status = constant.StatusStartErr
runtime.Message = out
return
return err
}
deleteImageID := ""
extensions := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS")
@@ -423,14 +419,14 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebu
if err = cmdMgr.Run("docker", "exec", "-i", runtime.ContainerName, "install-ext", extensions); err != nil {
runtime.Status = constant.StatusError
runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + err.Error()
return
return err
}
commitMgr := cmd2.NewCommandMgr(cmd2.WithTimeout(10*time.Minute), cmd2.WithOutputFile(logPath))
err = commitMgr.Run("docker", "commit", runtime.ContainerName, runtime.Image)
if err != nil {
runtime.Status = constant.StatusError
runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + err.Error()
return
return err
}
}
if oldImageID != "" {
@@ -443,10 +439,10 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebu
if out, err := compose.DownAndUp(composePath); err != nil {
runtime.Status = constant.StatusStartErr
runtime.Message = out
return
return err
}
runtime.Status = constant.StatusRunning
_ = runtimeRepo.Save(runtime)
return nil
}
func handleParams(create request.RuntimeCreate, projectDir string) (composeContent []byte, envContent []byte, forms []byte, err error) {
@@ -464,7 +460,7 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
return
}
for k := range env {
if strings.HasPrefix(k, "CONTAINER_PORT_") || strings.HasPrefix(k, "HOST_PORT_") || strings.HasPrefix(k, "HOST_IP_") || strings.Contains(k, "APP_PORT") {
if isComposePortEnvKey(k) || strings.Contains(k, "APP_PORT") {
delete(env, k)
}
}
@@ -510,7 +506,7 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
case constant.RuntimeNode:
create.Params["CODE_DIR"] = create.CodeDir
create.Params["NODE_VERSION"] = create.Version
if create.NodeConfig.Install {
if runtimeNodeShouldInstallDependencies(create) {
create.Params["RUN_INSTALL"] = "1"
} else {
create.Params["RUN_INSTALL"] = "0"
@@ -621,15 +617,16 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
if len(create.ExposedPorts) > 0 {
var ports []interface{}
for i, port := range create.ExposedPorts {
containerPortStr := fmt.Sprintf("CONTAINER_PORT_%d", i)
hostPortStr := fmt.Sprintf("HOST_PORT_%d", i)
containerPortStr, hostPortStr, hostIPStr, protocolStr := composePortEnvKeys(i)
existMap[containerPortStr] = struct{}{}
existMap[hostPortStr] = struct{}{}
hostIPStr := fmt.Sprintf("HOST_IP_%d", i)
ports = append(ports, fmt.Sprintf("${%s}:${%s}:${%s}", hostIPStr, hostPortStr, containerPortStr))
existMap[hostIPStr] = struct{}{}
existMap[protocolStr] = struct{}{}
ports = append(ports, formatComposePortMapping(hostIPStr, hostPortStr, containerPortStr, port.Protocol))
create.Params[containerPortStr] = port.ContainerPort
create.Params[hostPortStr] = port.HostPort
create.Params[hostIPStr] = port.HostIP
create.Params[protocolStr] = normalizeComposeProtocol(port.Protocol)
}
if create.Type == constant.RuntimePHP {
ports = append(ports, "127.0.0.1:${PANEL_APP_PORT_HTTP}:9000")
@@ -662,7 +659,7 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
volumes = append(volumes, fmt.Sprintf("%s:%s", k, v))
}
for _, volume := range create.Volumes {
volumes = append(volumes, fmt.Sprintf("%s:%s", volume.Source, volume.Target))
volumes = append(volumes, formatComposeVolume(volume.Source, volume.Target, volume.Mode))
}
var extraHosts []interface{}
@@ -678,7 +675,7 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
break
}
for k := range env {
if strings.Contains(k, "CONTAINER_PORT_") || strings.Contains(k, "HOST_PORT_") {
if isComposePortEnvKey(k) {
if _, ok := existMap[k]; !ok {
delete(env, k)
}
@@ -872,9 +869,14 @@ func getDockerComposeVolumes(yml []byte) ([]request.Volume, error) {
if len(envArray) > 1 {
target = envArray[1]
}
mode := ""
if len(envArray) > 2 {
mode = envArray[2]
}
res = append(res, request.Volume{
Source: source,
Target: target,
Mode: normalizeComposeVolumeMode(mode),
})
}
}
@@ -908,7 +910,31 @@ func getDockerComposeExtraHosts(yml []byte) ([]request.ExtraHost, error) {
return res, nil
}
func composePortCheckKey(port int, protocol string) string {
return fmt.Sprintf("%d/%s", port, normalizeComposeProtocol(protocol))
}
func runtimeOwnedPortKeys(envStr string) map[string]struct{} {
portKeys := make(map[string]struct{})
envs, err := gotenv.Unmarshal(envStr)
if err != nil {
return portKeys
}
exposedPorts, err := loadComposeExposedPortsFromEnv(envs, "", false)
if err != nil {
return portKeys
}
for _, port := range exposedPorts {
portKeys[composePortCheckKey(port.HostPort, port.Protocol)] = struct{}{}
}
return portKeys
}
func checkRuntimePortExist(port int, scanPort bool, runtimeID uint) error {
return checkRuntimePortExistWithProtocol(port, "", scanPort, runtimeID)
}
func checkRuntimePortExistWithProtocol(port int, protocol string, scanPort bool, runtimeID uint) error {
errMap := make(map[string]interface{})
errMap["port"] = port
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port))
@@ -933,7 +959,7 @@ func checkRuntimePortExist(port int, scanPort bool, runtimeID uint) error {
errMap["name"] = domain.Domain
return buserr.WithMap("ErrPortExist", errMap, nil)
}
if scanPort && common.ScanPort(port) {
if scanPort && isPortInUse(port, protocol) {
return buserr.WithDetail("ErrPortInUsed", port, nil)
}
return nil
@@ -1006,31 +1032,16 @@ func handleRuntimeDTO(res *response.RuntimeDTO, runtime model.Runtime) error {
return err
}
for k, v := range envs {
if strings.Contains(k, "CONTAINER_PORT") || strings.Contains(k, "HOST_PORT") {
if strings.Contains(k, "CONTAINER_PORT") {
matches := re.GetRegex(re.TrailingDigitsPattern).FindStringSubmatch(k)
if len(matches) < 2 {
return fmt.Errorf("invalid container port key: %s", k)
}
containerPort, err := strconv.Atoi(v)
if err != nil {
return err
}
hostPort, err := strconv.Atoi(envs[fmt.Sprintf("HOST_PORT_%s", matches[1])])
if err != nil {
return err
}
hostIP := envs[fmt.Sprintf("HOST_IP_%s", matches[1])]
res.ExposedPorts = append(res.ExposedPorts, request.ExposedPort{
ContainerPort: containerPort,
HostPort: hostPort,
HostIP: hostIP,
})
}
if isComposePortEnvKey(k) {
continue
} else {
res.Params[k] = v
}
}
res.ExposedPorts, err = loadComposeExposedPortsFromEnv(envs, "", true)
if err != nil {
return err
}
if v, ok := envs["CONTAINER_PACKAGE_URL"]; ok {
res.Source = v
}
+8 -38
View File
@@ -23,7 +23,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/env"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/1Panel-dev/1Panel/agent/utils/re"
)
type TensorRTLLMService struct{}
@@ -59,33 +58,7 @@ func (t TensorRTLLMService) Page(req request.TensorRTLLMSearch) response.TensorR
serverDTO.Image = envs["IMAGE"]
serverDTO.Command = getCommand(item.Env)
for k, v := range envs {
if strings.Contains(k, "CONTAINER_PORT") || strings.Contains(k, "HOST_PORT") {
if strings.Contains(k, "CONTAINER_PORT") {
matches := re.GetRegex(re.TrailingDigitsPattern).FindStringSubmatch(k)
if len(matches) < 2 {
continue
}
containerPort, err := strconv.Atoi(v)
if err != nil {
continue
}
hostPort, err := strconv.Atoi(envs[fmt.Sprintf("HOST_PORT_%s", matches[1])])
if err != nil {
continue
}
hostIP := envs[fmt.Sprintf("HOST_IP_%s", matches[1])]
if hostIP == "" {
hostIP = "0.0.0.0"
}
serverDTO.ExposedPorts = append(serverDTO.ExposedPorts, request.ExposedPort{
ContainerPort: containerPort,
HostPort: hostPort,
HostIP: hostIP,
})
}
}
}
serverDTO.ExposedPorts, _ = loadComposeExposedPortsFromEnv(envs, "0.0.0.0", false)
composeByte, err := files.NewFileOp().GetContent(path.Join(global.Dir.TensorRTLLMDir, item.Name, "docker-compose.yml"))
if err == nil {
@@ -149,11 +122,9 @@ func handleLLMParams(llm *model.TensorRTLLM, create request.TensorRTLLMCreate) e
delete(serviceValue, "ports")
if len(create.ExposedPorts) > 0 {
var ports []interface{}
for i := range create.ExposedPorts {
containerPortStr := fmt.Sprintf("CONTAINER_PORT_%d", i)
hostPortStr := fmt.Sprintf("HOST_PORT_%d", i)
hostIPStr := fmt.Sprintf("HOST_IP_%d", i)
ports = append(ports, fmt.Sprintf("${%s}:${%s}:${%s}", hostIPStr, hostPortStr, containerPortStr))
for i, port := range create.ExposedPorts {
containerPortStr, hostPortStr, hostIPStr, _ := composePortEnvKeys(i)
ports = append(ports, formatComposePortMapping(hostIPStr, hostPortStr, containerPortStr, port.Protocol))
}
serviceValue["ports"] = ports
}
@@ -174,7 +145,7 @@ func handleLLMParams(llm *model.TensorRTLLM, create request.TensorRTLLMCreate) e
volumes = append(volumes, fmt.Sprintf("%s:%s", k, v))
}
for _, volume := range create.Volumes {
volumes = append(volumes, fmt.Sprintf("%s:%s", volume.Source, volume.Target))
volumes = append(volumes, formatComposeVolume(volume.Source, volume.Target, volume.Mode))
}
serviceValue["volumes"] = volumes
@@ -195,12 +166,11 @@ func handleLLMEnv(llm *model.TensorRTLLM, create request.TensorRTLLMCreate) gote
envMap["IMAGE"] = create.Image
envMap["COMMAND"] = create.Command
for i, port := range create.ExposedPorts {
containerPortStr := fmt.Sprintf("CONTAINER_PORT_%d", i)
hostPortStr := fmt.Sprintf("HOST_PORT_%d", i)
hostIPStr := fmt.Sprintf("HOST_IP_%d", i)
containerPortStr, hostPortStr, hostIPStr, protocolStr := composePortEnvKeys(i)
envMap[containerPortStr] = strconv.Itoa(port.ContainerPort)
envMap[hostPortStr] = strconv.Itoa(port.HostPort)
envMap[hostIPStr] = port.HostIP
envMap[protocolStr] = normalizeComposeProtocol(port.Protocol)
}
orders := []string{"MODEL_PATH", "COMMAND"}
envStr, _ := env.MarshalWithOrder(envMap, orders)
@@ -219,7 +189,7 @@ func (t TensorRTLLMService) Create(create request.TensorRTLLMCreate) error {
}
}
for _, export := range create.ExposedPorts {
if err := checkPortExist(export.HostPort); err != nil {
if err := checkPortExistWithProtocol(export.HostPort, export.Protocol); err != nil {
return err
}
}
+16 -3
View File
@@ -12,6 +12,7 @@ const (
vllmImageEnvKey = "IMAGE"
vllmImageTypeNvidia = "nvidia"
vllmImageTypeIntel = "intel"
vllmImageTypeAscend = "ascend"
)
func resolveVllmVersionFamily(version, image string) string {
@@ -19,6 +20,9 @@ func resolveVllmVersionFamily(version, image string) string {
if strings.HasPrefix(normalizedVersion, vllmImageTypeIntel+"-") {
return vllmImageTypeIntel
}
if strings.HasPrefix(normalizedVersion, vllmImageTypeAscend+"-") {
return vllmImageTypeAscend
}
if strings.HasPrefix(normalizedVersion, vllmImageTypeNvidia+"-") {
return vllmImageTypeNvidia
}
@@ -26,13 +30,16 @@ func resolveVllmVersionFamily(version, image string) string {
if strings.Contains(normalizedImage, "intel/") || strings.Contains(normalizedImage, "llm-scaler-vllm") {
return vllmImageTypeIntel
}
if strings.Contains(normalizedImage, "ascend/") || strings.Contains(normalizedImage, "vllm-ascend") {
return vllmImageTypeAscend
}
return vllmImageTypeNvidia
}
func trimVllmVersionFamily(version string) string {
trimmed := strings.TrimSpace(version)
normalized := strings.ToLower(trimmed)
for _, family := range []string{vllmImageTypeNvidia, vllmImageTypeIntel} {
for _, family := range []string{vllmImageTypeNvidia, vllmImageTypeIntel, vllmImageTypeAscend} {
prefix := family + "-"
if strings.HasPrefix(normalized, prefix) {
return strings.TrimSpace(trimmed[len(prefix):])
@@ -43,12 +50,16 @@ func trimVllmVersionFamily(version string) string {
func buildDefaultVllmImageByVersion(version string) string {
tag := trimVllmVersionFamily(version)
if resolveVllmVersionFamily(version, "") == vllmImageTypeIntel {
family := resolveVllmVersionFamily(version, "")
if family == vllmImageTypeIntel {
return "intel/llm-scaler-vllm:" + tag
}
if tag != "" && !strings.HasPrefix(strings.ToLower(tag), "v") {
tag = "v" + tag
}
if family == vllmImageTypeAscend {
return "quay.io/ascend/vllm-ascend:" + tag
}
return "vllm/vllm-openai:" + tag
}
@@ -60,7 +71,9 @@ func isVllmUpgradeVersionAllowed(currentVersion, targetVersion, currentImage str
func hasVllmVersionFamilyPrefix(version string) bool {
normalized := strings.ToLower(strings.TrimSpace(version))
return strings.HasPrefix(normalized, vllmImageTypeNvidia+"-") || strings.HasPrefix(normalized, vllmImageTypeIntel+"-")
return strings.HasPrefix(normalized, vllmImageTypeNvidia+"-") ||
strings.HasPrefix(normalized, vllmImageTypeIntel+"-") ||
strings.HasPrefix(normalized, vllmImageTypeAscend+"-")
}
func isVllmUpgradeCandidate(currentVersion, targetVersion, currentImage string) bool {
+2 -4
View File
@@ -168,15 +168,13 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
opts = append(opts, repo.WithOrderRuleBy(req.OrderBy, req.Order), repo.WithOrderRuleBy("updated_at", "descending"))
if req.Name != "" {
domains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithDomainLike(req.Name))
var websiteIds []uint
if len(domains) > 0 {
var websiteIds []uint
for _, domain := range domains {
websiteIds = append(websiteIds, domain.WebsiteID)
}
opts = append(opts, repo.WithByIDs(websiteIds))
} else {
opts = append(opts, websiteRepo.WithDomainLike(req.Name))
}
opts = append(opts, websiteRepo.WithSearchKeyword(req.Name, websiteIds))
}
if req.WebsiteGroupID != 0 {
opts = append(opts, websiteRepo.WithGroupID(req.WebsiteGroupID))
+6 -2
View File
@@ -59,8 +59,12 @@ func (w WebsiteAcmeAccountService) Create(create request.WebsiteAcmeAccountCreat
acmeAccount.CaDirURL = create.CaDirURL
}
client, err := ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy))
if err != nil {
var client *ssl.AcmeClient
if err := withLegoLogger(nil, func() error {
var err error
client, err = ssl.NewRegisterClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy))
return err
}); err != nil {
return nil, err
}
privateKey, err := ssl.GetPrivateKey(client.User.GetPrivateKey(), ssl.KeyType(create.KeyType))
+6
View File
@@ -207,6 +207,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
Description: req.Description,
ExecShell: req.ExecShell,
}
setSSLPushConfig(websiteSSL, req.PushNode, req.Nodes)
if req.ExecShell {
websiteSSL.Shell = req.Shell
}
@@ -384,6 +385,11 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
}
}
reloadSystemSSL(websiteSSL, logger)
if websiteSSL.PushNode {
if err = pushSSLToNode(websiteSSL, logger); err != nil {
return nil, err
}
}
return websiteSSL, nil
}
+285 -82
View File
@@ -4,6 +4,7 @@ import (
"context"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"log"
@@ -12,16 +13,17 @@ import (
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/go-acme/lego/v5/certificate"
legoLogger "github.com/go-acme/lego/v5/log"
"github.com/jinzhu/gorm"
"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/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
@@ -32,11 +34,66 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
"github.com/1Panel-dev/1Panel/agent/utils/ssl"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
gormv2 "gorm.io/gorm"
)
type WebsiteSSLService struct {
}
const sslObtainTimeout = time.Hour
var legoLogMu sync.Mutex
func withLegoLogger(logger *log.Logger, fn func() error) error {
if logger == nil {
logger = log.New(io.Discard, "", log.LstdFlags)
}
legoLogMu.Lock()
defer legoLogMu.Unlock()
oldLogger := legoLogger.Default()
legoLogger.SetDefault(slog.New(slog.NewTextHandler(logger.Writer(), nil)))
defer legoLogger.SetDefault(oldLogger)
return fn()
}
func withLegoLoggerTimeout(logger *log.Logger, timeout time.Duration, fn func(context.Context) error) error {
return withLegoLogger(logger, func() error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return fn(ctx)
})
}
func newWebsiteSSLLegoClient(ctx context.Context, acmeAccount *model.WebsiteAcmeAccount) (*ssl.AcmeClient, error) {
client, err := ssl.NewAcmeClientWithContext(ctx, acmeAccount, getSystemProxy(acmeAccount.UseProxy))
if err == nil {
return client, nil
}
if !errors.Is(err, ssl.ErrAcmeAccountURLMissing) {
return nil, err
}
client, err = ssl.NewRegisterClientWithContext(ctx, acmeAccount, getSystemProxy(acmeAccount.UseProxy))
if err != nil {
return nil, err
}
if client.User.GetRegistration() == nil || client.User.GetRegistration().Location == "" {
return nil, ssl.ErrAcmeAccountURLMissing
}
acmeAccount.URL = client.User.GetRegistration().Location
if acmeAccount.PrivateKey == "" {
return nil, errors.New("private key can not blank")
}
if err := websiteAcmeRepo.Save(*acmeAccount); err != nil {
return nil, err
}
return client, nil
}
type IWebsiteSSLService interface {
Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error)
GetSSL(id uint) (*response.WebsiteSSLDTO, error)
@@ -47,6 +104,7 @@ type IWebsiteSSLService interface {
Delete(ids []uint) error
Update(update request.WebsiteSSLUpdate) error
Upload(req request.WebsiteSSLUpload) error
PushToNode(req request.WebsiteSSLPush) error
ObtainSSL(apply request.WebsiteSSLApply) error
AutoRenewSSL(id uint) error
SyncForRestart() error
@@ -66,7 +124,7 @@ func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []respo
if search.OrderBy != "" && search.Order != "null" {
opts = append(opts, repo.WithOrderRuleBy(search.OrderBy, search.Order))
} else {
opts = append(opts, repo.WithOrderDesc("created_at"))
opts = append(opts, repo.WithOrderDesc("updated_at"))
}
if search.Domain != "" {
opts = append(opts, websiteSSLRepo.WithByDomain(search.Domain))
@@ -99,7 +157,7 @@ func (w WebsiteSSLService) Search(search request.WebsiteSSLListReq) ([]response.
opts []repo.DBOption
result []response.WebsiteSSLDTO
)
opts = append(opts, repo.WithOrderDesc("created_at"))
opts = append(opts, repo.WithOrderDesc("updated_at"))
if search.AcmeAccountID != "" {
acmeAccountID, err := strconv.ParseUint(search.AcmeAccountID, 10, 64)
if err != nil {
@@ -157,10 +215,7 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
}
websiteSSL.Dir = create.Dir
}
if create.PushNode && global.IsMaster && len(create.Nodes) > 0 {
websiteSSL.PushNode = true
websiteSSL.Nodes = create.Nodes
}
setSSLPushConfig(&websiteSSL, create.PushNode, create.Nodes)
var domains []string
if create.OtherDomains != "" {
@@ -225,6 +280,47 @@ func printSSLLog(logger *log.Logger, msgKey string, params map[string]interface{
logger.Println(i18n.GetMsgWithMap(msgKey, params))
}
func normalizeSSLPushConfig(pushNode bool, nodes string) (bool, string) {
nodes = strings.TrimSpace(nodes)
if !pushNode || nodes == "" {
return false, ""
}
return true, nodes
}
func setSSLPushConfig(websiteSSL *model.WebsiteSSL, pushNode bool, nodes string) {
pushNode, nodes = normalizeSSLPushConfig(pushNode, nodes)
if !global.IsMaster || !xpack.MultiNodeProvider.IsXpack() {
pushNode = false
nodes = ""
}
websiteSSL.PushNode = pushNode
websiteSSL.Nodes = nodes
}
func pushSSLToNode(websiteSSL *model.WebsiteSSL, logger *log.Logger) error {
printSSLLog(logger, "StartPushSSLToNode", nil)
if err := xpack.MultiNodeProvider.PushSSLToNode(websiteSSL); err != nil {
printSSLLog(logger, "PushSSLToNodeFailed", map[string]interface{}{"err": err.Error()})
return err
}
printSSLLog(logger, "PushSSLToNodeSuccess", nil)
return nil
}
func pushSSLToNodeWithNewLogger(websiteSSL *model.WebsiteSSL) error {
if !websiteSSL.PushNode {
return nil
}
logFile, logger := newWebsiteSSLLogger(websiteSSL, false)
if logFile != nil {
defer func() {
_ = logFile.Close()
}()
}
return pushSSLToNode(websiteSSL, logger)
}
func newWebsiteSSLLogger(websiteSSL *model.WebsiteSSL, autoRenew bool) (*os.File, *log.Logger) {
flags := os.O_CREATE | os.O_WRONLY | os.O_TRUNC
if autoRenew {
@@ -330,19 +426,23 @@ func (w WebsiteSSLService) AutoRenewSSL(id uint) error {
func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
var (
err error
websiteSSL *model.WebsiteSSL
acmeAccount *model.WebsiteAcmeAccount
dnsAccount *model.WebsiteDnsAccount
client *ssl.AcmeClient
manualClient *ssl.ManualClient
resource certificate.Resource
err error
websiteSSL *model.WebsiteSSL
acmeAccount *model.WebsiteAcmeAccount
dnsAccount *model.WebsiteDnsAccount
logFile *os.File
logger *log.Logger
resource certificate.Resource
httpRoot string
)
websiteSSL, err = websiteSSLRepo.GetFirst(repo.WithByID(id))
if err != nil {
return err
}
if websiteSSL.Status == constant.SSLApply {
return buserr.New("InExecuting")
}
acmeAccount, err = websiteAcmeRepo.GetFirst(repo.WithByID(websiteSSL.AcmeAccountID))
if err != nil {
return err
@@ -352,24 +452,16 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
domains = append(domains, strings.Split(websiteSSL.Domains, ",")...)
}
if websiteSSL.Provider != constant.DnsManual {
client, err = ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy))
if err != nil {
return err
}
switch websiteSSL.Provider {
case constant.DNSAccount:
dnsAccount, err = websiteDnsRepo.GetFirst(repo.WithByID(websiteSSL.DnsAccountID))
if err != nil {
return err
}
if err = client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization, *websiteSSL); err != nil {
return err
}
case constant.Http:
appInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
if gorm.IsRecordNotFoundError(err) {
if errors.Is(err, gormv2.ErrRecordNotFound) {
return buserr.New("ErrOpenrestyNotFound")
}
return err
@@ -379,55 +471,83 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
return buserr.New("ErrWildcardDomain")
}
}
if err := client.UseHTTP(path.Join(appInstall.GetPath(), "root")); err != nil {
return err
}
httpRoot = path.Join(appInstall.GetPath(), "root")
}
}
websiteSSL.Status = constant.SSLApply
err = websiteSSLRepo.Save(websiteSSL)
marked, err := websiteSSLRepo.TryMarkApplying(websiteSSL.ID)
if err != nil {
return err
}
if !marked {
return buserr.New("InExecuting")
}
websiteSSL.Status = constant.SSLApply
go func() {
logFile, logger := newWebsiteSSLLogger(websiteSSL, autoRenew)
logFile, logger = newWebsiteSSLLogger(websiteSSL, autoRenew)
logFileOwnedByGoroutine := false
if logFile != nil {
defer func() {
if !logFileOwnedByGoroutine {
_ = logFile.Close()
}
}()
}
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
if websiteSSL.Provider == constant.DNSAccount {
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type})
}
logger.Println(startMsg)
logFileOwnedByGoroutine = true
go func(logFile *os.File, logger *log.Logger) {
if logFile != nil {
defer logFile.Close()
defer func() {
_ = logFile.Close()
}()
}
// lego v5 switched to slog. Bridge it to the existing *log.Logger
// so the SSL apply log is still written to the per-domain file
// under SSLLogDir.
legoLogger.SetDefault(slog.New(slog.NewTextHandler(logger.Writer(), nil)))
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
if websiteSSL.Provider == constant.DNSAccount {
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type})
}
logger.Println(startMsg)
if websiteSSL.Provider != constant.DnsManual {
privateKey, err := ssl.GetPrivateKeyByType(websiteSSL.KeyType, websiteSSL.PrivateKey)
if err != nil {
handleError(websiteSSL, err)
handleError(websiteSSL, logger, err)
return
}
if websiteSSL.IsIp {
resource, err = client.ObtainIPSSL(domains[0], privateKey)
} else {
resource, err = client.ObtainSSL(domains, privateKey)
}
err = withLegoLoggerTimeout(logger, sslObtainTimeout, func(ctx context.Context) error {
client, err := newWebsiteSSLLegoClient(ctx, acmeAccount)
if err != nil {
return err
}
switch websiteSSL.Provider {
case constant.DNSAccount:
if err = client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization, *websiteSSL); err != nil {
return err
}
case constant.Http:
if err = client.UseHTTP(httpRoot); err != nil {
return err
}
}
if websiteSSL.IsIp {
resource, err = client.ObtainIPSSL(ctx, domains[0], privateKey)
} else {
resource, err = client.ObtainSSL(ctx, domains, privateKey)
}
return err
})
if err != nil {
handleError(websiteSSL, err)
handleError(websiteSSL, logger, err)
return
}
} else {
manualClient, err = ssl.NewCustomAcmeClient(acmeAccount, logger)
manualClient, err := ssl.NewCustomAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy), logger)
if err != nil {
handleError(websiteSSL, err)
handleError(websiteSSL, logger, err)
return
}
resource, err = manualClient.RequestCertificate(context.Background(), websiteSSL)
ctx, cancel := context.WithTimeout(context.Background(), sslObtainTimeout)
resource, err = manualClient.RequestCertificate(ctx, websiteSSL)
cancel()
if err != nil {
handleError(websiteSSL, err)
handleError(websiteSSL, logger, err)
return
}
}
@@ -435,10 +555,9 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
websiteSSL.PrivateKey = string(resource.PrivateKey)
websiteSSL.Pem = string(resource.Certificate)
websiteSSL.CertURL = resource.CertURL
certBlock, _ := pem.Decode(resource.Certificate)
cert, err := x509.ParseCertificate(certBlock.Bytes)
cert, err := parseCertificatePEM(resource.Certificate)
if err != nil {
handleError(websiteSSL, err)
handleError(websiteSSL, logger, err)
return
}
websiteSSL.ExpireDate = cert.NotAfter
@@ -487,14 +606,11 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
}
reloadSystemSSL(websiteSSL, logger)
if websiteSSL.PushNode {
printSSLLog(logger, "StartPushSSLToNode", nil)
if err = xpack.MultiNodeProvider.PushSSLToNode(websiteSSL); err != nil {
printSSLLog(logger, "PushSSLToNodeFailed", map[string]interface{}{"err": err.Error()})
if err = pushSSLToNode(websiteSSL, logger); err != nil {
return
}
printSSLLog(logger, "PushSSLToNodeSuccess", nil)
}
}()
}(logFile, logger)
return nil
}
@@ -516,16 +632,24 @@ func runShellScriptFile(workDir, shell string, logger *log.Logger) error {
return cmdMgr.Run("bash", file.Name())
}
func handleError(websiteSSL *model.WebsiteSSL, err error) {
func parseCertificatePEM(certPEM []byte) (*x509.Certificate, error) {
certBlock, _ := pem.Decode(certPEM)
if certBlock == nil {
return nil, errors.New("invalid certificate PEM")
}
return x509.ParseCertificate(certBlock.Bytes)
}
func handleError(websiteSSL *model.WebsiteSSL, logger *log.Logger, err error) {
if websiteSSL.Status == constant.SSLInit || websiteSSL.Status == constant.SSLError {
websiteSSL.Status = constant.StatusError
} else {
websiteSSL.Status = constant.SSLApplyError
}
websiteSSL.Message = err.Error()
// lego v5 uses slog; use the same global default logger to write the
// failure message to the SSL log.
legoLogger.Default().Error(i18n.GetErrMsg("ApplySSLFailed", map[string]interface{}{"domain": websiteSSL.PrimaryDomain, "detail": err.Error()}))
if logger != nil {
logger.Println(i18n.GetErrMsg("ApplySSLFailed", map[string]interface{}{"domain": websiteSSL.PrimaryDomain, "detail": err.Error()}))
}
_ = websiteSSLRepo.Save(websiteSSL)
}
@@ -534,7 +658,7 @@ func (w WebsiteSSLService) GetDNSResolve(req request.WebsiteDNSReq) ([]response.
if err != nil {
return nil, err
}
client, err := ssl.NewCustomAcmeClient(acmeAccount, nil)
client, err := ssl.NewCustomAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy), nil)
if err != nil {
return nil, err
}
@@ -580,7 +704,7 @@ func (w WebsiteSSLService) Delete(ids []uint) error {
for _, id := range ids {
if websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(id)); len(websites) > 0 {
oldSSL, _ := websiteSSLRepo.GetFirst(repo.WithByID(id))
if oldSSL.ID > 0 {
if oldSSL != nil && oldSSL.ID > 0 {
websiteSSLS = append(websiteSSLS, oldSSL.PrimaryDomain)
}
continue
@@ -608,12 +732,13 @@ func (w WebsiteSSLService) Delete(ids []uint) error {
global.LOG.Errorf("Failed to get acme account for SSL revoke, err: %v", err)
return
}
client, err := ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy))
if err != nil {
global.LOG.Errorf("Failed to create ACME client for SSL revoke, err: %v", err)
return
}
err = client.RevokeSSL([]byte(websiteSSL.Pem))
err = withLegoLogger(nil, func() error {
client, err := newWebsiteSSLLegoClient(context.Background(), acmeAccount)
if err != nil {
return err
}
return client.RevokeSSL([]byte(websiteSSL.Pem))
})
if err != nil {
global.LOG.Errorf("Failed to revoke SSL for domain %s, err: %v", websiteSSL.PrimaryDomain, err)
return
@@ -651,13 +776,13 @@ func (w WebsiteSSLService) Update(update request.WebsiteSSLUpdate) error {
} else {
updateParams["shell"] = ""
}
if update.PushNode {
updateParams["push_node"] = true
updateParams["nodes"] = update.Nodes
} else {
updateParams["push_node"] = false
updateParams["nodes"] = ""
pushNode, nodes := normalizeSSLPushConfig(update.PushNode, update.Nodes)
if !global.IsMaster || !xpack.MultiNodeProvider.IsXpack() {
pushNode = false
nodes = ""
}
updateParams["push_node"] = pushNode
updateParams["nodes"] = nodes
if websiteSSL.Provider != constant.SelfSigned && websiteSSL.Provider != constant.Manual {
acmeAccount, err := websiteAcmeRepo.GetFirst(repo.WithByID(update.AcmeAccountID))
@@ -708,6 +833,7 @@ func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error {
Description: req.Description,
Status: constant.SSLReady,
}
setSSLPushConfig(websiteSSL, req.PushNode, req.Nodes)
var err error
if req.SSLID > 0 {
websiteSSL, err = websiteSSLRepo.GetFirst(repo.WithByID(req.SSLID))
@@ -715,6 +841,7 @@ func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error {
return err
}
websiteSSL.Description = req.Description
setSSLPushConfig(websiteSSL, req.PushNode, req.Nodes)
}
if req.Type == "local" {
fileOp := files.NewFileOp()
@@ -802,9 +929,73 @@ func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error {
if err := UpdateSSLConfig(*websiteSSL); err != nil {
return err
}
return websiteSSLRepo.Save(websiteSSL)
if err := websiteSSLRepo.Save(websiteSSL); err != nil {
return err
}
return pushSSLToNodeWithNewLogger(websiteSSL)
}
return websiteSSLRepo.Create(context.Background(), websiteSSL)
if err := websiteSSLRepo.Create(context.Background(), websiteSSL); err != nil {
return err
}
return pushSSLToNodeWithNewLogger(websiteSSL)
}
func (w WebsiteSSLService) PushToNode(req request.WebsiteSSLPush) error {
if !global.IsMaster {
return errors.New("only master node can push SSL to nodes")
}
if !xpack.MultiNodeProvider.IsXpack() {
return errors.New("SSL node push is an XPack feature")
}
pushNode, nodes := normalizeSSLPushConfig(req.PushNode, req.Nodes)
if !pushNode {
return errors.New("please select nodes to push SSL")
}
websiteSSL, err := websiteSSLRepo.GetFirst(repo.WithByID(req.ID))
if err != nil {
return err
}
if websiteSSL.Provider == constant.FromMaster {
return errors.New("SSL imported from master node can not be pushed")
}
if websiteSSL.Status != constant.SSLReady {
return errors.New("only ready SSL can be pushed")
}
if task.CheckResourceTaskIsExecuting(task.TaskPush, task.TaskScopeWebsite, websiteSSL.ID) {
return buserr.New("TaskIsExecuting")
}
if err := websiteSSLRepo.SaveByMap(websiteSSL, map[string]interface{}{
"push_node": pushNode,
"nodes": nodes,
}); err != nil {
return err
}
websiteSSL.PushNode = pushNode
websiteSSL.Nodes = nodes
if req.Sync {
return xpack.MultiNodeProvider.PushSSLToNode(websiteSSL)
}
pushTask, err := task.NewTask(task.GetTaskName(websiteSSL.PrimaryDomain, task.TaskPush, "SSL"), task.TaskPush, task.TaskScopeWebsite, req.TaskID, websiteSSL.ID)
if err != nil {
return err
}
pushTask.AddSubTask(i18n.GetMsgByKey("StartPushSSLToNode"), func(t *task.Task) error {
t.Log(i18n.GetMsgByKey("StartPushSSLToNode"))
if err := xpack.MultiNodeProvider.PushSSLToNode(websiteSSL); err != nil {
t.Log(i18n.GetMsgWithMap("PushSSLToNodeFailed", map[string]interface{}{"err": err.Error()}))
return err
}
t.Log(i18n.GetMsgByKey("PushSSLToNodeSuccess"))
return nil
}, nil)
go func() {
if err := pushTask.Execute(); err != nil {
global.LOG.Errorf("push ssl to node failed, sslID: %d, err: %v", websiteSSL.ID, err)
}
}()
return nil
}
func (w WebsiteSSLService) DownloadFile(id uint) (*os.File, error) {
@@ -851,7 +1042,13 @@ func (w WebsiteSSLService) SyncForRestart() error {
}
func (w WebsiteSSLService) ImportMasterSSL(create model.WebsiteSSL) error {
websiteSSL, _ := websiteSSLRepo.GetFirst(websiteSSLRepo.WithByMasterSSLID(create.ID))
websiteSSL, err := websiteSSLRepo.GetFirst(websiteSSLRepo.WithByMasterSSLID(create.ID))
if err != nil {
if !errors.Is(err, gormv2.ErrRecordNotFound) {
return err
}
websiteSSL = &model.WebsiteSSL{}
}
websiteSSL.Status = constant.SSLReady
websiteSSL.Provider = constant.FromMaster
websiteSSL.PrimaryDomain = create.PrimaryDomain
@@ -865,8 +1062,14 @@ func (w WebsiteSSLService) ImportMasterSSL(create model.WebsiteSSL) error {
websiteSSL.Organization = create.Organization
websiteSSL.MasterSSLID = create.ID
websiteSSL.Domains = create.Domains
if err := websiteSSLRepo.Save(websiteSSL); err != nil {
return err
if websiteSSL.ID == 0 {
if err := websiteSSLRepo.Create(context.Background(), websiteSSL); err != nil {
return err
}
} else {
if err := websiteSSLRepo.Save(websiteSSL); err != nil {
return err
}
}
websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(websiteSSL.ID))
if len(websites) == 0 {
+358 -53
View File
@@ -1247,6 +1247,33 @@
"formatZH": "更新 AI 网关分组 [name]",
"formatEN": "update AI proxy group [name]"
},
"/core/enterprise/ai-proxy/model-groups/create": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建 AI 网关模型组 [name]",
"formatEN": "create AI proxy model group [name]"
},
"/core/enterprise/ai-proxy/model-groups/delete": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除 AI 网关模型组 [id]",
"formatEN": "delete AI proxy model group [id]"
},
"/core/enterprise/ai-proxy/model-groups/update": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新 AI 网关模型组 [name]",
"formatEN": "update AI proxy model group [name]"
},
"/core/enterprise/ai-proxy/service/operate": {
"bodyKeys": [
"operate"
@@ -1368,13 +1395,14 @@
"/core/enterprise/skills-hub/import": {
"bodyKeys": [
"type",
"version",
"repoURL",
"packageURL"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "导入 Skills Hub 技能 [type]",
"formatEN": "import Skills Hub skill [type]"
"formatZH": "导入 Skills Hub 技能版本 [version]",
"formatEN": "import Skills Hub skill version [version]"
},
"/core/enterprise/skills-hub/status": {
"bodyKeys": [
@@ -1478,11 +1506,13 @@
"formatEN": "update Skills Hub install target [name]"
},
"/core/enterprise/skills-hub/upload": {
"bodyKeys": [],
"bodyKeys": [
"version"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "上传 Skills Hub 技能包",
"formatEN": "upload Skills Hub package"
"formatZH": "上传 Skills Hub 技能包版本 [version]",
"formatEN": "upload Skills Hub package version [version]"
},
"/core/enterprise/users": {
"bodyKeys": [
@@ -1493,6 +1523,25 @@
"formatZH": "添加用户 [name]",
"formatEN": "add user [name]"
},
"/core/enterprise/users/api/update": {
"bodyKeys": [
"id",
"ipWhiteList"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "users",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新用户 [name] API 接口配置 =\u003e IP 白名单: [ipWhiteList]",
"formatEN": "update user [name] api config =\u003e IP White List: [ipWhiteList]"
},
"/core/enterprise/users/del": {
"bodyKeys": [
"ids"
@@ -1529,6 +1578,255 @@
"formatZH": "更新 [name]",
"formatEN": "update user [name]"
},
"/core/enterprise/vms": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name]",
"formatEN": "create VM [name]"
},
"/core/enterprise/vms/del": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除虚拟机 [name]",
"formatEN": "delete VM [name]"
},
"/core/enterprise/vms/iso": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机镜像 [name] [type]",
"formatEN": "create VM ISO [name] [type]"
},
"/core/enterprise/vms/iso/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_isos",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机镜像 [name]",
"formatEN": "delete VM ISO [name]"
},
"/core/enterprise/vms/iso/update": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新虚拟机镜像 [name] [type]",
"formatEN": "update VM ISO [name] [type]"
},
"/core/enterprise/vms/networks": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机网络 [name] [type]",
"formatEN": "create VM network [name] [type]"
},
"/core/enterprise/vms/networks/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_networks",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机网络 [name]",
"formatEN": "delete VM network [name]"
},
"/core/enterprise/vms/networks/sync": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "同步虚拟机网络",
"formatEN": "sync VM networks"
},
"/core/enterprise/vms/networks/update": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_networks",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新虚拟机网络 [name]",
"formatEN": "update VM network [name]"
},
"/core/enterprise/vms/operate": {
"bodyKeys": [
"name",
"operate"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "操作虚拟机 [name] [operate]",
"formatEN": "operate VM [name] [operate]"
},
"/core/enterprise/vms/rename": {
"bodyKeys": [
"name",
"newName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "重命名虚拟机 [name] 为 [newName]",
"formatEN": "rename VM [name] to [newName]"
},
"/core/enterprise/vms/snapshots": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name] 快照 [snapshotName]",
"formatEN": "create VM [name] snapshot [snapshotName]"
},
"/core/enterprise/vms/snapshots/del": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除虚拟机 [name] 快照 [snapshotName]",
"formatEN": "delete VM [name] snapshot [snapshotName]"
},
"/core/enterprise/vms/snapshots/recover": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "恢复虚拟机 [name] 快照 [snapshotName]",
"formatEN": "recover VM [name] snapshot [snapshotName]"
},
"/core/enterprise/vms/storages": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机存储 [name] [type]",
"formatEN": "create VM storage [name] [type]"
},
"/core/enterprise/vms/storages/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_storages",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机存储 [name]",
"formatEN": "delete VM storage [name]"
},
"/core/enterprise/vms/storages/sync": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "同步虚拟机存储",
"formatEN": "sync VM storages"
},
"/core/enterprise/vms/storages/update": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_storages",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新虚拟机存储 [name]",
"formatEN": "update VM storage [name]"
},
"/core/enterprise/vms/templates": {
"bodyKeys": [
"name",
"templateName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name] 模板 [templateName]",
"formatEN": "create VM [name] template [templateName]"
},
"/core/enterprise/vms/templates/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_templates",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机模板 [name]",
"formatEN": "delete VM template [name]"
},
"/core/enterprise/vms/update": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新虚拟机 [name]",
"formatEN": "update VM [name]"
},
"/core/groups": {
"bodyKeys": [
"name",
@@ -1853,15 +2151,22 @@
},
"/core/xpack/exchange/file": {
"bodyKeys": [
"source",
"dest",
"sourceNode",
"targetNode",
"sourcePath",
"destPath"
"targetPath"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "交换文件 [sourcePath] 到 [destPath]",
"formatEN": "exchange file [sourcePath] to [destPath]"
"formatZH": "交换文件 [sourcePath] 到 [targetPath]",
"formatEN": "exchange file [sourcePath] to [targetPath]"
},
"/core/xpack/exchange/image": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "同步节点镜像",
"formatEN": "sync images"
},
"/core/xpack/nodes": {
"bodyKeys": [
@@ -2166,6 +2471,15 @@
"formatZH": "更新自定义应用配置 [status]",
"formatEN": "update custom app config [status]"
},
"/core/xpack/settings/app/image-prefix": {
"bodyKeys": [
"imagePrefix"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新应用镜像前缀 [imagePrefix]",
"formatEN": "update app image prefix [imagePrefix]"
},
"/core/xpack/settings/reset": {
"bodyKeys": [],
"paramKeys": [],
@@ -2191,27 +2505,11 @@
"formatEN": "update xpack setting [key]"
},
"/core/xpack/sync/app": {
"bodyKeys": [
"sourceNode",
"targetNode",
"appInstallID",
"appBackupID"
],
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "同步应用备份 [appInstallID] 到节点 [targetNode]",
"formatEN": "sync app backup [appInstallID] to node [targetNode]"
},
"/core/xpack/sync/app/install": {
"bodyKeys": [
"appKey",
"version",
"nodes"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "安装应用 [appKey] [version] 到节点 [nodes]",
"formatEN": "install app [appKey] [version] to nodes [nodes]"
"formatZH": "同步节点应用",
"formatEN": "sync app"
},
"/core/xpack/sync/ssl": {
"bodyKeys": [
@@ -2352,34 +2650,14 @@
"formatZH": "更新自定义应用配置 [status]",
"formatEN": "update custom app config [status]"
},
"/custom/app/install": {
"/custom/app/image-prefix": {
"bodyKeys": [
"name",
"appKey"
"imagePrefix"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "从节点安装应用 [name]",
"formatEN": "install app [name] from node"
},
"/custom/app/install/check": {
"bodyKeys": [
"name",
"appKey"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "检查应用安装 [name]",
"formatEN": "check app install [name]"
},
"/custom/app/push": {
"bodyKeys": [
"appBackupID"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "推送应用备份 [appBackupID] 到节点",
"formatEN": "push app backup [appBackupID] to node"
"formatZH": "更新应用镜像前缀 [imagePrefix]",
"formatEN": "update app image prefix [imagePrefix]"
},
"/custom/app/sync": {
"bodyKeys": [],
@@ -4440,6 +4718,24 @@
"formatZH": "申请证书 [domain]",
"formatEN": "apply ssl [domain]"
},
"/websites/ssl/push": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "website_ssls",
"output_column": "primary_domain",
"output_value": "domain"
}
],
"formatZH": "推送证书到节点 [domain]",
"formatEN": "Push ssl to nodes [domain]"
},
"/websites/ssl/update": {
"bodyKeys": [
"id"
@@ -4935,6 +5231,15 @@
"formatZH": "更新[scope]频率访问限制",
"formatEN": "update [scope] CC config"
},
"/xpack/waf/rule/common/apply": {
"bodyKeys": [
"scope"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "应用规则 [scope] 到网站",
"formatEN": "apply rule [scope] to websites"
},
"/xpack/waf/rule/common/create": {
"bodyKeys": [
"scope"
@@ -434,5 +434,12 @@
"file": "lz4.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"],
"installed": false
},
{
"name": "msgpack",
"check": "msgpack",
"file": "msgpack.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"],
"installed": false
}
]
+8 -8
View File
@@ -6,9 +6,9 @@ replace github.com/moby/go-archive => github.com/moby/go-archive v0.1.0
require (
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/compose-spec/compose-go/v2 v2.11.0
github.com/compose-spec/compose-go/v2 v2.12.1
github.com/creack/pty v1.1.24
github.com/docker/cli v29.5.3+incompatible
github.com/docker/cli v29.6.1+incompatible
github.com/docker/docker v28.5.2+incompatible
github.com/docker/go-connections v0.7.0
github.com/fsnotify/fsnotify v1.10.1
@@ -30,14 +30,14 @@ require (
github.com/klauspost/compress v1.18.6
github.com/mholt/archiver/v4 v4.0.0-alpha.8
github.com/miekg/dns v1.1.72
github.com/minio/minio-go/v7 v7.2.0
github.com/minio/minio-go/v7 v7.2.1
github.com/nicksnyder/go-i18n/v2 v2.6.1
github.com/opencontainers/image-spec v1.1.1
github.com/oschwald/maxminddb-golang v1.13.1
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
github.com/pkg/sftp v1.13.10
github.com/qiniu/go-sdk/v7 v7.26.12
github.com/qiniu/go-sdk/v7 v7.26.13
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.26.5
github.com/sirupsen/logrus v1.9.4
@@ -46,12 +46,12 @@ require (
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
github.com/subosito/gotenv v1.6.0
github.com/tencentyun/cos-go-sdk-v5 v0.7.73
github.com/tencentyun/cos-go-sdk-v5 v0.7.74
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19
github.com/upyun/go-sdk v2.1.0+incompatible
go.mongodb.org/mongo-driver/v2 v2.6.0
go.mongodb.org/mongo-driver/v2 v2.7.0
golang.org/x/crypto v0.53.0
golang.org/x/net v0.55.0
golang.org/x/net v0.56.0
golang.org/x/oauth2 v0.36.0
golang.org/x/sync v0.21.0
golang.org/x/sys v0.46.0
@@ -60,7 +60,7 @@ require (
google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478
gopkg.in/ini.v1 v1.67.3
gopkg.in/yaml.v3 v3.0.1
gorm.io/gorm v1.31.1
gorm.io/gorm v1.31.2
)
require (
+18 -16
View File
@@ -204,8 +204,8 @@ github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gE
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/compose-spec/compose-go/v2 v2.11.0 h1:xoq/ootgIL6TsHmbJHrkuh7+bzjhPV3NHftHRPPyVXM=
github.com/compose-spec/compose-go/v2 v2.11.0/go.mod h1:ZU6zlcweCZKyiB7BVfCizQT9XmkEIMFE+PRZydVcsZg=
github.com/compose-spec/compose-go/v2 v2.12.1 h1:+xBZNxcgSus4atQJwXPEdhHRgCEyZmj/BuqN5m33Ou0=
github.com/compose-spec/compose-go/v2 v2.12.1/go.mod h1:ZU6zlcweCZKyiB7BVfCizQT9XmkEIMFE+PRZydVcsZg=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
@@ -230,8 +230,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/cli v29.5.3+incompatible h1:nbEFfz774vBwQ5KRYv7c/AghjReqnGISvrRhzjV0evs=
github.com/docker/cli v29.5.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.6.1+incompatible h1:oO7F4nn3Ovr/5TlfTUWFbMwBSS/B7Xs6Epv26gBrUP8=
github.com/docker/cli v29.6.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
@@ -608,8 +608,8 @@ github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLT
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.2.0 h1:RCJM0R1XOsRs+A3x3UCaf3ZYbByDaLjFeAi+YCQEPhs=
github.com/minio/minio-go/v7 v7.2.0/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0=
github.com/minio/minio-go/v7 v7.2.1 h1:PfBfwvKB/MmqyN8Vb1G9voWisaM9OrLv+WwOvMwS9Dw=
github.com/minio/minio-go/v7 v7.2.1/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
@@ -746,8 +746,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/qiniu/go-sdk/v7 v7.26.12 h1:AnWiKjBY62XpULoB/MySKdNmlUo9S9pQB7/0s7QueCo=
github.com/qiniu/go-sdk/v7 v7.26.12/go.mod h1:ri7fGwbio0pRDFr8EK5TUpx0DbnpIMJ2bMSDxGWfCbk=
github.com/qiniu/go-sdk/v7 v7.26.13 h1:U4usDKoLldAqJntLN4wv4pkAjwvM9dueOUY/wbbO/yM=
github.com/qiniu/go-sdk/v7 v7.26.13/go.mod h1:ri7fGwbio0pRDFr8EK5TUpx0DbnpIMJ2bMSDxGWfCbk=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
@@ -833,8 +833,8 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.24/go.mod h
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.106 h1:YjsxDQwoiB+C0ISjnVk22Wbks02Ir+7Z5wmujcz69Vg=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.106/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
github.com/tencentyun/cos-go-sdk-v5 v0.7.73 h1:uFfgp1A7cQaAGR6QP9DsIkoEQ67b8ewj5r1RV6XB540=
github.com/tencentyun/cos-go-sdk-v5 v0.7.73/go.mod h1:STbTNaNKq03u+gscPEGOahKzLcGSYOj6Dzc5zNay7Pg=
github.com/tencentyun/cos-go-sdk-v5 v0.7.74 h1:YCsGhIK+Q5vMcmAiQqPuMyhSQ3zN1p7U+dhlHQD++6c=
github.com/tencentyun/cos-go-sdk-v5 v0.7.74/go.mod h1:STbTNaNKq03u+gscPEGOahKzLcGSYOj6Dzc5zNay7Pg=
github.com/tencentyun/qcloud-cos-sts-sdk v0.0.0-20250515025012-e0eec8a5d123/go.mod h1:b18KQa4IxHbxeseW1GcZox53d7J0z39VNONTxvvlkXw=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
@@ -898,8 +898,8 @@ go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lL
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
go.mongodb.org/mongo-driver/v2 v2.6.0 h1:b9sJOYrkmt4l8bY43ZenFBcPlhYIjaOfYHLtbB/5qi8=
go.mongodb.org/mongo-driver/v2 v2.6.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.mongodb.org/mongo-driver/v2 v2.7.0 h1:RO+zqavD2/GCL3cxOMyZhx6R9Irzr8/6gsoqx5tcY/c=
go.mongodb.org/mongo-driver/v2 v2.7.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -1073,8 +1073,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1409,8 +1409,10 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+1
View File
@@ -24,6 +24,7 @@ var langFiles = map[string]string{
"ko": "lang/ko.yaml",
"tr": "lang/tr.yaml",
"es-ES": "lang/es-ES.yaml",
"fa": "lang/fa.yaml",
}
func GetMsgWithMap(key string, maps map[string]interface{}) string {
+49 -39
View File
@@ -1,32 +1,47 @@
# base errors
ErrInvalidParams: 'Bad request: {{ .detail }}'
ErrTokenParse: 'Token error: {{ .detail }}'
ErrInitialPassword: 'Original password is incorrect'
ErrInternalServer: 'Internal server error: {{ .detail }}'
ErrRecordExist: 'Record already exists'
ErrRecordNotFound: 'Record not found'
ErrStructTransform: 'Type conversion failed: {{ .err }}'
ErrNotSupportType: 'Unsupported type: {{ .name }}'
# file AI
ErrFileAINotEnabled: 'File-management AI summary requires an enabled AI account in File Management. Content-only search still works when AI is off.'
ErrFileAISearchBadPattern: 'Invalid file search pattern (regex or whole word): {{ .detail }}'
FileAISearchEmptyDir: 'No files or directories found under this path (or all entries were filtered).'
# remote download
ErrWgetRemoteFailed: 'Remote download failed or URL is unreachable: {{ .detail }}'
ErrWgetInvalidContentType: 'Remote URL did not return a file (possibly an error page), {{ .detail }}'
ErrWgetProxyNotConfigured: 'Configure a proxy server in panel settings first'
ErrWgetProxyInvalid: 'Invalid proxy server address: {{ .detail }}'
ErrRecordExist: 'Record already exists'
ErrRecordNotFound: 'Record not found'
ErrStructTransform: 'Type conversion failed: {{ .err }}'
# login and security
ErrNotLogin: 'Not logged in: {{ .detail }}'
ErrPasswordExpired: 'Password expired: {{ .detail }}'
ErrNotSupportType: 'Unsupported type: {{ .name }}'
ErrAlertMethodNotSupported: 'This version does not support this alert method'
# request and API
ErrProxy: 'Request failed: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API access disabled: {{ .detail }}'
ErrApiConfigKeyInvalid: 'Invalid API key: {{ .detail }}'
ErrApiConfigIPInvalid: 'IP not in API allowlist: {{ .detail }}'
ErrApiConfigDisable: 'Endpoint not allowed via API: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'Invalid API timestamp: {{ .detail }}'
ErrShutDown: 'Command stopped manually'
ErrCreateHttpClient: 'Failed to create HTTP request: {{ .err }}'
ErrHttpReqTimeOut: 'HTTP request timed out: {{ .err }}'
ErrHttpReqFailed: 'HTTP request failed: {{ .err }}'
ErrNoSuchHost: 'Host resolve failed: {{ .err }}'
ErrHttpReqNotFound: 'Requested resource not found: {{ .err }}'
# quick jump
ErrMinQuickJump: 'Set at least one quick jump entry'
ErrMaxQuickJump: 'At most 4 quick jump entries'
#common
# common
ErrShutDown: 'Command stopped manually'
ErrUsernameIsExist: 'Username already exists'
ErrNameIsExist: 'Name already exists'
ErrDemoEnvironment: 'Disabled in demo environment'
@@ -45,7 +60,7 @@ ErrGroupIsDefault: 'Default group, cannot be deleted'
ErrGroupIsInWebsiteUse: 'Group is in use by a website'
Decrypt: "Decrypt"
#agent
# agent
ErrAgentAccountBound: 'Account is bound to an agent and cannot be deleted'
ErrTerminalAIAccountInUse: 'This model account is currently used by Terminal AI. Switch the account in Terminal AI settings or disable Terminal AI and try again.'
ErrAgentAccountUnavailable: 'Account connection unavailable: {{ .err }}'
@@ -71,7 +86,7 @@ ErrAgentWebsiteUnbindUnsupported: 'Deployment websites cannot be unbound manuall
ErrHermesPairingCodeUnavailable: 'The pairing code is temporarily unavailable in Hermes, possibly due to network issues. Please try again later.'
ErrHermesFeishuGroupAllowlistRequiresAllowlist: 'When the Feishu group policy is Allowlist, the DM policy cannot be Pairing Code.'
#backup
# backup and alert settings
Localhost: 'Local'
ErrBackupInUsed: 'Backup account is in use by a scheduled task'
ErrBackupCheck: 'Backup account connectivity test failed: {{ .err }}'
@@ -80,7 +95,7 @@ ErrBackupLocalCreate: 'Local backup accounts cannot be created'
ErrAlertConfigInUse: 'Alert configuration is in use and cannot be deleted'
ErrAlertConfigPhoneExist: 'Phone number already exists'
#app
# app
ErrPortInUsed: '{{ .detail }} port is already occupied!'
ErrAppLimit: 'App install limit reached'
ErrNotInstall: 'Application not installed'
@@ -98,16 +113,12 @@ ErrFileParseApp: 'Failed to parse {{ .name }}: {{ .err }}'
ErrAppDirNull: 'The version folder does not exist'
LocalAppErr: 'Failed to sync app {{ .name }}: {{ .err }}'
ErrContainerName: 'Container name already exists'
ErrCreateHttpClient: 'Failed to create HTTP request: {{ .err }}'
ErrHttpReqTimeOut: 'HTTP request timed out: {{ .err }}'
ErrHttpReqFailed: 'HTTP request failed: {{ .err }}'
ErrNoSuchHost: 'Host resolve failed: {{ .err }}'
ErrHttpReqNotFound: 'Requested resource not found: {{ .err }}'
ErrContainerNotFound: '{{ .name }} container does not exist'
ErrContainerMsg: 'Container {{ .name }} is abnormal; check logs'
ErrAppBackup: 'App backup failed: {{ .name }} {{ .err }}'
ErrVersionTooLow: '1Panel version too low to update App Store'
ErrAppNameExist: 'The application name already exists'
ErrAppInstallNameExist: 'App install name {{ .name }} already exists'
AppStoreIsSyncing: 'App Store is syncing; try again later'
ErrGetCompose: 'Failed to read docker-compose.yml: {{ .detail }}'
ErrAppWarn: 'App status abnormal; check logs'
@@ -133,7 +144,7 @@ ErrDockerComposeCmdNotFound: 'Docker Compose not found on host'
UseExistImage: 'Image exists; using existing image'
ErrDatabaseNotFound: 'Database not found: {{ .name }}'
#ssh
# SSH
ExportIP: "Login IP"
ExportArea: "Location"
ExportPort: "Port"
@@ -142,7 +153,7 @@ ExportUser: "User"
ExportStatus: "Login Status"
ExportDate: "Time"
#file
# file
ErrFileCanNotRead: 'File preview is not supported'
ErrSensitiveFileRead: 'Reading sensitive files is not allowed'
ErrFileToLarge: 'File is larger than 10 MB'
@@ -170,7 +181,7 @@ FileDropSuccess: 'Cleaned {{ .name }}; files: {{ .count }}, freed: {{ .size }}'
FileDropSum: 'Cleanup done; files: {{ .count }}, freed: {{ .size }}'
ErrBadDecrypt: 'Wrong archive password; extract failed'
#website
# website
ErrAliasIsExist: 'Alias already exists'
ErrBackupMatch: 'Backup data mismatch: {{ .detail }}'
ErrBackupExist: 'Backup source data missing: {{ .detail }}'
@@ -201,7 +212,7 @@ ErrDefaultWebsite: 'Default website already set; unset it first'
SSL: 'Certificate'
Set: 'Settings'
#ssl
# SSL certificate
ErrSSLCannotDelete: 'Certificate {{ .name }} is in use by a website'
ErrAccountCannotDelete: 'Account is linked to a certificate and cannot be deleted'
ErrSSLApply: 'Certificate renewed; OpenResty reload failed'
@@ -237,7 +248,7 @@ StartPushSSLToNode: 'Push certificate to node'
PushSSLToNodeFailed: "Failed to push certificate to node: {{ .err }}"
PushSSLToNodeSuccess: 'Certificate pushed to node'
#mysql
# MySQL
ErrUserIsExist: 'User already exists'
ErrDatabaseIsExist: 'Database already exists'
ErrExecTimeOut: 'SQL execution timed out'
@@ -247,10 +258,10 @@ RemoteBackup: 'Start local DB container with image {{ .name }} before backing up
RemoteRecover: 'Start local DB container with image {{ .name }} before restoring remote DB'
Arg: "Argument"
#redis
# Redis
ErrTypeOfRedis: 'Backup file type does not match current persistence mode'
#container
# container
ErrInUsed: "{{ .detail }} is in use and cannot be deleted!"
ErrObjectInUsed: "This object is in use and cannot be deleted!"
ErrObjectBeDependent: "This image is referenced by other images and cannot be deleted!"
@@ -293,7 +304,7 @@ ComposeRecoverContainers: 'Restore compose containers'
ComposeRecoverRecord: 'Save compose record'
ComposeRecoverCleanup: 'Cleanup compose restore workspace'
#runtime
# runtime
ErrFileNotExist: 'Source file not found: {{ .detail }}'
ErrImageBuildErr: 'Image build failed'
ErrImageExist: "Image already exists. Use a different image name."
@@ -307,7 +318,7 @@ ErrContainerNameIsNull: 'Container name is empty'
ErrPHPPortIsDefault: 'Port 9000 is reserved as default; use another port'
ErrPHPRuntimePortFailed: 'Port {{ .name }} is already used by this runtime'
#tool
# tool
ErrConfigNotFound: 'Configuration file does not exist'
ErrConfigParse: 'Invalid configuration file format'
ErrConfigIsNull: 'Configuration file cannot be empty'
@@ -315,7 +326,7 @@ ErrConfigDirNotFound: 'Run directory not found'
ErrConfigAlreadyExist: 'Configuration file with the same name already exists'
ErrUserFindErr: 'Failed to find user {{ .name }}: {{ .err }}'
#cronjob
# cron job
CutWebsiteLogSuccess: 'Website log rotated: {{ .name }} (backup: {{ .path }})'
HandleShell: 'Run script {{ .name }}'
HandleCurl: 'Request URL {{ .name }}'
@@ -335,7 +346,7 @@ CleanLog: 'Clean logs'
CleanLogByName: 'Clean {{ .name }} logs'
WafIpGroupNotFound: "WAF IP group not found. Go to [Advanced Features -> WAF -> Black/White List -> IP Group] and add one using Remote Download."
#toolbox
# toolbox
ErrNotExistUser: 'User does not exist'
ErrBanAction: 'Config failed: service {{ .name }} unavailable'
ErrClamdscanNotFound: 'clamdscan command not found'
@@ -345,14 +356,14 @@ CreateSwap: 'Create swap file'
FormatSwap: 'Format swap file'
EnableSwap: 'Enable swap'
#tamper
# tamper protection
CleanTamperSetting: 'Clean historical protection settings'
SetTamperWithRules: 'Enable directory protection by rules'
ProtectDir: "Protect directory {{ .name }}"
IgnoreFile: "Protect file {{ .name }}"
TamperSetSuccessful: 'Protection set. Monitoring these directories:'
#waf
# WAF
ErrScope: 'This config cannot be changed'
ErrStateChange: 'Failed to change state'
ErrRuleExist: 'Rule already exists'
@@ -408,7 +419,7 @@ remoteIpIsNull: "IP list is empty"
OpenrestyVersionErr: 'OpenResty version too low; upgrade to 1.27.1.2-2-2-focal'
ErrFileTooLarge: 'File is larger than 1 MB and cannot be uploaded'
#task
# task
TaskStart: '{{ .name }} [START]'
TaskEnd: '{{ .name }} [DONE]'
TaskFailed: '{{ .name }} task failed'
@@ -470,16 +481,16 @@ TaskExec: 'Execute'
TaskBatch: "Batch Operation"
FileConvert: 'File Conversion'
# task - clam
# task - ClamAV
Clamscan: "Scan {{ .name }}"
TaskScan: "Scan"
# task - ai
# task - AI
OllamaModelPull: 'Pull Ollama model {{ .name }}'
OllamaModelSize: 'Get Ollama model size: {{ .name }}'
AIBenchmarkRun: 'Run AI benchmark'
# task-snapshot
# task - snapshot
Snapshot: 'Snapshot'
SnapDBInfo: 'Write 1Panel DB info'
SnapCopy: 'Copy files/directories {{ .name }}'
@@ -505,7 +516,6 @@ SnapCompressSize: 'Snapshot size {{ .name }}'
SnapUpload: 'Upload snapshot file'
SnapUploadTo: 'Upload snapshot to {{ .name }}'
SnapUploadRes: 'Upload snapshot to {{ .name }}'
SnapshotRecover: 'Recover snapshot'
RecoverDownload: 'Download snapshot file'
Download: 'Download'
@@ -562,10 +572,11 @@ RecoverFailedStartRollBack: 'Recovery failed; starting rollback'
AppBackupFileIncomplete: 'Backup is incomplete: missing app.json or app.tar.gz'
AppAttributesNotMatch: 'Application type or name does not match'
#alert
# alert
ErrAlertMethodNotSupported: 'This version does not support this alert method'
ErrAlertSync: 'Failed to sync alert message'
#task - runtime
# task - runtime
ErrInstallExtension: 'Another extension install task is running'
# alert mail template
@@ -586,7 +597,7 @@ NodeExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} nodes are abnormal.
LicenseExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} licenses are abnormal. Log in to view details."
SSHAndPanelLoginAlert: "Panel {{ .node }}{{ .ip }}: abnormal {{ .name }} login from {{ .loginIp }}. Log in to view details."
#disk
# disk
DeviceNotFound: "Device {{ .name }} not found"
DeviceIsMounted: 'Device {{ .name }} is mounted; unmount first'
PartitionDiskErr: 'Partition failed: {{ .err }}'
@@ -599,9 +610,8 @@ TerminalAIBlockedRiskyCommand: 'blocked risky command: {{ .command }}'
TerminalAIThinking: 'AI is thinking...'
TerminalAIReadyToExecute: 'Thinking complete, press Enter to execute (duration: {{ .duration }}, tokens: {{ .tokens }})'
TerminalAIRequestFailed: 'AI request failed: {{ .err }}'
FileAISearchEmptyDir: 'No files or directories found under this path (or all entries were filtered).'
# ai proxy
# AI gateway
ErrAIProxyServiceNotInstalled: '{{ .name }} service is not installed'
ErrAIProxyModelAccountNotFound: 'Model account {{ .id }} not found'
ErrAIProxyModelMapRequired: 'This field is required.'
@@ -609,4 +619,4 @@ ErrAIProxyModelMapInvalid: 'Model mapping must be a valid JSON object: {{ .err }
ErrAIProxyModelMapEmpty: 'Model mapping cannot be empty'
ErrAIProxyModelMapEmptyExternalModel: 'Model mapping contains an empty request model'
ErrAIProxyModelMapEmptyUpstreamModel: 'Upstream model for {{ .model }} cannot be empty'
AIProxyUserFallback: 'User {{ .id }}'
AIProxyUserFallback: 'Deleted user (ID: {{ .id }})'
+91 -20
View File
@@ -1,29 +1,47 @@
# errores básicos
ErrInvalidParams: 'Solicitud inválida: {{ .detail }}'
ErrTokenParse: 'Token inválido: {{ .detail }}'
ErrInitialPassword: 'Contraseña original incorrecta'
ErrInternalServer: 'Error interno: {{ .detail }}'
ErrRecordExist: 'Registro ya existe'
ErrRecordNotFound: 'Registro no encontrado'
ErrStructTransform: 'Error de conversión: {{ .err }}'
ErrNotSupportType: 'Tipo no soportado: {{ .name }}'
# IA de archivos
ErrFileAINotEnabled: 'La búsqueda con IA de archivos está desactivada o sin cuenta. En Gestión de archivos, use Cuenta de IA, active y elija una cuenta.'
ErrFileAISearchBadPattern: 'Patrón de búsqueda no válido (regex u otra opción): {{ .detail }}'
FileAISearchEmptyDir: 'No hay archivos ni carpetas bajo esta ruta (o todos fueron filtrados).'
# descarga remota
ErrWgetRemoteFailed: 'La descarga remota falló o la URL no es accesible: {{ .detail }}'
ErrWgetInvalidContentType: 'La URL remota no devolvió un archivo (posiblemente una página de error), {{ .detail }}'
ErrWgetProxyNotConfigured: 'Configure primero un servidor proxy en los ajustes del panel'
ErrWgetProxyInvalid: 'Dirección del servidor proxy no válida: {{ .detail }}'
ErrRecordExist: 'Registro ya existe'
ErrRecordNotFound: 'Registro no encontrado'
ErrStructTransform: 'Error de conversión: {{ .err }}'
# inicio de sesión y seguridad
ErrNotLogin: 'No has iniciado sesión: {{ .detail }}'
ErrPasswordExpired: 'Contraseña expirada: {{ .detail }}'
ErrNotSupportType: 'Tipo no soportado: {{ .name }}'
ErrAlertMethodNotSupported: 'Esta versión no admite este método de alerta'
# solicitud y API
ErrProxy: 'Solicitud fallida: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API desactivada: {{ .detail }}'
ErrApiConfigKeyInvalid: 'Clave API inválida: {{ .detail }}'
ErrApiConfigIPInvalid: 'IP no permitida: {{ .detail }}'
ErrApiConfigDisable: 'API no permitida: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'Timestamp inválido: {{ .detail }}'
ErrShutDown: 'Comando detenido manualmente'
ErrCreateHttpClient: 'Fallo al crear la solicitud {{ .err }}'
ErrHttpReqTimeOut: 'La solicitud superó el tiempo de espera {{ .err }}'
ErrHttpReqFailed: 'La solicitud falló {{ .err }}'
ErrNoSuchHost: 'No se pudo encontrar el servidor solicitado {{ .err }}'
ErrHttpReqNotFound: 'No se pudo encontrar el recurso solicitado {{ .err }}'
# salto rápido
ErrMinQuickJump: 'Agrega al menos una entrada rápida'
ErrMaxQuickJump: 'Máximo 4 entradas rápidas'
# común
ErrShutDown: 'Comando detenido manualmente'
ErrUsernameIsExist: 'Usuario ya existe'
ErrNameIsExist: 'Nombre ya existe'
ErrDemoEnvironment: 'No disponible en demo'
@@ -41,6 +59,8 @@ SystemRestart: 'La tarea fue interrumpida debido a un reinicio del sistema'
ErrGroupIsDefault: 'Grupo predeterminado, no se puede eliminar'
ErrGroupIsInWebsiteUse: 'El grupo está siendo usado por otro sitio web y no se puede eliminar.'
Decrypt: 'Descifrar'
# agente
ErrAgentAccountBound: 'Cuenta vinculada a agente'
ErrTerminalAIAccountInUse: 'Esta cuenta de modelo esta siendo usada por Terminal AI. Cambia la cuenta en la configuracion de Terminal AI o desactiva Terminal AI y vuelve a intentarlo.'
ErrAgentAccountUnavailable: 'Conexión de cuenta no disponible: {{ .err }}'
@@ -65,14 +85,8 @@ ErrAgentWebsiteInUse: 'Este sitio web ya está vinculado a otro agente'
ErrAgentWebsiteUnbindUnsupported: 'Los sitios web de despliegue no se pueden desvincular manualmente'
ErrHermesPairingCodeUnavailable: 'El código de emparejamiento no está disponible temporalmente en Hermes, posiblemente por un problema de red. Inténtalo de nuevo más tarde.'
ErrHermesFeishuGroupAllowlistRequiresAllowlist: 'Cuando la política de grupo de Feishu es Lista permitida, la política de MD no puede ser Código de emparejamiento.'
ErrAIProxyServiceNotInstalled: 'El servicio {{ .name }} no está instalado'
ErrAIProxyModelAccountNotFound: 'Cuenta de modelo {{ .id }} no encontrada'
ErrAIProxyModelMapRequired: 'Este campo es obligatorio.'
ErrAIProxyModelMapInvalid: 'El mapeo de modelos debe ser un objeto JSON válido: {{ .err }}'
ErrAIProxyModelMapEmpty: 'El mapeo de modelos no puede estar vacío'
ErrAIProxyModelMapEmptyExternalModel: 'El mapeo de modelos contiene un modelo solicitado vacío'
ErrAIProxyModelMapEmptyUpstreamModel: 'El modelo upstream de {{ .model }} no puede estar vacío'
AIProxyUserFallback: 'Usuario {{ .id }}'
# copia de seguridad y configuración de alertas
Localhost: 'Máquina local'
ErrBackupInUsed: 'Cuenta de respaldo en uso por tarea programada'
ErrBackupCheck: 'Conexión de respaldo falló: {{ .err }}'
@@ -80,6 +94,8 @@ ErrBackupLocalDelete: 'No se puede eliminar cuentas de respaldo locales'
ErrBackupLocalCreate: 'No se pueden crear cuentas de respaldo locales'
ErrAlertConfigInUse: 'La configuración de alertas está en uso y no se puede eliminar'
ErrAlertConfigPhoneExist: 'El numero de telefono ya existe'
# aplicación
ErrPortInUsed: 'El puerto {{ .detail }} ya está ocupado'
ErrAppLimit: 'El número de aplicaciones instaladas ha superado el límite'
ErrNotInstall: 'Aplicación no instalada'
@@ -97,16 +113,12 @@ ErrFileParseApp: 'El archivo {{ .name }} falló al analizarse {{ .err }}'
ErrAppDirNull: 'La carpeta de la versión no existe'
LocalAppErr: 'La sincronización de la aplicación {{ .name }} falló {{ .err }}'
ErrContainerName: 'El nombre del contenedor ya existe'
ErrCreateHttpClient: 'Fallo al crear la solicitud {{ .err }}'
ErrHttpReqTimeOut: 'La solicitud superó el tiempo de espera {{ .err }}'
ErrHttpReqFailed: 'La solicitud falló {{ .err }}'
ErrNoSuchHost: 'No se pudo encontrar el servidor solicitado {{ .err }}'
ErrHttpReqNotFound: 'No se pudo encontrar el recurso solicitado {{ .err }}'
ErrContainerNotFound: 'El contenedor {{ .name }} no existe'
ErrContainerMsg: 'El contenedor {{ .name }} presenta anomalías. Revise el log en la página de contenedores para más detalles'
ErrAppBackup: 'Falló el respaldo de la aplicación {{ .name }} {{ .err }}'
ErrVersionTooLow: 'La versión actual de 1Panel es demasiado baja para actualizar la tienda de aplicaciones. Actualice primero.'
ErrAppNameExist: 'El nombre de la aplicación ya existe'
ErrAppInstallNameExist: 'El nombre de instalación de la aplicación {{ .name }} ya existe'
AppStoreIsSyncing: 'La tienda de aplicaciones está sincronizando, inténtelo más tarde'
ErrGetCompose: 'Fallo al obtener el archivo docker-compose.yml {{ .detail }}'
ErrAppWarn: 'Estado anómalo, revise el log'
@@ -131,6 +143,8 @@ ErrDockerFailed: 'El estado de Docker es anómalo, revise el servicio'
ErrDockerComposeCmdNotFound: 'El comando Docker Compose no existe instálelo primero en el host'
UseExistImage: 'La imagen ya existe, usando imagen existente'
ErrDatabaseNotFound: 'La base de datos {{ .name }} no existe'
# SSH
ExportIP: 'IP de inicio de sesión'
ExportArea: 'Ubicación'
ExportPort: 'Puerto'
@@ -138,6 +152,8 @@ ExportAuthMode: 'Método de inicio de sesión'
ExportUser: 'Usuario'
ExportStatus: 'Estado de inicio de sesión'
ExportDate: 'Fecha y hora'
# archivo
ErrFileCanNotRead: 'Este archivo no soporta vista previa'
ErrSensitiveFileRead: 'No se permite leer archivos sensibles'
ErrFileToLarge: 'El archivo es mayor a 10M y no puede abrirse'
@@ -164,6 +180,8 @@ FileDropFailed: 'Error al limpiar el archivo {{ .name }}: {{ .err }}'
FileDropSuccess: 'Archivo {{ .name }} limpiado correctamente, {{ .count }} archivos eliminados, {{ .size }} de espacio liberado'
FileDropSum: 'Limpieza de archivos completada, total {{ .count }} archivos eliminados, {{ .size }} de espacio liberado'
ErrBadDecrypt: 'Error de contraseña de descifrado del paquete comprimido, descompresión fallida, compruebe e inténtelo de nuevo'
# sitio web
ErrAliasIsExist: 'El alias ya existe'
ErrBackupMatch: 'El archivo de respaldo no coincide con parte de los datos actuales del sitio web {{ .detail }}'
ErrBackupExist: 'Parte de los datos fuente del respaldo no existe {{ .detail }}'
@@ -193,6 +211,8 @@ delete: 'Eliminar'
ErrDefaultWebsite: 'El sitio web predeterminado ya está configurado, cancélelo antes de configurar'
SSL: 'Certificado'
Set: 'Configuración'
# certificado SSL
ErrSSLCannotDelete: 'El certificado {{ .name }} está siendo utilizado por un sitio web y no puede eliminarse'
ErrAccountCannotDelete: 'La cuenta está asociada a un certificado y no puede eliminarse'
ErrSSLApply: 'Renovación del certificado exitosa, recarga de openresty fallida, revise la configuración'
@@ -227,6 +247,8 @@ ErrApplySSLCanNotDelete: 'El certificado {{ .name }} en proceso de solicitud no
StartPushSSLToNode: 'Iniciando envío de certificado al nodo'
PushSSLToNodeFailed: 'Error al enviar el certificado al nodo: {{ .err }}'
PushSSLToNodeSuccess: 'Certificado enviado correctamente al nodo'
# MySQL
ErrUserIsExist: 'El usuario actual ya existe, intente con otro'
ErrDatabaseIsExist: 'La base de datos actual ya existe, intente con otro nombre'
ErrExecTimeOut: 'Tiempo de espera en la ejecución SQL, revise la base de datos'
@@ -235,7 +257,11 @@ ErrLocalExist: 'El nombre ya existe en la base de datos local, modifíquelo e in
RemoteBackup: 'Para hacer una copia de seguridad de la base de datos remota, primero debe iniciarse el servicio de base de datos del contenedor local utilizando la imagen {{ .name }}, espere...'
RemoteRecover: 'Para restaurar la base de datos remota, primero debe iniciarse el servicio de base de datos del contenedor local utilizando la imagen {{ .name }}, espere...'
Arg: 'Argumento'
# Redis
ErrTypeOfRedis: 'El tipo de archivo de recuperación no coincide con el método de persistencia actual, modifíquelo e intente'
# contenedor
ErrInUsed: '{{ .detail }} está en uso y no se puede eliminar'
ErrObjectInUsed: 'Este objeto está en uso y no se puede eliminar'
ErrObjectBeDependent: 'Esta imagen es dependida por otras imágenes y no se puede eliminar'
@@ -277,6 +303,8 @@ ComposeRecoverFiles: 'Restaurar archivos de compose'
ComposeRecoverContainers: 'Restaurar contenedores del compose'
ComposeRecoverRecord: 'Guardar registro de compose'
ComposeRecoverCleanup: 'Limpiar espacio de restauracion de compose'
# entorno de ejecución
ErrFileNotExist: 'El archivo {{ .detail }} no existe, verifique la integridad'
ErrImageBuildErr: 'Fallo al construir la imagen'
ErrImageExist: 'La imagen ya existe Modifique el nombre de la imagen.'
@@ -289,12 +317,16 @@ ErrNodeModulesNotFound: 'La carpeta node_modules no existe, edite o espere a que
ErrContainerNameIsNull: 'El nombre del contenedor no existe'
ErrPHPPortIsDefault: 'El puerto 9000 es el predeterminado, modifique e intente de nuevo'
ErrPHPRuntimePortFailed: 'El puerto {{ .name }} ya está en uso por el entorno actual, modifique e intente'
# herramienta
ErrConfigNotFound: 'El archivo de configuración no existe'
ErrConfigParse: 'El formato del archivo de configuración es incorrecto'
ErrConfigIsNull: 'El archivo de configuración no puede estar vacío'
ErrConfigDirNotFound: 'El directorio de ejecución no existe'
ErrConfigAlreadyExist: 'Ya existe un archivo de configuración con ese nombre'
ErrUserFindErr: 'Búsqueda del usuario {{ .name }} fallida {{ .err }}'
# tarea programada
CutWebsiteLogSuccess: 'Log del sitio web {{ .name }} cortado con éxito, ruta de respaldo {{ .path }}'
HandleShell: 'Ejecutar script {{ .name }}'
HandleCurl: 'URL {{ .name }} にアクセス'
@@ -313,6 +345,8 @@ NoSuchResource: 'No se encontraron contenidos de respaldo en la base de datos, o
CleanLog: 'Limpiar registro'
CleanLogByName: 'Limpiar registro de {{ .name }}'
WafIpGroupNotFound: 'Grupo de IP de WAF no encontrado., vaya a [Funciones Avanzadas - WAF - Lista Blanca/Negra - Grupo de IP] para agregar un grupo de IP mediante el método de descarga remota.'
# caja de herramientas
ErrNotExistUser: 'El usuario actual no existe, modifíquelo e intente de nuevo'
ErrBanAction: 'Fallo al configurar. El servicio {{ .name }} no está disponible, revise e intente de nuevo'
ErrClamdscanNotFound: 'El comando clamdscan no fue detectado, siga la documentación para instalarlo'
@@ -321,11 +355,15 @@ SetSwap: 'Configurar Swap {{ .path }} - {{ .size }}'
CreateSwap: 'Crear Archivo Swap'
FormatSwap: 'Formatear Archivo Swap'
EnableSwap: 'Habilitar Swap'
# protección contra manipulación
CleanTamperSetting: 'Limpiar configuraciones de protección históricas'
SetTamperWithRules: 'Iniciar protección de directorio según reglas'
ProtectDir: 'Proteger directorio {{ .name }}'
IgnoreFile: 'Proteger archivo {{ .name }}'
TamperSetSuccessful: 'Configuración de protección completada, comenzando monitoreo de los siguientes directorios:'
# WAF
ErrScope: 'No se admite la modificación de esta configuración'
ErrStateChange: 'Fallo al cambiar de estado'
ErrRuleExist: 'La regla ya existe'
@@ -380,6 +418,8 @@ OpenrestyNotFound: 'OpenResty no está instalado'
remoteIpIsNull: 'La lista de IP está vacía'
OpenrestyVersionErr: 'La versión de OpenResty es demasiado antigua. Actualízalo a 1.27.1.2-2-2-focal'
ErrFileTooLarge: 'El archivo supera 1 MB y no se puede cargar'
# tarea
TaskStart: 'La tarea {{ .name }} inicia [INICIO]'
TaskEnd: 'La tarea {{ .name }} completada [COMPLETADA]'
TaskFailed: 'La tarea {{ .name }} falló'
@@ -440,10 +480,17 @@ CustomAppstore: 'Almacén de aplicaciones personalizado'
TaskExec: 'Ejecutar'
TaskBatch: 'Operación por Lotes'
FileConvert: 'Conversión de Formato de Archivo'
# tarea - ClamAV
Clamscan: 'Escanear {{ .name }}'
TaskScan: 'Escaneo'
# tarea - IA
OllamaModelPull: 'Descargar modelo Ollama {{ .name }}'
OllamaModelSize: 'Obtener tamaño del modelo Ollama {{ .name }}'
AIBenchmarkRun: "Ejecutar prueba de referencia de IA"
# tarea - instantánea
Snapshot: 'Instantánea'
SnapDBInfo: 'Escribir información de base de datos de 1Panel'
SnapCopy: 'Copiar archivos y directorios {{ .name }}'
@@ -493,6 +540,8 @@ RecoverDaemonJson: 'Restaurar archivo daemon.json de configuración de contenedo
RecoverDBData: 'Restaurar datos de base de datos'
RecoverBackups: 'Restaurar directorio local de respaldo'
RecoverPanelData: 'Recuperar directorio de datos'
# tarea - contenedor
ContainerImagePull: 'Descargar imagen de contenedor {{ .name }}'
ContainerRemoveOld: 'Eliminar el contenedor original {{ .name }}'
ContainerImageCheck: 'Comprobar si la imagen se descargó correctamente'
@@ -501,6 +550,8 @@ ContainerRecreate: 'Actualización del contenedor fallida, restaurando el conten
ContainerCreate: 'Crear nuevo contenedor {{ .name }}'
ContainerCreateFailed: 'Falló la creación del contenedor, eliminando el contenedor fallido'
ContainerStartCheck: 'Verificar si el contenedor ha iniciado'
# tarea - imagen
ImageBuild: 'Construcción de imagen'
ImagePull: 'Descargar imagen'
ImageRepoAuthFromDB: 'Obtener información de autenticación del repositorio desde la base de datos'
@@ -508,6 +559,8 @@ ImagePush: 'Subir imagen'
ImageRenameTag: 'Modificar etiqueta de imagen'
ImageNewTag: 'Nueva etiqueta de imagen {{ .name }}'
ComposeCreate: 'Crear orquestación'
# tarea - sitio web
BackupNginxConfig: 'Respaldar archivo de configuración OpenResty'
CompressFileSuccess: 'Directorio comprimido con éxito, guardado en {{ .name }}'
CompressDir: 'Directorio comprimido'
@@ -518,8 +571,15 @@ websiteDir: 'Directorio del sitio web'
RecoverFailedStartRollBack: 'Restauración fallida, iniciando reversión'
AppBackupFileIncomplete: 'El archivo de respaldo está incompleto, falta app.json o app.tar.gz'
AppAttributesNotMatch: 'El tipo o nombre de la aplicación no coincide'
# alerta
ErrAlertMethodNotSupported: 'Esta versión no admite este método de alerta'
ErrAlertSync: 'Error en la sincronización de la alerta, revise e intente de nuevo'
# tarea - entorno de ejecución
ErrInstallExtension: 'Ya hay una tarea de instalación en curso, espere a que termine'
# plantilla de correo de alerta
PanelAlertTitle: 'Notificación de alerta del panel'
TestAlertTitle: 'Correo de prueba - Verificar conectividad de correo'
TestAlert: 'Este es un correo de prueba para verificar que la configuración de envío de correos es correcta.'
@@ -536,6 +596,8 @@ CommonAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .msg }}. Inicie sesión en el pa
NodeExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} nodos son anómalos. Inicie sesión en el panel para ver los detalles.'
LicenseExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} licencias son anómalas. Inicie sesión en el panel para ver los detalles.'
SSHAndPanelLoginAlert: 'Su Panel {{ .node }}{{ .ip }}, el inicio de sesión {{ .name }} desde {{ .loginIp }} es anómalo. Inicie sesión en el panel para ver los detalles.'
# disco
DeviceNotFound: 'Dispositivo {{ .name }} no encontrado'
DeviceIsMounted: 'El dispositivo {{ .name }} está montado, desmóntelo primero'
PartitionDiskErr: 'Error al particionar, {{ .err }}'
@@ -548,4 +610,13 @@ TerminalAIBlockedRiskyCommand: 'Comando de riesgo bloqueado: {{ .command }}'
TerminalAIThinking: 'La IA está pensando...'
TerminalAIReadyToExecute: 'Pensamiento completado, pulsa Enter para ejecutar (duración: {{ .duration }}, token: {{ .tokens }})'
TerminalAIRequestFailed: 'La solicitud de AI ha fallado: {{ .err }}'
FileAISearchEmptyDir: 'No hay archivos ni carpetas bajo esta ruta (o todos fueron filtrados).'
# puerta de enlace de IA
ErrAIProxyServiceNotInstalled: 'El servicio {{ .name }} no está instalado'
ErrAIProxyModelAccountNotFound: 'Cuenta de modelo {{ .id }} no encontrada'
ErrAIProxyModelMapRequired: 'Este campo es obligatorio.'
ErrAIProxyModelMapInvalid: 'El mapeo de modelos debe ser un objeto JSON válido: {{ .err }}'
ErrAIProxyModelMapEmpty: 'El mapeo de modelos no puede estar vacío'
ErrAIProxyModelMapEmptyExternalModel: 'El mapeo de modelos contiene un modelo solicitado vacío'
ErrAIProxyModelMapEmptyUpstreamModel: 'El modelo upstream de {{ .model }} no puede estar vacío'
AIProxyUserFallback: 'Usuario eliminado (ID: {{ .id }})'
+622
View File
@@ -0,0 +1,622 @@
# خطاهای پایه
ErrInvalidParams: 'درخواست نامعتبر: {{ .detail }}'
ErrTokenParse: 'خطا در توکن: {{ .detail }}'
ErrInitialPassword: 'رمز عبور اصلی اشتباه است'
ErrInternalServer: 'خطای داخلی سرور: {{ .detail }}'
ErrRecordExist: 'رکورد از قبل وجود دارد'
ErrRecordNotFound: 'رکورد یافت نشد'
ErrStructTransform: 'تبدیل نوع ناموفق بود: {{ .err }}'
ErrNotSupportType: 'نوع پشتیبانی نمی‌شود: {{ .name }}'
# هوش مصنوعی فایل
ErrFileAINotEnabled: 'خلاصه‌سازی هوش مصنوعی مدیریت فایل نیاز به فعال بودن حساب هوش مصنوعی در مدیریت فایل دارد. جستجوی صرفاً محتوایی زمانی که هوش مصنوعی خاموش است همچنان کار می‌کند.'
ErrFileAISearchBadPattern: 'الگوی جستجوی فایل نامعتبر است (عبارت منظم یا کل کلمه): {{ .detail }}'
FileAISearchEmptyDir: 'هیچ فایل یا دایرکتوری در این مسیر یافت نشد (یا همه ورودی‌ها فیلتر شدند).'
# دانلود از راه دور
ErrWgetRemoteFailed: 'دانلود از راه دور ناموفق بود یا URL قابل دسترس نیست: {{ .detail }}'
ErrWgetInvalidContentType: 'URL از راه دور فایلی را برنگرداند (احتمالاً صفحه خطا)، {{ .detail }}'
ErrWgetProxyNotConfigured: 'ابتدا یک سرور پروکسی در تنظیمات پنل پیکربندی کنید'
ErrWgetProxyInvalid: 'آدرس سرور پروکسی نامعتبر است: {{ .detail }}'
# ورود و امنیت
ErrNotLogin: 'وارد نشده‌اید: {{ .detail }}'
ErrPasswordExpired: 'رمز عبور منقضی شده است: {{ .detail }}'
# درخواست و API
ErrProxy: 'درخواست ناموفق بود: {{ .detail }}'
ErrApiConfigStatusInvalid: 'دسترسی API غیرفعال است: {{ .detail }}'
ErrApiConfigKeyInvalid: 'کلید API نامعتبر است: {{ .detail }}'
ErrApiConfigIPInvalid: 'IP در لیست سفید API نیست: {{ .detail }}'
ErrApiConfigDisable: 'نقطه پایانی از طریق API مجاز نیست: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'مهر زمانی API نامعتبر است: {{ .detail }}'
ErrCreateHttpClient: 'ایجاد درخواست HTTP ناموفق بود: {{ .err }}'
ErrHttpReqTimeOut: 'زمان درخواست HTTP به پایان رسید: {{ .err }}'
ErrHttpReqFailed: 'درخواست HTTP ناموفق بود: {{ .err }}'
ErrNoSuchHost: 'تفکیک میزبان ناموفق بود: {{ .err }}'
ErrHttpReqNotFound: 'منبع درخواستی یافت نشد: {{ .err }}'
# پرش سریع
ErrMinQuickJump: 'حداقل یک ورودی پرش سریع تنظیم کنید'
ErrMaxQuickJump: 'حداکثر ۴ ورودی پرش سریع'
# عمومی
ErrShutDown: 'دستور به صورت دستی متوقف شد'
ErrUsernameIsExist: 'نام کاربری از قبل وجود دارد'
ErrNameIsExist: 'نام از قبل وجود دارد'
ErrDemoEnvironment: 'در محیط آزمایشی غیرفعال است'
ErrCmdTimeout: 'زمان اجرای دستور به پایان رسید'
ErrCmdIllegal: 'دستور شامل کاراکترهای غیرمجاز است'
ErrPortExist: 'پورت {{ .port }} قبلاً توسط {{ .type }} [{{ .name }}] اشغال شده است'
TYPE_APP: 'برنامه'
TYPE_RUNTIME: 'محیط اجرا'
TYPE_DOMAIN: 'دامنه'
ErrTypePort: 'فرمت پورت نامعتبر است: {{ .name }}'
ErrTypePortRange: 'محدوده پورت باید ۱-۶۵۵۳۵ باشد'
Success: 'موفق'
Failed: 'ناموفق'
SystemRestart: 'وظیفه به دلیل راه‌اندازی مجدد سیستم قطع شد'
ErrGroupIsDefault: 'گروه پیش‌فرض، قابل حذف نیست'
ErrGroupIsInWebsiteUse: 'گروه توسط یک وب‌سایت در حال استفاده است'
Decrypt: "رمزگشایی"
# عامل
ErrAgentAccountBound: 'حساب به یک عامل متصل است و قابل حذف نیست'
ErrTerminalAIAccountInUse: 'این حساب مدل در حال حاضر توسط ترمینال هوش مصنوعی استفاده می‌شود. حساب را در تنظیمات ترمینال هوش مصنوعی تغییر دهید یا ترمینال هوش مصنوعی را غیرفعال کرده و دوباره تلاش کنید.'
ErrAgentAccountUnavailable: 'اتصال حساب در دسترس نیست: {{ .err }}'
ErrAgentProviderNotSupported: 'ارائه‌دهنده عامل پشتیبانی نمی‌شود'
ErrAgentAccountRequired: 'ابتدا یک حساب عامل انتخاب کنید'
ErrAgentAccountNotVerified: 'حساب عامل تأیید نشده است'
ErrAgentAccountModelsRequired: 'لطفاً حداقل یک مدل پیکربندی کنید'
ErrAgentAccountSingleInitialModel: 'فقط یک مدل اولیه می‌تواند در هنگام ایجاد حساب پیکربندی شود'
ErrAgentModelNotInAccount: 'مدل در حساب انتخاب شده پیکربندی نشده است'
ErrAgentFallbackModelDuplicate: 'مدل‌های جایگزین نمی‌توانند تکراری باشند'
ErrAgentFallbackModelPrimary: 'مدل جایگزین نمی‌تواند با مدل اصلی یکی باشد'
ErrAgentModelInUse: 'یک عامل متصل هنوز از این مدل استفاده می‌کند: {{ .name }}'
ErrAgentBaseURLRequired: 'آدرس پایه الزامی است'
ErrAgentApiKeyRequired: 'کلید API الزامی است'
ErrAgentComposeRequired: 'محتوای Compose الزامی است'
ErrAgentIDRequired: 'شناسه عامل الزامی است'
ErrAgentAccountIDRequired: 'شناسه حساب الزامی است'
ErrAgentLimitReached: 'نسخه انجمن تا {{ .max }} عامل هوش مصنوعی را پشتیبانی می‌کند. برای عامل‌های نامحدود به نسخه تجاری ارتقاء دهید'
ErrAgentWebsiteBound: 'این عامل قبلاً به یک وب‌سایت متصل است'
ErrAgentWebsiteTypeUnsupported: 'فقط وب‌سایت‌های پراکسی یا استاتیک قابل اتصال هستند'
ErrAgentWebsiteInUse: 'این وب‌سایت قبلاً به عامل دیگری متصل است'
ErrAgentWebsiteUnbindUnsupported: 'وب‌سایت‌های استقرار یافته را نمی‌توان به صورت دستی قطع اتصال کرد'
ErrHermesPairingCodeUnavailable: 'کد جفت‌سازی به طور موقت در هرمس در دسترس نیست، احتمالاً به دلیل مشکلات شبکه. لطفاً بعداً دوباره تلاش کنید.'
ErrHermesFeishuGroupAllowlistRequiresAllowlist: 'وقتی خط مشی گروه فیشو لیست سفید باشد، خط مشی DM نمی‌تواند کد جفت‌سازی باشد.'
# پشتیبان‌گیری و تنظیمات هشدار
Localhost: 'محلی'
ErrBackupInUsed: 'حساب پشتیبان توسط یک وظیفه زمان‌بندی در حال استفاده است'
ErrBackupCheck: 'تست اتصال حساب پشتیبان ناموفق بود: {{ .err }}'
ErrBackupLocalDelete: 'حساب‌های پشتیبان محلی قابل حذف نیستند'
ErrBackupLocalCreate: 'حساب‌های پشتیبان محلی قابل ایجاد نیستند'
ErrAlertConfigInUse: 'پیکربندی هشدار در حال استفاده است و قابل حذف نیست'
ErrAlertConfigPhoneExist: 'شماره تلفن از قبل وجود دارد'
# برنامه
ErrPortInUsed: 'پورت {{ .detail }} قبلاً اشغال شده است!'
ErrAppLimit: 'محدودیت نصب برنامه رسیده است'
ErrNotInstall: 'برنامه نصب نشده است'
ErrPortInOtherApp: 'پورت {{ .port }} قبلاً توسط برنامه {{ .apps }} اشغال شده است!'
ErrDbUserNotValid: 'نام کاربری/رمز عبور پایگاه داده مطابقت ندارد'
ErrUpdateBuWebsite: 'برنامه به‌روزرسانی شد، اما به‌روزرسانی پیکربندی وب‌سایت ناموفق بود'
Err1PanelNetworkFailed: 'ایجاد شبکه پیش‌فرض کانتینر ناموفق بود! {{ .detail }}'
ErrFileParse: 'تجزیه فایل docker-compose برنامه ناموفق بود'
ErrInstallDirNotFound: 'دایرکتوری نصب یافت نشد؛ از حذف اجباری استفاده کنید'
AppStoreIsUpToDate: 'فروشگاه برنامه از قبل آخرین نسخه است'
LocalAppVersionNull: 'برنامه {{ .name }} هیچ نسخه همگام‌سازی شده‌ای ندارد'
LocalAppVersionErr: 'همگام‌سازی نسخه {{ .version }} برنامه {{ .name }} ناموفق بود: {{ .err }}'
ErrFileNotFound: 'فایل {{ .name }} وجود ندارد'
ErrFileParseApp: 'تجزیه {{ .name }} ناموفق بود: {{ .err }}'
ErrAppDirNull: 'پوشه نسخه وجود ندارد'
LocalAppErr: 'همگام‌سازی برنامه {{ .name }} ناموفق بود: {{ .err }}'
ErrContainerName: 'نام کانتینر از قبل وجود دارد'
ErrContainerNotFound: 'کانتینر {{ .name }} وجود ندارد'
ErrContainerMsg: 'کانتینر {{ .name }} غیرعادی است؛ لاگ‌ها را بررسی کنید'
ErrAppBackup: 'پشتیبان‌گیری از برنامه ناموفق بود: {{ .name }} {{ .err }}'
ErrVersionTooLow: 'نسخه 1Panel برای به‌روزرسانی فروشگاه برنامه بسیار پایین است'
ErrAppNameExist: 'نام برنامه از قبل وجود دارد'
ErrAppInstallNameExist: 'نام نصب برنامه {{ .name }} از قبل وجود دارد'
AppStoreIsSyncing: 'فروشگاه برنامه در حال همگام‌سازی است؛ بعداً دوباره تلاش کنید'
ErrGetCompose: 'خواندن docker-compose.yml ناموفق بود: {{ .detail }}'
ErrAppWarn: 'وضعیت برنامه غیرعادی است؛ لاگ‌ها را بررسی کنید'
ErrAppParamKey: 'پارامتر برنامه نامعتبر است: {{ .name }}'
ErrAppUpgrade: 'ارتقاء برنامه ناموفق بود: {{ .name }} {{ .err }}'
AppRecover: 'بازگشت برنامه {{ .name }}'
PullImageStart: 'دریافت تصویر [{{ .name }}]'
PullImageSuccess: 'تصویر دریافت شد'
AppStoreSyncSuccess: 'فروشگاه برنامه همگام‌سازی شد'
SyncAppDetail: 'همگام‌سازی پیکربندی برنامه'
AppVersionNotMatch: 'رد شدن {{ .name }}: نیاز به 1Panel جدیدتر دارد'
MoveSiteDir: 'دایرکتوری وب‌سایت تغییر کرد؛ مهاجرت دایرکتوری OpenResty به {{ .name }}'
MoveSiteDirSuccess: 'دایرکتوری وب‌سایت مهاجرت کرد'
DeleteRuntimePHP: 'حذف محیط اجرا PHP'
CustomAppStoreFileValid: 'بسته فروشگاه برنامه باید .tar.gz باشد'
PullImageTimeout: 'زمان دریافت تصویر به پایان رسید؛ آینه دیگری را امتحان کنید'
ErrAppIsDown: 'برنامه {{ .name }} غیرعادی است'
ErrCustomApps: 'برنامه‌های نصب شده شناسایی شدند؛ ابتدا حذف نصب کنید'
ErrCustomRuntimes: 'محیط‌های اجرایی نصب شده شناسایی شدند؛ ابتدا حذف کنید'
ErrAppVersionDeprecated: "برنامه {{ .name }} با نسخه فعلی 1Panel سازگار نیست، رد شد"
ErrDockerFailed: 'Docker غیرعادی است؛ وضعیت سرویس را بررسی کنید'
ErrDockerComposeCmdNotFound: 'Docker Compose روی میزبان یافت نشد'
UseExistImage: 'تصویر وجود دارد؛ استفاده از تصویر موجود'
ErrDatabaseNotFound: 'پایگاه داده یافت نشد: {{ .name }}'
# SSH
ExportIP: "IP ورود"
ExportArea: "مکان"
ExportPort: "پورت"
ExportAuthMode: "روش ورود"
ExportUser: "کاربر"
ExportStatus: "وضعیت ورود"
ExportDate: "زمان"
# فایل
ErrFileCanNotRead: 'پیش‌نمایش فایل پشتیبانی نمی‌شود'
ErrSensitiveFileRead: 'خواندن فایل‌های حساس مجاز نیست'
ErrFileToLarge: 'فایل بزرگتر از ۱۰ مگابایت است'
ErrPathNotFound: 'دایرکتوری وجود ندارد'
ErrMovePathFailed: 'مسیر هدف نمی‌تواند شامل مسیر منبع باشد'
ErrLinkPathNotFound: 'مسیر هدف وجود ندارد!'
ErrFileIsExist: 'فایل یا پوشه از قبل وجود دارد!'
ErrFileUpload: 'بارگذاری ناموفق بود: {{ .name }} {{ .detail }}'
ErrFileDownloadDir: 'دانلود دایرکتوری پشتیبانی نمی‌شود'
ErrFileSharePath: 'مسیر فایل نامعتبر است یا هدف یک فایل نیست'
ErrFileShareInvalid: 'لینک اشتراک نامعتبر یا منقضی شده است'
ErrFileShareExpired: 'لینک اشتراک منقضی شده است'
ErrFileSharePassword: 'رمز عبور اشتراک اشتباه است'
ErrFileShareExhausted: 'محدودیت دانلود برای این اشتراک رسیده است'
ErrFileSharePasswordPolicy: 'رمز عبور باید حداقل ۴ کاراکتر باشد'
ErrFileShareRateLimit: 'تعداد درخواست‌ها زیاد است، لطفاً بعداً دوباره تلاش کنید'
ErrCmdNotFound: 'دستور یافت نشد: {{ .name }}'
ErrSourcePathNotFound: 'دایرکتوری منبع وجود ندارد'
ErrFavoriteExist: 'مسیر از قبل در نشان‌ها وجود دارد'
ErrInvalidChar: 'کاراکترهای غیرمجاز مجاز نیستند'
ErrPathNotDelete: 'دایرکتوری انتخاب شده قابل حذف نیست'
ErrLogFileToLarge: 'فایل لاگ بیش از ۵۰۰ مگابایت است'
FileDropFailed: 'پاک‌سازی فایل ناموفق بود: {{ .name }} {{ .err }}'
FileDropSuccess: 'پاک‌سازی {{ .name }}؛ فایل‌ها: {{ .count }}، آزاد شده: {{ .size }}'
FileDropSum: 'پاک‌سازی انجام شد؛ فایل‌ها: {{ .count }}، آزاد شده: {{ .size }}'
ErrBadDecrypt: 'رمز عبور آرشیو اشتباه است؛ استخراج ناموفق بود'
# وب‌سایت
ErrAliasIsExist: 'نام مستعار از قبل وجود دارد'
ErrBackupMatch: 'داده‌های پشتیبان مطابقت ندارند: {{ .detail }}'
ErrBackupExist: 'داده‌های منبع پشتیبان وجود ندارند: {{ .detail }}'
ErrPHPResource: 'تغییر محیط اجرای محلی پشتیبانی نمی‌شود'
ErrPathPermission: 'پوشه غیر ۱۰۰۰:۱۰۰۰ در دایرکتوری ایندکس ممکن است باعث خطای دسترسی شود'
ErrDomainIsUsed: 'نام دامنه قبلاً توسط وب‌سایت [{{ .name }}] استفاده می‌شود'
ErrDomainFormat: 'فرمت دامنه نامعتبر است: {{ .name }}'
ErrDefaultAlias: 'default رزرو شده است؛ از نام مستعار دیگری استفاده کنید'
ErrParentWebsite: 'ابتدا زیرسایت {{ .name }} را حذف کنید'
ErrBuildDirNotFound: 'دایرکتوری ساخت وجود ندارد'
ErrImageNotExist: 'تصویر محیط اجرا یافت نشد: {{ .name }}'
ErrProxyIsUsed: 'تعادل بار توسط پراکسی معکوس استفاده می‌شود'
ErrSSLValid: 'فایل گواهی نامعتبر است'
ErrWebsiteDir: 'یک زیردایرکتوری در زیر دایرکتوری وب‌سایت انتخاب کنید'
ErrComposerFileNotFound: "فایل composer.json وجود ندارد"
ErrRuntimeNoPort: 'پورت محیط اجرا تنظیم نشده است'
ErrRuntimeProjectDirContainsCodeDir: 'دایرکتوری محیط اجرا شامل دایرکتوری پروژه انتخاب شده است. لطفاً دایرکتوری پروژه دیگری را انتخاب کرده و دوباره ایجاد کنید.'
Status: 'وضعیت'
start: 'شروع'
stop: 'توقف'
restart: 'راه‌اندازی مجدد'
kill: 'توقف اجباری'
pause: 'مکث'
unpause: 'ادامه'
remove: 'حذف'
delete: 'حذف'
ErrDefaultWebsite: 'وب‌سایت پیش‌فرض از قبل تنظیم شده است؛ ابتدا آن را لغو تنظیم کنید'
SSL: 'گواهی'
Set: 'تنظیمات'
# گواهی SSL
ErrSSLCannotDelete: 'گواهی {{ .name }} توسط یک وب‌سایت در حال استفاده است'
ErrAccountCannotDelete: 'حساب به یک گواهی متصل است و قابل حذف نیست'
ErrSSLApply: 'گواهی تمدید شد؛ بارگذاری مجدد OpenResty ناموفق بود'
ErrEmailIsExist: 'ایمیل از قبل وجود دارد'
ErrSSLKeyNotFound: 'فایل کلید خصوصی وجود ندارد'
ErrSSLCertificateNotFound: 'فایل گواهی وجود ندارد'
ErrSSLKeyFormat: 'تأیید فایل کلید خصوصی ناموفق بود'
ErrSSLCertificateFormat: 'فرمت گواهی نامعتبر است؛ از PEM استفاده کنید'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid یا EabHmacKey نمی‌توانند خالی باشند'
ErrOpenrestyNotFound: 'حالت HTTP نیاز به OpenResty دارد'
ApplySSLStart: 'شروع درخواست گواهی. دامنه: [{{ .domain }}]، روش: [{{ .type }}]'
dnsAccount: 'خودکار DNS'
dnsManual: 'دستی DNS'
http: 'HTTP'
ApplySSLFailed: 'درخواست گواهی برای [{{ .domain }}] ناموفق بود: {{ .detail }}'
ApplySSLSuccess: 'گواهی برای [{{ .domain }}] صادر شد'
DNSAccountName: 'حساب DNS [{{ .name }}]، ارائه‌دهنده [{{ .type }}]'
PushDirLog: 'گواهی به دایرکتوری [{{ .path }}] ارسال شد {{ .status }}'
ErrDeleteCAWithSSL: 'CA گواهی‌هایی صادر کرده است؛ قابل حذف نیست'
ErrDeleteWithPanelSSL: 'گواهی در تنظیمات SSL پنل در حال استفاده است'
ErrDefaultCA: 'مرجع پیش‌فرض قابل حذف نیست'
ApplyWebSiteSSLLog: 'به‌روزرسانی گواهی وب‌سایت: {{ .name }}'
ErrUpdateWebsiteSSL: 'به‌روزرسانی گواهی وب‌سایت {{ .name }} ناموفق بود: {{ .err }}'
ApplyWebSiteSSLSuccess: 'گواهی وب‌سایت به‌روزرسانی شد'
ErrExecShell: 'اجرای اسکریپت ناموفق بود {{ .err }}'
ExecShellStart: 'اجرای اسکریپت'
ExecShellSuccess: 'اسکریپت با موفقیت اجرا شد'
StartUpdateSystemSSL: 'به‌روزرسانی گواهی سیستم'
UpdateSystemSSLSuccess: 'گواهی سیستم به‌روزرسانی شد'
ErrWildcardDomain: 'گواهی‌های عام در حالت HTTP پشتیبانی نمی‌شوند'
ErrApplySSLCanNotDelete: 'گواهی {{ .name }} در حال صدور است و هنوز قابل حذف نیست'
StartPushSSLToNode: 'ارسال گواهی به گره'
PushSSLToNodeFailed: "ارسال گواهی به گره ناموفق بود: {{ .err }}"
PushSSLToNodeSuccess: 'گواهی به گره ارسال شد'
# MySQL
ErrUserIsExist: 'کاربر از قبل وجود دارد'
ErrDatabaseIsExist: 'پایگاه داده از قبل وجود دارد'
ErrExecTimeOut: 'زمان اجرای SQL به پایان رسید'
ErrRemoteExist: 'نام پایگاه داده از راه دور از قبل وجود دارد'
ErrLocalExist: 'نام پایگاه داده محلی از قبل وجود دارد'
RemoteBackup: 'قبل از پشتیبان‌گیری از پایگاه داده از راه دور، کانتینر پایگاه داده محلی را با تصویر {{ .name }} راه‌اندازی کنید'
RemoteRecover: 'قبل از بازیابی پایگاه داده از راه دور، کانتینر پایگاه داده محلی را با تصویر {{ .name }} راه‌اندازی کنید'
Arg: "آرگومان"
# Redis
ErrTypeOfRedis: 'نوع فایل پشتیبان با حالت ماندگاری فعلی مطابقت ندارد'
# کانتینر
ErrInUsed: "{{ .detail }} در حال استفاده است و قابل حذف نیست!"
ErrObjectInUsed: "این شی در حال استفاده است و قابل حذف نیست!"
ErrObjectBeDependent: "این تصویر توسط تصاویر دیگر ارجاع داده شده است و قابل حذف نیست!"
ErrPortRules: 'تعداد پورت‌ها مطابقت ندارد'
ErrPgImagePull: 'زمان دریافت تصویر به پایان رسید؛ شتاب را پیکربندی کنید یا {{ .name }} را به صورت دستی دریافت کنید'
PruneHelper: "پاک‌سازی {{ .name }} فضای دیسک {{ .size }} را آزاد کرد"
ImageRemoveHelper: "تصویر {{ .name }} حذف شد، فضای دیسک {{ .size }} آزاد شد"
BuildCache: 'کش ساخت'
Volume: 'حجم'
Network: "شبکه"
PruneStart: 'پاک‌سازی در حال انجام است'
ContainerBackupPrepare: 'آماده‌سازی فضای کاری پشتیبان کانتینر'
ContainerBackupStop: 'توقف کانتینر قبل از پشتیبان‌گیری'
ContainerBackupInspect: 'بررسی و شبکه کانتینر پشتیبان'
ContainerBackupMounts: 'نصب‌های کانتینر پشتیبان'
ContainerBackupMeta: 'نوشتن فراداده پشتیبان کانتینر'
ContainerBackupStart: 'شروع کانتینر پس از پشتیبان‌گیری'
ContainerRecoverPrepare: 'آماده‌سازی فضای کاری بازیابی کانتینر'
ContainerRecoverExtract: 'استخراج آرشیو پشتیبان کانتینر'
ContainerRecoverParse: 'بارگذاری فراداده پشتیبان کانتینر'
ContainerRecoverCreate: 'بازآفرینی کانتینر'
ContainerRecoverMounts: 'بازیابی نصب‌های کانتینر'
ContainerRecoverStart: 'شروع کانتینر بازیابی شده'
ContainerRecoverCleanup: 'پاک‌سازی فضای کاری بازیابی کانتینر'
ComposeBackupPrepare: 'آماده‌سازی فضای کاری پشتیبان Compose'
ComposeBackupStop: 'توقف Compose قبل از پشتیبان‌گیری'
ComposeBackupFiles: 'پشتیبان‌گیری از فایل‌های Compose'
ComposeBackupContainers: 'پشتیبان‌گیری از کانتینرهای Compose'
ComposeBackupMeta: 'نوشتن فراداده پشتیبان Compose'
ComposeBackupStart: 'شروع Compose پس از پشتیبان‌گیری'
ComposeBackupCleanup: 'پاک‌سازی فضای کاری پشتیبان Compose'
ComposeRecoverPrepare: 'آماده‌سازی فضای کاری بازیابی Compose'
ComposeRecoverExtract: 'استخراج آرشیو پشتیبان Compose'
ComposeRecoverMeta: 'بارگذاری فراداده پشتیبان Compose'
ComposeRecoverMetaLogName: 'فراداده بازیابی Compose: name={{ .name }}'
ComposeRecoverMetaLogPath: 'فراداده بازیابی Compose: backupPath={{ .backupPath }} targetDir={{ .targetDir }}'
ComposeRecoverMetaLogCount: 'فراداده بازیابی Compose: files={{ .files }} containers={{ .containers }}'
ComposeRecoverFiles: 'بازیابی فایل‌های Compose'
ComposeRecoverContainers: 'بازیابی کانتینرهای Compose'
ComposeRecoverRecord: 'ذخیره رکورد Compose'
ComposeRecoverCleanup: 'پاک‌سازی فضای کاری بازیابی Compose'
# محیط اجرا
ErrFileNotExist: 'فایل منبع یافت نشد: {{ .detail }}'
ErrImageBuildErr: 'ساخت تصویر ناموفق بود'
ErrImageExist: "تصویر از قبل وجود دارد. از نام تصویر دیگری استفاده کنید."
ErrDelWithWebsite: 'محیط اجرا به یک وب‌سایت متصل است و قابل حذف نیست'
ErrRuntimeStart: 'شروع ناموفق بود'
ErrPackageJsonNotFound: 'فایل package.json وجود ندارد'
ErrScriptsNotFound: 'هیچ بخش scripts در package.json یافت نشد'
ErrContainerNameNotFound: 'نام کانتینر یافت نشد؛ .env را بررسی کنید'
ErrNodeModulesNotFound: 'node_modules یافت نشد؛ محیط اجرا را ویرایش کنید یا منتظر شروع باشید'
ErrContainerNameIsNull: 'نام کانتینر خالی است'
ErrPHPPortIsDefault: 'پورت ۹۰۰۰ به عنوان پیش‌فرض رزرو شده است؛ از پورت دیگری استفاده کنید'
ErrPHPRuntimePortFailed: 'پورت {{ .name }} قبلاً توسط این محیط اجرا استفاده می‌شود'
# ابزار
ErrConfigNotFound: 'فایل پیکربندی وجود ندارد'
ErrConfigParse: 'فرمت فایل پیکربندی نامعتبر است'
ErrConfigIsNull: 'فایل پیکربندی نمی‌تواند خالی باشد'
ErrConfigDirNotFound: 'دایرکتوری اجرا یافت نشد'
ErrConfigAlreadyExist: 'فایل پیکربندی با همین نام از قبل وجود دارد'
ErrUserFindErr: 'یافتن کاربر {{ .name }} ناموفق بود: {{ .err }}'
# وظیفه زمان‌بندی‌شده
CutWebsiteLogSuccess: 'لاگ وب‌سایت چرخش یافت: {{ .name }} (پشتیبان: {{ .path }})'
HandleShell: 'اجرای اسکریپت {{ .name }}'
HandleCurl: 'درخواست به URL {{ .name }}'
HandleNtpSync: 'همگام‌سازی زمان سیستم'
HandleSystemClean: 'پاک‌سازی کش سیستم'
SystemLog: 'لاگ سیستم'
CutWebsiteLog: 'چرخش لاگ وب‌سایت'
FileOrDir: 'دایرکتوری / فایل'
UploadFile: 'بارگذاری پشتیبان {{ .file }} به {{ .backup }}'
Upload: "بارگذاری"
IgnoreBackupErr: 'پشتیبان‌گیری ناموفق بود: {{ .detail }} (نادیده گرفته شد)'
IgnoreUploadErr: 'بارگذاری ناموفق بود: {{ .detail }} (نادیده گرفته شد)'
LoadBackupFailed: 'دریافت اطلاعات اتصال حساب پشتیبان ناموفق بود: {{ .detail }}'
InExecuting: 'وظیفه در حال اجراست'
NoSuchResource: 'هیچ رکورد پشتیبان یافت نشد؛ رد شد'
CleanLog: 'پاک‌سازی لاگ'
CleanLogByName: 'پاک‌سازی لاگ‌های {{ .name }}'
WafIpGroupNotFound: "گروه IP WAF یافت نشد. به [ویژگی‌های پیشرفته -> WAF -> لیست سیاه/سفید -> گروه IP] بروید و با استفاده از دانلود از راه دور یکی را اضافه کنید."
# جعبه‌ابزار
ErrNotExistUser: 'کاربر وجود ندارد'
ErrBanAction: 'پیکربندی ناموفق بود: سرویس {{ .name }} در دسترس نیست'
ErrClamdscanNotFound: 'دستور clamdscan یافت نشد'
TaskSwapSet: 'تنظیم swap'
SetSwap: "تنظیم Swap {{ .path }} - {{ .size }}"
CreateSwap: 'ایجاد فایل swap'
FormatSwap: 'فرمت فایل swap'
EnableSwap: 'فعال‌سازی swap'
# محافظت در برابر دستکاری
CleanTamperSetting: 'پاک‌سازی تنظیمات حفاظتی قدیمی'
SetTamperWithRules: 'فعال‌سازی محافظت دایرکتوری بر اساس قوانین'
ProtectDir: "محافظت از دایرکتوری {{ .name }}"
IgnoreFile: "محافظت از فایل {{ .name }}"
TamperSetSuccessful: 'محافظت تنظیم شد. نظارت بر این دایرکتوری‌ها:'
# WAF
ErrScope: 'این پیکربندی قابل تغییر نیست'
ErrStateChange: 'تغییر وضعیت ناموفق بود'
ErrRuleExist: 'قانون از قبل وجود دارد'
ErrRuleNotExist: 'قانون وجود ندارد'
ErrParseIP: 'فرمت IP نامعتبر است'
ErrDefaultIP: 'default رزرو شده است؛ از نام دیگری استفاده کنید'
ErrGroupInUse: 'گروه IP توسط قوانین لیست سیاه/سفید استفاده می‌شود'
ErrIPGroupAclUse: 'گروه IP توسط قوانین سفارشی وب‌سایت {{ .name }} استفاده می‌شود'
ErrGroupExist: 'نام گروه IP از قبل وجود دارد'
ErrIPRange: 'محدوده IP نامعتبر است'
ErrIPExist: 'IP از قبل وجود دارد'
urlDefense: 'قوانین URL'
urlHelper: 'URL مسدود شده'
dirFilter: 'فیلتر دایرکتوری'
xss: 'XSS'
phpExec: 'اجرای اسکریپت PHP'
oneWordTrojan: 'تروژان یک کلمه‌ای'
appFilter: 'فیلتر دایرکتوری خطرناک برنامه'
webShell: 'WebShell'
args: 'قوانین پارامتر'
protocolFilter: 'فیلتر پروتکل'
javaFilter: 'فیلتر فایل خطرناک جاوا'
scannerFilter: 'فیلتر اسکنر'
escapeFilter: 'فیلتر فرار'
customRule: 'قوانین سفارشی'
httpMethod: 'فیلتر روش HTTP'
fileExt: 'محدودیت‌های بارگذاری فایل'
defaultIpBlack: 'گروه IP مخرب'
cookie: 'قوانین کوکی'
urlBlack: 'لیست سیاه URL'
uaBlack: 'لیست سیاه User-Agent'
attackCount: 'محدودیت فرکانس حمله'
fileExtCheck: 'محدودیت‌های بارگذاری فایل'
geoRestrict: 'محدودیت‌های دسترسی منطقه‌ای'
unknownWebsite: 'دسترسی به دامنه غیرمجاز'
notFoundCount: 'محدودیت نرخ ۴۰۴'
headerDefense: 'قوانین هدر'
defaultUaBlack: 'قوانین User-Agent'
methodWhite: 'قوانین HTTP'
captcha: 'تأیید انسانی'
fiveSeconds: 'تأیید ۵ ثانیه‌ای'
vulnCheck: 'قوانین تکمیلی'
acl: 'قوانین سفارشی'
sql: 'تزریق SQL'
cc: 'محدودیت فرکانس دسترسی'
defaultUrlBlack: 'قوانین URL'
sqlInject: 'تزریق SQL'
ErrDBNotExist: 'پایگاه داده وجود ندارد'
allow: 'مجاز'
deny: 'ممنوع'
OpenrestyNotFound: 'OpenResty نصب نشده است'
remoteIpIsNull: "لیست IP خالی است"
OpenrestyVersionErr: 'نسخه OpenResty بسیار پایین است؛ به ۱.۲۷.۱.۲-۲-۲-focal ارتقاء دهید'
ErrFileTooLarge: 'فایل بزرگتر از ۱ مگابایت است و قابل بارگذاری نیست'
# وظیفه
TaskStart: '{{ .name }} [شروع]'
TaskEnd: '{{ .name }} [انجام شد]'
TaskFailed: 'وظیفه {{ .name }} ناموفق بود'
TaskTimeout: 'زمان وظیفه {{ .name }} به پایان رسید'
TaskSuccess: 'وظیفه {{ .name }} با موفقیت انجام شد'
TaskRetry: 'تلاش مجدد #{{ .name }}'
SubTaskSuccess: '{{ .name }} با موفقیت انجام شد'
SubTaskFailed: '{{ .name }} ناموفق بود: {{ .err }}'
TaskInstall: 'نصب'
TaskUninstall: 'حذف نصب'
TaskCreate: 'ایجاد'
TaskDelete: 'حذف'
TaskUpgrade: 'ارتقاء'
TaskUpdate: 'به‌روزرسانی'
TaskRestart: 'راه‌اندازی مجدد'
TaskProtect: "محافظت"
TaskBackup: 'پشتیبان‌گیری'
TaskRecover: 'بازیابی'
TaskRollback: 'بازگشت'
TaskPull: 'دریافت'
TaskCommit: 'ثبت'
TaskBuild: 'ساخت'
TaskPush: 'ارسال'
TaskClean: "پاک‌سازی"
TaskHandle: 'اجرا'
TaskImport: "واردات"
TaskExport: "خروجی"
Website: 'وب‌سایت'
App: 'برنامه'
Runtime: 'محیط اجرا'
Database: 'پایگاه داده'
ConfigFTP: 'ایجاد کاربر FTP {{ .name }}'
ConfigOpenresty: 'ایجاد فایل پیکربندی OpenResty'
InstallAppSuccess: 'برنامه {{ .name }} نصب شد'
ConfigRuntime: 'پیکربندی محیط اجرا'
ConfigApp: 'پیکربندی برنامه'
SuccessStatus: '{{ .name }} با موفقیت انجام شد'
FailedStatus: '{{ .name }} ناموفق بود: {{ .err }}'
HandleLink: 'پردازش لینک‌های برنامه'
HandleDatabaseApp: 'پردازش پارامترهای برنامه'
ExecShell: 'اجرای اسکریپت {{ .name }}'
PullImage: 'دریافت تصویر'
Start: 'شروع'
Run: 'شروع'
Stop: 'توقف'
Image: 'تصویر'
Compose: 'Compose'
Container: 'کانتینر'
AppLink: 'برنامه متصل'
EnableSSL: 'فعال‌سازی HTTPS'
AppStore: 'فروشگاه برنامه'
TaskSync: 'همگام‌سازی'
LocalApp: 'برنامه محلی'
SubTask: 'وظیفه فرعی'
RuntimeExtension: 'پسوند محیط اجرا'
TaskIsExecuting: 'وظیفه در حال اجراست'
CustomAppstore: 'مخزن برنامه سفارشی'
TaskExec: 'اجرا'
TaskBatch: "عملیات دسته‌ای"
FileConvert: 'تبدیل فایل'
# وظیفه - ClamAV
Clamscan: "اسکن {{ .name }}"
TaskScan: "اسکن"
# وظیفه - هوش مصنوعی
OllamaModelPull: 'دریافت مدل Ollama {{ .name }}'
OllamaModelSize: 'دریافت اندازه مدل Ollama: {{ .name }}'
AIBenchmarkRun: 'اجرای معیار سنجش هوش مصنوعی'
# وظیفه - اسنپ‌شات
Snapshot: 'تصویر لحظه‌ای'
SnapDBInfo: 'نوشتن اطلاعات پایگاه داده 1Panel'
SnapCopy: 'کپی فایل‌ها/دایرکتوری‌ها {{ .name }}'
SnapNewDB: 'راه‌اندازی اتصال پایگاه داده {{ .name }}'
SnapDeleteOperationLog: 'حذف لاگ‌های عملیات'
SnapDeleteLoginLog: 'حذف لاگ‌های دسترسی'
SnapDeleteMonitor: 'حذف داده‌های نظارت'
SnapRemoveSystemIP: 'حذف IP سیستم'
SnapBaseInfo: 'نوشتن اطلاعات پایه 1Panel'
SnapInstallAppImageEmpty: 'هیچ تصویر برنامه‌ای انتخاب نشد؛ رد شد'
SnapInstallApp: 'پشتیبان‌گیری از برنامه‌های نصب شده'
SnapDockerSave: 'فشرده‌سازی برنامه‌های نصب شده'
SnapLocalBackup: 'پشتیبان‌گیری از دایرکتوری پشتیبان محلی'
SnapCompressBackup: 'فشرده‌سازی دایرکتوری پشتیبان محلی'
SnapPanelData: 'پشتیبان‌گیری از دایرکتوری داده 1Panel'
SnapCompressPanel: 'فشرده‌سازی دایرکتوری داده'
SnapWebsite: 'پشتیبان‌گیری از دایرکتوری وب‌سایت'
SnapCloseDBConn: 'بستن اتصال پایگاه داده'
SnapCompress: 'ایجاد فایل تصویر لحظه‌ای'
SnapCompressFile: 'فشرده‌سازی فایل تصویر لحظه‌ای'
SnapCheckCompress: 'بررسی آرشیو تصویر لحظه‌ای'
SnapCompressSize: 'اندازه تصویر لحظه‌ای {{ .name }}'
SnapUpload: 'بارگذاری فایل تصویر لحظه‌ای'
SnapUploadTo: 'بارگذاری تصویر لحظه‌ای به {{ .name }}'
SnapUploadRes: 'بارگذاری تصویر لحظه‌ای به {{ .name }}'
SnapshotRecover: 'بازیابی تصویر لحظه‌ای'
RecoverDownload: 'دانلود فایل تصویر لحظه‌ای'
Download: 'دانلود'
RecoverDownloadAccount: 'دریافت حساب دانلود تصویر لحظه‌ای {{ .name }}'
RecoverDecompress: 'باز کردن فشرده آرشیو تصویر لحظه‌ای'
Decompress: 'باز کردن فشرده'
BackupBeforeRecover: 'پشتیبان‌گیری از داده‌های سیستم قبل از بازیابی'
ReadJson: 'خواندن فایل JSON تصویر لحظه‌ای'
ReadJsonPath: 'دریافت مسیر JSON تصویر لحظه‌ای'
ReadJsonContent: 'خواندن محتوای JSON'
ReadJsonMarshal: 'پردازش فرار JSON'
RecoverApp: 'بازیابی برنامه‌های نصب شده'
RecoverWebsite: 'بازیابی دایرکتوری وب‌سایت'
RecoverAppImage: 'بازیابی پشتیبان تصویر تصویر لحظه‌ای'
RecoverCompose: 'بازیابی سایر پروژه‌های Compose'
RecoverComposeList: 'دریافت همه پروژه‌های Compose برای بازیابی'
RecoverComposeItem: 'بازیابی Compose {{ .name }}'
RecoverAppEmpty: 'هیچ پشتیبان تصویر برنامه در تصویر لحظه‌ای یافت نشد'
RecoverBaseData: 'بازیابی داده‌های پایه و فایل‌ها'
RecoverDaemonJsonEmpty: 'daemon.json در تصویر لحظه‌ای یا میزبان فعلی یافت نشد'
RecoverDaemonJson: 'بازیابی daemon.json'
RecoverDBData: 'بازیابی داده‌های پایگاه داده'
RecoverBackups: 'بازیابی دایرکتوری پشتیبان محلی'
RecoverPanelData: 'بازیابی دایرکتوری داده'
# وظیفه - کانتینر
ContainerImagePull: 'دریافت تصویر کانتینر {{ .name }}'
ContainerRemoveOld: 'حذف کانتینر اصلی {{ .name }}'
ContainerImageCheck: 'بررسی نتیجه دریافت تصویر'
ContainerLoadInfo: 'بارگذاری اطلاعات کانتینر'
ContainerRecreate: 'به‌روزرسانی کانتینر ناموفق بود؛ بازگردانی کانتینر اصلی'
ContainerCreate: 'ایجاد کانتینر جدید {{ .name }}'
ContainerCreateFailed: 'ایجاد کانتینر ناموفق بود؛ حذف کانتینر ناموفق'
ContainerStartCheck: 'بررسی شروع کانتینر'
# وظیفه - تصویر
ImageBuild: 'ساخت تصویر'
ImagePull: 'دریافت تصویر'
ImageRepoAuthFromDB: 'بارگذاری احراز هویت مخزن از پایگاه داده'
ImagePush: 'ارسال تصویر'
ImageRenameTag: 'به‌روزرسانی برچسب تصویر'
ImageNewTag: 'برچسب تصویر جدید {{ .name }}'
ComposeCreate: 'ایجاد پروژه Compose'
# وظیفه - وب‌سایت
BackupNginxConfig: 'پشتیبان‌گیری از پیکربندی OpenResty وب‌سایت'
CompressFileSuccess: 'دایرکتوری به {{ .name }} فشرده شد'
CompressDir: 'فشرده‌سازی دایرکتوری'
DeCompressFile: 'باز کردن فشرده فایل {{ .name }}'
ErrCheckValid: 'تأیید فایل پشتیبان ناموفق بود: {{ .name }}'
Rollback: 'بازگشت'
websiteDir: 'دایرکتوری وب‌سایت'
RecoverFailedStartRollBack: 'بازیابی ناموفق بود؛ شروع بازگشت'
AppBackupFileIncomplete: 'پشتیبان ناقص است: app.json یا app.tar.gz وجود ندارد'
AppAttributesNotMatch: 'نوع یا نام برنامه مطابقت ندارد'
# هشدار
ErrAlertMethodNotSupported: 'این نسخه از این روش هشدار پشتیبانی نمی‌کند'
ErrAlertSync: 'همگام‌سازی پیام هشدار ناموفق بود'
# وظیفه - محیط اجرا
ErrInstallExtension: 'وظیفه نصب پسوند دیگری در حال اجراست'
# قالب ایمیل هشدار
PanelAlertTitle: "اعلان هشدار پنل"
TestAlertTitle: "ایمیل آزمایشی - تأیید اتصال ایمیل"
TestAlert: "این یک ایمیل آزمایشی برای تأیید صحت پیکربندی ارسال ایمیل شما است."
LicenseExpirationAlert: "پنل {{ .node }}{{ .ip }}: مجوز در {{ .day }} روز منقضی می‌شود. برای مشاهده جزئیات وارد شوید."
CronJobFailedAlert: "پنل {{ .node }}{{ .ip }}: وظیفه زمان‌بندی {{ .name }} ناموفق بود. برای مشاهده جزئیات وارد شوید."
ClamAlert: "پنل {{ .node }}{{ .ip }}: اسکن ویروس {{ .num }} فایل آلوده پیدا کرد. برای مشاهده جزئیات وارد شوید."
WebSiteAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} وب‌سایت در {{ .day }} روز منقضی می‌شوند. برای مشاهده جزئیات وارد شوید."
SSLAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} گواهی SSL در {{ .day }} روز منقضی می‌شوند. برای مشاهده جزئیات وارد شوید."
DiskUsedAlert: "پنل {{ .node }}{{ .ip }}: استفاده از دیسک {{ .name }} به {{ .used }} رسید. برای مشاهده جزئیات وارد شوید."
ResourceAlert: "پنل {{ .node }}{{ .ip }}: میانگین استفاده {{ .name }} در {{ .time }} دقیقه برابر با {{ .used }} است. برای مشاهده جزئیات وارد شوید."
PanelVersionAlert: "پنل {{ .node }}{{ .ip }}: نسخه جدید پنل موجود است. برای مشاهده جزئیات وارد شوید."
PanelPwdExpirationAlert: "پنل {{ .node }}{{ .ip }}: رمز عبور پنل در {{ .day }} روز منقضی می‌شود. برای مشاهده جزئیات وارد شوید."
CommonAlert: "پنل {{ .node }}{{ .ip }}: {{ .msg }}. برای مشاهده جزئیات وارد شوید."
NodeExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} گره غیرعادی هستند. برای مشاهده جزئیات وارد شوید."
LicenseExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} مجوز غیرعادی است. برای مشاهده جزئیات وارد شوید."
SSHAndPanelLoginAlert: "پنل {{ .node }}{{ .ip }}: ورود غیرعادی {{ .name }} از {{ .loginIp }}. برای مشاهده جزئیات وارد شوید."
# دیسک
DeviceNotFound: "دستگاه {{ .name }} یافت نشد"
DeviceIsMounted: 'دستگاه {{ .name }} متصل است؛ ابتدا قطع اتصال کنید'
PartitionDiskErr: 'پارتیشن‌بندی ناموفق بود: {{ .err }}'
FormatDiskErr: 'فرمت کردن ناموفق بود: {{ .err }}'
MountDiskErr: 'اتصال ناموفق بود: {{ .err }}'
XfsNotFound: 'xfs یافت نشد؛ ابتدا xfsprogs را نصب کنید'
# ترمینال
TerminalAIBlockedRiskyCommand: 'دستور پرخطر مسدود شد: {{ .command }}'
TerminalAIThinking: 'هوش مصنوعی در حال فکر کردن است...'
TerminalAIReadyToExecute: 'تفکر کامل شد، برای اجرا Enter را فشار دهید (مدت زمان: {{ .duration }}، توکن‌ها: {{ .tokens }})'
TerminalAIRequestFailed: 'درخواست هوش مصنوعی ناموفق بود: {{ .err }}'
# دروازه هوش مصنوعی
ErrAIProxyServiceNotInstalled: 'سرویس {{ .name }} نصب نشده است'
ErrAIProxyModelAccountNotFound: 'حساب مدل {{ .id }} یافت نشد'
ErrAIProxyModelMapRequired: 'این فیلد الزامی است.'
ErrAIProxyModelMapInvalid: 'نگاشت مدل باید یک شی JSON معتبر باشد: {{ .err }}'
ErrAIProxyModelMapEmpty: 'نگاشت مدل نمی‌تواند خالی باشد'
ErrAIProxyModelMapEmptyExternalModel: 'نگاشت مدل شامل یک مدل درخواست خالی است'
ErrAIProxyModelMapEmptyUpstreamModel: 'مدل بالادست برای {{ .model }} نمی‌تواند خالی باشد'
AIProxyUserFallback: 'کاربر حذف شده (شناسه: {{ .id }})'
+92 -21
View File
@@ -1,29 +1,47 @@
# 基本エラー
ErrInvalidParams: '不正なリクエスト: {{ .detail }}'
ErrTokenParse: 'トークンエラー: {{ .detail }}'
ErrInitialPassword: '元のパスワードが違います'
ErrInternalServer: '内部エラー: {{ .detail }}'
ErrRecordExist: 'レコードが既に存在します'
ErrRecordNotFound: 'レコードが見つかりません'
ErrStructTransform: '型変換エラー: {{ .err }}'
ErrNotSupportType: '未対応のタイプ: {{ .name }}'
# ファイルAI
ErrFileAINotEnabled: 'ファイル管理の「AI アカウント」でファイル AI 検索を有効にし、アカウントを選んでください。'
ErrFileAISearchBadPattern: '検索パターンが無効です(正規表現など): {{ .detail }}'
FileAISearchEmptyDir: 'このパス以下に一覧できるファイルまたはフォルダーがありません(またはすべて除外されました)。'
# リモートダウンロード
ErrWgetRemoteFailed: 'リモートのダウンロード URL が無効か、到達できません:{{ .detail }}'
ErrWgetInvalidContentType: 'リモート URL がファイルではなくエラーページ等を返しました、{{ .detail }}'
ErrWgetProxyNotConfigured: '先にパネル設定でプロキシサーバーを設定してください'
ErrWgetProxyInvalid: 'プロキシサーバーのアドレスが無効です:{{ .detail }}'
ErrRecordExist: 'レコードが既に存在します'
ErrRecordNotFound: 'レコードが見つかりません'
ErrStructTransform: '型変換エラー: {{ .err }}'
# ログインとセキュリティ
ErrNotLogin: 'ログインしていません: {{ .detail }}'
ErrPasswordExpired: 'パスワード期限切れ: {{ .detail }}'
ErrNotSupportType: '未対応のタイプ: {{ .name }}'
ErrAlertMethodNotSupported: 'このバージョンではこの通知方法は利用できません'
# リクエストとAPI
ErrProxy: 'リクエストに失敗しました: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API利用不可: {{ .detail }}'
ErrApiConfigKeyInvalid: 'APIキーが無効です: {{ .detail }}'
ErrApiConfigIPInvalid: 'IPが許可されていません: {{ .detail }}'
ErrApiConfigDisable: 'APIは無効です: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'タイムスタンプが無効です: {{ .detail }}'
ErrShutDown: 'コマンドは手動で停止されました'
ErrCreateHttpClient: 'リクエスト {{ .err }} の作成に失敗しました'
ErrHttpReqTimeOut: 'リクエストがタイムアウトしました {{ .err }}'
ErrHttpReqFailed: 'リクエストが失敗しました {{ .err }}'
ErrNoSuchHost: '要求されたサーバー {{ .err }} が見つかりません'
ErrHttpReqNotFound: '要求されたリソース {{ .err }} が見つかりませんでした'
# クイックジャンプ
ErrMinQuickJump: 'クイックアクセスを1件以上追加'
ErrMaxQuickJump: '最大4件のクイックアクセス'
# 共通
ErrShutDown: 'コマンドは手動で停止されました'
ErrUsernameIsExist: 'ユーザー名はすでに使われています'
ErrNameIsExist: '名称はすでに使われています'
ErrDemoEnvironment: 'デモ環境では利用できません'
@@ -41,6 +59,8 @@ SystemRestart: 'システムの再起動によりタスクが中断されまし
ErrGroupIsDefault: 'デフォルト グループ、削除できません'
ErrGroupIsInWebsiteUse: 'グループは別の Web サイトで使用されているため、削除できません。'
Decrypt: '復号化'
# エージェント
ErrAgentAccountBound: 'アカウントはエージェントに紐づいています'
ErrTerminalAIAccountInUse: 'このモデルアカウントは Terminal AI で使用中です。Terminal AI の設定で別のアカウントへ切り替えるか、Terminal AI を無効にしてから再試行してください。'
ErrAgentAccountUnavailable: 'アカウント接続不可: {{ .err }}'
@@ -65,14 +85,8 @@ ErrAgentWebsiteInUse: 'このサイトはすでに別のエージェントに関
ErrAgentWebsiteUnbindUnsupported: 'ワンクリックデプロイのサイトは手動で関連解除できません'
ErrHermesPairingCodeUnavailable: 'Hermes でペアリングコードが一時的に見つかりません。ネットワーク要因の可能性があるため、しばらくしてから再試行してください。'
ErrHermesFeishuGroupAllowlistRequiresAllowlist: 'Feishu のグループポリシーが許可リストの場合、DM ポリシーをペアリングコードにはできません。'
ErrAIProxyServiceNotInstalled: '{{ .name }} サービスはインストールされていません'
ErrAIProxyModelAccountNotFound: 'モデルアカウント {{ .id }} が見つかりません'
ErrAIProxyModelMapRequired: 'この項目は必須です。'
ErrAIProxyModelMapInvalid: 'モデルマッピングは有効な JSON オブジェクトである必要があります: {{ .err }}'
ErrAIProxyModelMapEmpty: 'モデルマッピングは空にできません'
ErrAIProxyModelMapEmptyExternalModel: 'モデルマッピングに空のリクエストモデルがあります'
ErrAIProxyModelMapEmptyUpstreamModel: '{{ .model }} の上流モデルは空にできません'
AIProxyUserFallback: 'ユーザー {{ .id }}'
# バックアップとアラート設定
Localhost: 'ローカルマシン'
ErrBackupInUsed: 'バックアップアカウントがスケジュールで使用中'
ErrBackupCheck: '接続テストに失敗しました: {{ .err }}'
@@ -80,6 +94,8 @@ ErrBackupLocalDelete: 'ローカルバックアップは削除できません'
ErrBackupLocalCreate: 'ローカルバックアップは作成できません'
ErrAlertConfigInUse: 'アラート設定は使用中のため削除できません'
ErrAlertConfigPhoneExist: '電話番号はすでに存在します'
# アプリ
ErrPortInUsed: '{{ .detail }} ポートはすでに使用されています'
ErrAppLimit: 'インストールされているアプリケーションの数が制限を超えました'
ErrNotInstall: 'アプリケーションがインストールされていません'
@@ -97,16 +113,12 @@ ErrFileParseApp: '{{ .name }} ファイルの解析に失敗しました {{ .err
ErrAppDirNull: 'バージョン フォルダーが存在しません'
LocalAppErr: 'アプリケーション {{ .name }} の同期に失敗しました {{ .err }}'
ErrContainerName: 'コンテナ名が既に存在します'
ErrCreateHttpClient: 'リクエスト {{ .err }} の作成に失敗しました'
ErrHttpReqTimeOut: 'リクエストがタイムアウトしました {{ .err }}'
ErrHttpReqFailed: 'リクエストが失敗しました {{ .err }}'
ErrNoSuchHost: '要求されたサーバー {{ .err }} が見つかりません'
ErrHttpReqNotFound: '要求されたリソース {{ .err }} が見つかりませんでした'
ErrContainerNotFound: '{{ .name }} コンテナが存在しません'
ErrContainerMsg: '{{ .name }} コンテナが異常です。詳細についてはコンテナページのログを確認してください。'
ErrAppBackup: '{{ .name }} アプリケーションのバックアップに失敗しました。エラー {{ .err }}'
ErrVersionTooLow: '現在の 1Panel のバージョンが低すぎるため、App Store を更新できません。操作する前にバージョンをアップグレードしてください。'
ErrAppNameExist: 'アプリケーション名がすでに存在します'
ErrAppInstallNameExist: 'アプリケーションインストール名 {{ .name }} はすでに存在します'
AppStoreIsSyncing: 'App Store が同期中です。しばらくしてからもう一度お試しください'
ErrGetCompose: 'docker-compose.yml ファイルの取得に失敗しました {{ .detail }}'
ErrAppWarn: '異常な状態です。ログを確認してください'
@@ -131,6 +143,8 @@ ErrDockerFailed: 'Docker の状態が異常です。サービス状態を確認
ErrDockerComposeCmdNotFound: 'Docker Compose コマンドは存在しません。ホストマシンにこのコマンドを先にインストールしてください'
UseExistImage: 'イメージは既に存在します。既存のイメージを使用します'
ErrDatabaseNotFound: 'データベース {{ .name }} は存在しません'
# SSH
ExportIP: 'ログインIP'
ExportArea: '地域'
ExportPort: 'ポート'
@@ -138,6 +152,8 @@ ExportAuthMode: 'ログイン方式'
ExportUser: 'ユーザー'
ExportStatus: 'ログイン状態'
ExportDate: '時間'
# ファイル
ErrFileCanNotRead: 'このファイルはプレビューをサポートしていません'
ErrSensitiveFileRead: '機密ファイルの読み取りは許可されていません'
ErrFileToLarge: 'ファイルは 10M より大きいため開けません'
@@ -164,6 +180,8 @@ FileDropFailed: 'ファイル {{ .name }} のクリーンアップに失敗し
FileDropSuccess: 'ファイル {{ .name }} のクリーンアップに成功しました、{{ .count }} ファイルを削除、{{ .size }} のディスク領域を解放'
FileDropSum: 'ファイルのクリーンアップが完了しました、合計 {{ .count }} ファイルを削除、合計 {{ .size }} のディスク領域を解放'
ErrBadDecrypt: '圧縮パッケージの解凍パスワードが間違っています。解凍に失敗しました。確認後再試行!'
# ウェブサイト
ErrAliasIsExist: 'エイリアスがすでに存在します'
ErrBackupMatch: 'バックアップ ファイルは、現在の Web サイト データ {{ .detail }} の一部と一致しません'
ErrBackupExist: 'バックアップ ファイル内のソース データの対応する部分が存在しません {{ .detail }}'
@@ -193,6 +211,8 @@ delete: '削除'
ErrDefaultWebsite: 'デフォルト Web サイトが既に設定されています。設定する前にキャンセルしてください!'
SSL: '証明書'
Set: '設定'
# SSL証明書
ErrSSLCannotDelete: '{{ .name }} 証明書は Web サイトで使用されているため、削除できません'
ErrAccountCannotDelete: 'アカウントは証明書に関連付けられているため、削除できません'
ErrSSLApply: '証明書の更新は成功しましたが、openresty の再ロードに失敗しました。設定を確認してください。'
@@ -227,6 +247,8 @@ ErrApplySSLCanNotDelete: '申請中の証明書 {{ .name }} は削除できま
StartPushSSLToNode: '証明書をノードにプッシュ開始'
PushSSLToNodeFailed: 'ノードに証明書をプッシュ失敗: {{ .err }}'
PushSSLToNodeSuccess: 'ノードに証明書をプッシュ成功'
# MySQL
ErrUserIsExist: '現在のユーザーは既に存在します。再入力してください'
ErrDatabaseIsExist: '現在のデータベースは既に存在します。再入力してください'
ErrExecTimeOut: 'SQL 実行がタイムアウトしました。データベースを確認してください'
@@ -235,7 +257,11 @@ ErrLocalExist: '名前はローカル データベースに既に存在します
RemoteBackup: 'リモートデータベースをバックアップするには、まずイメージ {{ .name }} を使用してローカルコンテナデータベースサービスを起動する必要があります。しばらくお待ちください...'
RemoteRecover: 'リモートデータベースを復元するには、まずイメージ {{ .name }} を使用してローカルコンテナデータベースサービスを起動する必要があります。しばらくお待ちください...'
Arg: '引数'
# Redis
ErrTypeOfRedis: 'リカバリ ファイルの種類が現在の永続化方法と一致しません。変更して再試行してください'
# コンテナ
ErrInUsed: '{{ .detail }} は使用中であり、削除できません!'
ErrObjectInUsed: 'このオブジェクトは使用中であり、削除できません!'
ErrObjectBeDependent: 'このイメージは他のイメージに依存されているため、削除できません!'
@@ -277,6 +303,8 @@ ComposeRecoverFiles: 'Compose ファイルを復元'
ComposeRecoverContainers: 'Compose コンテナを復元'
ComposeRecoverRecord: 'Compose 記録を保存'
ComposeRecoverCleanup: 'Compose 復元作業領域をクリーンアップ'
# ランタイム
ErrFileNotExist: '{{ .detail }} ファイルが存在しません。ソース ファイルの整合性を確認してください。'
ErrImageBuildErr: 'イメージのビルドに失敗しました'
ErrImageExist: 'イメージはすでに存在します!イメージ名を変更してください。'
@@ -289,12 +317,16 @@ ErrNodeModulesNotFound: 'node_modules フォルダが存在しません。ラン
ErrContainerNameIsNull: 'コンテナ名が存在しません'
ErrPHPPortIsDefault: 'ポート9000はデフォルトポートです。修正してから再試行してください'
ErrPHPRuntimePortFailed: 'ポート {{ .name }} は現在のランタイム環境で使用されています。修正してから再試行してください'
# ツール
ErrConfigNotFound: '構成ファイルが存在しません'
ErrConfigParse: '構成ファイルの形式が正しくありません'
ErrConfigIsNull: '構成ファイルは空にできません'
ErrConfigDirNotFound: '実行ディレクトリが存在しません'
ErrConfigAlreadyExist: '同じ名前の設定ファイルがすでに存在します'
ErrUserFindErr: 'ユーザー {{ .name }} の検索に失敗しました {{ .err }}'
# Cronジョブ
CutWebsiteLogSuccess: '{{ .name }} ウェブサイトのログが正常にカットされました。バックアップ パス {{ .path }}'
HandleShell: 'スクリプト {{ .name }} を実行します'
HandleCurl: 'URL {{ .name }} にアクセス'
@@ -313,6 +345,8 @@ NoSuchResource: 'データベースにバックアップ内容が見つかりま
CleanLog: 'ログをクリーン'
CleanLogByName: '{{ .name }} のログをクリーン'
WafIpGroupNotFound: 'WAF IPグループが見つかりません。[高度な機能 - WAF - ブラックリスト/ホワイトリスト - IPグループ]に移動し、リモートダウンロード方式でIPグループを追加してください。'
# ツールボックス
ErrNotExistUser: '現在のユーザーは存在しません。変更してもう一度お試しください。'
ErrBanAction: '設定に失敗しました。現在の {{ .name }} サービスは利用できません。確認してもう一度お試しください。'
ErrClamdscanNotFound: 'clamdscan コマンドが検出されませんでした。インストールするにはドキュメントを参照してください。'
@@ -321,11 +355,15 @@ SetSwap: 'Swap設定 {{ .path }} - {{ .size }}'
CreateSwap: 'Swapファイル作成'
FormatSwap: 'Swapファイルフォーマット'
EnableSwap: 'Swapファイル有効化'
# 改ざん防止
CleanTamperSetting: '履歴保護設定をクリーンアップ'
SetTamperWithRules: 'ルールに従ってディレクトリ保護を開始'
ProtectDir: 'ディレクトリを保護 {{ .name }}'
IgnoreFile: 'ファイルを保護 {{ .name }}'
TamperSetSuccessful: '保護設定が完了し、以下のディレクトリの監視を開始します:'
# WAF
ErrScope: 'この構成の変更はサポートされていません'
ErrStateChange: '状態の変更に失敗しました'
ErrRuleExist: 'ルールは既に存在します'
@@ -380,6 +418,8 @@ OpenrestyNotFound: 'OpenResty がインストールされていません'
remoteIpIsNull: 'IPリストが空です'
OpenrestyVersionErr: 'OpenResty のバージョンが古すぎます。1.27.1.2-2-2-focal 以降にアップグレードしてください'
ErrFileTooLarge: 'ファイルが 1MB を超えているためアップロードできません'
# タスク
TaskStart: '{{ .name }} タスクが開始されました [START]'
TaskEnd: '{{ .name }} タスクが完了しました [COMPLETED]'
TaskFailed: '{{ .name }} タスクが失敗しました'
@@ -440,10 +480,17 @@ CustomAppstore: 'カスタム アプリケーション ウェアハウス'
TaskExec: '実行'
TaskBatch: '一括操作'
FileConvert: 'ファイル形式の変換'
# タスク - ClamAV
Clamscan: '{{ .name }} をスキャン'
TaskScan: 'スキャン'
# タスク - AI
OllamaModelPull: 'Ollama モデル {{ .name }} をプルします'
OllamaModelSize: 'Ollama モデル {{ .name }} のサイズを取得します'
AIBenchmarkRun: "AIベンチマークを実行"
# タスク - スナップショット
Snapshot: 'スナップショット'
SnapDBInfo: '1Panel データベース情報を書き込む'
SnapCopy: 'ファイルとディレクトリ {{ .name }} をコピーします'
@@ -493,6 +540,8 @@ RecoverDaemonJson: 'コンテナ構成 daemon.json ファイルを復元する'
RecoverDBData: 'データベース データを回復する'
RecoverBackups: 'ローカル バックアップ ディレクトリを復元する'
RecoverPanelData: 'リカバリデータディレクトリ'
# タスク - コンテナ
ContainerImagePull: 'コンテナ イメージ {{ .name }} をプルします'
ContainerRemoveOld: '元のコンテナ {{ .name }} を削除します'
ContainerImageCheck: 'イメージが正常にプルされているかどうかを確認します'
@@ -501,6 +550,8 @@ ContainerRecreate: 'コンテナの更新に失敗しました。元のコンテ
ContainerCreate: '新しいコンテナ {{ .name }} を作成します'
ContainerCreateFailed: 'コンテナの作成に失敗しました。失敗したコンテナを削除してください'
ContainerStartCheck: 'コンテナが起動されているかどうかを確認します'
# タスク - イメージ
ImageBuild: 'イメージビルド'
ImagePull: '画像をプル'
ImageRepoAuthFromDB: 'データベースからリポジトリ認証情報を取得する'
@@ -508,6 +559,8 @@ ImagePush: '画像をプッシュ'
ImageRenameTag: '画像タグを変更する'
ImageNewTag: '新しい画像タグ {{ .name }}'
ComposeCreate: 'コンポジションを作成する'
# タスク - ウェブサイト
BackupNginxConfig: 'ウェブサイトの OpenResty 構成ファイルをバックアップする'
CompressFileSuccess: 'ディレクトリを正常に圧縮しました。{{ .name }} に圧縮されました'
CompressDir: '圧縮ディレクトリ'
@@ -518,8 +571,15 @@ websiteDir: 'ウェブサイトディレクトリ'
RecoverFailedStartRollBack: 'リカバリに失敗しました。ロールバックを開始します'
AppBackupFileIncomplete: 'バックアップ ファイルが不完全で、app.json または app.tar.gz ファイルが不足しています'
AppAttributesNotMatch: 'アプリケーションの種類または名前が一致しません'
# アラート
ErrAlertMethodNotSupported: 'このバージョンではこの通知方法は利用できません'
ErrAlertSync: 'アラーム情報の同期エラーです。確認してもう一度お試しください。'
# タスク - ランタイム
ErrInstallExtension: 'インストールタスクが進行中です、タスクが終了するのを待ってください'
# アラートメールテンプレート
PanelAlertTitle: 'パネルアラート通知'
TestAlertTitle: 'テストメール - メール接続の確認'
TestAlert: 'これはテストメールです。メール送信設定が正しく構成されているかを確認します。'
@@ -536,6 +596,8 @@ CommonAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .msg }}。詳細
NodeExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のノードに異常が発生しています。詳細はパネルにログインして'
LicenseExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のライセンスに異常が発生しています。詳細はパネルにログインして'
SSHAndPanelLoginAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .loginIp }} からの {{ .name }} ログインに異常があります。詳細はパネルにログインして'
# ディスク
DeviceNotFound: 'デバイス {{ .name }} が見つかりません'
DeviceIsMounted: 'デバイス {{ .name }} はマウントされています、まずアンマウントしてください'
PartitionDiskErr: 'パーティションに失敗しました、{{ .err }}'
@@ -543,9 +605,18 @@ FormatDiskErr: 'ディスクのフォーマットに失敗しました、{{ .err
MountDiskErr: 'ディスクのマウントに失敗しました、{{ .err }}'
XfsNotFound: 'xfs ファイルシステムが検出されませんでした、最初に xfsprogs をインストールしてください'
# terminal
# ターミナル
TerminalAIBlockedRiskyCommand: '危険なコマンドをブロックしました: {{ .command }}'
TerminalAIThinking: 'AI が考えています...'
TerminalAIReadyToExecute: '思考が完了しました。Enter で実行してください(所要時間 {{ .duration }}、token: {{ .tokens }})'
TerminalAIRequestFailed: 'AI リクエストに失敗しました: {{ .err }}'
FileAISearchEmptyDir: 'このパス以下に一覧できるファイルまたはフォルダーがありません(またはすべて除外されました)。'
# AIゲートウェイ
ErrAIProxyServiceNotInstalled: '{{ .name }} サービスはインストールされていません'
ErrAIProxyModelAccountNotFound: 'モデルアカウント {{ .id }} が見つかりません'
ErrAIProxyModelMapRequired: 'この項目は必須です。'
ErrAIProxyModelMapInvalid: 'モデルマッピングは有効な JSON オブジェクトである必要があります: {{ .err }}'
ErrAIProxyModelMapEmpty: 'モデルマッピングは空にできません'
ErrAIProxyModelMapEmptyExternalModel: 'モデルマッピングに空のリクエストモデルがあります'
ErrAIProxyModelMapEmptyUpstreamModel: '{{ .model }} の上流モデルは空にできません'
AIProxyUserFallback: '削除済みユーザー (ID: {{ .id }})'
+92 -21
View File
@@ -1,29 +1,47 @@
# 기본 오류
ErrInvalidParams: '잘못된 요청: {{ .detail }}'
ErrTokenParse: '토큰 오류: {{ .detail }}'
ErrInitialPassword: '원래 비밀번호가 틀립니다'
ErrInternalServer: '서버 오류: {{ .detail }}'
ErrRecordExist: '레코드가 이미 있습니다'
ErrRecordNotFound: '레코드를 찾을 수 없습니다'
ErrStructTransform: '형 변환 실패: {{ .err }}'
ErrNotSupportType: '지원하지 않는 타입: {{ .name }}'
# 파일 AI
ErrFileAINotEnabled: '파일 관리에서 「AI 계정」으로 파일 AI 검색을 켜고 계정을 선택하세요.'
ErrFileAISearchBadPattern: '검색 패턴이 잘못되었습니다(정규식 등): {{ .detail }}'
FileAISearchEmptyDir: '이 경로에 나열할 파일이나 디렉터리가 없습니다(또는 모두 필터링됨).'
# 원격 다운로드
ErrWgetRemoteFailed: '원격 다운로드 주소가 잘못되었거나 접근할 수 없습니다: {{ .detail }}'
ErrWgetInvalidContentType: '원격 주소가 파일이 아닌 콘텐츠를 반환했습니다 (오류 페이지일 수 있음), {{ .detail }}'
ErrWgetProxyNotConfigured: '먼저 패널 설정에서 프록시 서버를 구성하세요'
ErrWgetProxyInvalid: '프록시 서버 주소가 잘못되었습니다: {{ .detail }}'
ErrRecordExist: '레코드가 이미 있습니다'
ErrRecordNotFound: '레코드를 찾을 수 없습니다'
ErrStructTransform: '형 변환 실패: {{ .err }}'
# 로그인 및 보안
ErrNotLogin: '로그인하지 않음: {{ .detail }}'
ErrPasswordExpired: '비밀번호 만료: {{ .detail }}'
ErrNotSupportType: '지원하지 않는 타입: {{ .name }}'
ErrAlertMethodNotSupported: '현재 버전에서는 이 알림 방식을 지원하지 않습니다'
# 요청 및 API
ErrProxy: '요청 실패: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API 사용 중지: {{ .detail }}'
ErrApiConfigKeyInvalid: 'API 키가 잘못됨: {{ .detail }}'
ErrApiConfigIPInvalid: 'IP 허용되지 않음: {{ .detail }}'
ErrApiConfigDisable: 'API 접근 금지: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: '타임스탬프 오류: {{ .detail }}'
ErrShutDown: '명령이 수동으로 중단되었습니다'
ErrCreateHttpClient: '요청 {{ .err }}을(를) 생성하지 못했습니다.'
ErrHttpReqTimeOut: '요청 시간이 초과되었습니다 {{ .err }}'
ErrHttpReqFailed: '요청이 실패했습니다 {{ .err }}'
ErrNoSuchHost: '요청한 서버 {{ .err }}을 찾을 수 없습니다'
ErrHttpReqNotFound: '요청한 리소스 {{ .err }}을 찾을 수 없습니다.'
# 빠른 이동
ErrMinQuickJump: '퀵 액세스를 하나 이상 추가하세요'
ErrMaxQuickJump: '퀵 액세스는 최대 4개'
# 공통
ErrShutDown: '명령이 수동으로 중단되었습니다'
ErrUsernameIsExist: '아이디가 이미 존재합니다'
ErrNameIsExist: '이름이 이미 존재합니다'
ErrDemoEnvironment: '데모 환경에서는 불가'
@@ -41,6 +59,8 @@ SystemRestart: '시스템 재시작으로 인해 작업이 중단되었습니다
ErrGroupIsDefault: '기본 그룹, 삭제할 수 없습니다'
ErrGroupIsInWebsiteUse: '그룹이 다른 웹사이트에서 사용 중이므로 삭제할 수 없습니다.'
Decrypt: '복호화'
# 에이전트
ErrAgentAccountBound: '계정이 에이전트에 묶여 있습니다'
ErrTerminalAIAccountInUse: '이 모델 계정은 현재 터미널 AI에서 사용 중입니다. 터미널 AI 설정에서 다른 계정으로 변경하거나 터미널 AI를 비활성화한 뒤 다시 시도하세요.'
ErrAgentAccountUnavailable: '계정 연결 불가: {{ .err }}'
@@ -65,14 +85,8 @@ ErrAgentWebsiteInUse: '이 웹사이트는 이미 다른 에이전트에 연결
ErrAgentWebsiteUnbindUnsupported: '원클릭 배포 웹사이트는 수동으로 연결 해제할 수 없습니다'
ErrHermesPairingCodeUnavailable: 'Hermes에서 페어링 코드가 일시적으로 존재하지 않습니다. 네트워크 문제일 수 있으니 잠시 후 다시 시도해 주세요.'
ErrHermesFeishuGroupAllowlistRequiresAllowlist: 'Feishu 그룹 정책이 허용 목록이면 DM 정책을 페어링 코드로 설정할 수 없습니다.'
ErrAIProxyServiceNotInstalled: '{{ .name }} 서비스가 설치되어 있지 않습니다'
ErrAIProxyModelAccountNotFound: '모델 계정 {{ .id }}을(를) 찾을 수 없습니다'
ErrAIProxyModelMapRequired: '이 필드는 필수입니다.'
ErrAIProxyModelMapInvalid: '모델 매핑은 유효한 JSON 객체여야 합니다: {{ .err }}'
ErrAIProxyModelMapEmpty: '모델 매핑은 비워 둘 수 없습니다'
ErrAIProxyModelMapEmptyExternalModel: '모델 매핑에 빈 요청 모델이 있습니다'
ErrAIProxyModelMapEmptyUpstreamModel: '{{ .model }}의 업스트림 모델은 비워 둘 수 없습니다'
AIProxyUserFallback: '사용자 {{ .id }}'
# 백업 및 알림 설정
Localhost: '로컬 머신'
ErrBackupInUsed: '백업 계정이 예약에 사용 중'
ErrBackupCheck: '연결 테스트 실패: {{ .err }}'
@@ -80,6 +94,8 @@ ErrBackupLocalDelete: '로컬 백업은 삭제할 수 없습니다'
ErrBackupLocalCreate: '로컬 백업은 만들 수 없습니다'
ErrAlertConfigInUse: '경고 설정이 사용 중이므로 삭제할 수 없습니다'
ErrAlertConfigPhoneExist: '전화번호가 이미 존재합니다'
# 앱
ErrPortInUsed: '{{ .detail }} 포트가 이미 사용 중입니다'
ErrAppLimit: '설치된 애플리케이션 수가 한도를 초과했습니다'
ErrNotInstall: '응용 프로그램이 설치되지 않았습니다'
@@ -97,16 +113,12 @@ ErrFileParseApp: '{{ .name }} 파일 구문 분석에 실패했습니다 {{ .err
ErrAppDirNull: '버전 폴더가 존재하지 않습니다'
LocalAppErr: '애플리케이션 {{ .name }} 동기화에 실패했습니다 {{ .err }}'
ErrContainerName: '컨테이너 이름이 이미 존재합니다'
ErrCreateHttpClient: '요청 {{ .err }}을(를) 생성하지 못했습니다.'
ErrHttpReqTimeOut: '요청 시간이 초과되었습니다 {{ .err }}'
ErrHttpReqFailed: '요청이 실패했습니다 {{ .err }}'
ErrNoSuchHost: '요청한 서버 {{ .err }}을 찾을 수 없습니다'
ErrHttpReqNotFound: '요청한 리소스 {{ .err }}을 찾을 수 없습니다.'
ErrContainerNotFound: '{{ .name }} 컨테이너가 존재하지 않습니다'
ErrContainerMsg: '{{ .name }} 컨테이너가 비정상입니다. 자세한 내용은 컨테이너 페이지의 로그를 확인하세요.'
ErrAppBackup: '{{ .name }} 애플리케이션 백업에 실패했습니다 {{ .err }}'
ErrVersionTooLow: '현재 1Panel 버전이 너무 낮아 App Store를 업데이트할 수 없습니다. 작동하시기 전에 버전을 업그레이드하세요.'
ErrAppNameExist: '응용 프로그램 이름이 이미 존재합니다'
ErrAppInstallNameExist: '앱 설치 이름 {{ .name }} 이(가) 이미 존재합니다'
AppStoreIsSyncing: 'App Store가 동기화 중입니다. 나중에 다시 시도하세요.'
ErrGetCompose: 'docker-compose.yml 파일을 가져오지 못했습니다 {{ .detail }}'
ErrAppWarn: '비정상적인 상태입니다. 로그를 확인해 주세요.'
@@ -131,6 +143,8 @@ ErrDockerFailed: 'Docker의 상태가 비정상입니다. 서비스 상태를
ErrDockerComposeCmdNotFound: 'Docker Compose 명령이 없습니다. 호스트 머신에 먼저 이 명령을 설치하세요'
UseExistImage: '이미지가 이미 존재하여 기존 이미지를 사용합니다'
ErrDatabaseNotFound: '데이터베이스 {{ .name }} 이(가) 존재하지 않습니다'
# SSH
ExportIP: '로그인 IP'
ExportArea: '위치'
ExportPort: '포트'
@@ -138,6 +152,8 @@ ExportAuthMode: '로그인 방식'
ExportUser: '사용자'
ExportStatus: '로그인 상태'
ExportDate: '시간'
# 파일
ErrFileCanNotRead: '이 파일은 미리보기를 지원하지 않습니다'
ErrSensitiveFileRead: '민감한 파일을 읽을 수 없습니다'
ErrFileToLarge: '파일이 10M보다 커서 열 수 없습니다'
@@ -164,6 +180,8 @@ FileDropFailed: '파일 {{ .name }} 정리 실패: {{ .err }}'
FileDropSuccess: '파일 {{ .name }} 정리 성공, {{ .count }}개 파일 삭제, {{ .size }} 디스크 공간 확보'
FileDropSum: '파일 정리 완료, 총 {{ .count }}개 파일 삭제, 총 {{ .size }} 디스크 공간 확보'
ErrBadDecrypt: '압축 패키지 암호 해제 비밀번호 오류, 압축 해제 실패, 확인 후 다시 시도하세요'
# 웹사이트
ErrAliasIsExist: '별칭이 이미 존재합니다'
ErrBackupMatch: '백업 파일이 현재 웹사이트 데이터 중 일부 {{ .detail }}와 일치하지 않습니다.'
ErrBackupExist: '백업 파일에 있는 소스 데이터의 해당 부분이 존재하지 않습니다 {{ .detail }}'
@@ -193,6 +211,8 @@ delete: '삭제'
ErrDefaultWebsite: '기본 웹사이트가 이미 설정되었습니다. 설정하기 전에 취소하세요'
SSL: '인증서'
Set: '설정'
# SSL 인증서
ErrSSLCannotDelete: '{{ .name }} 인증서는 웹사이트에서 사용 중이므로 삭제할 수 없습니다.'
ErrAccountCannotDelete: '계정이 인증서와 연결되어 있어 삭제할 수 없습니다.'
ErrSSLApply: '인증서 갱신이 성공했으나, openresty 재로드에 실패했습니다. 구성을 확인해 주세요'
@@ -227,6 +247,8 @@ ErrApplySSLCanNotDelete: '신청 중인 인증서 {{ .name }}는 삭제할 수
StartPushSSLToNode: '인증서를 노드로 푸시 시작'
PushSSLToNodeFailed: '노드로 인증서 푸시 실패: {{ .err }}'
PushSSLToNodeSuccess: '노드로 인증서 푸시 성공'
# MySQL
ErrUserIsExist: '현재 사용자가 이미 존재합니다. 다시 입력하세요'
ErrDatabaseIsExist: '현재 데이터베이스가 이미 존재합니다. 다시 입력하세요'
ErrExecTimeOut: 'SQL 실행 시간이 초과되었습니다. 데이터베이스를 확인하십시오.'
@@ -235,7 +257,11 @@ ErrLocalExist: '이름이 로컬 데이터베이스에 이미 존재합니다.
RemoteBackup: '원격 데이터베이스를 백업하려면 먼저 이미지 {{ .name }}을(를) 사용하여 로컬 컨테이너 데이터베이스 서비스를 시작해야 합니다. 잠시만 기다려 주세요...'
RemoteRecover: '원격 데이터베이스를 복원하려면 먼저 이미지 {{ .name }}을(를) 사용하여 로컬 컨테이너 데이터베이스 서비스를 시작해야 합니다. 잠시만 기다려 주세요...'
Arg: '인수'
# Redis
ErrTypeOfRedis: '복구 파일 유형이 현재 지속성 방법과 일치하지 않습니다. 수정하고 다시 시도하세요'
# 컨테이너
ErrInUsed: '{{ .detail }}(이)가 사용 중이어서 삭제할 수 없습니다'
ErrObjectInUsed: '이 객체는 사용 중이어서 삭제할 수 없습니다'
ErrObjectBeDependent: '이 이미지는 다른 이미지에 의존되어 있어 삭제할 수 없습니다'
@@ -277,6 +303,8 @@ ComposeRecoverFiles: 'Compose 파일 복원'
ComposeRecoverContainers: 'Compose 컨테이너 복원'
ComposeRecoverRecord: 'Compose 기록 저장'
ComposeRecoverCleanup: 'Compose 복원 작업 공간 정리'
# 런타임
ErrFileNotExist: '{{ .detail }} 파일이 존재하지 않습니다 소스 파일의 무결성을 확인하세요'
ErrImageBuildErr: '이미지 빌드 실패'
ErrImageExist: '이미지가 이미 존재합니다 이미지 이름을 수정하세요.'
@@ -289,12 +317,16 @@ ErrNodeModulesNotFound: 'node_modules 폴더가 없습니다 런타임 환경을
ErrContainerNameIsNull: '컨테이너 이름이 존재하지 않습니다'
ErrPHPPortIsDefault: '9000 포트는 기본 포트입니다. 수정 후 다시 시도하세요'
ErrPHPRuntimePortFailed: '포트 {{ .name }} 는 현재 런타임 환경에서 이미 사용 중입니다. 수정 후 다시 시도하세요'
# 도구
ErrConfigNotFound: '구성 파일이 존재하지 않습니다'
ErrConfigParse: '구성 파일 형식이 올바르지 않습니다'
ErrConfigIsNull: '구성 파일은 비어 있을 수 없습니다'
ErrConfigDirNotFound: '실행 디렉토리가 존재하지 않습니다'
ErrConfigAlreadyExist: '같은 이름의 구성 파일이 이미 존재합니다'
ErrUserFindErr: '사용자 {{ .name }} 검색에 실패했습니다 {{ .err }}'
# 예약 작업
CutWebsiteLogSuccess: '{{ .name }} 웹사이트 로그가 성공적으로 잘렸습니다. 백업 경로 {{ .path }}'
HandleShell: '스크립트 {{ .name }} 실행'
HandleCurl: 'URL {{ .name }} 접근'
@@ -313,6 +345,8 @@ NoSuchResource: '데이터베이스에서 백업 내용을 찾을 수 없어 건
CleanLog: '로그 정리'
CleanLogByName: '{{ .name }} 로그 정리'
WafIpGroupNotFound: 'WAF IP 그룹을 찾을 수 없습니다. [고급 기능 - WAF - 블랙/화이트 목록 - IP 그룹]으로 이동하여 원격 다운로드 방식으로 IP 그룹을 추가하십시오.'
# 도구 상자
ErrNotExistUser: '현재 사용자가 존재하지 않습니다. 수정한 후 다시 시도하세요'
ErrBanAction: '설정에 실패했습니다. 현재 {{ .name }} 서비스를 사용할 수 없습니다. 확인하고 다시 시도하세요'
ErrClamdscanNotFound: 'clamdscan 명령이 감지되지 않았습니다. 설명서를 참조하여 설치하세요'
@@ -321,11 +355,15 @@ SetSwap: 'Swap 설정 {{ .path }} - {{ .size }}'
CreateSwap: 'Swap 파일 생성'
FormatSwap: 'Swap 파일 포맷'
EnableSwap: 'Swap 활성화'
# 변조 방지
CleanTamperSetting: '히스토리 보호 설정 정리'
SetTamperWithRules: '규칙에 따라 디렉토리 보호 시작'
ProtectDir: '디렉토리 보호 {{ .name }}'
IgnoreFile: '파일 보호 {{ .name }}'
TamperSetSuccessful: '보호 설정 완료, 다음 디렉토리 모니터링 시작:'
# WAF
ErrScope: '이 구성을 수정하는 것은 지원되지 않습니다'
ErrStateChange: '상태 변경에 실패했습니다'
ErrRuleExist: '규칙이 이미 존재합니다'
@@ -380,6 +418,8 @@ OpenrestyNotFound: 'OpenResty가 설치되어 있지 않습니다'
remoteIpIsNull: 'IP 목록이 비어 있습니다'
OpenrestyVersionErr: 'OpenResty 버전이 너무 낮습니다. 1.27.1.2-2-2-focal 이상으로 업그레이드하세요'
ErrFileTooLarge: '파일이 1MB를 초과하여 업로드할 수 없습니다'
# 작업
TaskStart: '[START] {{ .name }} 작업이 시작됩니다.'
TaskEnd: '{{ .name }} 작업이 완료되었습니다 [완료]'
TaskFailed: '{{ .name }} 작업이 실패했습니다'
@@ -440,10 +480,17 @@ CustomAppstore: '사용자 정의 애플리케이션 웨어하우스'
TaskExec: '실행'
TaskBatch: '일괄 작업'
FileConvert: '파일 형식 변환'
# 작업 - ClamAV
Clamscan: '{{ .name }} 스캔'
TaskScan: '스캔'
# 작업 - AI
OllamaModelPull: 'Ollama 모델 {{ .name }}을(를) 끌어오세요'
OllamaModelSize: 'Ollama 모델 {{ .name }}의 크기를 가져옵니다'
AIBenchmarkRun: "AI 벤치마크 실행"
# 작업 - 스냅샷
Snapshot: '스냅샷'
SnapDBInfo: '1Panel 데이터베이스 정보 쓰기'
SnapCopy: '파일 및 디렉토리 {{ .name }} 복사'
@@ -493,6 +540,8 @@ RecoverDaemonJson: '컨테이너 구성 daemon.json 파일 복원'
RecoverDBData: '데이터베이스 데이터 복구'
RecoverBackups: '로컬 백업 디렉토리 복원'
RecoverPanelData: '복구 데이터 디렉토리'
# 작업 - 컨테이너
ContainerImagePull: '컨테이너 이미지 {{ .name }} 가져오기'
ContainerRemoveOld: '원래 컨테이너 {{ .name }} 제거'
ContainerImageCheck: '이미지가 정상적으로 당겨졌는지 확인'
@@ -501,6 +550,8 @@ ContainerRecreate: '컨테이너 업데이트에 실패했습니다. 이제 원
ContainerCreate: '새로운 컨테이너 {{ .name }}를 만듭니다'
ContainerCreateFailed: '컨테이너 생성에 실패했습니다. 실패한 컨테이너를 삭제하세요'
ContainerStartCheck: '컨테이너가 시작되었는지 확인'
# 작업 - 이미지
ImageBuild: '이미지 빌드'
ImagePull: '이미지 가져오기'
ImageRepoAuthFromDB: '데이터베이스에서 저장소 인증 정보 가져오기'
@@ -508,6 +559,8 @@ ImagePush: '이미지 푸시'
ImageRenameTag: '이미지 태그 수정'
ImageNewTag: '새로운 이미지 태그 {{ .name }}'
ComposeCreate: '작곡 만들기'
# 작업 - 웹사이트
BackupNginxConfig: '웹사이트 OpenResty 구성 파일 백업'
CompressFileSuccess: '디렉토리 압축이 성공했습니다. {{ .name }}으로 압축되었습니다.'
CompressDir: '압축 디렉토리'
@@ -518,8 +571,15 @@ websiteDir: '웹사이트 디렉토리'
RecoverFailedStartRollBack: '복구 실패, 롤백 시작'
AppBackupFileIncomplete: '백업 파일이 불완전하며 app.json 또는 app.tar.gz 파일이 없습니다.'
AppAttributesNotMatch: '애플리케이션 유형 또는 이름이 일치하지 않습니다'
# 알림
ErrAlertMethodNotSupported: '현재 버전에서는 이 알림 방식을 지원하지 않습니다'
ErrAlertSync: '알람 정보 동기화 오류입니다. 확인하고 다시 시도하세요'
# 작업 - 런타임
ErrInstallExtension: '이미 설치 작업이 진행 중입니다. 작업이 완료될 때까지 기다려 주세요.'
# 알림 메일 템플릿
PanelAlertTitle: '패널 알림 통지'
TestAlertTitle: '테스트 이메일 - 이메일 연결 확인'
TestAlert: '이것은 테스트 이메일이며 이메일 발송 설정이 올바른지 확인합니다.'
@@ -536,6 +596,8 @@ CommonAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .msg }}。자세한 내
NodeExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
LicenseExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 라이센스에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
SSHAndPanelLoginAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .loginIp }}에서의 {{ .name }} 로그인에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
# 디스크
DeviceNotFound: '장치 {{ .name }} 을(를) 찾을 수 없습니다'
DeviceIsMounted: '장치 {{ .name }} 이(가) 마운트되었습니다, 먼저 마운트 해제하세요'
PartitionDiskErr: '파티션 분할에 실패했습니다, {{ .err }}'
@@ -543,9 +605,18 @@ FormatDiskErr: '디스크 포맷에 실패했습니다, {{ .err }}'
MountDiskErr: '디스크 마운트에 실패했습니다, {{ .err }}'
XfsNotFound: 'xfs 파일 시스템이 감지되지 않았습니다, 먼저 xfsprogs 를 설치하세요'
# terminal
# 터미널
TerminalAIBlockedRiskyCommand: '위험한 명령이 차단되었습니다: {{ .command }}'
TerminalAIThinking: 'AI가 생각 중입니다...'
TerminalAIReadyToExecute: '생각이 완료되었습니다. Enter를 눌러 실행하세요 (소요 시간 {{ .duration }}, token: {{ .tokens }})'
TerminalAIRequestFailed: 'AI 요청 실패: {{ .err }}'
FileAISearchEmptyDir: '이 경로에 나열할 파일이나 디렉터리가 없습니다(또는 모두 필터링됨).'
# AI 게이트웨이
ErrAIProxyServiceNotInstalled: '{{ .name }} 서비스가 설치되어 있지 않습니다'
ErrAIProxyModelAccountNotFound: '모델 계정 {{ .id }}을(를) 찾을 수 없습니다'
ErrAIProxyModelMapRequired: '이 필드는 필수입니다.'
ErrAIProxyModelMapInvalid: '모델 매핑은 유효한 JSON 객체여야 합니다: {{ .err }}'
ErrAIProxyModelMapEmpty: '모델 매핑은 비워 둘 수 없습니다'
ErrAIProxyModelMapEmptyExternalModel: '모델 매핑에 빈 요청 모델이 있습니다'
ErrAIProxyModelMapEmptyUpstreamModel: '{{ .model }}의 업스트림 모델은 비워 둘 수 없습니다'
AIProxyUserFallback: '삭제된 사용자 (ID: {{ .id }})'
+91 -20
View File
@@ -1,29 +1,47 @@
# ralat asas
ErrInvalidParams: 'Permintaan tidak sah: {{ .detail }}'
ErrTokenParse: 'Token tidak sah: {{ .detail }}'
ErrInitialPassword: 'Kata laluan asal salah'
ErrInternalServer: 'Ralat pelayan: {{ .detail }}'
ErrRecordExist: 'Rekod sudah wujud'
ErrRecordNotFound: 'Rekod tidak ditemui'
ErrStructTransform: 'Ralat penukaran: {{ .err }}'
ErrNotSupportType: 'Jenis tidak disokong: {{ .name }}'
# AI fail
ErrFileAINotEnabled: 'Carian AI fail dimatikan atau tiada akaun. Dalam Pengurusan Fail, gunakan Akaun AI, aktifkan dan pilih akaun.'
ErrFileAISearchBadPattern: 'Corak carian tidak sah (regex dsb.): {{ .detail }}'
FileAISearchEmptyDir: 'Tiada fail atau direktori di bawah laluan ini (atau semua ditapis).'
# muat turun jauh
ErrWgetRemoteFailed: 'Muat turun jauh gagal atau URL tidak boleh diakses: {{ .detail }}'
ErrWgetInvalidContentType: 'URL jauh tidak mengembalikan fail (mungkin halaman ralat), {{ .detail }}'
ErrWgetProxyNotConfigured: 'Sila konfigurasikan pelayan proksi dalam tetapan panel dahulu'
ErrWgetProxyInvalid: 'Alamat pelayan proksi tidak sah: {{ .detail }}'
ErrRecordExist: 'Rekod sudah wujud'
ErrRecordNotFound: 'Rekod tidak ditemui'
ErrStructTransform: 'Ralat penukaran: {{ .err }}'
# log masuk dan keselamatan
ErrNotLogin: 'Belum log masuk: {{ .detail }}'
ErrPasswordExpired: 'Kata laluan tamat: {{ .detail }}'
ErrNotSupportType: 'Jenis tidak disokong: {{ .name }}'
ErrAlertMethodNotSupported: 'Versi ini tidak menyokong kaedah amaran ini'
# permintaan dan API
ErrProxy: 'Permintaan gagal: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API dilumpuhkan: {{ .detail }}'
ErrApiConfigKeyInvalid: 'Kunci API tidak sah: {{ .detail }}'
ErrApiConfigIPInvalid: 'IP tidak dibenarkan: {{ .detail }}'
ErrApiConfigDisable: 'API tidak dibenarkan: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'Cap masa tidak sah: {{ .detail }}'
ErrShutDown: 'Arahan diberhentikan manual'
ErrCreateHttpClient: 'Gagal membuat permintaan {{ .err }}'
ErrHttpReqTimeOut: 'Permintaan tamat masa {{ .err }}'
ErrHttpReqFailed: 'Permintaan gagal {{ .err }}'
ErrNoSuchHost: 'Tidak dapat mencari pelayan yang diminta {{ .err }}'
ErrHttpReqNotFound: 'Sumber yang diminta {{ .err }} tidak dapat ditemui'
# lompat pantas
ErrMinQuickJump: 'Tambah sekurang-kurangnya satu pintasan pantas'
ErrMaxQuickJump: 'Maksimum 4 pintasan pantas'
# umum
ErrShutDown: 'Arahan diberhentikan manual'
ErrUsernameIsExist: 'Nama pengguna sudah wujud'
ErrNameIsExist: 'Nama sudah wujud'
ErrDemoEnvironment: 'Tidak tersedia dalam demo'
@@ -41,6 +59,8 @@ SystemRestart: 'Tugas terganggu kerana sistem mula semula'
ErrGroupIsDefault: 'Kumpulan lalai, tidak boleh dipadamkan'
ErrGroupIsInWebsiteUse: 'Kumpulan sedang digunakan oleh tapak web lain dan tidak boleh dipadamkan.'
Decrypt: 'Dekripsi'
# ejen
ErrAgentAccountBound: 'Akaun terikat kepada ejen'
ErrTerminalAIAccountInUse: 'Akaun model ini sedang digunakan oleh Terminal AI. Tukar akaun dalam tetapan Terminal AI atau nyahaktifkan Terminal AI, kemudian cuba lagi.'
ErrAgentAccountUnavailable: 'Sambungan akaun tidak tersedia: {{ .err }}'
@@ -65,14 +85,8 @@ ErrAgentWebsiteInUse: 'Laman web ini sudah dipautkan ke ejen lain'
ErrAgentWebsiteUnbindUnsupported: 'Laman web one-click deployment tidak menyokong nyahikat manual'
ErrHermesPairingCodeUnavailable: 'Kod pasangan buat sementara waktu tidak wujud dalam Hermes, mungkin disebabkan masalah rangkaian. Sila cuba lagi sebentar nanti.'
ErrHermesFeishuGroupAllowlistRequiresAllowlist: 'Apabila dasar kumpulan Feishu ialah senarai benarkan, dasar DM tidak boleh menggunakan kod pasangan.'
ErrAIProxyServiceNotInstalled: 'Perkhidmatan {{ .name }} belum dipasang'
ErrAIProxyModelAccountNotFound: 'Akaun model {{ .id }} tidak ditemui'
ErrAIProxyModelMapRequired: 'Medan ini wajib diisi.'
ErrAIProxyModelMapInvalid: 'Pemetaan model mestilah objek JSON yang sah: {{ .err }}'
ErrAIProxyModelMapEmpty: 'Pemetaan model tidak boleh kosong'
ErrAIProxyModelMapEmptyExternalModel: 'Pemetaan model mengandungi model permintaan kosong'
ErrAIProxyModelMapEmptyUpstreamModel: 'Model upstream untuk {{ .model }} tidak boleh kosong'
AIProxyUserFallback: 'Pengguna {{ .id }}'
# sandaran dan tetapan amaran
Localhost: 'Mesin Tempatan'
ErrBackupInUsed: 'Akaun sandaran sedang digunakan oleh tugas'
ErrBackupCheck: 'Ujian sambungan gagal: {{ .err }}'
@@ -80,6 +94,8 @@ ErrBackupLocalDelete: 'Tidak boleh padam sandaran tempatan'
ErrBackupLocalCreate: 'Tidak boleh buat sandaran tempatan'
ErrAlertConfigInUse: 'Konfigurasi amaran sedang digunakan dan tidak boleh dipadamkan'
ErrAlertConfigPhoneExist: 'Nombor telefon sudah wujud'
# aplikasi
ErrPortInUsed: 'Port {{ .detail }} sudah diduduki'
ErrAppLimit: 'Bilangan aplikasi yang dipasang telah melebihi had'
ErrNotInstall: 'Aplikasi tidak dipasang'
@@ -97,16 +113,12 @@ ErrFileParseApp: '{{ .name }} penghuraian fail gagal {{ .err }}'
ErrAppDirNull: 'Folder versi tidak wujud'
LocalAppErr: 'Penyegerakan {{ .name }} aplikasi gagal {{ .err }}'
ErrContainerName: 'Nama kontena sudah wujud'
ErrCreateHttpClient: 'Gagal membuat permintaan {{ .err }}'
ErrHttpReqTimeOut: 'Permintaan tamat masa {{ .err }}'
ErrHttpReqFailed: 'Permintaan gagal {{ .err }}'
ErrNoSuchHost: 'Tidak dapat mencari pelayan yang diminta {{ .err }}'
ErrHttpReqNotFound: 'Sumber yang diminta {{ .err }} tidak dapat ditemui'
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 }}'
ErrVersionTooLow: 'Versi 1Panel semasa terlalu rendah untuk mengemas kini App Store. Sila tingkatkan versi sebelum beroperasi.'
ErrAppNameExist: 'Nama aplikasi sudah wujud'
ErrAppInstallNameExist: 'Nama pemasangan aplikasi {{ .name }} sudah wujud'
AppStoreIsSyncing: 'App Store sedang menyegerak, sila cuba sebentar lagi'
ErrGetCompose: 'Gagal mendapatkan fail docker-compose.yml {{ .detail }}'
ErrAppWarn: 'Status tidak normal, sila semak log'
@@ -131,6 +143,8 @@ ErrDockerFailed: 'Keadaan Docker tidak normal, sila periksa status perkhidmatan'
ErrDockerComposeCmdNotFound: 'Perintah Docker Compose tidak wujud, sila pasang perintah ini di mesin tuan terlebih dahulu'
UseExistImage: 'Imej sudah wujud, menggunakan imej sedia ada'
ErrDatabaseNotFound: 'Pangkalan data {{ .name }} tidak wujud'
# SSH
ExportIP: 'IP Log Masuk'
ExportArea: 'Lokasi'
ExportPort: 'Port'
@@ -138,6 +152,8 @@ ExportAuthMode: 'Kaedah Log Masuk'
ExportUser: 'Pengguna'
ExportStatus: 'Status Log Masuk'
ExportDate: 'Masa'
# fail
ErrFileCanNotRead: 'Fail ini tidak menyokong pratonton'
ErrSensitiveFileRead: 'Membaca fail sensitif tidak dibenarkan'
ErrFileToLarge: 'Fail lebih besar daripada 10M dan tidak boleh dibuka'
@@ -164,6 +180,8 @@ FileDropFailed: 'Gagal membersihkan fail {{ .name }}: {{ .err }}'
FileDropSuccess: 'Berjaya membersihkan fail {{ .name }}, {{ .count }} fail dibersihkan, {{ .size }} ruang cakera dibebaskan'
FileDropSum: 'Pembersihan fail selesai, sejumlah {{ .count }} fail dibersihkan, sejumlah {{ .size }} ruang cakera dibebaskan'
ErrBadDecrypt: 'Kata laluan penyahsulitan pakej termampat salah, penyahmampatan gagal, sila periksa dan cuba lagi'
# laman web
ErrAliasIsExist: 'Alias sudah wujud'
ErrBackupMatch: 'Fail sandaran tidak sepadan dengan beberapa data tapak web semasa {{ .detail }}'
ErrBackupExist: 'Bahagian sepadan data sumber dalam fail sandaran tidak wujud {{ .detail }}'
@@ -193,6 +211,8 @@ delete: 'Padam'
ErrDefaultWebsite: 'Laman web lalai telah ditetapkan, sila batalkan sebelum menetapkan'
SSL: 'Sijil'
Set: 'Tetapan'
# sijil SSL
ErrSSLCannotDelete: 'Sijil {{ .name }} sedang digunakan oleh tapak web dan tidak boleh dipadamkan'
ErrAccountCannotDelete: 'Akaun dikaitkan dengan sijil dan tidak boleh dipadamkan'
ErrSSLApply: 'Pembaharuan sijil berjaya, muat semula openresty gagal, sila semak konfigurasi'
@@ -227,6 +247,8 @@ ErrApplySSLCanNotDelete: 'Sertifikat {{ .name }} yang sedang diajukan tidak dapa
StartPushSSLToNode: 'Mula menolak sijil ke nod'
PushSSLToNodeFailed: 'Gagal menolak sijil ke nod: {{ .err }}'
PushSSLToNodeSuccess: 'Berjaya menolak sijil ke nod'
# MySQL
ErrUserIsExist: 'Pengguna semasa sudah wujud, sila masukkan semula'
ErrDatabaseIsExist: 'Pangkalan data semasa sudah wujud, sila masukkan semula'
ErrExecTimeOut: 'Pelaksanaan SQL tamat masa, sila semak pangkalan data'
@@ -235,7 +257,11 @@ ErrLocalExist: 'Nama sudah wujud dalam pangkalan data tempatan, sila ubah suai d
RemoteBackup: 'Untuk menyandarkan pangkalan data jauh, perkhidmatan pangkalan data bekas tempatan perlu dimulakan terlebih dahulu menggunakan imej {{ .name }}, sila tunggu...'
RemoteRecover: 'Untuk memulihkan pangkalan data jauh, perkhidmatan pangkalan data bekas tempatan perlu dimulakan terlebih dahulu menggunakan imej {{ .name }}, sila tunggu...'
Arg: 'Hujah'
# Redis
ErrTypeOfRedis: 'Jenis fail pemulihan tidak sepadan dengan kaedah kegigihan semasa, sila ubah suai dan cuba lagi'
# bekas
ErrInUsed: '{{ .detail }} sedang digunakan dan tidak boleh dipadam'
ErrObjectInUsed: 'Objek ini sedang digunakan dan tidak boleh dipadam'
ErrObjectBeDependent: 'Imej ini digantungkan oleh imej lain dan tidak boleh dipadam'
@@ -277,6 +303,8 @@ ComposeRecoverFiles: 'Pulihkan fail compose'
ComposeRecoverContainers: 'Pulihkan kontena compose'
ComposeRecoverRecord: 'Simpan rekod compose'
ComposeRecoverCleanup: 'Bersihkan ruang kerja pemulihan compose'
# runtime
ErrFileNotExist: 'Fail {{ .detail }} tidak wujud Sila semak integriti fail sumber'
ErrImageBuildErr: 'Pembinaan imej gagal'
ErrImageExist: 'Imej sudah wujud Sila ubah nama imej.'
@@ -289,12 +317,16 @@ ErrNodeModulesNotFound: 'Folder node_modules tidak wujud Sila edit persekitaran
ErrContainerNameIsNull: 'Nama kontena tidak wujud'
ErrPHPPortIsDefault: 'Port 9000 ialah port lalai. Sila tukar dan cuba lagi'
ErrPHPRuntimePortFailed: 'Port {{ .name }} sudah digunakan oleh runtime semasa. Sila tukar dan cuba lagi'
# alat
ErrConfigNotFound: 'Fail konfigurasi tidak wujud'
ErrConfigParse: 'Format fail konfigurasi tidak betul'
ErrConfigIsNull: 'Fail konfigurasi tidak boleh kosong'
ErrConfigDirNotFound: 'Direktori berjalan tidak wujud'
ErrConfigAlreadyExist: 'Fail konfigurasi dengan nama yang sama sudah wujud'
ErrUserFindErr: 'Pengguna {{ .name }} carian gagal {{ .err }}'
# tugas cron
CutWebsiteLogSuccess: '{{ .name }} log tapak web berjaya dipotong, laluan sandaran {{ .path }}'
HandleShell: 'Laksanakan skrip {{ .name }}'
HandleCurl: 'Akses URL {{ .name }}'
@@ -313,6 +345,8 @@ NoSuchResource: 'Tiada kandungan sandaran ditemui dalam pangkalan data, dilangka
CleanLog: 'Bersihkan log'
CleanLogByName: 'Bersihkan log {{ .name }}'
WafIpGroupNotFound: 'Kumpulan IP WAF tidak ditemui. Sila pergi ke [Ciri Lanjutan - WAF - Senarai Hitam/Putih - Kumpulan IP] untuk menambah kumpulan IP melalui kaedah muat turun jauh.'
# kotak alat
ErrNotExistUser: 'Pengguna semasa tidak wujud, sila ubah suai dan cuba lagi'
ErrBanAction: 'Tetapan gagal. Perkhidmatan {{ .name }} semasa tidak tersedia. Sila semak dan cuba lagi'
ErrClamdscanNotFound: 'Arahan clamdscan tidak dikesan, sila rujuk dokumentasi untuk memasangnya'
@@ -321,11 +355,15 @@ SetSwap: 'Tetapkan Swap {{ .path }} - {{ .size }}'
CreateSwap: 'Buat Fail Swap'
FormatSwap: 'Format Fail Swap'
EnableSwap: 'Dayakan Swap'
# perlindungan ubah suai
CleanTamperSetting: 'Bersihkan tetapan perlindungan sejarah'
SetTamperWithRules: 'Mulakan perlindungan direktori mengikut peraturan'
ProtectDir: 'Lindungi direktori {{ .name }}'
IgnoreFile: 'Lindungi fail {{ .name }}'
TamperSetSuccessful: 'Tetapan perlindungan selesai, mula memantau direktori berikut:'
# WAF
ErrScope: 'Mengubah suai konfigurasi ini tidak disokong'
ErrStateChange: 'Perubahan keadaan gagal'
ErrRuleExist: 'Peraturan sudah wujud'
@@ -380,6 +418,8 @@ OpenrestyNotFound: 'OpenResty tidak dipasang'
remoteIpIsNull: 'Senarai IP kosong'
OpenrestyVersionErr: 'Versi OpenResty terlalu lama. Naik taraf ke 1.27.1.2-2-2-focal'
ErrFileTooLarge: 'Fail melebihi 1 MB dan tidak boleh dimuat naik'
# tugas
TaskStart: '{{ .name }} Task mula [MULA]'
TaskEnd: '{{ .name }} Task selesai [SELESAI]'
TaskFailed: 'tugas {{ .name }} gagal'
@@ -440,10 +480,17 @@ CustomAppstore: 'Gudang aplikasi tersuai'
TaskExec: 'Laksanakan'
TaskBatch: 'Operasi Batch'
FileConvert: 'Penukaran Format Fail'
# tugas - ClamAV
Clamscan: 'Imbas {{ .name }}'
TaskScan: 'Imbas'
# tugas - AI
OllamaModelPull: 'Tarik model Ollama {{ .name }}'
OllamaModelSize: 'Dapatkan saiz model Ollama {{ .name }}'
AIBenchmarkRun: "Jalankan penanda aras AI"
# tugas - syot kilat
Snapshot: 'Snapshot'
SnapDBInfo: 'Tulis maklumat pangkalan data 1Panel'
SnapCopy: 'Salin fail & direktori {{ .name }}'
@@ -493,6 +540,8 @@ RecoverDaemonJson: 'Pulihkan fail daemon.json konfigurasi bekas'
RecoverDBData: 'Pulihkan data pangkalan data'
RecoverBackups: 'Pulihkan direktori sandaran tempatan'
RecoverPanelData: 'Direktori data pemulihan'
# tugas - bekas
ContainerImagePull: 'Tarik imej bekas {{ .name }}'
ContainerRemoveOld: 'Alih keluar bekas asal {{ .name }}'
ContainerImageCheck: 'Periksa sama ada imej ditarik seperti biasa'
@@ -501,6 +550,8 @@ ContainerRecreate: 'Kemas kini kontena gagal, kini mula memulihkan bekas asal'
ContainerCreate: 'Buat bekas baharu {{ .name }}'
ContainerCreateFailed: 'Penciptaan bekas gagal, padamkan bekas yang gagal'
ContainerStartCheck: 'Periksa sama ada bekas telah dimulakan'
# tugas - imej
ImageBuild: 'Membina Imej'
ImagePull: 'Tarik imej'
ImageRepoAuthFromDB: 'Dapatkan maklumat pengesahan repositori daripada pangkalan data'
@@ -508,6 +559,8 @@ ImagePush: 'Tolak imej'
ImageRenameTag: 'Ubah suai teg imej'
ImageNewTag: 'Teg imej baharu {{ .name }}'
ComposeCreate: 'Buat gubahan'
# tugas - laman web
BackupNginxConfig: 'Fail konfigurasi OpenResty tapak web sandaran'
CompressFileSuccess: 'Mampatkan direktori berjaya, dimampatkan ke {{ .name }}'
CompressDir: 'Direktori mampatan'
@@ -518,8 +571,15 @@ websiteDir: 'Direktori Laman Web'
RecoverFailedStartRollBack: 'Pemulihan gagal, mulakan rollback'
AppBackupFileIncomplete: 'Fail sandaran tidak lengkap dan tidak mempunyai fail app.json atau app.tar.gz'
AppAttributesNotMatch: 'Jenis atau nama aplikasi tidak sepadan'
# amaran
ErrAlertMethodNotSupported: 'Versi ini tidak menyokong kaedah amaran ini'
ErrAlertSync: 'Ralat penyegerakan maklumat penggera, sila semak dan cuba lagi'
# tugas - runtime
ErrInstallExtension: 'Tugas pemasangan sudah sedang berjalan, silakan tunggu tugas selesai'
# templat mel amaran
PanelAlertTitle: 'Notifikasi Amaran Panel'
TestAlertTitle: 'E-mel Ujian - Sahkan Sambungan E-mel'
TestAlert: 'Ini adalah e-mel ujian untuk mengesahkan konfigurasi penghantaran e-mel anda betul.'
@@ -536,6 +596,8 @@ CommonAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .msg }}. Sila log masuk ke pan
NodeExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} nod tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
LicenseExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} lesen tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
SSHAndPanelLoginAlert: 'Panel {{ .node }}{{ .ip }} Anda, log masuk {{ .name }} dari {{ .loginIp }} tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
# cakera
DeviceNotFound: 'Peranti {{ .name }} tidak ditemui'
DeviceIsMounted: 'Peranti {{ .name }} telah dikaitkan, sila nyahkaitkan terlebih dahulu'
PartitionDiskErr: 'Gagal membahagikan, {{ .err }}'
@@ -548,4 +610,13 @@ TerminalAIBlockedRiskyCommand: 'Perintah berisiko telah disekat: {{ .command }}'
TerminalAIThinking: 'AI sedang berfikir...'
TerminalAIReadyToExecute: 'Pemikiran selesai, tekan Enter untuk jalankan (tempoh: {{ .duration }}, token: {{ .tokens }})'
TerminalAIRequestFailed: 'Permintaan AI gagal: {{ .err }}'
FileAISearchEmptyDir: 'Tiada fail atau direktori di bawah laluan ini (atau semua ditapis).'
# gerbang AI
ErrAIProxyServiceNotInstalled: 'Perkhidmatan {{ .name }} belum dipasang'
ErrAIProxyModelAccountNotFound: 'Akaun model {{ .id }} tidak ditemui'
ErrAIProxyModelMapRequired: 'Medan ini wajib diisi.'
ErrAIProxyModelMapInvalid: 'Pemetaan model mestilah objek JSON yang sah: {{ .err }}'
ErrAIProxyModelMapEmpty: 'Pemetaan model tidak boleh kosong'
ErrAIProxyModelMapEmptyExternalModel: 'Pemetaan model mengandungi model permintaan kosong'
ErrAIProxyModelMapEmptyUpstreamModel: 'Model upstream untuk {{ .model }} tidak boleh kosong'
AIProxyUserFallback: 'Pengguna dipadam (ID: {{ .id }})'
+91 -20
View File
@@ -1,29 +1,47 @@
# erros básicos
ErrInvalidParams: 'Requisição inválida: {{ .detail }}'
ErrTokenParse: 'Erro de token: {{ .detail }}'
ErrInitialPassword: 'Senha original incorreta'
ErrInternalServer: 'Erro interno: {{ .detail }}'
ErrRecordExist: 'Registro já existe'
ErrRecordNotFound: 'Registro não encontrado'
ErrStructTransform: 'Erro de conversão: {{ .err }}'
ErrNotSupportType: 'Tipo não suportado: {{ .name }}'
# IA de arquivos
ErrFileAINotEnabled: 'A pesquisa com IA de arquivos está desativada ou sem conta. Em Gerenciamento de arquivos, use Conta de IA, ative e escolha uma conta.'
ErrFileAISearchBadPattern: 'Padrão de busca inválido (regex ou opção): {{ .detail }}'
FileAISearchEmptyDir: 'Nenhum arquivo ou pasta encontrado neste caminho (ou todos foram filtrados).'
# download remoto
ErrWgetRemoteFailed: 'Falha no download remoto ou URL inacessível: {{ .detail }}'
ErrWgetInvalidContentType: 'A URL remota não retornou um arquivo (possivelmente uma página de erro), {{ .detail }}'
ErrWgetProxyNotConfigured: 'Configure primeiro um servidor proxy nas configurações do painel'
ErrWgetProxyInvalid: 'Endereço do servidor proxy inválido: {{ .detail }}'
ErrRecordExist: 'Registro já existe'
ErrRecordNotFound: 'Registro não encontrado'
ErrStructTransform: 'Erro de conversão: {{ .err }}'
# login e segurança
ErrNotLogin: 'Sem sessão: {{ .detail }}'
ErrPasswordExpired: 'Senha expirada: {{ .detail }}'
ErrNotSupportType: 'Tipo não suportado: {{ .name }}'
ErrAlertMethodNotSupported: 'Esta versão não oferece suporte a este método de alerta'
# requisição e API
ErrProxy: 'Requisição falhou: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API desativada: {{ .detail }}'
ErrApiConfigKeyInvalid: 'Chave API inválida: {{ .detail }}'
ErrApiConfigIPInvalid: 'IP sem permissão: {{ .detail }}'
ErrApiConfigDisable: 'API bloqueada: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'Timestamp inválido: {{ .detail }}'
ErrShutDown: 'Comando parado manualmente'
ErrCreateHttpClient: 'Falha ao criar solicitação {{ .err }}'
ErrHttpReqTimeOut: 'Tempo limite da solicitação esgotado {{ .err }}'
ErrHttpReqFailed: 'Falha na solicitação {{ .err }}'
ErrNoSuchHost: 'Não foi possível encontrar o servidor solicitado {{ .err }}'
ErrHttpReqNotFound: 'O recurso solicitado {{ .err }} não pôde ser encontrado'
# atalho rápido
ErrMinQuickJump: 'Adicione pelo menos um atalho rápido'
ErrMaxQuickJump: 'Máximo 4 atalhos rápidos'
# comum
ErrShutDown: 'Comando parado manualmente'
ErrUsernameIsExist: 'Usuário já existe'
ErrNameIsExist: 'Nome já existe'
ErrDemoEnvironment: 'Indisponível em demo'
@@ -41,6 +59,8 @@ SystemRestart: 'Tarefa interrompida devido à reinicialização do sistema'
ErrGroupIsDefault: 'Grupo padrão, não pode ser excluído'
ErrGroupIsInWebsiteUse: 'O grupo está sendo usado por outro site e não pode ser excluído.'
Decrypt: 'Descriptografar'
# agente
ErrAgentAccountBound: 'Conta vinculada a agente'
ErrTerminalAIAccountInUse: 'Esta conta de modelo esta sendo usada pelo Terminal AI. Troque a conta nas configuracoes do Terminal AI ou desative o Terminal AI e tente novamente.'
ErrAgentAccountUnavailable: 'Conexão da conta indisponível: {{ .err }}'
@@ -65,14 +85,8 @@ ErrAgentWebsiteInUse: 'Este site já está vinculado a outro agente'
ErrAgentWebsiteUnbindUnsupported: 'Sites implantados em um clique não podem ser desvinculados manualmente'
ErrHermesPairingCodeUnavailable: 'O código de pareamento está temporariamente indisponível no Hermes, possivelmente por causa de rede. Tente novamente mais tarde.'
ErrHermesFeishuGroupAllowlistRequiresAllowlist: 'Quando a política de grupo do Feishu é Lista de permissões, a política de DM não pode ser Código de pareamento.'
ErrAIProxyServiceNotInstalled: 'O serviço {{ .name }} não está instalado'
ErrAIProxyModelAccountNotFound: 'Conta de modelo {{ .id }} não encontrada'
ErrAIProxyModelMapRequired: 'Este campo é obrigatório.'
ErrAIProxyModelMapInvalid: 'O mapeamento de modelos deve ser um objeto JSON válido: {{ .err }}'
ErrAIProxyModelMapEmpty: 'O mapeamento de modelos não pode estar vazio'
ErrAIProxyModelMapEmptyExternalModel: 'O mapeamento de modelos contém um modelo solicitado vazio'
ErrAIProxyModelMapEmptyUpstreamModel: 'O modelo upstream de {{ .model }} não pode estar vazio'
AIProxyUserFallback: 'Usuário {{ .id }}'
# backup e configurações de alerta
Localhost: 'Máquina Local'
ErrBackupInUsed: 'Conta de backup em uso por tarefa'
ErrBackupCheck: 'Teste de conexão falhou: {{ .err }}'
@@ -80,6 +94,8 @@ ErrBackupLocalDelete: 'Não é permitido excluir contas locais'
ErrBackupLocalCreate: 'Não é permitido criar contas locais'
ErrAlertConfigInUse: 'A configuração de alerta está em uso e não pode ser excluída'
ErrAlertConfigPhoneExist: 'O numero de telefone ja existe'
# aplicativo
ErrPortInUsed: 'A porta {{ .detail }} já está ocupada'
ErrAppLimit: 'O número de aplicativos instalados excedeu o limite'
ErrNotInstall: 'Aplicativo não instalado'
@@ -97,16 +113,12 @@ 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'
ErrCreateHttpClient: 'Falha ao criar solicitação {{ .err }}'
ErrHttpReqTimeOut: 'Tempo limite da solicitação esgotado {{ .err }}'
ErrHttpReqFailed: 'Falha na solicitação {{ .err }}'
ErrNoSuchHost: 'Não foi possível encontrar o servidor solicitado {{ .err }}'
ErrHttpReqNotFound: 'O recurso solicitado {{ .err }} não pôde ser encontrado'
ErrContainerNotFound: '{{ .name }} container não existe'
ErrContainerMsg: 'O contêiner {{ .name }} está anormal., verifique o log na página do contêiner para obter detalhes.'
ErrAppBackup: '{{ .name }} falha no backup do aplicativo {{ .err }}'
ErrVersionTooLow: 'A versão atual do 1Panel é muito antiga para atualizar a App Store., atualize a versão antes de operar.'
ErrAppNameExist: 'O nome do aplicativo já existe'
ErrAppInstallNameExist: 'O nome de instalação do aplicativo {{ .name }} já existe'
AppStoreIsSyncing: 'A App Store está sincronizando mais tarde'
ErrGetCompose: 'Falha ao obter o arquivo docker-compose.yml {{ .detail }}'
ErrAppWarn: 'Status anormal, verifique o log'
@@ -131,6 +143,8 @@ ErrDockerFailed: 'O estado do Docker está anormal, verifique o status do servi
ErrDockerComposeCmdNotFound: 'O comando Docker Compose não existe, instale este comando na máquina host primeiro'
UseExistImage: 'A imagem já existe, usando imagem existente'
ErrDatabaseNotFound: 'O banco de dados {{ .name }} não existe'
# SSH
ExportIP: 'IP de Login'
ExportArea: 'Localização'
ExportPort: 'Porta'
@@ -138,6 +152,8 @@ ExportAuthMode: 'Método de Login'
ExportUser: 'Usuário'
ExportStatus: 'Status de Login'
ExportDate: 'Hora'
# arquivo
ErrFileCanNotRead: 'Este arquivo não suporta visualização'
ErrSensitiveFileRead: 'A leitura de arquivos sensíveis não é permitida'
ErrFileToLarge: 'O arquivo é maior que 10M e não pode ser aberto'
@@ -164,6 +180,8 @@ FileDropFailed: 'Falha ao limpar arquivo {{ .name }}: {{ .err }}'
FileDropSuccess: 'Arquivo {{ .name }} limpo com sucesso, {{ .count }} arquivos removidos, {{ .size }} de espaço em disco liberado'
FileDropSum: 'Limpeza de arquivos concluída com sucesso, total de {{ .count }} arquivos removidos, total de {{ .size }} de espaço em disco liberado'
ErrBadDecrypt: 'Erro de senha de descriptografia do pacote compactado, descompactação falhou, verifique e'
# site
ErrAliasIsExist: 'Alias já existe'
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 }}'
@@ -193,6 +211,8 @@ delete: 'Excluir'
ErrDefaultWebsite: 'O site padrão já foi definido, cancele-o antes de definir'
SSL: 'Certificado'
Set: 'Configurações'
# certificado SSL
ErrSSLCannotDelete: 'O certificado {{ .name }} está sendo usado por um site e não pode ser excluído'
ErrAccountCannotDelete: 'A conta está associada a um certificado e não pode ser excluída'
ErrSSLApply: 'Renovação do certificado concluída, mas a recarga do OpenResty falhou. Verifique a configuração'
@@ -227,6 +247,8 @@ ErrApplySSLCanNotDelete: 'O certificado {{ .name }} que está sendo solicitado n
StartPushSSLToNode: 'Iniciando o envio do certificado para o nó'
PushSSLToNodeFailed: 'Falha ao enviar o certificado para o nó: {{ .err }}'
PushSSLToNodeSuccess: 'Certificado enviado com sucesso para o nó'
# MySQL
ErrUserIsExist: 'O usuário atual já existe, digite novamente'
ErrDatabaseIsExist: 'O banco de dados atual já existe, digite novamente'
ErrExecTimeOut: 'Tempo limite de execução do SQL expirou, verifique o banco de dados'
@@ -235,7 +257,11 @@ ErrLocalExist: 'O nome já existe no banco de dados local, modifique-o e'
RemoteBackup: 'Para fazer backup do banco de dados remoto, o serviço de banco de dados do contêiner local precisa ser iniciado primeiro usando a imagem {{ .name }} aguarde...'
RemoteRecover: 'Para restaurar o banco de dados remoto, o serviço de banco de dados do contêiner local precisa ser iniciado primeiro usando a imagem {{ .name }} aguarde...'
Arg: 'Argumento'
# Redis
ErrTypeOfRedis: 'O tipo de arquivo de recuperação não corresponde ao método de persistência atual, modifique-o e'
# contêiner
ErrInUsed: '{{ .detail }} está em uso e não pode ser excluído'
ErrObjectInUsed: 'Este objeto está em uso e não pode ser excluído'
ErrObjectBeDependent: 'Esta imagem é dependida por outras imagens e não pode ser excluída'
@@ -277,6 +303,8 @@ ComposeRecoverFiles: 'Restaurar arquivos do compose'
ComposeRecoverContainers: 'Restaurar contêineres do compose'
ComposeRecoverRecord: 'Salvar registro do compose'
ComposeRecoverCleanup: 'Limpar área de restauração do compose'
# runtime
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, modifique o nome da imagem.'
@@ -289,12 +317,16 @@ ErrNodeModulesNotFound: 'A pasta node_modules não existe Edite o ambiente de ex
ErrContainerNameIsNull: 'O nome do contêiner não existe'
ErrPHPPortIsDefault: 'A porta 9000 é a porta padrão. Altere-a e tente novamente'
ErrPHPRuntimePortFailed: 'A porta {{ .name }} já está em uso neste runtime. Altere-a e tente novamente'
# ferramenta
ErrConfigNotFound: 'O arquivo de configuração não existe'
ErrConfigParse: 'O formato do arquivo de configuração está incorreto'
ErrConfigIsNull: 'O arquivo de configuração não pode estar vazio'
ErrConfigDirNotFound: 'O diretório de execução não existe'
ErrConfigAlreadyExist: 'Um arquivo de configuração com o mesmo nome já existe'
ErrUserFindErr: 'Falha na pesquisa do usuário {{ .name }} {{ .err }}'
# tarefa cron
CutWebsiteLogSuccess: '{{ .name }} registro do site cortado com sucesso, caminho de backup {{ .path }}'
HandleShell: 'Executar script {{ .name }}'
HandleCurl: 'Acessar URL {{ .name }}'
@@ -313,6 +345,8 @@ NoSuchResource: 'Nenhum conteúdo de backup encontrado no banco de dados, ignora
CleanLog: 'Limpar log'
CleanLogByName: 'Limpar log de {{ .name }}'
WafIpGroupNotFound: 'Grupo de IP do WAF não encontrado., vá para [Recursos Avançados - WAF - Lista Branca/Preta - Grupo de IP] para adicionar um grupo de IP via método de download remoto.'
# caixa de ferramentas
ErrNotExistUser: 'O usuário atual não existe, modifique e'
ErrBanAction: 'Falha na configuração. O serviço atual {{ .name }} não está disponível. Verifique e'
ErrClamdscanNotFound: 'O comando clamdscan não foi detectado, consulte a documentação para instalá-lo'
@@ -321,11 +355,15 @@ SetSwap: 'Configurar Swap {{ .path }} - {{ .size }}'
CreateSwap: 'Criar Arquivo Swap'
FormatSwap: 'Formatar Arquivo Swap'
EnableSwap: 'Ativar Swap'
# proteção contra adulteração
CleanTamperSetting: 'Limpar configurações de proteção históricas'
SetTamperWithRules: 'Iniciar proteção de diretório de acordo com as regras'
ProtectDir: 'Proteger diretório {{ .name }}'
IgnoreFile: 'Proteger arquivo {{ .name }}'
TamperSetSuccessful: 'Configurações de proteção concluídas, iniciando monitoramento dos seguintes diretórios:'
# WAF
ErrScope: 'A modificação desta configuração não é suportada'
ErrStateChange: 'Falha na alteração de estado'
ErrRuleExist: 'A regra já existe'
@@ -380,6 +418,8 @@ OpenrestyNotFound: 'OpenResty não está instalado'
remoteIpIsNull: 'A lista de IP está vazia'
OpenrestyVersionErr: 'A versão do OpenResty é muito antiga. Atualize para 1.27.1.2-2-2-focal'
ErrFileTooLarge: 'O arquivo excede 1 MB e não pode ser enviado'
# tarefa
TaskStart: '{{ .name }} A tarefa inicia [START]'
TaskEnd: '{{ .name }} Tarefa concluída [CONCLUÍDA]'
TaskFailed: '{{ .name }} tarefa falhou'
@@ -440,10 +480,17 @@ CustomAppstore: 'Armazém de aplicativos personalizados'
TaskExec: 'Executar'
TaskBatch: 'Operação em Lote'
FileConvert: 'Conversão de Formato de Arquivo'
# tarefa - ClamAV
Clamscan: 'Escanear {{ .name }}'
TaskScan: 'Escanear'
# tarefa - IA
OllamaModelPull: 'Puxar modelo Ollama {{ .name }}'
OllamaModelSize: 'Obtenha o tamanho do modelo Ollama {{ .name }}'
AIBenchmarkRun: "Executar benchmark de IA"
# tarefa - snapshot
Snapshot: 'Instantâneo'
SnapDBInfo: 'Escrever informações do banco de dados 1Panel'
SnapCopy: 'Copiar arquivos e diretórios {{ .name }}'
@@ -493,6 +540,8 @@ RecoverDaemonJson: 'Restaurar arquivo daemon.json de configuração do contêine
RecoverDBData: 'Recuperar dados do banco de dados'
RecoverBackups: 'Restaurar diretório de backup local'
RecoverPanelData: 'Diretório de dados de recuperação'
# tarefa - contêiner
ContainerImagePull: 'Puxar imagem do contêiner {{ .name }}'
ContainerRemoveOld: 'Remover o contêiner original {{ .name }}'
ContainerImageCheck: 'Verifique se a imagem é extraída normalmente'
@@ -501,6 +550,8 @@ ContainerRecreate: 'A atualização do contêiner falhou, agora iniciando a rest
ContainerCreate: 'Criar um novo contêiner {{ .name }}'
ContainerCreateFailed: 'Falha na criação do contêiner, exclua o contêiner com falha'
ContainerStartCheck: 'Verifique se o contêiner foi iniciado'
# tarefa - imagem
ImageBuild: 'Construção de imagem'
ImagePull: 'Puxar imagem'
ImageRepoAuthFromDB: 'Obter informações de autenticação do repositório do banco de dados'
@@ -508,6 +559,8 @@ ImagePush: 'Enviar imagem'
ImageRenameTag: 'Modificar tag de imagem'
ImageNewTag: 'Nova tag de imagem {{ .name }}'
ComposeCreate: 'Criar uma composição'
# tarefa - site
BackupNginxConfig: 'Fazer backup do arquivo de configuração do OpenResty do site'
CompressFileSuccess: 'Diretório compactado com sucesso, compactado para {{ .name }}'
CompressDir: 'Diretório de compressão'
@@ -518,8 +571,15 @@ websiteDir: 'Diretório de sites'
RecoverFailedStartRollBack: 'Falha na recuperação, iniciar reversão'
AppBackupFileIncomplete: 'O arquivo de backup está incompleto e não contém os arquivos app.json ou app.tar.gz'
AppAttributesNotMatch: 'O tipo ou nome do aplicativo não corresponde'
# alerta
ErrAlertMethodNotSupported: 'Esta versão não oferece suporte a este método de alerta'
ErrAlertSync: 'Erro de sincronização de informações de alarme, verifique e'
# tarefa - runtime
ErrInstallExtension: 'Já existe uma tarefa de instalação em andamento, aguarde a conclusão da tarefa'
# modelo de e-mail de alerta
PanelAlertTitle: 'Notificação de Alerta do Painel'
TestAlertTitle: 'E-mail de Teste - Verificar Conectividade de E-mail'
TestAlert: 'Este é um e-mail de teste para verificar se sua configuração de envio de e-mails está correta.'
@@ -536,6 +596,8 @@ CommonAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .msg }}. Faça login no painel
NodeExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} nós estão anormais. Faça login no painel para obter detalhes.'
LicenseExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} licenças estão anormais. Faça login no painel para obter detalhes.'
SSHAndPanelLoginAlert: 'Seu Painel {{ .node }}{{ .ip }}, o login {{ .name }} a partir de {{ .loginIp }} é anormal. Faça login no painel para obter detalhes.'
# disco
DeviceNotFound: 'Dispositivo {{ .name }} não encontrado'
DeviceIsMounted: 'Dispositivo {{ .name }} está montado desmonte primeiro'
PartitionDiskErr: 'Falha ao particionar, {{ .err }}'
@@ -548,4 +610,13 @@ TerminalAIBlockedRiskyCommand: 'Comando de risco bloqueado: {{ .command }}'
TerminalAIThinking: 'A IA está pensando...'
TerminalAIReadyToExecute: 'Pensamento concluído, pressione Enter para executar (duração: {{ .duration }}, token: {{ .tokens }})'
TerminalAIRequestFailed: 'Falha na solicitação de AI: {{ .err }}'
FileAISearchEmptyDir: 'Nenhum arquivo ou pasta encontrado neste caminho (ou todos foram filtrados).'
# gateway de IA
ErrAIProxyServiceNotInstalled: 'O serviço {{ .name }} não está instalado'
ErrAIProxyModelAccountNotFound: 'Conta de modelo {{ .id }} não encontrada'
ErrAIProxyModelMapRequired: 'Este campo é obrigatório.'
ErrAIProxyModelMapInvalid: 'O mapeamento de modelos deve ser um objeto JSON válido: {{ .err }}'
ErrAIProxyModelMapEmpty: 'O mapeamento de modelos não pode estar vazio'
ErrAIProxyModelMapEmptyExternalModel: 'O mapeamento de modelos contém um modelo solicitado vazio'
ErrAIProxyModelMapEmptyUpstreamModel: 'O modelo upstream de {{ .model }} não pode estar vazio'
AIProxyUserFallback: 'Usuário excluído (ID: {{ .id }})'
+92 -21
View File
@@ -1,29 +1,47 @@
# базовые ошибки
ErrInvalidParams: 'Неверный запрос: {{ .detail }}'
ErrTokenParse: 'Ошибка токена: {{ .detail }}'
ErrInitialPassword: 'Исходный пароль неверен'
ErrInternalServer: 'Внутренняя ошибка: {{ .detail }}'
ErrRecordExist: 'Запись уже существует'
ErrRecordNotFound: 'Запись не найдена'
ErrStructTransform: 'Ошибка преобразования: {{ .err }}'
ErrNotSupportType: 'Тип не поддерживается: {{ .name }}'
# файловый AI
ErrFileAINotEnabled: 'Поиск с AI в файлах выключен или не выбран аккаунт. В управлении файлами откройте «AI-аккаунт», включите и выберите аккаунт.'
ErrFileAISearchBadPattern: 'Недопустимый шаблон поиска (regex и т.п.): {{ .detail }}'
FileAISearchEmptyDir: 'В этом пути нет файлов или каталогов для отображения (или все отфильтрованы).'
# удаленная загрузка
ErrWgetRemoteFailed: 'Неверный или недоступный URL удалённой загрузки: {{ .detail }}'
ErrWgetInvalidContentType: 'Удалённый сервер вернул не файл (возможно, страницу ошибки), {{ .detail }}'
ErrWgetProxyNotConfigured: 'Сначала настройте прокси-сервер в настройках панели'
ErrWgetProxyInvalid: 'Недопустимый адрес прокси-сервера: {{ .detail }}'
ErrRecordExist: 'Запись уже существует'
ErrRecordNotFound: 'Запись не найдена'
ErrStructTransform: 'Ошибка преобразования: {{ .err }}'
# вход и безопасность
ErrNotLogin: 'Не авторизован: {{ .detail }}'
ErrPasswordExpired: 'Пароль истёк: {{ .detail }}'
ErrNotSupportType: 'Тип не поддерживается: {{ .name }}'
ErrAlertMethodNotSupported: 'Эта версия не поддерживает данный способ оповещения'
# запрос и API
ErrProxy: 'Запрос не удался: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API отключено: {{ .detail }}'
ErrApiConfigKeyInvalid: 'Неверный API-ключ: {{ .detail }}'
ErrApiConfigIPInvalid: 'IP не в списке: {{ .detail }}'
ErrApiConfigDisable: 'Доступ по API запрещён: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'Неверная метка времени: {{ .detail }}'
ErrShutDown: 'Команда остановлена вручную'
ErrCreateHttpClient: 'Не удалось создать запрос {{ .err }}'
ErrHttpReqTimeOut: 'Истекло время ожидания запроса {{ .err }}'
ErrHttpReqFailed: 'Запрос не выполнен {{ .err }}'
ErrNoSuchHost: 'Не удалось найти запрошенный сервер {{ .err }}'
ErrHttpReqNotFound: 'Запрошенный ресурс {{ .err }} не найден'
# быстрый переход
ErrMinQuickJump: 'Добавьте хотя бы одну быструю ссылку'
ErrMaxQuickJump: 'Не более 4 быстрых ссылок'
# общее
ErrShutDown: 'Команда остановлена вручную'
ErrUsernameIsExist: 'Имя пользователя занято'
ErrNameIsExist: 'Имя уже занято'
ErrDemoEnvironment: 'Недоступно в демо'
@@ -41,6 +59,8 @@ SystemRestart: 'Задача прервана из-за перезапуска
ErrGroupIsDefault: 'Группа по умолчанию, не может быть удалена'
ErrGroupIsInWebsiteUse: 'Группа используется другим веб-сайтом и не может быть удалена.'
Decrypt: 'Расшифровать'
# агент
ErrAgentAccountBound: 'Акаунт привязан к агенту'
ErrTerminalAIAccountInUse: 'Эта учетная запись модели сейчас используется Terminal AI. Смените учетную запись в настройках Terminal AI или отключите Terminal AI и повторите попытку.'
ErrAgentAccountUnavailable: 'Связь с аккаунтом недоступна: {{ .err }}'
@@ -65,14 +85,8 @@ ErrAgentWebsiteInUse: 'Этот сайт уже связан с другим а
ErrAgentWebsiteUnbindUnsupported: 'Сайты one-click deployment нельзя отвязать вручную'
ErrHermesPairingCodeUnavailable: 'Код сопряжения временно недоступен в Hermes, возможно из-за проблем с сетью. Повторите попытку позже.'
ErrHermesFeishuGroupAllowlistRequiresAllowlist: 'Когда групповая политика Feishu — белый список, политика личных сообщений не может быть кодом сопряжения.'
ErrAIProxyServiceNotInstalled: 'Сервис {{ .name }} не установлен'
ErrAIProxyModelAccountNotFound: 'Аккаунт модели {{ .id }} не найден'
ErrAIProxyModelMapRequired: 'Это поле обязательно.'
ErrAIProxyModelMapInvalid: 'Сопоставление моделей должно быть допустимым JSON-объектом: {{ .err }}'
ErrAIProxyModelMapEmpty: 'Сопоставление моделей не может быть пустым'
ErrAIProxyModelMapEmptyExternalModel: 'Сопоставление моделей содержит пустую запрошенную модель'
ErrAIProxyModelMapEmptyUpstreamModel: 'Upstream-модель для {{ .model }} не может быть пустой'
AIProxyUserFallback: 'Пользователь {{ .id }}'
# резервное копирование и настройки оповещений
Localhost: 'Локальная машина'
ErrBackupInUsed: 'Аккаунт бэкапа занят задачей'
ErrBackupCheck: 'Проверка подключения не удалась: {{ .err }}'
@@ -80,6 +94,8 @@ ErrBackupLocalDelete: 'Нельзя удалить локальные бэкап
ErrBackupLocalCreate: 'Нельзя создать локальные бэкапы'
ErrAlertConfigInUse: 'Конфигурация оповещений используется и не может быть удалена'
ErrAlertConfigPhoneExist: 'Номер телефона уже существует'
# приложение
ErrPortInUsed: '{{ .detail }} порт уже занят'
ErrAppLimit: 'Количество установленных приложений превысило лимит'
ErrNotInstall: 'Приложение не установлено'
@@ -97,16 +113,12 @@ ErrFileParseApp: '{{ .name }} анализ файла не удался {{ .err
ErrAppDirNull: 'Папка версии не существует'
LocalAppErr: 'Синхронизация приложения {{ .name }} не удалась {{ .err }}'
ErrContainerName: 'Имя контейнера уже существует'
ErrCreateHttpClient: 'Не удалось создать запрос {{ .err }}'
ErrHttpReqTimeOut: 'Истекло время ожидания запроса {{ .err }}'
ErrHttpReqFailed: 'Запрос не выполнен {{ .err }}'
ErrNoSuchHost: 'Не удалось найти запрошенный сервер {{ .err }}'
ErrHttpReqNotFound: 'Запрошенный ресурс {{ .err }} не найден'
ErrContainerNotFound: 'Контейнер {{ .name }} не существует'
ErrContainerMsg: 'Контейнер {{ .name }} ненормален. Подробности смотрите в журнале на странице контейнера.'
ErrAppBackup: '{{ .name }} резервное копирование приложения не удалось {{ .err }}'
ErrVersionTooLow: 'Текущая версия 1Panel слишком низкая для обновления App Store. Пожалуйста, обновите версию перед началом работы.'
ErrAppNameExist: 'Имя приложения уже существует'
ErrAppInstallNameExist: 'Имя установки приложения {{ .name }} уже существует'
AppStoreIsSyncing: 'App Store синхронизируется, повторите попытку позже'
ErrGetCompose: 'Не удалось получить файл docker-compose.yml {{ .detail }}'
ErrAppWarn: 'Ненормальное состояние, проверьте журнал'
@@ -131,6 +143,8 @@ ErrDockerFailed: 'Состояние Docker аномально, проверьт
ErrDockerComposeCmdNotFound: 'Команда Docker Compose отсутствует, пожалуйста, установите эту команду на хост-машине сначала'
UseExistImage: 'Образ уже существует, используется существующий образ'
ErrDatabaseNotFound: 'База данных {{ .name }} не существует'
# SSH
ExportIP: 'IP входа'
ExportArea: 'Местоположение'
ExportPort: 'Порт'
@@ -138,6 +152,8 @@ ExportAuthMode: 'Способ входа'
ExportUser: 'Пользователь'
ExportStatus: 'Статус входа'
ExportDate: 'Время'
# файл
ErrFileCanNotRead: 'Этот файл не поддерживает предварительный просмотр'
ErrSensitiveFileRead: 'Чтение конфиденциальных файлов запрещено'
ErrFileToLarge: 'Файл больше 10 МБ и не может быть открыт'
@@ -164,6 +180,8 @@ FileDropFailed: 'Не удалось очистить файл {{ .name }}: {{ .
FileDropSuccess: 'Файл {{ .name }} успешно очищен, удалено {{ .count }} файлов, освобождено {{ .size }} дискового пространства'
FileDropSum: 'Очистка файлов завершена успешно, всего удалено {{ .count }} файлов, освобождено {{ .size }} дискового пространства'
ErrBadDecrypt: 'Ошибка пароля расшифровки сжатого пакета, распаковка не удалась, проверьте и повторите попытку'
# сайт
ErrAliasIsExist: 'Псевдоним уже существует'
ErrBackupMatch: 'Файл резервной копии не соответствует некоторым текущим данным веб-сайта {{ .detail }}'
ErrBackupExist: 'Соответствующая часть исходных данных в файле резервной копии не существует {{ .detail }}'
@@ -193,6 +211,8 @@ delete: 'Удалить'
ErrDefaultWebsite: 'Веб-сайт по умолчанию уже установлен, отмените его перед настройкой'
SSL: 'Сертификат'
Set: 'Настройки'
# SSL-сертификат
ErrSSLCannotDelete: 'Сертификат {{ .name }} используется веб-сайтом и не может быть удален'
ErrAccountCannotDelete: 'Учетная запись связана с сертификатом и не может быть удалена'
ErrSSLApply: 'Обновление сертификата прошло успешно, перезагрузка OpenResty не удалась, проверьте конфигурацию'
@@ -227,6 +247,8 @@ ErrApplySSLCanNotDelete: 'Нельзя удалить сертификат {{ .n
StartPushSSLToNode: 'Начало отправки сертификата на узел'
PushSSLToNodeFailed: 'Не удалось отправить сертификат на узел: {{ .err }}'
PushSSLToNodeSuccess: 'Сертификат успешно отправлен на узел'
# MySQL
ErrUserIsExist: 'Текущий пользователь уже существует, пожалуйста, введите еще раз'
ErrDatabaseIsExist: 'Текущая база данных уже существует, пожалуйста, введите ее повторно'
ErrExecTimeOut: 'Время выполнения SQL истекло, проверьте базу данных'
@@ -235,7 +257,11 @@ ErrLocalExist: 'Имя уже существует в локальной баз
RemoteBackup: 'Для резервного копирования удаленной базы данных необходимо сначала запустить службу базы данных локального контейнера с помощью образа {{ .name }}, пожалуйста, подождите...'
RemoteRecover: 'Для восстановления удаленной базы данных необходимо сначала запустить службу базы данных локального контейнера с помощью образа {{ .name }}, пожалуйста, подождите...'
Arg: 'Аргумент'
# Redis
ErrTypeOfRedis: 'Тип файла восстановления не соответствует текущему методу сохранения. Измените его и повторите попытку'
# контейнер
ErrInUsed: '{{ .detail }} используется и не может быть удалено'
ErrObjectInUsed: 'Этот объект используется и не может быть удален'
ErrObjectBeDependent: 'Этот образ зависит от других образов и не может быть удален'
@@ -277,6 +303,8 @@ ComposeRecoverFiles: 'Восстановить файлы compose'
ComposeRecoverContainers: 'Восстановить контейнеры compose'
ComposeRecoverRecord: 'Сохранить запись compose'
ComposeRecoverCleanup: 'Очистить рабочую область восстановления compose'
# среда выполнения
ErrFileNotExist: 'Файл {{ .detail }} не существует Проверьте целостность исходного файла'
ErrImageBuildErr: 'Сборка образа не удалась'
ErrImageExist: 'Изображение уже существует Пожалуйста, измените имя изображения.'
@@ -289,12 +317,16 @@ ErrNodeModulesNotFound: 'Папка node_modules не существует Из
ErrContainerNameIsNull: 'Имя контейнера не существует'
ErrPHPPortIsDefault: 'Порт 9000 зарезервирован как стандартный. Укажите другой порт'
ErrPHPRuntimePortFailed: 'Порт {{ .name }} уже используется этой средой выполнения'
# инструмент
ErrConfigNotFound: 'Файл конфигурации не существует'
ErrConfigParse: 'Неверный формат файла конфигурации'
ErrConfigIsNull: 'Файл конфигурации не может быть пустым'
ErrConfigDirNotFound: 'Рабочий каталог не существует'
ErrConfigAlreadyExist: 'Файл конфигурации с таким именем уже существует'
ErrUserFindErr: 'Поиск пользователя {{ .name }} не удался {{ .err }}'
# задание cron
CutWebsiteLogSuccess: 'Журнал веб-сайта {{ .name }} успешно вырезан, путь к резервной копии {{ .path }}'
HandleShell: 'Выполнить скрипт {{ .name }}'
HandleCurl: 'Доступ к URL {{ .name }}'
@@ -313,6 +345,8 @@ NoSuchResource: 'В базе данных не найдено содержимо
CleanLog: 'Очистить журнал'
CleanLogByName: 'Очистить журнал {{ .name }}'
WafIpGroupNotFound: 'Группа IP WAF не найдена. Пожалуйста, перейдите в [Расширенные функции - WAF - Черный/Белый список - Группа IP], чтобы добавить группу IP с помощью метода удаленной загрузки.'
# панель инструментов
ErrNotExistUser: 'Текущий пользователь не существует, измените его и повторите попытку'
ErrBanAction: 'Настройка не удалась. Текущая служба {{ .name }} недоступна. Проверьте и повторите попытку'
ErrClamdscanNotFound: 'Команда clamdscan не обнаружена, обратитесь к документации, чтобы установить ее'
@@ -321,11 +355,15 @@ SetSwap: 'Настроить Swap {{ .path }} - {{ .size }}'
CreateSwap: 'Создать Файл Swap'
FormatSwap: 'Форматировать Файл Swap'
EnableSwap: 'Включить Swap'
# защита от изменений
CleanTamperSetting: 'Очистить исторические настройки защиты'
SetTamperWithRules: 'Запустить защиту каталога согласно правилам'
ProtectDir: 'Защитить каталог {{ .name }}'
IgnoreFile: 'Защитить файл {{ .name }}'
TamperSetSuccessful: 'Настройки защиты завершены, начинаю мониторинг следующих каталогов:'
# WAF
ErrScope: 'Изменение этой конфигурации не поддерживается'
ErrStateChange: 'He удалось изменить состояние'
ErrRuleExist: 'Правило уже существует'
@@ -380,6 +418,8 @@ OpenrestyNotFound: 'OpenResty не установлен'
remoteIpIsNull: 'Список IP пуст'
OpenrestyVersionErr: 'Версия OpenResty слишком старая. Обновите до 1.27.1.2-2-2-focal'
ErrFileTooLarge: 'Файл больше 1 МБ и не может быть загружен'
# задача
TaskStart: '{{ .name }} Задача начинается [START]'
TaskEnd: '{{ .name }} Задача выполнена [ЗАВЕРШЕНО]'
TaskFailed: 'Задача {{ .name }} не выполнена'
@@ -440,10 +480,17 @@ CustomAppstore: 'Хранилище пользовательских прило
TaskExec: 'Выполнить'
TaskBatch: 'Пакетная операция'
FileConvert: 'Преобразование формата файла'
# задача - ClamAV
Clamscan: 'Сканировать {{ .name }}'
TaskScan: 'Сканировать'
# задача - AI
OllamaModelPull: 'Вытянуть модель Ollama {{ .name }}'
OllamaModelSize: 'Получить размер модели Ollama {{ .name }}'
AIBenchmarkRun: "Выполнить тест производительности AI"
# задача - снимок
Snapshot: 'Снимок'
SnapDBInfo: 'Запись информации о базе данных 1Panel'
SnapCopy: 'Копировать файлы и каталоги {{ .name }}'
@@ -493,6 +540,8 @@ RecoverDaemonJson: 'Восстановить файл конфигурации
RecoverDBData: 'Восстановить данные базы данных'
RecoverBackups: 'Восстановить локальный каталог резервных копий'
RecoverPanelData: 'Каталог данных восстановления'
# задача - контейнер
ContainerImagePull: 'Извлечь изображение контейнера {{ .name }}'
ContainerRemoveOld: 'Удалить исходный контейнер {{ .name }}'
ContainerImageCheck: 'Проверьте, нормально ли извлекается изображение'
@@ -501,6 +550,8 @@ ContainerRecreate: 'Обновление контейнера не удалос
ContainerCreate: 'Создать новый контейнер {{ .name }}'
ContainerCreateFailed: 'Создание контейнера не удалось, удалите неудавшийся контейнер'
ContainerStartCheck: 'Проверить, запущен ли контейнер'
# задача - образ
ImageBuild: 'Создание изображения'
ImagePull: 'Вытащить изображение'
ImageRepoAuthFromDB: 'Получить информацию об аутентификации репозитория из базы данных'
@@ -508,6 +559,8 @@ ImagePush: 'Отправить изображение'
ImageRenameTag: 'Изменить тег изображения'
ImageNewTag: 'Новый тег изображения {{ .name }}'
ComposeCreate: 'Создать композицию'
# задача - сайт
BackupNginxConfig: 'Резервное копирование файла конфигурации OpenResty веб-сайта'
CompressFileSuccess: 'Каталог успешно сжат, сжат в {{ .name }}'
CompressDir: 'Каталог сжатия'
@@ -518,8 +571,15 @@ websiteDir: 'Каталог веб-сайтов'
RecoverFailedStartRollBack: 'Восстановление не удалось, начать откат'
AppBackupFileIncomplete: 'Файл резервной копии неполный и в нем отсутствуют файлы app.json или app.tar.gz'
AppAttributesNotMatch: 'Тип или имя приложения не совпадают'
# оповещение
ErrAlertMethodNotSupported: 'Эта версия не поддерживает данный способ оповещения'
ErrAlertSync: 'Ошибка синхронизации информации о тревоге, проверьте и повторите попытку'
# задача - среда выполнения
ErrInstallExtension: 'Уже выполняется задача установки, подождите, пока задача завершится'
# шаблон письма оповещения
PanelAlertTitle: 'Уведомление панели'
TestAlertTitle: 'Тестовое письмо - проверка подключения'
TestAlert: 'Это тестовое письмо для проверки правильности настройки отправки писем.'
@@ -536,6 +596,8 @@ CommonAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .msg }}. Войдит
NodeExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} узлов работают с ошибками. Войдите в панель для получения деталей.'
LicenseExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} лицензий имеют ошибки. Войдите в панель для получения деталей.'
SSHAndPanelLoginAlert: 'Ваш панель {{ .node }}{{ .ip }}, вход {{ .name }} с адреса {{ .loginIp }} является аномальным. Войдите в панель для получения деталей.'
# диск
DeviceNotFound: 'Устройство {{ .name }} не найдено'
DeviceIsMounted: 'Устройство {{ .name }} подключено, сначала отключите'
PartitionDiskErr: 'Не удалось разделить, {{ .err }}'
@@ -543,9 +605,18 @@ FormatDiskErr: 'Не удалось отформатировать диск, {{
MountDiskErr: 'Не удалось подключить диск, {{ .err }}'
XfsNotFound: 'Файловая система xfs не обнаружена, сначала установите xfsprogs'
# terminal
# терминал
TerminalAIBlockedRiskyCommand: 'Опасная команда заблокирована: {{ .command }}'
TerminalAIThinking: 'AI думает...'
TerminalAIReadyToExecute: 'Обдумывание завершено, нажмите Enter для выполнения (время: {{ .duration }}, token: {{ .tokens }})'
TerminalAIRequestFailed: 'Ошибка запроса AI: {{ .err }}'
FileAISearchEmptyDir: 'В этом пути нет файлов или каталогов для отображения (или все отфильтрованы).'
# AI-шлюз
ErrAIProxyServiceNotInstalled: 'Сервис {{ .name }} не установлен'
ErrAIProxyModelAccountNotFound: 'Аккаунт модели {{ .id }} не найден'
ErrAIProxyModelMapRequired: 'Это поле обязательно.'
ErrAIProxyModelMapInvalid: 'Сопоставление моделей должно быть допустимым JSON-объектом: {{ .err }}'
ErrAIProxyModelMapEmpty: 'Сопоставление моделей не может быть пустым'
ErrAIProxyModelMapEmptyExternalModel: 'Сопоставление моделей содержит пустую запрошенную модель'
ErrAIProxyModelMapEmptyUpstreamModel: 'Upstream-модель для {{ .model }} не может быть пустой'
AIProxyUserFallback: 'Удаленный пользователь (ID: {{ .id }})'
+91 -20
View File
@@ -1,29 +1,47 @@
# temel hatalar
ErrInvalidParams: 'İstek geçersiz: {{ .detail }}'
ErrTokenParse: 'Token hatası: {{ .detail }}'
ErrInitialPassword: 'Orijinal şifre yanlış'
ErrInternalServer: 'Sunucu hatası: {{ .detail }}'
ErrRecordExist: 'Kayıt zaten var'
ErrRecordNotFound: 'Kayıt bulunamadı'
ErrStructTransform: 'Dönüşüm hatası: {{ .err }}'
ErrNotSupportType: 'Bu tür desteklenmiyor: {{ .name }}'
# dosya AI
ErrFileAINotEnabled: 'Dosya yönetimi AI araması kapalı veya hesap yok. Dosya Yönetimi’nde AI hesabını açıp etkinleştirin ve bir hesap seçin.'
ErrFileAISearchBadPattern: 'Geçersiz arama deseni (regex vb.): {{ .detail }}'
FileAISearchEmptyDir: 'Bu yol altında listelenecek dosya veya klasör yok (veya tümü filtrelendi).'
# uzaktan indirme
ErrWgetRemoteFailed: 'Uzak indirme başarısız veya URL erişilemiyor: {{ .detail }}'
ErrWgetInvalidContentType: 'Uzak URL bir dosya değil, muhtemelen bir hata sayfası döndürdü, {{ .detail }}'
ErrWgetProxyNotConfigured: 'Önce panel ayarlarında bir proxy sunucusu yapılandırın'
ErrWgetProxyInvalid: 'Proxy sunucusu adresi geçersiz: {{ .detail }}'
ErrRecordExist: 'Kayıt zaten var'
ErrRecordNotFound: 'Kayıt bulunamadı'
ErrStructTransform: 'Dönüşüm hatası: {{ .err }}'
# oturum açma ve güvenlik
ErrNotLogin: 'Oturum açılmamış: {{ .detail }}'
ErrPasswordExpired: 'Şifre süresi doldu: {{ .detail }}'
ErrNotSupportType: 'Bu tür desteklenmiyor: {{ .name }}'
ErrAlertMethodNotSupported: 'Bu sürüm bu uyarı yöntemini desteklemiyor'
# istek ve API
ErrProxy: 'İstek başarısız: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API kapalı: {{ .detail }}'
ErrApiConfigKeyInvalid: 'Geçersiz API anahtarı: {{ .detail }}'
ErrApiConfigIPInvalid: 'IP izinli değil: {{ .detail }}'
ErrApiConfigDisable: 'API erişimi kapalı: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'Zaman damgası hatalı: {{ .detail }}'
ErrShutDown: 'Komut manuel durduruldu'
ErrCreateHttpClient: 'İstek oluşturma başarısız {{ .err }}'
ErrHttpReqTimeOut: 'İstek zaman aşımı {{ .err }}'
ErrHttpReqFailed: 'İstek başarısız {{ .err }}'
ErrNoSuchHost: 'İstenen sunucu bulunamıyor {{ .err }}'
ErrHttpReqNotFound: 'İstenen kaynak {{ .err }} bulunamadı'
# hızlı geçiş
ErrMinQuickJump: 'En az bir hızlı erişim ekle'
ErrMaxQuickJump: '4 hızlı erişim hakkı var'
# genel
ErrShutDown: 'Komut manuel durduruldu'
ErrUsernameIsExist: 'Kullanıcı adı mevcut'
ErrNameIsExist: 'İsim mevcut'
ErrDemoEnvironment: 'Demo modunda yok'
@@ -41,6 +59,8 @@ SystemRestart: 'Sistem yeniden başlatması nedeniyle görev kesildi'
ErrGroupIsDefault: 'Varsayılan grup, silinemez'
ErrGroupIsInWebsiteUse: 'Grup başka bir web sitesi tarafından kullanılıyor ve silinemez.'
Decrypt: 'Şifre Çöz'
# ajan
ErrAgentAccountBound: 'Hesap bir ajana bağlı'
ErrTerminalAIAccountInUse: 'Bu model hesabi su anda Terminal AI tarafindan kullaniliyor. Terminal AI ayarlarinda baska bir hesaba gecin veya Terminal AI yi devre disi birakip tekrar deneyin.'
ErrAgentAccountUnavailable: 'Hesap bağlantısı yok: {{ .err }}'
@@ -65,14 +85,8 @@ ErrAgentWebsiteInUse: 'Bu web sitesi zaten başka bir ajana bağlı'
ErrAgentWebsiteUnbindUnsupported: 'Tek tıkla dağıtılan web sitelerinin bağlantısı manuel olarak kaldırılamaz'
ErrHermesPairingCodeUnavailable: 'Eşleştirme kodu Hermes içinde geçici olarak bulunamıyor; bu durum ağ kaynaklı olabilir. Lütfen daha sonra tekrar deneyin.'
ErrHermesFeishuGroupAllowlistRequiresAllowlist: 'Feishu grup ilkesi izin listesi olduğunda, DM ilkesi eşleştirme kodu olamaz.'
ErrAIProxyServiceNotInstalled: '{{ .name }} servisi kurulu değil'
ErrAIProxyModelAccountNotFound: 'Model hesabı {{ .id }} bulunamadı'
ErrAIProxyModelMapRequired: 'Bu alan zorunludur.'
ErrAIProxyModelMapInvalid: 'Model eşlemesi geçerli bir JSON nesnesi olmalıdır: {{ .err }}'
ErrAIProxyModelMapEmpty: 'Model eşlemesi boş olamaz'
ErrAIProxyModelMapEmptyExternalModel: 'Model eşlemesi boş bir istek modeli içeriyor'
ErrAIProxyModelMapEmptyUpstreamModel: '{{ .model }} için upstream model boş olamaz'
AIProxyUserFallback: 'Kullanıcı {{ .id }}'
# yedekleme ve uyarı ayarları
Localhost: 'Yerel Makine'
ErrBackupInUsed: 'Yedek hesabı görevde kullanılıyor'
ErrBackupCheck: 'Bağlantı testi başarısız: {{ .err }}'
@@ -80,6 +94,8 @@ ErrBackupLocalDelete: 'Yerel yedek silme yok'
ErrBackupLocalCreate: 'Yerel yedek oluşturma yok'
ErrAlertConfigInUse: 'Uyarı yapılandırması kullanımda ve silinemez'
ErrAlertConfigPhoneExist: 'Telefon numarasi zaten mevcut'
# uygulama
ErrPortInUsed: '{{ .detail }} portu zaten kullanılıyor'
ErrAppLimit: 'Yüklenen uygulama sayısı sınırı aştı'
ErrNotInstall: 'Uygulama yüklenmedi'
@@ -97,16 +113,12 @@ ErrFileParseApp: '{{ .name }} dosya ayrıştırma başarısız {{ .err }}'
ErrAppDirNull: 'Sürüm klasörü mevcut değil'
LocalAppErr: 'Uygulama {{ .name }} senkronizasyonu başarısız {{ .err }}'
ErrContainerName: 'Konteyner adı zaten mevcut'
ErrCreateHttpClient: 'İstek oluşturma başarısız {{ .err }}'
ErrHttpReqTimeOut: 'İstek zaman aşımı {{ .err }}'
ErrHttpReqFailed: 'İstek başarısız {{ .err }}'
ErrNoSuchHost: 'İstenen sunucu bulunamıyor {{ .err }}'
ErrHttpReqNotFound: 'İstenen kaynak {{ .err }} bulunamadı'
ErrContainerNotFound: '{{ .name }} konteyneri mevcut değil'
ErrContainerMsg: '{{ .name }} konteyneri anormal. Lütfen ayrıntılar için konteyner sayfasındaki günlüğü kontrol edin'
ErrAppBackup: '{{ .name }} uygulama yedeklemesi başarısız {{ .err }}'
ErrVersionTooLow: 'Mevcut 1Panel sürümü Uygulama Mağazasını güncellemek için çok düşük. Lütfen işlemden önce sürümü yükseltin.'
ErrAppNameExist: 'Uygulama adı zaten mevcut'
ErrAppInstallNameExist: '{{ .name }} uygulama kurulum adı zaten mevcut'
AppStoreIsSyncing: 'Uygulama Mağazası senkronize ediliyor, lütfen daha sonra tekrar deneyin'
ErrGetCompose: 'docker-compose.yml dosyası alınamadı {{ .detail }}'
ErrAppWarn: 'Anormal durum, lütfen günlüğü kontrol edin'
@@ -131,6 +143,8 @@ ErrDockerFailed: 'Docker durumu anormal, lütfen servis durumunu kontrol edin'
ErrDockerComposeCmdNotFound: 'Docker Compose komutu mevcut değil, lütfen önce bu komutu host makinesine yükleyin'
UseExistImage: 'Görüntü zaten mevcut, mevcut görüntü kullanılıyor'
ErrDatabaseNotFound: '{{ .name }} veritabanı mevcut değil'
# SSH
ExportIP: 'Giriş IP'
ExportArea: 'Konum'
ExportPort: 'Port'
@@ -138,6 +152,8 @@ ExportAuthMode: 'Giriş Yöntemi'
ExportUser: 'Kullanıcı'
ExportStatus: 'Giriş Durumu'
ExportDate: 'Zaman'
# dosya
ErrFileCanNotRead: 'Bu dosya önizlemeyi desteklemiyor'
ErrSensitiveFileRead: 'Hassas dosyaların okunmasına izin verilmez'
ErrFileToLarge: 'Dosya 10M dan büyük ve açılamıyor'
@@ -164,6 +180,8 @@ FileDropFailed: '{{ .name }} dosyası temizlenemedi: {{ .err }}'
FileDropSuccess: '{{ .name }} dosyası başarıyla temizlendi, {{ .count }} dosya silindi, {{ .size }} disk alanı boşaltıldı'
FileDropSum: 'Dosya temizleme tamamlandı, toplam {{ .count }} dosya silindi, toplam {{ .size }} disk alanı boşaltıldı'
ErrBadDecrypt: 'Sıkıştırılmış paket şifre çözme parolası hatalı, açma başarısız, lütfen kontrol edip tekrar deneyin'
# web sitesi
ErrAliasIsExist: 'Takma ad zaten mevcut'
ErrBackupMatch: 'Yedekleme dosyası mevcut web sitesi verilerinin bir kısmıyla eşleşmiyor {{ .detail }}'
ErrBackupExist: 'Yedekleme dosyasındaki kaynak verilerin karşılık gelen kısmı mevcut değil {{ .detail }}'
@@ -193,6 +211,8 @@ delete: 'Sil'
ErrDefaultWebsite: 'Varsayılan bir web sitesi zaten ayarlanmış. Yeni bir tane ayarlamadan önce lütfen iptal edin'
SSL: 'Sertifika'
Set: 'Ayarlar'
# SSL sertifikası
ErrSSLCannotDelete: '{{ .name }} sertifikası bir web sitesi tarafından kullanılıyor ve silinemez'
ErrAccountCannotDelete: 'Hesap bir sertifikayla ilişkili ve silinemez'
ErrSSLApply: 'Sertifika yenileme başarılı, openresty yeniden yükleme başarısız, lütfen yapılandırmayı kontrol edin'
@@ -227,6 +247,8 @@ ErrApplySSLCanNotDelete: 'İşlemde olan sertifika {{ .name }} silinemez, lütfe
StartPushSSLToNode: 'Sertifika düğüme gönderilmeye başlandı'
PushSSLToNodeFailed: 'Sertifika düğüme gönderilemedi: {{ .err }}'
PushSSLToNodeSuccess: 'Sertifika düğüme başarıyla gönderildi'
# MySQL
ErrUserIsExist: 'Mevcut kullanıcı zaten mevcut, lütfen yeniden girin'
ErrDatabaseIsExist: 'Mevcut veritabanı zaten mevcut, lütfen yeniden girin'
ErrExecTimeOut: 'SQL yürütme zaman aşımı, lütfen veritabanını kontrol edin'
@@ -235,7 +257,11 @@ ErrLocalExist: 'Yerel veritabanında bu ad zaten mevcut, lütfen değiştirin ve
RemoteBackup: 'Uzak veritabanını yedeklemek için önce {{ .name }} görüntüsü kullanılarak yerel konteyner veritabanı hizmetinin başlatılması gerekiyor, lütfen bekleyin...'
RemoteRecover: 'Uzak veritabanını geri yüklemek için önce {{ .name }} görüntüsü kullanılarak yerel konteyner veritabanı hizmetinin başlatılması gerekiyor, lütfen bekleyin...'
Arg: 'Argüman'
# Redis
ErrTypeOfRedis: 'Kurtarma dosyası türü mevcut kalıcılık yöntemiyle eşleşmiyor, lütfen değiştirin ve tekrar deneyin'
# konteyner
ErrInUsed: '{{ .detail }} kullanımda ve silinemez'
ErrObjectInUsed: 'Bu nesne kullanımda ve silinemez'
ErrObjectBeDependent: 'Bu görüntü diğer görüntüler tarafından bağımlıdır ve silinemez'
@@ -277,6 +303,8 @@ ComposeRecoverFiles: 'Compose dosyalarını geri yükle'
ComposeRecoverContainers: 'Compose konteynerlerini geri yükle'
ComposeRecoverRecord: 'Compose kaydını sakla'
ComposeRecoverCleanup: 'Compose geri yükleme çalışma alanını temizle'
# çalışma zamanı
ErrFileNotExist: '{{ .detail }} dosyası mevcut değil Lütfen kaynak dosyanın bütünlüğünü kontrol edin'
ErrImageBuildErr: 'Image yapı başarısız'
ErrImageExist: 'Görüntü zaten var Lütfen görüntü adını değiştirin.'
@@ -289,12 +317,16 @@ ErrNodeModulesNotFound: 'node_modules klasörü mevcut değil Lütfen çalışma
ErrContainerNameIsNull: 'Konteyner adı mevcut değil'
ErrPHPPortIsDefault: '9000 portu varsayılan port, lütfen değiştirin ve tekrar deneyin'
ErrPHPRuntimePortFailed: '{{ .name }} portu mevcut çalışma ortamı tarafından zaten kullanılıyor, lütfen değiştirin ve tekrar deneyin'
# araç
ErrConfigNotFound: 'Yapılandırma dosyası mevcut değil'
ErrConfigParse: 'Yapılandırma dosyası formatı yanlış'
ErrConfigIsNull: 'Yapılandırma dosyası boş olamaz'
ErrConfigDirNotFound: 'Çalışma dizini mevcut değil'
ErrConfigAlreadyExist: 'Aynı ada sahip bir yapılandırma dosyası zaten mevcut'
ErrUserFindErr: 'Kullanıcı {{ .name }} arama başarısız {{ .err }}'
# cron görevi
CutWebsiteLogSuccess: '{{ .name }} web sitesi günlüğü başarıyla kesildi, yedekleme yolu {{ .path }}'
HandleShell: 'Betik {{ .name }} yürüt'
HandleCurl: 'URL''ye Eriş {{ .name }}'
@@ -313,6 +345,8 @@ NoSuchResource: 'Veritabanında yedek içeriği bulunamadı, atlanıyor...'
CleanLog: 'Günlüğü temizle'
CleanLogByName: '{{ .name }} günlüğünü temizle'
WafIpGroupNotFound: 'WAF IP grubu bulunamadı. Lütfen [Gelişmiş Özellikler - WAF - Kara/Beyaz Liste - IP Grubu] bölümüne giderek uzaktan indirme yöntemiyle bir IP grubu ekleyin.'
# araç kutusu
ErrNotExistUser: 'Mevcut kullanıcı mevcut değil, lütfen değiştirin ve tekrar deneyin'
ErrBanAction: 'Ayarlama başarısız. Mevcut {{ .name }} servisi kullanılamıyor. Lütfen kontrol edin ve tekrar deneyin'
ErrClamdscanNotFound: 'clamdscan komutu tespit edilmedi, lütfen yüklemek için belgeleri inceleyin'
@@ -321,11 +355,15 @@ SetSwap: 'Swap Ayarla {{ .path }} - {{ .size }}'
CreateSwap: 'Swap Dosyası Oluştur'
FormatSwap: 'Swap Dosyasını Biçimlendir'
EnableSwap: 'Swap Etkinleştir'
# kurcalama koruması
CleanTamperSetting: 'Geçmiş koruma ayarlarını temizle'
SetTamperWithRules: 'Kurallara göre dizin korumasını başlat'
ProtectDir: 'Dizini koru {{ .name }}'
IgnoreFile: 'Dosyayı koru {{ .name }}'
TamperSetSuccessful: 'Koruma ayarları tamamlandı, aşağıdaki dizinler izlenmeye başlanıyor:'
# WAF
ErrScope: 'Bu yapılandırmayı değiştirme desteklenmiyor'
ErrStateChange: 'Durum değiştirme başarısız'
ErrRuleExist: 'Kural zaten mevcut'
@@ -380,6 +418,8 @@ OpenrestyNotFound: 'OpenResty yüklü değil'
remoteIpIsNull: 'IP listesi boş'
OpenrestyVersionErr: 'OpenResty sürümü çok eski. 1.27.1.2-2-2-focal sürümüne yükseltin'
ErrFileTooLarge: 'Dosya 1 MB''den büyük olduğu için yüklenemiyor'
# görev
TaskStart: '{{ .name }} görevi başlıyor [BAŞLAT]'
TaskEnd: '{{ .name }} Görev tamamlandı [TAMAMLANDI]'
TaskFailed: '{{ .name }} görev başarısız'
@@ -440,10 +480,17 @@ CustomAppstore: 'Özel uygulama deposu'
TaskExec: 'Çalıştır'
TaskBatch: 'Toplu İşlem'
FileConvert: 'Dosya Formatı Dönüştürme'
# görev - ClamAV
Clamscan: '{{ .name }} Tara'
TaskScan: 'Tara'
# görev - AI
OllamaModelPull: 'Ollama modeli {{ .name }} çek'
OllamaModelSize: 'Ollama modeli {{ .name }} boyutunu al'
AIBenchmarkRun: "AI kıyaslama testini çalıştır"
# görev - anlık görüntü
Snapshot: 'Anlık Görüntü'
SnapDBInfo: '1Panel veritabanı bilgilerini yaz'
SnapCopy: 'Dosya ve dizinleri {{ .name }} kopyala'
@@ -493,6 +540,8 @@ RecoverDaemonJson: 'Konteyner yapılandırma daemon.json dosyasını geri yükle
RecoverDBData: 'Veritabanı verilerini kurtar'
RecoverBackups: 'Yerel yedekleme dizinini geri yükle'
RecoverPanelData: 'Veri dizinini kurtar'
# görev - konteyner
ContainerImagePull: 'Konteyner imajını çek {{ .name }}'
ContainerRemoveOld: 'Orijinal konteyneri kaldır {{ .name }}'
ContainerImageCheck: 'İmajın normal çekilip çekilmediğini kontrol et'
@@ -501,6 +550,8 @@ ContainerRecreate: 'Konteyner güncellemesi başarısız, şimdi orijinal kontey
ContainerCreate: 'Yeni konteyner oluştur {{ .name }}'
ContainerCreateFailed: 'Konteyner oluşturma başarısız, başarısız konteyneri sil'
ContainerStartCheck: 'Konteynerin başlatılıp başlatılmadığını kontrol et'
# görev - imaj
ImageBuild: 'İmaj Oluşturma'
ImagePull: 'İmaj çek'
ImageRepoAuthFromDB: 'Veritabanından depo kimlik doğrulama bilgilerini al'
@@ -508,6 +559,8 @@ ImagePush: 'İmaj gönder'
ImageRenameTag: 'İmaj etiketini değiştir'
ImageNewTag: 'Yeni imaj etiketi {{ .name }}'
ComposeCreate: 'Kompozisyon oluştur'
# görev - web sitesi
BackupNginxConfig: 'Web sitesi OpenResty yapılandırma dosyasını yedekle'
CompressFileSuccess: 'Dizin başarıyla sıkıştırıldı, şuraya sıkıştırıldı {{ .name }}'
CompressDir: 'Sıkıştırma dizini'
@@ -518,8 +571,15 @@ websiteDir: 'Web Sitesi Dizini'
RecoverFailedStartRollBack: 'Kurtarma başarısız, geri almayı başlat'
AppBackupFileIncomplete: 'Yedekleme dosyası eksik ve app.json veya app.tar.gz dosyalarından yoksun'
AppAttributesNotMatch: 'Uygulama türü veya adı eşleşmiyor'
# uyarı
ErrAlertMethodNotSupported: 'Bu sürüm bu uyarı yöntemini desteklemiyor'
ErrAlertSync: 'Alarm bilgisi senkronizasyon hatası, lütfen kontrol edin ve tekrar deneyin'
# görev - çalışma zamanı
ErrInstallExtension: 'Zaten devam eden bir kurulum görevi var, görevin bitmesini bekleyin'
# uyarı e-posta şablonu
PanelAlertTitle: 'Panel Uyarı Bildirimi'
TestAlertTitle: 'Test E-postası - E-posta Bağlantısını Doğrula'
TestAlert: 'Bu, e-posta gönderim ayarlarınızın doğru yapılandırıldığını doğrulamak için gönderilen bir test e-postasıdır.'
@@ -536,6 +596,8 @@ CommonAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .msg }}. Detaylar için panelin
NodeExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} düğüm anormal durumda. Detaylar için paneline giriş yapın.'
LicenseExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} lisans anormal durumda. Detaylar için paneline giriş yapın.'
SSHAndPanelLoginAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .loginIp }} adresinden {{ .name }} girişi anormal. Detaylar için paneline giriş yapın.'
# disk
DeviceNotFound: 'Cihaz {{ .name }} bulunamadı'
DeviceIsMounted: 'Cihaz {{ .name }} bağlandı, önce çıkarın'
PartitionDiskErr: 'Bölümleme başarısız, {{ .err }}'
@@ -548,4 +610,13 @@ TerminalAIBlockedRiskyCommand: 'Riskli komut engellendi: {{ .command }}'
TerminalAIThinking: 'AI dusunuyor...'
TerminalAIReadyToExecute: 'Dusunme tamamlandi, calistirmak icin Enter tusuna basin (sure: {{ .duration }}, token: {{ .tokens }})'
TerminalAIRequestFailed: 'AI istegi basarisiz oldu: {{ .err }}'
FileAISearchEmptyDir: 'Bu yol altında listelenecek dosya veya klasör yok (veya tümü filtrelendi).'
# AI ağ geçidi
ErrAIProxyServiceNotInstalled: '{{ .name }} servisi kurulu değil'
ErrAIProxyModelAccountNotFound: 'Model hesabı {{ .id }} bulunamadı'
ErrAIProxyModelMapRequired: 'Bu alan zorunludur.'
ErrAIProxyModelMapInvalid: 'Model eşlemesi geçerli bir JSON nesnesi olmalıdır: {{ .err }}'
ErrAIProxyModelMapEmpty: 'Model eşlemesi boş olamaz'
ErrAIProxyModelMapEmptyExternalModel: 'Model eşlemesi boş bir istek modeli içeriyor'
ErrAIProxyModelMapEmptyUpstreamModel: '{{ .model }} için upstream model boş olamaz'
AIProxyUserFallback: 'Silinen kullanıcı (ID: {{ .id }})'
+91 -21
View File
@@ -1,29 +1,47 @@
# 基礎錯誤
ErrInvalidParams: '請求參數錯誤: {{ .detail }}'
ErrTokenParse: 'Token 產生錯誤: {{ .detail }}'
ErrInitialPassword: '原密碼錯誤'
ErrInternalServer: '服務內部錯誤: {{ .detail }}'
ErrRecordExist: '記錄已存在'
ErrRecordNotFound: '記錄未能找到'
ErrStructTransform: '型別轉換失敗: {{ .err }}'
ErrNotSupportType: '系統暫不支援目前類型: {{ .name }}'
# 檔案 AI
ErrFileAINotEnabled: '請先在檔案管理頁透過「AI 帳號」啟用檔案 AI 搜尋並選擇帳號。'
ErrFileAISearchBadPattern: '搜尋條件無效(正則或全詞): {{ .detail }}'
FileAISearchEmptyDir: '目前目錄下沒有可列出的檔案或資料夾(或均被過濾)。'
# 遠端下載
ErrWgetRemoteFailed: '遠端下載網址無效或無法存取:{{ .detail }}'
ErrWgetInvalidContentType: '遠端網址未回傳檔案內容(可能是錯誤頁),{{ .detail }}'
ErrWgetProxyNotConfigured: '請先在面板設定中設定代理伺服器'
ErrWgetProxyInvalid: '代理伺服器位址無效:{{ .detail }}'
ErrRecordExist: '記錄已存在'
ErrRecordNotFound: '記錄未能找到'
ErrStructTransform: '型別轉換失敗: {{ .err }}'
# 登入與安全
ErrNotLogin: '使用者未登入: {{ .detail }}'
ErrPasswordExpired: '目前密碼已過期: {{ .detail }}'
ErrNotSupportType: '系統暫不支援目前類型: {{ .name }}'
ErrAlertMethodNotSupported: '當前版本不支援此告警方式'
# 請求與 API
ErrProxy: '請求錯誤,請檢查該節點狀態: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API 介面禁止存取: {{ .detail }}'
ErrApiConfigKeyInvalid: 'API 介面金鑰錯誤: {{ .detail }}'
ErrApiConfigIPInvalid: '呼叫 API 介面 IP 不在白名單: {{ .detail }}'
ErrApiConfigDisable: '此介面禁止使用 API 介面呼叫: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'API 介面時間戳記錯誤: {{ .detail }}'
ErrShutDown: '指令被手動結束!'
ErrCreateHttpClient: '建立請求失敗{{ .err }}'
ErrHttpReqTimeOut: '請求逾時{{ .err }}'
ErrHttpReqFailed: '請求失敗{{ .err }}'
ErrNoSuchHost: '無法找到請求的伺服器{{ .err }}'
ErrHttpReqNotFound: '無法找到要求的資源{{ .err }}'
# 快速跳轉
ErrMinQuickJump: '請至少設定一個快速跳轉入口!'
ErrMaxQuickJump: '最多可設定四個快速跳轉入口!'
# 通用
ErrShutDown: '指令被手動結束!'
ErrUsernameIsExist: '使用者名稱已存在'
ErrNameIsExist: '名稱已存在'
ErrDemoEnvironment: '示範伺服器,禁止此操作!'
@@ -41,6 +59,8 @@ SystemRestart: '系統重新啟動導致任務中斷'
ErrGroupIsDefault: '預設分組,無法刪除'
ErrGroupIsInWebsiteUse: '分組正在被其他網站使用,無法刪除'
Decrypt: '解密'
# 智慧體
ErrAgentAccountBound: '該帳號已綁定到智能體,無法刪除,請重試。'
ErrTerminalAIAccountInUse: '該模型帳號正被終端 AI 使用,請在終端 AI 設定中更換帳號或關閉終端 AI 後再試。'
ErrAgentAccountUnavailable: '帳號連線資訊不可用,錯誤:{{ .err }},請重試'
@@ -65,14 +85,8 @@ ErrAgentWebsiteInUse: '該網站已被其他智能體關聯'
ErrAgentWebsiteUnbindUnsupported: '一鍵部署網站不支援手動解綁'
ErrHermesPairingCodeUnavailable: '配對碼在 Hermes 中暫時不存在,可能是由於網路原因,請稍後再試'
ErrHermesFeishuGroupAllowlistRequiresAllowlist: '飛書群組策略為白名單時,私聊策略不能為配對碼'
ErrAIProxyServiceNotInstalled: '{{ .name }} 服務未安裝'
ErrAIProxyModelAccountNotFound: '模型帳號 {{ .id }} 不存在'
ErrAIProxyModelMapRequired: '請填寫必填項'
ErrAIProxyModelMapInvalid: '模型映射必須是合法的 JSON 物件:{{ .err }}'
ErrAIProxyModelMapEmpty: '模型映射不能為空'
ErrAIProxyModelMapEmptyExternalModel: '模型映射中存在空的請求模型'
ErrAIProxyModelMapEmptyUpstreamModel: '模型 {{ .model }} 的上游模型不能為空'
AIProxyUserFallback: '使用者 {{ .id }}'
# 備份與告警設定
Localhost: '本機'
ErrBackupInUsed: '此備份帳號已在排程任務中使用,無法刪除'
ErrBackupCheck: '備份帳號測試連線失敗{{ .err }}'
@@ -80,6 +94,8 @@ ErrBackupLocalDelete: '暫時不支援刪除本機伺服器備份帳號'
ErrBackupLocalCreate: '暫時不支援建立本機伺服器備份帳號'
ErrAlertConfigInUse: '告警配置正在使用中,無法刪除'
ErrAlertConfigPhoneExist: '手機號碼已存在'
# 應用
ErrPortInUsed: '{{ .detail }} 連接埠已被佔用!'
ErrAppLimit: '應用程式超出安裝數量限制'
ErrNotInstall: '應用程式未安裝'
@@ -97,16 +113,12 @@ ErrFileParseApp: '{{ .name }} 檔案解析失敗{{ .err }}'
ErrAppDirNull: '版本資料夾不存在'
LocalAppErr: '應用程式{{ .name }} 同步失敗!{{ .err }}'
ErrContainerName: '容器名稱已存在'
ErrCreateHttpClient: '建立請求失敗{{ .err }}'
ErrHttpReqTimeOut: '請求逾時{{ .err }}'
ErrHttpReqFailed: '請求失敗{{ .err }}'
ErrNoSuchHost: '無法找到請求的伺服器{{ .err }}'
ErrHttpReqNotFound: '無法找到要求的資源{{ .err }}'
ErrContainerNotFound: '{{ .name }} 容器不存在'
ErrContainerMsg: '{{ .name }} 容器異常,請在容器頁面上檢視日誌'
ErrAppBackup: '{{ .name }} 應用備份失敗 {{ .err }}'
ErrVersionTooLow: '目前 1Panel 版本過低,無法更新應用程式商店,請升級版本之後操作'
ErrAppNameExist: '應用程式名稱已存在'
ErrAppInstallNameExist: '應用程式安裝名稱 {{ .name }} 已存在'
AppStoreIsSyncing: '應用程式商店正在同步中,請稍後重試'
ErrGetCompose: 'docker-compose.yml 檔案取得失敗!{{ .detail }}'
ErrAppWarn: '狀態異常,請檢視日誌'
@@ -131,6 +143,8 @@ ErrDockerFailed: 'Docker 狀態異常,請檢查服務狀態'
ErrDockerComposeCmdNotFound: 'Docker Compose 指令不存在,請先在宿主機安裝此指令'
UseExistImage: '映像已存在,使用現有映像'
ErrDatabaseNotFound: '資料庫 {{ .name }} 不存在'
# SSH
ExportIP: '登入IP'
ExportArea: '歸屬地'
ExportPort: '埠'
@@ -138,6 +152,8 @@ ExportAuthMode: '登入方式'
ExportUser: '使用者'
ExportStatus: '登入狀態'
ExportDate: '時間'
# 檔案
ErrFileCanNotRead: '此檔案不支援預覽'
ErrSensitiveFileRead: '不允許讀取敏感檔案'
ErrFileToLarge: '檔案超過10M,無法開啟'
@@ -164,6 +180,8 @@ FileDropFailed: '清理檔案 {{ .name }} 失敗: {{ .err }}'
FileDropSuccess: '清理檔案 {{ .name }} 成功,清理 {{ .count }} 個檔案,釋放 {{ .size }} 磁碟空間'
FileDropSum: '檔案清理完成,共清理 {{ .count }} 個檔案,共釋放 {{ .size }} 磁碟空間'
ErrBadDecrypt: '壓縮檔解壓密碼錯誤,解壓失敗,請重試。'
# 網站
ErrAliasIsExist: '代號已存在'
ErrBackupMatch: '該備份檔案與目前網站部分資料不符{{ .detail }}'
ErrBackupExist: '該備份檔案對應部分來源資料不存在{{ .detail }}'
@@ -193,6 +211,8 @@ delete: '刪除'
ErrDefaultWebsite: '已經設定預設網站,請取消後再設定!'
SSL: '憑證'
Set: '設定'
# SSL 憑證
ErrSSLCannotDelete: '{{ .name }} 憑證正在被網站使用,無法刪除'
ErrAccountCannotDelete: '帳號關聯憑證,無法刪除'
ErrSSLApply: '憑證續簽成功,openresty reload 失敗,請檢查設定!'
@@ -227,6 +247,8 @@ ErrApplySSLCanNotDelete: '正在申請的憑證 {{ .name }} 無法刪除,請
StartPushSSLToNode: '開始推送憑證到節點'
PushSSLToNodeFailed: '推送憑證到節點失敗: {{ .err }}'
PushSSLToNodeSuccess: '推送憑證到節點成功'
# MySQL
ErrUserIsExist: '目前使用者已存在,請重新輸入'
ErrDatabaseIsExist: '目前資料庫已存在,請重新輸入'
ErrExecTimeOut: 'SQL 執行逾時,請檢查資料庫'
@@ -235,7 +257,11 @@ ErrLocalExist: '本機資料庫已存在該名稱,請修改後重試'
RemoteBackup: '備份遠端資料庫需要先使用映像 {{ .name }} 啟動本機容器資料庫服務,請稍候...'
RemoteRecover: '復原遠端資料庫需要先使用映像 {{ .name }} 啟動本機容器資料庫服務,請稍候...'
Arg: '參數'
# Redis
ErrTypeOfRedis: '復原檔案類型與目前持久化方式不符,請修改後重試'
# 容器
ErrInUsed: '{{ .detail }} 正被使用,無法刪除!'
ErrObjectInUsed: '該對象正被使用,無法刪除!'
ErrObjectBeDependent: '該映像正被其他映像所依賴,無法刪除!'
@@ -277,6 +303,8 @@ ComposeRecoverFiles: '還原編排檔案'
ComposeRecoverContainers: '還原編排容器'
ComposeRecoverRecord: '保存編排記錄'
ComposeRecoverCleanup: '清理編排還原工作目錄'
# 執行環境
ErrFileNotExist: '{{ .detail }} 檔案不存在!請檢查來源檔案完整性!'
ErrImageBuildErr: '映像build 失敗'
ErrImageExist: '映像已存在!請修改映像名稱。'
@@ -289,12 +317,16 @@ ErrNodeModulesNotFound: 'node_modules 資料夾不存在!請編輯執行環境
ErrContainerNameIsNull: '容器名稱不存在'
ErrPHPPortIsDefault: '9000 埠為預設埠,請修改後重試'
ErrPHPRuntimePortFailed: '{{ .name }} 埠已被目前執行環境使用,請修改後重試'
# 工具
ErrConfigNotFound: '設定檔不存在'
ErrConfigParse: '設定檔格式有誤'
ErrConfigIsNull: '設定檔不允許為空'
ErrConfigDirNotFound: '執行目錄不存在'
ErrConfigAlreadyExist: '已存在同名設定檔'
ErrUserFindErr: '使用者{{ .name }} 尋找失敗{{ .err }}'
# 計劃任務
CutWebsiteLogSuccess: '{{ .name }} 網站日誌切割成功,備份路徑{{ .path }}'
HandleShell: '執行腳本{{ .name }}'
HandleCurl: '存取 URL {{ .name }}'
@@ -313,6 +345,8 @@ NoSuchResource: '資料庫中未能查詢到備份內容,跳過...'
CleanLog: '清理日誌'
CleanLogByName: '清理 {{ .name }} 日誌'
WafIpGroupNotFound: 'WAF IP 群組未找到,請先到【進階功能-WAF-黑白名單-IP群組】添加遠端下載方式的IP群組'
# 工具箱
ErrNotExistUser: '目前使用者不存在,請修改後重試!'
ErrBanAction: '設定失敗,目前{{ .name }} 服務不可用,請重試'
ErrClamdscanNotFound: '未偵測到clamdscan 指令,請參考檔案安裝!'
@@ -321,11 +355,15 @@ SetSwap: '設定 Swap {{ .path }} - {{ .size }}'
CreateSwap: '建立 Swap 檔案'
FormatSwap: '格式化 Swap 檔案'
EnableSwap: '啟用 Swap'
# 防篡改
CleanTamperSetting: '清理歷史防護設定'
SetTamperWithRules: '根據規則啟動目錄防護'
ProtectDir: '防護目錄 {{ .name }}'
IgnoreFile: '保護檔案 {{ .name }}'
TamperSetSuccessful: '防護設定完成,開始監聽以下目錄:'
# WAF
ErrScope: '不支援修改此設定'
ErrStateChange: '狀態修改失敗'
ErrRuleExist: '規則已存在'
@@ -380,6 +418,8 @@ OpenrestyNotFound: 'OpenResty 未安裝'
remoteIpIsNull: 'IP 列表為空'
OpenrestyVersionErr: 'OpenResty 版本過低,請升級 OpenResty 至 1.27.1.2-2-2-focal'
ErrFileTooLarge: '檔案超過 1MB,無法上傳'
# 任務
TaskStart: '{{ .name }} 任務開始[START]'
TaskEnd: '{{ .name }} 任務結束[COMPLETED]'
TaskFailed: '{{ .name }} 任務失敗'
@@ -440,11 +480,17 @@ CustomAppstore: '自訂應用程式倉庫'
TaskExec: '執行'
TaskBatch: '批次操作'
FileConvert: '檔案格式轉換'
# 任務 - ClamAV
Clamscan: '掃描 {{ .name }}'
TaskScan: '掃描'
# 任務 - AI
OllamaModelPull: '拉取 Ollama 模型{{ .name }}'
OllamaModelSize: '取得 Ollama 模型{{ .name }} 大小'
AIBenchmarkRun: '執行 AI 基準測試'
# 任務 - 快照
Snapshot: '快照'
SnapDBInfo: '寫入1Panel 資料庫資訊'
SnapCopy: '複製檔案&目錄{{ .name }}'
@@ -494,6 +540,8 @@ RecoverDaemonJson: '復原容器設定daemon.json 檔'
RecoverDBData: '復原資料庫資料'
RecoverBackups: '還原本機備份目錄'
RecoverPanelData: '復原資料目錄'
# 任務 - 容器
ContainerImagePull: '拉取容器映像{{ .name }}'
ContainerRemoveOld: '刪除原容器{{ .name }}'
ContainerImageCheck: '檢查映像是否正常拉取'
@@ -502,6 +550,8 @@ ContainerRecreate: '容器更新失敗,現在開始復原原容器'
ContainerCreate: '建立新容器{{ .name }}'
ContainerCreateFailed: '容器建立失敗,刪除失敗容器'
ContainerStartCheck: '檢查容器是否已啟動'
# 任務 - 映像
ImageBuild: '映像建置'
ImagePull: '拉取映像'
ImageRepoAuthFromDB: '從資料庫取得倉庫認證資訊'
@@ -509,6 +559,8 @@ ImagePush: '推播映像'
ImageRenameTag: '修改映像Tag'
ImageNewTag: '新映像Tag {{ .name }}'
ComposeCreate: '建立編排'
# 任務 - 網站
BackupNginxConfig: '備份網站OpenResty 設定檔'
CompressFileSuccess: '壓縮目錄成功,壓縮為{{ .name }}'
CompressDir: '壓縮目錄'
@@ -519,8 +571,15 @@ websiteDir: '網站目錄'
RecoverFailedStartRollBack: '復原失敗,開始回溯'
AppBackupFileIncomplete: '備份檔案不完整缺少 app.json 或 app.tar.gz 檔案'
AppAttributesNotMatch: '應用程式類型或名稱不一致'
# 告警
ErrAlertMethodNotSupported: '當前版本不支援此告警方式'
ErrAlertSync: '警告訊息同步錯誤,請重試。'
# 任務 - 執行環境
ErrInstallExtension: '已有安裝任務正在進行,請等待任務結束'
# 告警郵件範本
PanelAlertTitle: '面板警示通知'
TestAlertTitle: '測試郵件 - 驗證信箱連通性'
TestAlert: '這是一封測試郵件,用於確認您的郵件發送設定是否正確。'
@@ -537,6 +596,8 @@ CommonAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},詳情請登入
NodeExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個節點出現異常,詳情請登入面板檢視。'
LicenseExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個授權出現異常,詳情請登入面板檢視。'
SSHAndPanelLoginAlert: '您的 {{ .node }}{{ .ip }} 面板,來自 {{ .loginIp }} 的 {{ .name }} 登入出現異常,詳情請登入面板檢視。'
# 磁碟
DeviceNotFound: '裝置 {{ .name }} 未找到'
DeviceIsMounted: '裝置 {{ .name }} 已掛載,請先移除'
PartitionDiskErr: '分區失敗,{{ .err }}'
@@ -544,9 +605,18 @@ FormatDiskErr: '格式化磁碟失敗,{{ .err }}'
MountDiskErr: '掛載磁碟失敗,{{ .err }}'
XfsNotFound: '未偵測到 xfs 檔案系統,請先安裝 xfsprogs'
# terminal
# 終端
TerminalAIBlockedRiskyCommand: '已攔截風險命令:{{ .command }}'
TerminalAIThinking: 'AI 正在思考...'
TerminalAIReadyToExecute: '思考完成,請按 Enter 執行(耗時 {{ .duration }},token:{{ .tokens }})'
TerminalAIRequestFailed: 'AI 請求失敗:{{ .err }}'
FileAISearchEmptyDir: '目前目錄下沒有可列出的檔案或資料夾(或均被過濾)。'
# AI 閘道
ErrAIProxyServiceNotInstalled: '{{ .name }} 服務未安裝'
ErrAIProxyModelAccountNotFound: '模型帳號 {{ .id }} 不存在'
ErrAIProxyModelMapRequired: '請填寫必填項'
ErrAIProxyModelMapInvalid: '模型映射必須是合法的 JSON 物件:{{ .err }}'
ErrAIProxyModelMapEmpty: '模型映射不能為空'
ErrAIProxyModelMapEmptyExternalModel: '模型映射中存在空的請求模型'
ErrAIProxyModelMapEmptyUpstreamModel: '模型 {{ .model }} 的上游模型不能為空'
AIProxyUserFallback: '已刪除使用者 (ID: {{ .id }})'
+54 -44
View File
@@ -1,32 +1,47 @@
# 基础错误
ErrInvalidParams: "参数错误: {{ .detail }}"
ErrTokenParse: "Token 错误: {{ .detail }}"
ErrInitialPassword: "原密码错误"
ErrInternalServer: "服务错误: {{ .detail }}"
ErrRecordExist: "记录已存在"
ErrRecordNotFound: "记录不存在"
ErrStructTransform: "类型转换失败: {{ .err }}"
ErrNotSupportType: "不支持当前类型: {{ .name }}"
# 文件 AI
ErrFileAINotEnabled: "生成 AI 总结需在文件管理中启用并保存文件 AI 账号。关闭 AI 时仍可使用纯内容检索。"
ErrFileAISearchBadPattern: "搜索条件无效(正则或全词匹配): {{ .detail }}"
FileAISearchEmptyDir: "当前目录下没有可列出的文件或文件夹(或均被过滤)。"
# 远程下载
ErrWgetRemoteFailed: "远程下载地址无效或无法访问:{{ .detail }}"
ErrWgetInvalidContentType: "远程地址返回的不是文件内容(可能是错误页),{{ .detail }}"
ErrWgetProxyNotConfigured: "请先在面板设置中配置代理服务器"
ErrWgetProxyInvalid: "代理服务器地址无效:{{ .detail }}"
ErrRecordExist: "记录已存在"
ErrRecordNotFound: "记录不存在"
ErrStructTransform: "类型转换失败: {{ .err }}"
# 登录与安全
ErrNotLogin: "用户未登录: {{ .detail }}"
ErrPasswordExpired: "当前密码已过期: {{ .detail }}"
ErrNotSupportType: "不支持当前类型: {{ .name }}"
ErrAlertMethodNotSupported: "当前版本不支持此告警方式"
# 请求与 API
ErrProxy: "请求失败,请检查节点状态: {{ .detail }}"
ErrApiConfigStatusInvalid: "API 禁止访问: {{ .detail }}"
ErrApiConfigKeyInvalid: "API 接口密钥错误: {{ .detail }}"
ErrApiConfigIPInvalid: "调用 API 接口 IP 不在白名单: {{ .detail }}"
ErrApiConfigDisable: "此接口禁止 API 调用: {{ .detail }}"
ErrApiConfigKeyTimeInvalid: "API 时间戳错误: {{ .detail }}"
ErrShutDown: "命令已手动终止"
ErrCreateHttpClient: "创建请求失败 {{ .err }}"
ErrHttpReqTimeOut: "请求超时 {{ .err }}"
ErrHttpReqFailed: "请求失败 {{ .err }}"
ErrNoSuchHost: "无法找到请求的服务器 {{ .err }}"
ErrHttpReqNotFound: "无法找到请求的资源 {{ .err }}"
# 快速跳转
ErrMinQuickJump: "至少设置 1 个快速跳转入口"
ErrMaxQuickJump: "最多设置 4 个快速跳转入口"
#common
# 通用
ErrShutDown: "命令已手动终止"
ErrUsernameIsExist: "用户名已存在"
ErrNameIsExist: "名称已存在"
ErrDemoEnvironment: "演示环境,禁止操作"
@@ -45,7 +60,7 @@ ErrGroupIsDefault: "默认分组不可删除"
ErrGroupIsInWebsiteUse: "分组正在被其他网站使用,无法删除"
Decrypt: "解密"
#agent
# 智能体
ErrAgentAccountBound: "该账号已绑定智能体,无法删除"
ErrTerminalAIAccountInUse: "该模型账号正被终端 AI 使用,请在终端 AI 设置中更换账号或关闭终端 AI 后重试"
ErrAgentAccountUnavailable: "账号连接信息不可用: {{ .err }}"
@@ -71,7 +86,7 @@ ErrAgentWebsiteUnbindUnsupported: "一键部署网站不支持手动解绑"
ErrHermesPairingCodeUnavailable: "配对码在 hermes 中暂时不存在,可能是由于网络原因,请稍后尝试"
ErrHermesFeishuGroupAllowlistRequiresAllowlist: "飞书群组策略为白名单时,私聊策略不能为配对码"
#backup
# 备份与告警配置
Localhost: '本机'
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
ErrBackupCheck: "备份账号测试连接失败 {{ .err }}"
@@ -80,7 +95,7 @@ ErrBackupLocalCreate: "暂不支持创建本地服务器备份账号"
ErrAlertConfigInUse: "告警配置正在使用中,无法删除"
ErrAlertConfigPhoneExist: "手机号已存在"
#app
# 应用
ErrPortInUsed: "{{ .detail }} 端口已被占用!"
ErrAppLimit: "应用安装数量超限"
ErrNotInstall: "应用未安装"
@@ -98,16 +113,12 @@ ErrFileParseApp: '{{ .name }} 文件解析失败 {{ .err }}'
ErrAppDirNull: '版本文件夹不存在'
LocalAppErr: "应用 {{ .name }} 同步失败!{{ .err }}"
ErrContainerName: "容器名称已存在"
ErrCreateHttpClient: "创建请求失败 {{ .err }}"
ErrHttpReqTimeOut: "请求超时 {{ .err }}"
ErrHttpReqFailed: "请求失败 {{ .err }}"
ErrNoSuchHost: "无法找到请求的服务器 {{ .err }}"
ErrHttpReqNotFound: "无法找到请求的资源 {{ .err }}"
ErrContainerNotFound: '{{ .name }} 容器不存在'
ErrContainerMsg: '{{ .name }} 容器异常,具体请在容器页面查看日志'
ErrAppBackup: '{{ .name }} 应用备份失败 {{ .err }}'
ErrVersionTooLow: '当前 1Panel 版本过低,无法更新应用商店,请升级版本之后操作'
ErrAppNameExist: '应用名称已存在'
ErrAppInstallNameExist: '应用安装名称 {{ .name }} 已存在'
AppStoreIsSyncing: '应用商店同步中,请稍后重试'
ErrGetCompose: "docker-compose.yml 文件获取失败!{{ .detail }}"
ErrAppWarn: "状态异常,请查看日志"
@@ -133,7 +144,7 @@ ErrDockerComposeCmdNotFound: "Docker Compose 命令不存在,请先在宿主
UseExistImage: "镜像已存在,使用存量镜像"
ErrDatabaseNotFound: "数据库 {{ .name }} 不存在"
#ssh
# SSH
ExportIP: "登录 IP"
ExportArea: "归属地"
ExportPort: "端口"
@@ -142,7 +153,7 @@ ExportUser: "用户"
ExportStatus: "登录状态"
ExportDate: "时间"
#file
# 文件
ErrFileCanNotRead: "此文件不支持预览"
ErrSensitiveFileRead: "不允许读取敏感文件"
ErrFileToLarge: "文件超过 10M,无法打开"
@@ -170,7 +181,7 @@ FileDropSuccess: "清理 {{ .name }} 成功,文件 {{ .count }},释放 {{ .s
FileDropSum: "文件清理完成,共 {{ .count }},释放 {{ .size }}"
ErrBadDecrypt: "压缩包密码错误,解压失败,请检查后重试"
#website
# 网站
ErrAliasIsExist: "标识已存在"
ErrBackupMatch: '该备份文件与当前网站部分数据不匹配 {{ .detail }}'
ErrBackupExist: '该备份文件对应部分源数据不存在 {{ .detail }}'
@@ -201,7 +212,7 @@ ErrDefaultWebsite: "已设置默认网站,请先取消再设置"
SSL: '证书'
Set: '设置'
#ssl
# SSL 证书
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"
ErrAccountCannotDelete: "账号关联证书,无法删除"
ErrSSLApply: "证书续签成功,但 OpenResty reload 失败,请检查配置"
@@ -237,7 +248,7 @@ StartPushSSLToNode: "开始推送证书到节点"
PushSSLToNodeFailed: "推送证书到节点失败: {{ .err }}"
PushSSLToNodeSuccess: "推送证书到节点成功"
#mysql
# MySQL
ErrUserIsExist: "用户已存在,请重新输入"
ErrDatabaseIsExist: "数据库已存在,请重新输入"
ErrExecTimeOut: "SQL 执行超时,请检查数据库"
@@ -247,10 +258,10 @@ RemoteBackup: "备份远程数据库前需先用镜像 {{ .name }} 启动本地
RemoteRecover: "恢复远程数据库前需先用镜像 {{ .name }} 启动本地容器数据库服务,请稍候"
Arg: "参数"
#redis
# Redis
ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后重试"
#container
# 容器
ErrInUsed: "{{ .detail }} 正被使用,无法删除!"
ErrObjectInUsed: "该对象正被使用,无法删除!"
ErrObjectBeDependent: "该镜像正被其他镜像所依赖,无法删除!"
@@ -293,7 +304,7 @@ ComposeRecoverContainers: "恢复编排容器"
ComposeRecoverRecord: "保存编排记录"
ComposeRecoverCleanup: "清理编排恢复工作目录"
#runtime
# 运行环境
ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!"
ErrImageBuildErr: "镜像 build 失败"
ErrImageExist: "镜像已存在!请修改镜像名称"
@@ -307,7 +318,7 @@ ErrContainerNameIsNull: "容器名称不存在"
ErrPHPPortIsDefault: "9000 端口为默认端口,请修改后重试"
ErrPHPRuntimePortFailed: "{{ .name }} 端口已被当前运行环境使用,请修改后重试"
#tool
# 工具
ErrConfigNotFound: "配置文件不存在"
ErrConfigParse: "配置文件格式有误"
ErrConfigIsNull: "配置文件不允许为空"
@@ -315,7 +326,7 @@ ErrConfigDirNotFound: "运行目录不存在"
ErrConfigAlreadyExist: "已存在同名配置文件"
ErrUserFindErr: "用户 {{ .name }} 查找失败 {{ .err }}"
#cronjob
# 计划任务
CutWebsiteLogSuccess: "{{ .name }} 网站日志切割成功,备份路径 {{ .path }}"
HandleShell: "执行脚本 {{ .name }}"
HandleCurl: "访问 URL {{ .name }}"
@@ -335,7 +346,7 @@ CleanLog: "清理日志"
CleanLogByName: "清理 {{ .name }} 日志"
WafIpGroupNotFound: "WAF IP组未找到,请先到【高级功能-WAF-黑白名单-IP组】添加远程下载方式的IP组"
#toolbox
# 工具箱
ErrNotExistUser: "当前用户不存在,请修改后重试!"
ErrBanAction: "设置失败,{{ .name }} 服务不可用,请检查后重试"
ErrClamdscanNotFound: "未检测到 clamdscan 命令,请按文档安装"
@@ -345,14 +356,14 @@ CreateSwap: "创建 Swap 文件"
FormatSwap: "格式化 Swap 文件"
EnableSwap: "启用 Swap"
#tamper
# 防篡改
CleanTamperSetting: "清理历史防护设置"
SetTamperWithRules: "根据规则启动目录防护"
ProtectDir: "防护目录 {{ .name }}"
IgnoreFile: "保护文件 {{ .name }}"
TamperSetSuccessful: "防护设置完成,开始监听以下目录:"
#waf
# WAF
ErrScope: "不支持修改此配置"
ErrStateChange: "状态修改失败"
ErrRuleExist: "规则已存在"
@@ -408,7 +419,7 @@ remoteIpIsNull: "IP 列表为空"
OpenrestyVersionErr: "OpenResty 版本过低,请升级到 1.27.1.2-2-2-focal"
ErrFileTooLarge: "文件超过 1MB,无法上传"
#task
# 任务
TaskStart: "{{ .name }} 任务开始 [START]"
TaskEnd: "{{ .name }} 任务结束 [COMPLETED]"
TaskFailed: "{{ .name }} 任务失败"
@@ -470,16 +481,16 @@ TaskExec: "执行"
TaskBatch: "批量操作"
FileConvert: "文件格式转换"
# task - clam
# 任务 - ClamAV
Clamscan: "扫描 {{ .name }}"
TaskScan: "扫描"
# task - ai
# 任务 - AI
OllamaModelPull: "拉取 Ollama 模型 {{ .name }}"
OllamaModelSize: "获取 Ollama 模型 {{ .name }} 大小"
AIBenchmarkRun: "执行 AI 基准测试"
# task - snapshot
# 任务 - 快照
Snapshot: "快照"
SnapDBInfo: "写入 1Panel 数据库信息"
SnapCopy: "复制文件/目录 {{ .name }}"
@@ -505,7 +516,6 @@ SnapCompressSize: "快照文件大小 {{ .name }}"
SnapUpload: "上传快照文件"
SnapUploadTo: "上传快照文件到 {{ .name }}"
SnapUploadRes: "上传快照文件到 {{ .name }}"
SnapshotRecover: "快照恢复"
RecoverDownload: "下载快照文件"
Download: "下载"
@@ -531,7 +541,7 @@ RecoverDBData: "恢复数据库数据"
RecoverBackups: "恢复本地备份目录"
RecoverPanelData: "恢复数据目录"
# task - container
# 任务 - 容器
ContainerImagePull: "拉取容器镜像 {{ .name }}"
ContainerRemoveOld: "删除原容器 {{ .name }}"
ContainerImageCheck: "检查镜像是否正常拉取"
@@ -541,7 +551,7 @@ ContainerCreate: "创建新容器 {{ .name }}"
ContainerCreateFailed: "容器创建失败,删除失败容器"
ContainerStartCheck: "检查容器是否已启动"
# task - image
# 任务 - 镜像
ImageBuild: "镜像构建"
ImagePull: "拉取镜像"
ImageRepoAuthFromDB: "从数据库获取仓库认证信息"
@@ -550,7 +560,7 @@ ImageRenameTag: "修改镜像 Tag"
ImageNewTag: "新镜像 Tag {{ .name }}"
ComposeCreate: "创建编排"
# task - website
# 任务 - 网站
BackupNginxConfig: "备份网站 OpenResty 配置文件"
CompressFileSuccess: "压缩目录成功,压缩为 {{ .name }}"
CompressDir: "压缩目录"
@@ -562,13 +572,14 @@ RecoverFailedStartRollBack: "恢复失败,开始回滚"
AppBackupFileIncomplete: "备份文件不完整,缺少 app.json 或 app.tar.gz 文件"
AppAttributesNotMatch: "应用类型或名称不一致"
#alert
# 告警
ErrAlertMethodNotSupported: "当前版本不支持此告警方式"
ErrAlertSync: "告警信息同步错误,请检查后重试"
#task - runtime
# 任务 - 运行环境
ErrInstallExtension: "已有安装任务正在进行,请等待任务结束"
# alert mail template
# 告警邮件模板
PanelAlertTitle: "面板告警通知"
TestAlertTitle: "测试邮件 - 验证邮箱连通性"
TestAlert: "这是一封测试邮件,用于验证邮箱发件配置。"
@@ -586,7 +597,7 @@ NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个节点
LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个许可证存在异常,请登录面板查看详情。"
SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,面板 {{ .name }} 登录 {{ .loginIp }} 异常,请登录面板查看详情。"
#disk
# 磁盘
DeviceNotFound: "设备 {{ .name }} 未找到"
DeviceIsMounted: "设备 {{ .name }} 已挂载,请先卸载"
PartitionDiskErr: "分区失败,{{ .err }}"
@@ -594,14 +605,13 @@ FormatDiskErr: "格式化磁盘失败,{{ .err }}"
MountDiskErr: "挂载磁盘失败,{{ .err }}"
XfsNotFound: "未检测到 xfs 文件系统,请先安装 xfsprogs"
# terminal
# 终端
TerminalAIBlockedRiskyCommand: "已拦截风险命令:{{ .command }}"
TerminalAIThinking: "AI 正在思考..."
TerminalAIReadyToExecute: "思考完成,请回车执行(耗时 {{ .duration }},token:{{ .tokens }})"
TerminalAIRequestFailed: "AI 请求失败:{{ .err }}"
FileAISearchEmptyDir: "当前目录下没有可列出的文件或文件夹(或均被过滤)。"
# ai proxy
# AI 网关
ErrAIProxyServiceNotInstalled: "{{ .name }} 服务未安装"
ErrAIProxyModelAccountNotFound: "模型账号 {{ .id }} 不存在"
ErrAIProxyModelMapRequired: "请填写必填项"
@@ -609,4 +619,4 @@ ErrAIProxyModelMapInvalid: "模型映射必须是合法的 JSON 对象:{{ .err
ErrAIProxyModelMapEmpty: "模型映射不能为空"
ErrAIProxyModelMapEmptyExternalModel: "模型映射中存在空的请求模型"
ErrAIProxyModelMapEmptyUpstreamModel: "模型 {{ .model }} 的上游模型不能为空"
AIProxyUserFallback: "用户 {{ .id }}"
AIProxyUserFallback: "已删除用户 (ID: {{ .id }})"
+1
View File
@@ -40,6 +40,7 @@ func InitAgentDB() {
migrations.UpdateWebsiteSSL,
migrations.AddQuickJump,
migrations.UpdateMcpServerAddType,
migrations.UpdateMcpServerGatewayConfig,
migrations.InitLocalSSHConn,
migrations.InitLocalSSHShow,
migrations.InitRecordStatus,
+19
View File
@@ -685,6 +685,25 @@ var UpdateMcpServerAddType = &gormigrate.Migration{
},
}
var UpdateMcpServerGatewayConfig = &gormigrate.Migration{
ID: "20260615-update-mcp-server-gateway-config",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.McpServer{}); err != nil {
return err
}
if err := tx.Model(&model.McpServer{}).Where("gateway_image IS NULL OR gateway_image = ''").Where("type = ?", "uvx").Update("gateway_image", "supercorp/supergateway:uvx").Error; err != nil {
return err
}
if err := tx.Model(&model.McpServer{}).Where("gateway_image IS NULL OR gateway_image = ''").Where("type <> ? OR type IS NULL OR type = ''", "uvx").Update("gateway_image", "supercorp/supergateway:3.4.3").Error; err != nil {
return err
}
if err := tx.Model(&model.McpServer{}).Where("protocol_version IS NULL OR protocol_version = ''").Update("protocol_version", "2025-06-18").Error; err != nil {
return err
}
return nil
},
}
var InitLocalSSHConn = &gormigrate.Migration{
ID: "20250905-init-local-ssh",
Migrate: func(tx *gorm.DB) error {
+3
View File
@@ -29,10 +29,13 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter.GET("/gpu/options", baseApi.GetCPUOptions)
aiToolsRouter.POST("/mcp/search", baseApi.PageMcpServers)
aiToolsRouter.POST("/mcp/server/detail", baseApi.LoadMcpServerDetail)
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/server/status/sync", baseApi.SyncMcpServerStatus)
aiToolsRouter.POST("/mcp/server/connection/test", baseApi.TestMcpServerConnection)
aiToolsRouter.POST("/mcp/domain/bind", baseApi.BindMcpDomain)
aiToolsRouter.GET("/mcp/domain/get", baseApi.GetMcpBindDomain)
aiToolsRouter.POST("/mcp/domain/update", baseApi.UpdateMcpBindDomain)
+1
View File
@@ -21,6 +21,7 @@ func (a *WebsiteSSLRouter) InitRouter(Router *gin.RouterGroup) {
groupRouter.GET("/website/:websiteId", baseApi.GetWebsiteSSLByWebsiteId)
groupRouter.GET("/:id", baseApi.GetWebsiteSSLById)
groupRouter.POST("/update", baseApi.UpdateWebsiteSSL)
groupRouter.POST("/push", baseApi.PushWebsiteSSLToNode)
groupRouter.POST("/upload", baseApi.UploadWebsiteSSL)
groupRouter.POST("/obtain", baseApi.ApplyWebsiteSSL)
groupRouter.POST("/download", baseApi.DownloadWebsiteSSL)
-1
View File
@@ -60,7 +60,6 @@ func pushByLegacyMethod(alertRepo repo.IAlertRepo, alert dto.AlertDTO, pushAlert
}
config, err := alertRepo.GetConfig(alertRepo.WithByType(configType))
if err != nil {
global.LOG.Errorf("alert config not found for type %s: %v", configType, err)
return
}
sendAlert(alertRepo, alert, pushAlert, config)
+39
View File
@@ -2,6 +2,7 @@ package cmd
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
@@ -67,3 +68,41 @@ func Which(name string) bool {
_, err := exec.LookPath(name)
return err == nil
}
func commandStartDiagnostics(name, resolvedName, workDir string, env []string) string {
items := []string{
fmt.Sprintf("command=%q", name),
fmt.Sprintf("resolved=%q", resolvedName),
fmt.Sprintf("path=%q", envValue(env, "PATH")),
}
if workDir != "" {
items = append(items, describeWorkDir(workDir))
}
return "; " + strings.Join(items, "; ")
}
func describeWorkDir(name string) string {
statText := "stat=ok"
if info, err := os.Stat(name); err != nil {
statText = fmt.Sprintf("stat=%v", err)
} else {
statText = fmt.Sprintf("stat=mode:%s", info.Mode().String())
}
lstatText := "lstat=ok"
if info, err := os.Lstat(name); err != nil {
lstatText = fmt.Sprintf("lstat=%v", err)
} else {
lstatText = fmt.Sprintf("lstat=mode:%s", info.Mode().String())
}
return fmt.Sprintf("workdir=%q[%s %s]", name, statText, lstatText)
}
func envValue(env []string, key string) string {
prefix := key + "="
for i := len(env) - 1; i >= 0; i-- {
if strings.HasPrefix(env[i], prefix) {
return strings.TrimPrefix(env[i], prefix)
}
}
return os.Getenv(key)
}
+8 -17
View File
@@ -240,7 +240,7 @@ func (c *CommandHelper) pipeContext() (context.Context, context.CancelFunc) {
func (c *CommandHelper) buildPipeCommands(ctx context.Context, commands []PipeCommand) []*exec.Cmd {
cmds := make([]*exec.Cmd, 0, len(commands))
for _, item := range commands {
cmdItem := exec.CommandContext(ctx, item.Name, filterEmptyArgs(item.Args)...)
cmdItem := exec.CommandContext(ctx, item.Name, item.Args...)
cmdItem.Env = append(os.Environ(), c.env...)
cmdItem.Env = append(cmdItem.Env, item.Env...)
cmdItem.Dir = c.workDir
@@ -274,7 +274,10 @@ func startPipeCommands(cmds []*exec.Cmd) error {
for i := len(cmds) - 1; i >= 0; i-- {
if err := cmds[i].Start(); err != nil {
killStarted(cmds[i+1:])
return err
if os.IsNotExist(err) {
return fmt.Errorf("cmd start failed: %w%s", err, commandStartDiagnostics(cmds[i].Args[0], cmds[i].Path, cmds[i].Dir, cmds[i].Env))
}
return fmt.Errorf("cmd start failed: %w", err)
}
}
return nil
@@ -305,7 +308,6 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
var newContext context.Context
var cancel context.CancelFunc
var outputFile *os.File
arg = filterEmptyArgs(arg)
if c.timeout != 0 {
if c.context == nil {
@@ -377,6 +379,9 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
}()
if err := cmd.Start(); err != nil {
if os.IsNotExist(err) {
return "", fmt.Errorf("cmd start failed: %w%s", err, commandStartDiagnostics(cmd.Args[0], cmd.Path, cmd.Dir, cmd.Env))
}
return "", fmt.Errorf("cmd start failed: %w", err)
}
if c.taskItem != nil {
@@ -412,20 +417,6 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
}
}
func filterEmptyArgs(args []string) []string {
if len(args) == 0 {
return args
}
filtered := args[:0]
for _, arg := range args {
if arg == "" {
continue
}
filtered = append(filtered, arg)
}
return filtered
}
func contextDone(ctx context.Context) <-chan struct{} {
if ctx == nil {
return nil
+5 -1
View File
@@ -49,7 +49,7 @@ func Up(filePath string) (string, error) {
}
func UpWithTask(filePath string, task *task.Task, forcePull bool) error {
if err := pullComposeImages(filePath, forcePull, task); err != nil {
if err := PullComposeImages(filePath, forcePull, task); err != nil {
return err
}
base, extra := getComposeBaseCmd()
@@ -58,6 +58,10 @@ func UpWithTask(filePath string, task *task.Task, forcePull bool) error {
return cmd.NewCommandMgr(cmd.WithTask(*task), cmd.WithTimeout(20*time.Minute)).Run(base, args...)
}
func PullComposeImages(filePath string, forcePull bool, task *task.Task) error {
return pullComposeImages(filePath, forcePull, task)
}
func pullComposeImages(filePath string, forcePull bool, task *task.Task) error {
images, err := GetComposeImages(filePath)
if err != nil {
+18 -5
View File
@@ -199,20 +199,33 @@ func (c Client) PullImageWithProcessAndOptions(task *task.Task, imageName string
return err
}
defer out.Close()
return handlePullImageProcess(out, task)
}
func handlePullImageProcess(out io.Reader, task *task.Task) error {
decoder := json.NewDecoder(out)
for {
var progress map[string]interface{}
if err = decoder.Decode(&progress); err != nil {
if err := decoder.Decode(&progress); err != nil {
if err == io.EOF {
break
}
return err
}
status, _ := progress["status"].(string)
if status == "Downloading" || status == "Extracting" {
logProcess(progress, task)
if msg, ok := progress["errorDetail"]; ok {
return fmt.Errorf("image pull failed, err: %v", msg)
}
if status == "Pull complete" || status == "Download complete" {
if msg, ok := progress["error"]; ok {
return fmt.Errorf("image pull failed, err: %v", msg)
}
if task == nil {
continue
}
status, _ := progress["status"].(string)
switch status {
case "Downloading", "Extracting":
logProcess(progress, task)
case "Pull complete", "Download complete", "Already exists", "Verifying Checksum":
id, _ := progress["id"].(string)
progressStr := fmt.Sprintf("%s %s", status, id)
_ = setLog(id, progressStr, task)
+2 -2
View File
@@ -1376,8 +1376,8 @@ func (f FileOp) TarGzFilesWithCompressPro(list []string, dst, secret string) err
}
func (f FileOp) TarGzExtractPro(src, dst string, secret string) error {
if _, err := os.Stat(path.Dir(dst)); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(dst), os.ModePerm); err != nil {
if _, err := os.Stat(dst); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dst, os.ModePerm); err != nil {
return err
}
}
+70
View File
@@ -2,11 +2,21 @@ package req_helper
import (
"bytes"
"crypto/md5"
"crypto/rand"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"io"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"time"
)
func PostLocalCore(url string) error {
@@ -27,6 +37,12 @@ func PostLocalCore(url string) error {
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
token, err := loadDailyLocalToken()
if err != nil {
return err
}
req.Header.Set("X-Panel-Local-Token", token)
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
@@ -39,5 +55,59 @@ func PostLocalCore(url string) error {
return err
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
return fmt.Errorf("core request failed: status=%s, body=%s", resp.Status, strings.TrimSpace(string(bodyBytes)))
}
var result struct {
Code int `json:"code"`
Message string `json:"message"`
}
if len(bytes.TrimSpace(bodyBytes)) > 0 {
if err := json.Unmarshal(bodyBytes, &result); err == nil && result.Code != 0 && result.Code != http.StatusOK {
return fmt.Errorf("core request failed: code=%d, message=%s", result.Code, result.Message)
}
}
return nil
}
func loadDailyLocalToken() (string, error) {
secret, err := loadOrGenerateLocalSecret()
if err != nil {
return "", err
}
today := time.Now().Format("2006-01-02")
h := md5.New()
h.Write([]byte(secret + "-" + today))
return hex.EncodeToString(h.Sum(nil))[:16], nil
}
func loadOrGenerateLocalSecret() (string, error) {
secretFilePath := path.Join(global.Dir.TmpDir, ".secret")
data, err := os.ReadFile(secretFilePath)
if err == nil {
secret := strings.TrimSpace(string(data))
if secret != "" {
return secret, nil
}
}
if err != nil && !os.IsNotExist(err) {
return "", err
}
dir := filepath.Dir(secretFilePath)
if err := os.MkdirAll(dir, 0755); err != nil {
return "", err
}
key := make([]byte, 32)
if _, err := rand.Read(key); err != nil {
return "", err
}
secret := hex.EncodeToString(key)
if err := os.WriteFile(secretFilePath, []byte(secret), 0600); err != nil {
return "", err
}
return secret, nil
}
+69 -53
View File
@@ -20,8 +20,6 @@ import (
"strings"
"time"
"golang.org/x/crypto/acme"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/buserr"
@@ -30,10 +28,9 @@ import (
"github.com/go-acme/lego/v5/certcrypto/compat"
"github.com/go-acme/lego/v5/lego"
"github.com/go-acme/lego/v5/registration"
"golang.org/x/crypto/acme"
)
var Orders = make(map[uint]*acme.Order)
// parsePrivateKeyPEM decodes a PEM-encoded private key produced by either
// lego v4 (SEC1 for EC, PKCS#1 for RSA) or lego v5 (PKCS#8 for both). It tries
// PKCS#8 first because that is what v5 and any modern tooling emits, then
@@ -139,6 +136,62 @@ func GetPrivateKey(priKey crypto.PrivateKey, keyType KeyType) ([]byte, error) {
}
func NewRegisterClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemProxy) (*AcmeClient, error) {
return NewRegisterClientWithContext(context.Background(), acmeAccount, proxy)
}
func NewRegisterClientWithContext(ctx context.Context, acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemProxy) (*AcmeClient, error) {
client, err := newAcmeClient(acmeAccount, proxy, nil)
if err != nil {
return nil, err
}
var reg *legoacme.ExtendedAccount
if acmeAccount.Type == "zerossl" || acmeAccount.Type == "google" || acmeAccount.Type == "freessl" || (acmeAccount.Type == "custom" && acmeAccount.UseEAB) {
if acmeAccount.Type == "zerossl" {
var res *zeroSSLRes
res, err = getZeroSSLEabCredentials(ctx, acmeAccount.Email)
if err != nil {
return nil, err
}
if res.Success {
acmeAccount.EabKid = res.EabKid
acmeAccount.EabHmacKey = res.EabHmacKey
} else {
return nil, fmt.Errorf("get zero ssl eab credentials failed")
}
}
eabOptions := registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: acmeAccount.EabKid,
HmacEncoded: acmeAccount.EabHmacKey,
}
reg, err = client.Client.Registration.RegisterWithExternalAccountBinding(ctx, eabOptions)
if err != nil {
return nil, err
}
} else {
reg, err = client.Client.Registration.Register(ctx, registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, err
}
}
client.User.Registration = reg
if reg != nil {
acmeAccount.URL = reg.Location
}
if acmeAccount.PrivateKey == "" {
privateKey, err := GetPrivateKey(client.User.GetPrivateKey(), normalizeKeyType(acmeAccount.KeyType))
if err != nil {
return nil, err
}
acmeAccount.PrivateKey = string(privateKey)
}
return client, nil
}
func newAcmeClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemProxy, registration *legoacme.ExtendedAccount) (*AcmeClient, error) {
var (
priKey crypto.Signer
err error
@@ -165,8 +218,9 @@ func NewRegisterClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemP
}
myUser := &AcmeUser{
Email: acmeAccount.Email,
Key: priKey,
Email: acmeAccount.Email,
Registration: registration,
Key: priKey,
}
config := NewConfigWithProxy(myUser, acmeAccount.Type, acmeAccount.CaDirURL, proxy)
client, err := lego.NewClient(config)
@@ -174,41 +228,6 @@ func NewRegisterClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemP
return nil, err
}
ctx := context.Background()
var reg *legoacme.ExtendedAccount
if acmeAccount.Type == "zerossl" || acmeAccount.Type == "google" || acmeAccount.Type == "freessl" || (acmeAccount.Type == "custom" && acmeAccount.UseEAB) {
if acmeAccount.Type == "zerossl" {
var res *zeroSSLRes
res, err = getZeroSSLEabCredentials(acmeAccount.Email)
if err != nil {
return nil, err
}
if res.Success {
acmeAccount.EabKid = res.EabKid
acmeAccount.EabHmacKey = res.EabHmacKey
} else {
return nil, fmt.Errorf("get zero ssl eab credentials failed")
}
}
eabOptions := registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: acmeAccount.EabKid,
HmacEncoded: acmeAccount.EabHmacKey,
}
reg, err = client.Registration.RegisterWithExternalAccountBinding(ctx, eabOptions)
if err != nil {
return nil, err
}
} else {
reg, err = client.Registration.Register(ctx, registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, err
}
}
myUser.Registration = reg
acmeClient := &AcmeClient{
User: myUser,
Client: client,
@@ -252,15 +271,12 @@ func NewConfigWithProxy(user registration.User, accountType, customCaURL string,
}
// lego v5 removed lego.CertificateConfig.KeyType; the key type is now
// taken from the user's account for each ObtainRequest call.
return &lego.Config{
CADirURL: caDirURL,
UserAgent: "1Panel",
User: user,
HTTPClient: createHTTPClientWithProxy(proxyURL, proxyUser, proxyPassword),
Certificate: lego.CertificateConfig{
Timeout: 60 * time.Second,
},
}
config := lego.NewConfig(user)
config.CADirURL = caDirURL
config.UserAgent = "1Panel"
config.HTTPClient = createHTTPClientWithProxy(proxyURL, proxyUser, proxyPassword)
config.Certificate.Timeout = 60 * time.Second
return config
}
func initCertPool() *x509.CertPool {
@@ -319,20 +335,20 @@ func createHTTPClientWithProxy(proxyURL, username, password string) *http.Client
}
}
func getZeroSSLEabCredentials(email string) (*zeroSSLRes, error) {
func getZeroSSLEabCredentials(ctx context.Context, email string) (*zeroSSLRes, error) {
baseURL := "https://api.zerossl.com/acme/eab-credentials-email"
params := url.Values{}
params.Add("email", email)
requestURL := fmt.Sprintf("%s?%s", baseURL, params.Encode())
req, err := http.NewRequest("POST", requestURL, nil)
req, err := http.NewRequestWithContext(ctx, "POST", requestURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
client := &http.Client{Timeout: 2 * time.Minute}
defer client.CloseIdleConnections()
resp, err := client.Do(req)
if err != nil {
+97 -47
View File
@@ -7,13 +7,16 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"net"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
legoacme "github.com/go-acme/lego/v5/acme"
"github.com/go-acme/lego/v5/certificate"
"github.com/go-acme/lego/v5/challenge/dns01"
"github.com/go-acme/lego/v5/lego"
@@ -28,6 +31,8 @@ import (
// occasionally fail propagation checks against the wrong resolver.
var dnsChallengeMu sync.Mutex
var ErrAcmeAccountURLMissing = errors.New("acme account url is empty")
type AcmeClientOption func(*AcmeClientOptions)
type AcmeClientOptions struct {
@@ -40,18 +45,35 @@ type AcmeClient struct {
User *AcmeUser
ProxyURL string
// dnsChallengeLocked records whether this client currently holds
// dnsChallengeMu. It is set by UseDns and cleared by ObtainSSL/
// ObtainIPSSL once the DNS-01 flow finishes. UseHTTP does not touch it.
dnsChallengeLocked bool
dnsChallengeConfig *dnsChallengeConfig
}
type dnsChallengeConfig struct {
recursiveNameservers []string
timeout time.Duration
disableCNAME bool
}
func NewAcmeClient(acmeAccount *model.WebsiteAcmeAccount, systemProxy *dto.SystemProxy) (*AcmeClient, error) {
return NewAcmeClientWithContext(context.Background(), acmeAccount, systemProxy)
}
func NewAcmeClientWithContext(ctx context.Context, acmeAccount *model.WebsiteAcmeAccount, systemProxy *dto.SystemProxy) (*AcmeClient, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
if acmeAccount.Email == "" {
return nil, errors.New("email can not blank")
}
accountURL := strings.TrimSpace(acmeAccount.URL)
if accountURL == "" {
return nil, ErrAcmeAccountURLMissing
}
if strings.TrimSpace(acmeAccount.PrivateKey) == "" {
return nil, errors.New("private key can not blank")
}
client, err := NewRegisterClient(acmeAccount, systemProxy)
client, err := newAcmeClient(acmeAccount, systemProxy, &legoacme.ExtendedAccount{Location: accountURL})
if err != nil {
return nil, err
}
@@ -59,7 +81,11 @@ func NewAcmeClient(acmeAccount *model.WebsiteAcmeAccount, systemProxy *dto.Syste
}
func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.WebsiteSSL) error {
p, err := getDNSProviderConfig(dnsType, params)
var httpClient *http.Client
if c.Config != nil {
httpClient = c.Config.HTTPClient
}
p, err := getDNSProviderConfig(dnsType, params, httpClient)
if err != nil {
return err
}
@@ -71,46 +97,20 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
nameservers = append(nameservers, websiteSSL.Nameserver2)
}
// Hold the global DNS-01 lock for the entire flow that follows, including
// the Obtain call. lego v5 reads dns01.DefaultClient() inside its own
// propagation-precheck loop, so the lock cannot be released right after
// SetDefaultClient/SetDNS01Provider; it has to span the whole DNS-01
// challenge. ObtainSSL / ObtainIPSSL release it once the request returns.
dnsChallengeMu.Lock()
c.dnsChallengeLocked = true
if websiteSSL.DisableCNAME {
_ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "true")
} else {
_ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "false")
}
// lego v5 removed dns01.AddRecursiveNameservers and dns01.AddDNSTimeout;
// configure them via dns01.NewClient(&dns01.Options{...}) + SetDefaultClient.
dns01.SetDefaultClient(dns01.NewClient(&dns01.Options{
RecursiveNameservers: nameservers,
Timeout: dnsTimeOut,
}))
var opts []dns01.ChallengeOption
if websiteSSL.SkipDNS {
opts = append(opts, dns01.DisableAuthoritativeNssPropagationRequirement())
}
if err := c.Client.Challenge.SetDNS01Provider(p, opts...); err != nil {
c.releaseDNSLock()
return err
}
return nil
}
// releaseDNSLock releases dnsChallengeMu if it was acquired by UseDns.
// Safe to call multiple times; calls after the first one are no-ops.
func (c *AcmeClient) releaseDNSLock() {
if c.dnsChallengeLocked {
c.dnsChallengeLocked = false
dnsChallengeMu.Unlock()
c.dnsChallengeConfig = &dnsChallengeConfig{
recursiveNameservers: append([]string(nil), nameservers...),
timeout: dnsTimeOut,
disableCNAME: websiteSSL.DisableCNAME,
}
return nil
}
func (c *AcmeClient) UseHTTP(path string) error {
@@ -123,11 +123,14 @@ func (c *AcmeClient) UseHTTP(path string) error {
if err != nil {
return err
}
c.dnsChallengeConfig = nil
return nil
}
func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (certificate.Resource, error) {
defer c.releaseDNSLock()
func (c *AcmeClient) ObtainSSL(ctx context.Context, domains []string, privateKey crypto.Signer) (certificate.Resource, error) {
unlockDNSChallenge := c.lockDNSChallenge()
defer unlockDNSChallenge()
// lego v5 disables Common Name by default; explicitly enable it to keep
// the v4 behaviour, so legacy Java/router clients that still rely on the
// CommonName field do not fail TLS handshake.
@@ -138,8 +141,6 @@ func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (cert
EnableCommonName: true,
}
ctx := context.Background()
var certificates *certificate.Resource
var err error
@@ -152,7 +153,9 @@ func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (cert
if isHTTP503Error(err) && attempt < maxRetryAttempts {
global.LOG.Warnf("ACME server returned 503, retrying in %v (attempt %d/%d)",
retryDelayOn503, attempt, maxRetryAttempts)
time.Sleep(retryDelayOn503)
if err := waitForRetry(ctx, retryDelayOn503); err != nil {
return certificate.Resource{}, err
}
continue
}
@@ -163,8 +166,10 @@ func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (cert
return certificate.Resource{}, err
}
func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.Signer) (certificate.Resource, error) {
defer c.releaseDNSLock()
func (c *AcmeClient) ObtainIPSSL(ctx context.Context, ipAddress string, privKey crypto.Signer) (certificate.Resource, error) {
unlockDNSChallenge := c.lockDNSChallenge()
defer unlockDNSChallenge()
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: "",
@@ -192,8 +197,6 @@ func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.Signer) (certi
Bundle: true,
}
ctx := context.Background()
var certificates *certificate.Resource
for attempt := 1; attempt <= maxRetryAttempts; attempt++ {
certificates, err = c.Client.Certificate.ObtainForCSR(ctx, req)
@@ -204,7 +207,9 @@ func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.Signer) (certi
if isHTTP503Error(err) && attempt < maxRetryAttempts {
global.LOG.Warnf("ACME server returned 503 for IP SSL, retrying in %v (attempt %d/%d)",
retryDelayOn503, attempt, maxRetryAttempts)
time.Sleep(retryDelayOn503)
if err := waitForRetry(ctx, retryDelayOn503); err != nil {
return certificate.Resource{}, err
}
continue
}
@@ -217,3 +222,48 @@ func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.Signer) (certi
func (c *AcmeClient) RevokeSSL(pemSSL []byte) error {
return c.Client.Certificate.Revoke(context.Background(), pemSSL)
}
func (c *AcmeClient) lockDNSChallenge() func() {
if c.dnsChallengeConfig == nil {
return func() {}
}
dnsChallengeMu.Lock()
oldCNAME, hadCNAME := os.LookupEnv("LEGO_DISABLE_CNAME_SUPPORT")
if c.dnsChallengeConfig.disableCNAME {
_ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "true")
} else {
_ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "false")
}
previousClient := dns01.DefaultClient()
// lego v5 removed dns01.AddRecursiveNameservers and dns01.AddDNSTimeout;
// configure them via dns01.NewClient(&dns01.Options{...}) + SetDefaultClient.
dns01.SetDefaultClient(dns01.NewClient(&dns01.Options{
RecursiveNameservers: c.dnsChallengeConfig.recursiveNameservers,
Timeout: c.dnsChallengeConfig.timeout,
}))
return func() {
dns01.SetDefaultClient(previousClient)
if hadCNAME {
_ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", oldCNAME)
} else {
_ = os.Unsetenv("LEGO_DISABLE_CNAME_SUPPORT")
}
dnsChallengeMu.Unlock()
}
}
func waitForRetry(ctx context.Context, delay time.Duration) error {
timer := time.NewTimer(delay)
defer timer.Stop()
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
return nil
}
}
+90 -46
View File
@@ -3,6 +3,8 @@ package ssl
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"time"
"github.com/go-acme/lego/v5/challenge"
@@ -18,6 +20,8 @@ import (
"github.com/go-acme/lego/v5/providers/dns/freemyip"
"github.com/go-acme/lego/v5/providers/dns/godaddy"
"github.com/go-acme/lego/v5/providers/dns/huaweicloud"
"github.com/go-acme/lego/v5/providers/dns/ionos"
"github.com/go-acme/lego/v5/providers/dns/ionoscloud"
"github.com/go-acme/lego/v5/providers/dns/namecheap"
"github.com/go-acme/lego/v5/providers/dns/namedotcom"
"github.com/go-acme/lego/v5/providers/dns/namesilo"
@@ -35,34 +39,35 @@ import (
)
type DnsType string
const (
AcmeDNS DnsType = "AcmeDNS"
AliYun DnsType = "AliYun"
AliESA DnsType = "AliESA"
AWSRoute53 DnsType = "AWSRoute53"
CloudFlare DnsType = "CloudFlare"
BaiduCloud DnsType = "BaiduCloud"
CloudDns DnsType = "CloudDns"
NameSilo DnsType = "NameSilo"
CloudFlare DnsType = "CloudFlare"
ClouDNS DnsType = "ClouDNS"
Dynadot DnsType = "Dynadot"
Dynu DnsType = "Dynu"
FreeMyIP DnsType = "FreeMyIP"
Godaddy DnsType = "Godaddy"
HuaweiCloud DnsType = "HuaweiCloud"
Ionos DnsType = "Ionos"
IonosCloud DnsType = "IonosCloud"
NameCheap DnsType = "NameCheap"
NameCom DnsType = "NameCom"
Godaddy DnsType = "Godaddy"
TencentCloud DnsType = "TencentCloud"
RainYun DnsType = "RainYun"
Volcengine DnsType = "Volcengine"
HuaweiCloud DnsType = "HuaweiCloud"
FreeMyIP DnsType = "FreeMyIP"
Vercel DnsType = "Vercel"
Spaceship DnsType = "Spaceship"
WestCN DnsType = "WestCN"
ClouDNS DnsType = "ClouDNS"
RegRu DnsType = "RegRu"
Dynu DnsType = "Dynu"
Dynadot DnsType = "Dynadot"
BaiduCloud DnsType = "BaiduCloud"
NameSilo DnsType = "NameSilo"
Ovh DnsType = "Ovh"
AcmeDNS DnsType = "AcmeDNS"
PorkBun DnsType = "PorkBun"
RainYun DnsType = "RainYun"
RegRu DnsType = "RegRu"
AWSRoute53 DnsType = "AWSRoute53"
Spaceship DnsType = "Spaceship"
Technitium DnsType = "Technitium"
TencentCloud DnsType = "TencentCloud"
Vercel DnsType = "Vercel"
Volcengine DnsType = "Volcengine"
WestCN DnsType = "WestCN"
)
type DNSParam struct {
@@ -74,6 +79,7 @@ type DNSParam struct {
APIkey string `json:"apiKey"`
APIUser string `json:"apiUser"`
APISecret string `json:"apiSecret"`
APIPrefix string `json:"apiPrefix"`
SecretID string `json:"secretID"`
ClientID string `json:"clientID"`
Password string `json:"password"`
@@ -95,7 +101,7 @@ var (
manualDnsTimeout = 10 * time.Minute
)
func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, error) {
func getDNSProviderConfig(dnsType DnsType, params string, httpClient *http.Client) (challenge.Provider, error) {
var (
param DNSParam
p challenge.Provider
@@ -106,7 +112,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
}
switch dnsType {
case AliYun:
config := alidns.NewDefaultConfig()
config := newDNSProviderConfig(alidns.NewDefaultConfig(), httpClient)
config.SecretKey = param.SecretKey
config.APIKey = param.AccessKey
config.PropagationTimeout = propagationTimeout
@@ -114,7 +120,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = alidns.NewDNSProviderConfig(config)
case AliESA:
config := aliesa.NewDefaultConfig()
config := newDNSProviderConfig(aliesa.NewDefaultConfig(), httpClient)
config.SecretKey = param.SecretKey
config.APIKey = param.AccessKey
config.PropagationTimeout = propagationTimeout
@@ -122,7 +128,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = aliesa.NewDNSProviderConfig(config)
case AWSRoute53:
config := route53.NewDefaultConfig()
config := newDNSProviderConfig(route53.NewDefaultConfig(), httpClient)
config.AccessKeyID = param.AccessKey
config.SecretAccessKey = param.SecretKey
config.Region = param.Region
@@ -135,7 +141,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = route53.NewDNSProviderConfig(config)
case CloudFlare:
config := cloudflare.NewDefaultConfig()
config := newDNSProviderConfig(cloudflare.NewDefaultConfig(), httpClient)
config.AuthEmail = param.Email
config.AuthToken = param.APIkey
config.PropagationTimeout = propagationTimeout
@@ -143,7 +149,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = cloudflare.NewDNSProviderConfig(config)
case CloudDns:
config := clouddns.NewDefaultConfig()
config := newDNSProviderConfig(clouddns.NewDefaultConfig(), httpClient)
config.ClientID = param.ClientID
config.Email = param.Email
config.Password = param.Password
@@ -152,7 +158,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = clouddns.NewDNSProviderConfig(config)
case NameCheap:
config := namecheap.NewDefaultConfig()
config := newDNSProviderConfig(namecheap.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.APIUser = param.APIUser
config.PropagationTimeout = propagationTimeout
@@ -160,14 +166,14 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = namecheap.NewDNSProviderConfig(config)
case NameSilo:
config := namesilo.NewDefaultConfig()
config := newDNSProviderConfig(namesilo.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval
config.TTL = ttl
p, err = namesilo.NewDNSProviderConfig(config)
case Godaddy:
config := godaddy.NewDefaultConfig()
config := newDNSProviderConfig(godaddy.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.APISecret = param.APISecret
config.PropagationTimeout = propagationTimeout
@@ -175,7 +181,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = godaddy.NewDNSProviderConfig(config)
case NameCom:
config := namedotcom.NewDefaultConfig()
config := newDNSProviderConfig(namedotcom.NewDefaultConfig(), httpClient)
config.APIToken = param.Token
config.Username = param.APIUser
config.PropagationTimeout = propagationTimeout
@@ -183,7 +189,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = namedotcom.NewDNSProviderConfig(config)
case TencentCloud:
config := tencentcloud.NewDefaultConfig()
config := newDNSProviderConfig(tencentcloud.NewDefaultConfig(), httpClient)
config.SecretID = param.SecretID
config.SecretKey = param.SecretKey
config.PropagationTimeout = propagationTimeout
@@ -191,14 +197,14 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = tencentcloud.NewDNSProviderConfig(config)
case RainYun:
config := rainyun.NewDefaultConfig()
config := newDNSProviderConfig(rainyun.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval
config.TTL = ttl
p, err = rainyun.NewDNSProviderConfig(config)
case Volcengine:
config := volcengine.NewDefaultConfig()
config := newDNSProviderConfig(volcengine.NewDefaultConfig(), httpClient)
config.SecretKey = param.SecretKey
config.AccessKey = param.AccessKey
config.PropagationTimeout = propagationTimeout
@@ -206,7 +212,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = volcengine.NewDNSProviderConfig(config)
case HuaweiCloud:
config := huaweicloud.NewDefaultConfig()
config := newDNSProviderConfig(huaweicloud.NewDefaultConfig(), httpClient)
config.AccessKeyID = param.AccessKey
config.SecretAccessKey = param.SecretKey
config.Region = param.Region
@@ -217,20 +223,34 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.PollingInterval = pollingInterval
config.TTL = int32(ttl)
p, err = huaweicloud.NewDNSProviderConfig(config)
case Ionos:
config := newDNSProviderConfig(ionos.NewDefaultConfig(), httpClient)
config.APIKey = param.APIPrefix + "." + param.APISecret
config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval
config.TTL = ttl
p, err = ionos.NewDNSProviderConfig(config)
case IonosCloud:
config := newDNSProviderConfig(ionoscloud.NewDefaultConfig(), httpClient)
config.APIToken = param.Token
config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval
config.TTL = ttl
p, err = ionoscloud.NewDNSProviderConfig(config)
case FreeMyIP:
config := freemyip.NewDefaultConfig()
config := newDNSProviderConfig(freemyip.NewDefaultConfig(), httpClient)
config.Token = param.Token
config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval
p, err = freemyip.NewDNSProviderConfig(config)
case Vercel:
config := vercel.NewDefaultConfig()
config := newDNSProviderConfig(vercel.NewDefaultConfig(), httpClient)
config.AuthToken = param.Token
config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval
p, err = vercel.NewDNSProviderConfig(config)
case Spaceship:
config := spaceship.NewDefaultConfig()
config := newDNSProviderConfig(spaceship.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.APISecret = param.APISecret
config.PropagationTimeout = propagationTimeout
@@ -238,7 +258,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = spaceship.NewDNSProviderConfig(config)
case WestCN:
config := westcn.NewDefaultConfig()
config := newDNSProviderConfig(westcn.NewDefaultConfig(), httpClient)
config.Username = param.Username
config.Password = param.Password
config.PropagationTimeout = propagationTimeout
@@ -246,7 +266,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = westcn.NewDNSProviderConfig(config)
case ClouDNS:
config := cloudns.NewDefaultConfig()
config := newDNSProviderConfig(cloudns.NewDefaultConfig(), httpClient)
config.AuthID = param.AuthID
config.SubAuthID = param.SubAuthID
config.AuthPassword = param.AuthPassword
@@ -255,7 +275,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = cloudns.NewDNSProviderConfig(config)
case RegRu:
config := regru.NewDefaultConfig()
config := newDNSProviderConfig(regru.NewDefaultConfig(), httpClient)
config.Username = param.Username
config.Password = param.Password
config.PropagationTimeout = propagationTimeout
@@ -263,14 +283,14 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = regru.NewDNSProviderConfig(config)
case Dynu:
config := dynu.NewDefaultConfig()
config := newDNSProviderConfig(dynu.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval
config.TTL = ttl
p, err = dynu.NewDNSProviderConfig(config)
case Dynadot:
config := dynadot.NewDefaultConfig()
config := newDNSProviderConfig(dynadot.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.APISecret = param.APISecret
config.PropagationTimeout = propagationTimeout
@@ -278,7 +298,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = dynadot.NewDNSProviderConfig(config)
case BaiduCloud:
config := baiducloud.NewDefaultConfig()
config := newDNSProviderConfig(baiducloud.NewDefaultConfig(), httpClient)
config.AccessKeyID = param.AccessKey
config.SecretAccessKey = param.SecretKey
config.PropagationTimeout = propagationTimeout
@@ -286,7 +306,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = baiducloud.NewDNSProviderConfig(config)
case Ovh:
config := ovh.NewDefaultConfig()
config := newDNSProviderConfig(ovh.NewDefaultConfig(), httpClient)
config.APIEndpoint = param.Endpoint
config.AccessToken = param.AccessToken
config.PropagationTimeout = propagationTimeout
@@ -294,12 +314,12 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = ovh.NewDNSProviderConfig(config)
case AcmeDNS:
config := acmedns.NewDefaultConfig()
config := newDNSProviderConfig(acmedns.NewDefaultConfig(), httpClient)
config.APIBase = param.Endpoint
config.StorageBaseURL = param.BaseURL
p, err = acmedns.NewDNSProviderConfig(config)
case PorkBun:
config := porkbun.NewDefaultConfig()
config := newDNSProviderConfig(porkbun.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.SecretAPIKey = param.SecretKey
config.PropagationTimeout = propagationTimeout
@@ -307,7 +327,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = porkbun.NewDNSProviderConfig(config)
case Technitium:
config := technitium.NewDefaultConfig()
config := newDNSProviderConfig(technitium.NewDefaultConfig(), httpClient)
config.BaseURL = param.BaseURL
config.APIToken = param.Token
config.PropagationTimeout = propagationTimeout
@@ -331,3 +351,27 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
}
return p, nil
}
func newDNSProviderConfig[T any](config *T, httpClient *http.Client) *T {
setDNSProviderHTTPClient(config, httpClient)
return config
}
func setDNSProviderHTTPClient(config any, httpClient *http.Client) {
if config == nil || httpClient == nil {
return
}
value := reflect.ValueOf(config)
if value.Kind() != reflect.Ptr || value.IsNil() {
return
}
field := value.Elem().FieldByName("HTTPClient")
if !field.IsValid() || !field.CanSet() {
return
}
httpClientType := reflect.TypeOf((*http.Client)(nil))
if field.Type() != httpClientType {
return
}
field.Set(reflect.ValueOf(httpClient))
}
+60 -12
View File
@@ -10,14 +10,17 @@ import (
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"net"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/go-acme/lego/v5/certificate"
"github.com/miekg/dns"
"golang.org/x/crypto/acme"
"log"
"net"
"strings"
"time"
)
type ManualClient struct {
@@ -30,7 +33,34 @@ type RequestCertRequest struct {
WebsiteSSL *model.WebsiteSSL
}
func NewCustomAcmeClient(acmeAccount *model.WebsiteAcmeAccount, logger *log.Logger) (*ManualClient, error) {
var manualOrders sync.Map
func loadManualOrder(sslID uint) (*acme.Order, bool) {
value, ok := manualOrders.Load(sslID)
if !ok {
return nil, false
}
order, ok := value.(*acme.Order)
return order, ok && order != nil
}
func storeManualOrder(sslID uint, order *acme.Order) {
if order == nil {
manualOrders.Delete(sslID)
return
}
manualOrders.Store(sslID, order)
}
func deleteManualOrder(sslID uint, order *acme.Order) {
if order == nil {
manualOrders.Delete(sslID)
return
}
manualOrders.CompareAndDelete(sslID, order)
}
func NewCustomAcmeClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemProxy, logger *log.Logger) (*ManualClient, error) {
key, err := parsePrivateKeyPEM([]byte(acmeAccount.PrivateKey))
if err != nil {
return nil, err
@@ -38,9 +68,20 @@ func NewCustomAcmeClient(acmeAccount *model.WebsiteAcmeAccount, logger *log.Logg
if logger == nil {
logger = log.Default()
}
var (
proxyURL string
proxyUser string
proxyPassword string
)
if proxy != nil {
proxyURL = fmt.Sprintf("%s://%s:%s", proxy.Type, proxy.URL, proxy.Port)
proxyUser = proxy.User
proxyPassword = proxy.Password
}
client := &acme.Client{
Key: key,
HTTPClient: createHTTPClientWithProxy(proxyURL, proxyUser, proxyPassword),
DirectoryURL: getCaDirURL(acmeAccount.Type, acmeAccount.CaDirURL),
}
return &ManualClient{
@@ -61,7 +102,7 @@ func (c *ManualClient) GetDNSResolve(ctx context.Context, websiteSSL *model.Webs
var err error
// Check if we have an existing valid order for this SSL
existingOrder, exists := Orders[websiteSSL.ID]
existingOrder, exists := loadManualOrder(websiteSSL.ID)
if exists && existingOrder != nil {
// Verify the order is still valid (not expired and still pending)
// If Expires is zero, order is still valid (ACME doesn't always set expiry immediately)
@@ -75,7 +116,7 @@ func (c *ManualClient) GetDNSResolve(ctx context.Context, websiteSSL *model.Webs
// If extraction failed, fall through to create a new order
}
// Existing order is expired or invalid, remove it
delete(Orders, websiteSSL.ID)
deleteManualOrder(websiteSSL.ID, existingOrder)
}
// Create a new order
@@ -83,7 +124,7 @@ func (c *ManualClient) GetDNSResolve(ctx context.Context, websiteSSL *model.Webs
if err != nil {
return nil, err
}
Orders[websiteSSL.ID] = order
storeManualOrder(websiteSSL.ID, order)
return c.extractDNSChallenges(ctx, order)
}
@@ -178,6 +219,9 @@ func (c *ManualClient) handleAuthorization(ctx context.Context, authzURL string,
c.logger.Printf("[INFO] [%s] acme: Checking TXT record %s", domain, expectedRecord)
for {
if err := ctx.Err(); err != nil {
return err
}
c.logger.Printf("[INFO] [%s] acme: Checking DNS record propagation.", domain)
var currentRecords map[string][]string
var queryErr error
@@ -218,7 +262,9 @@ func (c *ManualClient) handleAuthorization(ctx context.Context, authzURL string,
c.logger.Printf("[INFO] [%s] TXT record mismatch for %s: expected %s, got %v", domain, domain, expectedRecord, providedRecords)
return fmt.Errorf("TXT record mismatch for %s: expected %s, got %v", domain, expectedRecord, providedRecords)
}
time.Sleep(pollingInterval)
if err := waitForRetry(ctx, pollingInterval); err != nil {
return err
}
}
_, err = c.client.Accept(ctx, dnsChallenge)
@@ -226,7 +272,9 @@ func (c *ManualClient) handleAuthorization(ctx context.Context, authzURL string,
return fmt.Errorf("failed to accept challenge: %v", err)
}
for {
time.Sleep(pollingInterval)
if err := waitForRetry(ctx, pollingInterval); err != nil {
return err
}
authz, err = c.client.GetAuthorization(ctx, authzURL)
if err != nil {
return fmt.Errorf("failed to get authorization while polling: %v", err)
@@ -299,11 +347,11 @@ func (c *ManualClient) RequestCertificate(ctx context.Context, websiteSSL *model
return res, err
}
order, ok := Orders[websiteSSL.ID]
order, ok := loadManualOrder(websiteSSL.ID)
if !ok {
return res, fmt.Errorf("order not found")
}
defer delete(Orders, websiteSSL.ID)
defer deleteManualOrder(websiteSSL.ID, order)
for _, authzURL := range order.AuthzURLs {
if err := c.handleAuthorization(ctx, authzURL, getNameservers(*websiteSSL)); err != nil {
+106 -3
View File
@@ -5,6 +5,8 @@ import (
"encoding/json"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"time"
@@ -250,8 +252,13 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
if sessions, ok := loadLoginctlSSHSessions(ctx); ok && len(sessions) > 0 {
res, err = json.Marshal(filterSSHSessions(sessions, config))
return
}
users, err = host.UsersWithContext(ctx)
if err != nil {
if err != nil || len(users) == 0 {
res, err = json.Marshal(result)
return
}
@@ -275,6 +282,22 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) {
return
}
connections, err := net.ConnectionsMaxWithContext(ctx, "all", 32768)
if err != nil {
res, err = json.Marshal(result)
return
}
pidConnections := make(map[int32][]net.ConnectionStat, 256)
for _, conn := range connections {
if conn.Pid == 0 || conn.Raddr.IP == "" {
continue
}
if _, ok := usersByHost[conn.Raddr.IP]; !ok {
continue
}
pidConnections[conn.Pid] = append(pidConnections[conn.Pid], conn)
}
processes, err = process.ProcessesWithContext(ctx)
if err != nil {
res, err = json.Marshal(result)
@@ -286,12 +309,12 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) {
if name != "sshd" || proc.Pid == 0 {
continue
}
connections, _ := proc.Connections()
connections := pidConnections[proc.Pid]
if len(connections) == 0 {
continue
}
cmdline, cmdErr := proc.Cmdline()
cmdline, cmdErr := proc.CmdlineWithContext(ctx)
if cmdErr != nil {
continue
}
@@ -320,6 +343,86 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) {
return
}
func loadLoginctlSSHSessions(ctx context.Context) ([]sshSession, bool) {
if _, err := exec.LookPath("loginctl"); err != nil {
return nil, false
}
output, err := exec.CommandContext(ctx, "loginctl", "list-sessions", "--no-legend", "--no-pager").Output()
if err != nil {
return nil, false
}
var result []sshSession
for _, line := range strings.Split(string(output), "\n") {
fields := strings.Fields(line)
if len(fields) == 0 {
continue
}
sessionOutput, err := exec.CommandContext(ctx, "loginctl", "show-session", fields[0], "--no-pager",
"-p", "Name", "-p", "Remote", "-p", "RemoteHost", "-p", "TTY", "-p", "Timestamp", "-p", "Leader", "-p", "Service", "-p", "State").Output()
if err != nil {
continue
}
session, ok := parseLoginctlSSHSession(string(sessionOutput))
if !ok {
continue
}
result = append(result, session)
}
return result, true
}
func filterSSHSessions(sessions []sshSession, config SSHSessionConfig) []sshSession {
result := make([]sshSession, 0, len(sessions))
for _, session := range sessions {
if config.LoginUser != "" && !strings.Contains(session.Username, config.LoginUser) {
continue
}
if config.LoginIP != "" && !strings.Contains(session.Host, config.LoginIP) {
continue
}
result = append(result, session)
}
return result
}
func parseLoginctlSSHSession(output string) (sshSession, bool) {
props := map[string]string{}
for _, line := range strings.Split(output, "\n") {
key, value, ok := strings.Cut(line, "=")
if ok {
props[key] = strings.TrimSpace(value)
}
}
service := props["Service"]
if props["Remote"] != "yes" || props["Name"] == "" || props["RemoteHost"] == "" || props["State"] != "active" || (service != "sshd" && service != "ssh") {
return sshSession{}, false
}
pid, _ := strconv.ParseInt(props["Leader"], 10, 32)
return sshSession{
Username: props["Name"],
Host: props["RemoteHost"],
Terminal: props["TTY"],
PID: int32(pid),
LoginTime: parseLoginctlTimestamp(props["Timestamp"]),
}, true
}
func parseLoginctlTimestamp(value string) string {
fields := strings.Fields(value)
for i := 0; i < len(fields)-1; i++ {
if strings.Count(fields[i], "-") != 2 || strings.Count(fields[i+1], ":") != 2 {
continue
}
candidate := fields[i] + " " + fields[i+1]
t, err := time.ParseInLocation("2006-01-02 15:04:05", candidate, time.Local)
if err != nil {
return candidate
}
return t.Format("2006-1-2 15:04:05")
}
return value
}
func getNetConnections(config NetConfig) (res []byte, err error) {
result := make([]ProcessConnect, 0, 1024)
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
+11 -10
View File
@@ -248,16 +248,17 @@ func (b *BaseApi) GetLoginSetting(c *gin.Context) {
ip := common.GetRealClientIP(c)
needCaptcha := global.IPTracker.NeedCaptcha(ip)
res := &dto.LoginSetting{
IsDemo: global.CONF.Base.IsDemo,
IsIntl: global.CONF.Base.Edition == "intl",
IsFxplay: global.CONF.Base.IsFxplay,
IsOffline: global.CONF.Base.IsOffline,
IsEnterprise: global.CONF.Base.IsEnterprise,
Language: settingInfo.Language,
MenuTabs: settingInfo.MenuTabs,
PanelName: settingInfo.PanelName,
Theme: settingInfo.Theme,
NeedCaptcha: needCaptcha,
IsDemo: global.CONF.Base.IsDemo,
IsIntl: global.CONF.Base.Edition == "intl",
IsFxplay: global.CONF.Base.IsFxplay,
IsOffline: global.CONF.Base.IsOffline,
IsEnterprise: global.CONF.Base.IsEnterprise,
Language: settingInfo.Language,
MenuTabs: settingInfo.MenuTabs,
MenuAccordion: settingInfo.MenuAccordion,
PanelName: settingInfo.PanelName,
Theme: settingInfo.Theme,
NeedCaptcha: needCaptcha,
}
res.PasskeySetting = xpack.AuthProvider.PasskeyStatus(c)
helper.SuccessWithData(c, res)
+2 -1
View File
@@ -3,6 +3,7 @@ package v2
import (
"encoding/base64"
"errors"
"net"
"net/http"
"os"
"path"
@@ -350,7 +351,7 @@ func (b *BaseApi) UpdatePort(c *gin.Context) {
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"重载系统 SSL","formatEN":"reload system SSL"}
func (b *BaseApi) ReloadSSL(c *gin.Context) {
clientIP := c.ClientIP()
if clientIP != "127.0.0.1" {
if ip := net.ParseIP(clientIP); ip == nil || !ip.IsLoopback() {
helper.InternalServer(c, errors.New("only localhost can reload ssl"))
return
}
+51 -3
View File
@@ -1,8 +1,11 @@
package auth
import (
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/hex"
"errors"
"net"
"strconv"
"strings"
@@ -10,6 +13,7 @@ import (
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/utils/common"
@@ -42,6 +46,11 @@ func APIAuthMiddleware(loadConfig APIAuthConfigLoader, onSuccess APIAuthSuccessH
config, err := loadConfig(c)
if err != nil {
var bizErr buserr.BusinessError
if errors.As(err, &bizErr) && strings.HasPrefix(bizErr.Msg, "ErrApiConfig") {
helper.BadAuth(c, bizErr.Msg, bizErr.Err)
return
}
helper.InternalServer(c, err)
return
}
@@ -93,7 +102,7 @@ func LoadAPIAuthConfig(_ *gin.Context) (APIAuthConfig, error) {
return config, nil
}
func isValid1PanelTimestamp(panelTimestamp string, apiKeyValidityTime int) bool {
func IsValid1PanelTimestamp(panelTimestamp string, apiKeyValidityTime int) bool {
apiTime := apiKeyValidityTime
if apiTime < 0 {
return false
@@ -115,11 +124,32 @@ func isValid1PanelTimestamp(panelTimestamp string, apiKeyValidityTime int) bool
return nowTime-panelTime <= int64(apiTime)*60+tolerance
}
func isValid1PanelToken(panelToken string, panelTimestamp string, apiKey string) bool {
func IsValid1PanelToken(panelToken string, panelTimestamp string, apiKey string) bool {
return IsValid1PanelTokenWithVersion(panelToken, panelTimestamp, apiKey, "")
}
func IsValid1PanelTokenWithVersion(panelToken string, panelTimestamp string, apiKey string, signatureVersion string) bool {
panelToken = strings.ToLower(strings.TrimSpace(panelToken))
version := strings.ToLower(strings.TrimSpace(signatureVersion))
switch version {
case "v1", "md5":
return isValidMD5Token(panelToken, panelTimestamp, apiKey)
case "hmac-sha256":
return isValidHMACSHA256Token(panelToken, panelTimestamp, apiKey)
default:
return isValidMD5Token(panelToken, panelTimestamp, apiKey) || isValidHMACSHA256Token(panelToken, panelTimestamp, apiKey)
}
}
func isValidMD5Token(panelToken string, panelTimestamp string, apiKey string) bool {
return panelToken == GenerateMD5("1panel"+apiKey+panelTimestamp)
}
func isIPInWhiteList(clientIP string, ipWhiteString string) bool {
func isValidHMACSHA256Token(panelToken string, panelTimestamp string, apiKey string) bool {
return panelToken == GenerateHMACSHA256(apiKey, "1panel:"+panelTimestamp)
}
func IsIPInWhiteList(clientIP string, ipWhiteString string) bool {
if strings.TrimSpace(ipWhiteString) == "" {
global.LOG.Error("IP whitelist is empty")
return false
@@ -158,8 +188,26 @@ func isIPInWhiteList(clientIP string, ipWhiteString string) bool {
return false
}
func isValid1PanelTimestamp(panelTimestamp string, apiKeyValidityTime int) bool {
return IsValid1PanelTimestamp(panelTimestamp, apiKeyValidityTime)
}
func isValid1PanelToken(panelToken string, panelTimestamp string, apiKey string) bool {
return IsValid1PanelToken(panelToken, panelTimestamp, apiKey)
}
func isIPInWhiteList(clientIP string, ipWhiteString string) bool {
return IsIPInWhiteList(clientIP, ipWhiteString)
}
func GenerateMD5(param string) string {
hash := md5.New()
hash.Write([]byte(param))
return hex.EncodeToString(hash.Sum(nil))
}
func GenerateHMACSHA256(secret string, message string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(message))
return hex.EncodeToString(mac.Sum(nil))
}
+1 -1
View File
@@ -23,7 +23,7 @@ type Login struct {
Password string `json:"password" validate:"required"`
Captcha string `json:"captcha"`
CaptchaID string `json:"captchaID"`
Language string `json:"language" validate:"required,oneof=zh en 'zh-Hant' ko ja ru ms 'pt-BR' tr 'es-ES'"`
Language string `json:"language" validate:"required,oneof=zh en 'zh-Hant' ko ja ru ms 'pt-BR' tr 'es-ES' fa"`
}
type SystemSetting struct {
+15 -11
View File
@@ -17,6 +17,7 @@ type SettingInfo struct {
Edition string `json:"edition"`
Theme string `json:"theme"`
MenuTabs string `json:"menuTabs"`
MenuAccordion string `json:"menuAccordion"`
Language string `json:"language"`
DocSource string `json:"docSource"`
@@ -52,16 +53,17 @@ type SettingBaseInfo struct {
DeveloperMode string `json:"developerMode"`
UpgradeBackupCopies string `json:"upgradeBackupCopies"`
Port string `json:"port"`
Ipv6 string `json:"ipv6"`
BindAddress string `json:"bindAddress"`
PanelName string `json:"panelName"`
Edition string `json:"edition"`
Theme string `json:"theme"`
MenuTabs string `json:"menuTabs"`
Language string `json:"language"`
HideMenu string `json:"hideMenu"`
DocSource string `json:"docSource"`
Port string `json:"port"`
Ipv6 string `json:"ipv6"`
BindAddress string `json:"bindAddress"`
PanelName string `json:"panelName"`
Edition string `json:"edition"`
Theme string `json:"theme"`
MenuTabs string `json:"menuTabs"`
MenuAccordion string `json:"menuAccordion"`
Language string `json:"language"`
HideMenu string `json:"hideMenu"`
DocSource string `json:"docSource"`
ServerPort string `json:"serverPort"`
SecurityEntrance string `json:"securityEntrance"`
@@ -246,12 +248,13 @@ type TerminalInfo struct {
}
type AppstoreUpdate struct {
Scope string `json:"scope" validate:"required,oneof=UninstallDeleteImage UpgradeBackup UninstallDeleteBackup InstallAllowPort"`
Scope string `json:"scope" validate:"required,oneof=UninstallDeleteImage UpgradeBackup UpgradeDeleteImage UninstallDeleteBackup InstallAllowPort"`
Status string `json:"status" validate:"required,oneof=Disable Enable"`
}
type AppstoreConfig struct {
UninstallDeleteImage string `json:"uninstallDeleteImage"`
UpgradeBackup string `json:"upgradeBackup"`
UpgradeDeleteImage string `json:"upgradeDeleteImage"`
UninstallDeleteBackup string `json:"uninstallDeleteBackup"`
InstallAllowPort string `json:"installAllowPort"`
}
@@ -264,6 +267,7 @@ type LoginSetting struct {
IsEnterprise bool `json:"isEnterprise"`
Language string `json:"language"`
MenuTabs string `json:"menuTabs"`
MenuAccordion string `json:"menuAccordion"`
PanelName string `json:"panelName"`
Theme string `json:"theme"`
NeedCaptcha bool `json:"needCaptcha"`
+8
View File
@@ -43,6 +43,14 @@ func WithByName(name string) global.DBOption {
return g.Where("`name` = ?", name)
}
}
func WithByLikeName(name string) global.DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(name) == 0 {
return g
}
return g.Where("name like ?", "%"+name+"%")
}
}
func WithByUserID(userID string) global.DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("user_id = ?", userID)
+13 -1
View File
@@ -101,6 +101,10 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
info.Edition = "cn"
_ = settingRepo.UpdateOrCreate("Edition", info.Edition)
}
if info.MenuAccordion == "" {
info.MenuAccordion = constant.StatusDisable
_ = settingRepo.UpdateOrCreate("MenuAccordion", info.MenuAccordion)
}
if info.ProxyPasswdKeep != constant.StatusEnable {
info.ProxyPasswd = ""
} else {
@@ -140,6 +144,10 @@ func (u *SettingService) GetSettingBaseInfo() (*dto.SettingBaseInfo, error) {
info.Edition = "cn"
_ = settingRepo.UpdateOrCreate("Edition", info.Edition)
}
if info.MenuAccordion == "" {
info.MenuAccordion = constant.StatusDisable
_ = settingRepo.UpdateOrCreate("MenuAccordion", info.MenuAccordion)
}
return &info, err
}
@@ -704,6 +712,10 @@ func (u *SettingService) GetAppstoreConfig() (*dto.AppstoreConfig, error) {
if res.UpgradeBackup == "" {
res.UpgradeBackup = constant.StatusDisable
}
res.UpgradeDeleteImage, _ = settingRepo.GetValueByKey("UpgradeDeleteImage")
if res.UpgradeDeleteImage == "" {
res.UpgradeDeleteImage = constant.StatusDisable
}
res.UninstallDeleteBackup, _ = settingRepo.GetValueByKey("UninstallDeleteBackup")
if res.UninstallDeleteBackup == "" {
res.UninstallDeleteBackup = constant.StatusDisable
@@ -762,7 +774,7 @@ func checkProxy(req dto.ProxyUpdate) error {
case "", "close":
return nil
default:
return buserr.WithDetail("ErrNotSupportType", req.ProxyType, nil)
return buserr.WithName("ErrNotSupportType", req.ProxyType)
}
defer func() {
if r := recover(); r != nil {
+5
View File
@@ -49,6 +49,9 @@ type SubTask struct {
const (
TaskInstall = "TaskInstall"
TaskCreate = "TaskCreate"
TaskUpdate = "TaskUpdate"
TaskDelete = "TaskDelete"
TaskUpgrade = "TaskUpgrade"
TaskAddNode = "TaskAddNode"
TaskSync = "TaskSync"
@@ -65,10 +68,12 @@ const (
TaskScopeSystem = "System"
TaskScopeScript = "ScriptLibrary"
TaskScopeNodeFile = "NodeFile"
TaskScopeNodeSSL = "NodeSSL"
TaskScopeAppBackup = "AppBackup"
TaskScopeCluster = "Cluster"
TaskScopeAppInstall = "AppInstallTask"
TaskScopeAI = "AI"
TaskScopeVm = "VirtualMachine"
)
func GetTaskName(resourceName, operate, scope string) string {
+359 -3
View File
@@ -74,6 +74,7 @@ const docTemplate = `{
"description": "OK",
"schema": {
"additionalProperties": {
"format": "int64",
"type": "integer"
},
"type": "object"
@@ -2883,6 +2884,44 @@ const docTemplate = `{
]
}
},
"/ai/mcp/server/connection/test": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/request.McpServerConnectionTest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.McpServerConnectionTestRes"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Test mcp server connection",
"tags": [
"McpServer"
]
}
},
"/ai/mcp/server/del": {
"post": {
"consumes": [
@@ -2918,6 +2957,44 @@ const docTemplate = `{
]
}
},
"/ai/mcp/server/detail": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/request.McpServerDetail"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.McpServerDTO"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Load mcp server detail",
"tags": [
"McpServer"
]
}
},
"/ai/mcp/server/op": {
"post": {
"consumes": [
@@ -2953,6 +3030,47 @@ const docTemplate = `{
]
}
},
"/ai/mcp/server/status/sync": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/request.McpServerStatusSync"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"items": {
"$ref": "#/definitions/response.McpServerStatusDTO"
},
"type": "array"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Sync mcp server status",
"tags": [
"McpServer"
]
}
},
"/ai/mcp/server/update": {
"post": {
"consumes": [
@@ -27588,6 +27706,59 @@ const docTemplate = `{
}
}
},
"/websites/ssl/push": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteSSLPush"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Push ssl to nodes",
"tags": [
"Website SSL"
],
"x-panel-log": {
"BeforeFunctions": [
{
"db": "website_ssls",
"input_column": "id",
"input_value": "id",
"isList": false,
"output_column": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Push ssl to nodes [domain]",
"formatZH": "推送证书到节点 [domain]",
"paramKeys": []
}
}
},
"/websites/ssl/resolve": {
"post": {
"consumes": [
@@ -28347,6 +28518,12 @@ const docTemplate = `{
"cpuQuota": {
"type": "number"
},
"dashboardPassword": {
"type": "string"
},
"dashboardUsername": {
"type": "string"
},
"dockerCompose": {
"type": "string"
},
@@ -28720,6 +28897,12 @@ const docTemplate = `{
"cpuQuota": {
"type": "number"
},
"dashboardPassword": {
"type": "string"
},
"dashboardUsername": {
"type": "string"
},
"dockerCompose": {
"type": "string"
},
@@ -29294,6 +29477,12 @@ const docTemplate = `{
"createdAt": {
"type": "string"
},
"dashboardPassword": {
"type": "string"
},
"dashboardUsername": {
"type": "string"
},
"id": {
"type": "integer"
},
@@ -29395,6 +29584,12 @@ const docTemplate = `{
"browserEnabled": {
"type": "boolean"
},
"dashboardPassword": {
"type": "string"
},
"dashboardUsername": {
"type": "string"
},
"npmRegistry": {
"type": "string"
},
@@ -29412,6 +29607,12 @@ const docTemplate = `{
"browserEnabled": {
"type": "boolean"
},
"dashboardPassword": {
"type": "string"
},
"dashboardUsername": {
"type": "string"
},
"npmRegistry": {
"type": "string"
},
@@ -30876,6 +31077,9 @@ const docTemplate = `{
},
"upgradeBackup": {
"type": "string"
},
"upgradeDeleteImage": {
"type": "string"
}
},
"type": "object"
@@ -30886,6 +31090,7 @@ const docTemplate = `{
"enum": [
"UninstallDeleteImage",
"UpgradeBackup",
"UpgradeDeleteImage",
"UninstallDeleteBackup",
"InstallAllowPort"
],
@@ -31531,6 +31736,9 @@ const docTemplate = `{
"fileName": {
"type": "string"
},
"isImmediate": {
"type": "boolean"
},
"name": {
"type": "string"
},
@@ -31713,7 +31921,8 @@ const docTemplate = `{
"restart",
"stop",
"down",
"delete"
"delete",
"rebuild"
],
"type": "string"
},
@@ -34458,6 +34667,9 @@ const docTemplate = `{
"es-es": {
"type": "string"
},
"fa": {
"type": "string"
},
"ja": {
"type": "string"
},
@@ -34515,7 +34727,8 @@ const docTemplate = `{
"ms",
"pt-BR",
"tr",
"es-ES"
"es-ES",
"fa"
],
"type": "string"
},
@@ -39396,6 +39609,9 @@ const docTemplate = `{
},
"hostPort": {
"type": "integer"
},
"protocol": {
"type": "string"
}
},
"type": "object"
@@ -40282,6 +40498,17 @@ const docTemplate = `{
],
"type": "object"
},
"request.McpServerConnectionTest": {
"properties": {
"id": {
"type": "integer"
}
},
"required": [
"id"
],
"type": "object"
},
"request.McpServerCreate": {
"properties": {
"baseUrl": {
@@ -40299,6 +40526,9 @@ const docTemplate = `{
},
"type": "array"
},
"gatewayImage": {
"type": "string"
},
"hostIP": {
"type": "string"
},
@@ -40311,12 +40541,18 @@ const docTemplate = `{
"port": {
"type": "integer"
},
"protocolVersion": {
"type": "string"
},
"ssePath": {
"type": "string"
},
"streamableHttpPath": {
"type": "string"
},
"taskID": {
"type": "string"
},
"type": {
"type": "string"
},
@@ -40347,6 +40583,17 @@ const docTemplate = `{
],
"type": "object"
},
"request.McpServerDetail": {
"properties": {
"id": {
"type": "integer"
}
},
"required": [
"id"
],
"type": "object"
},
"request.McpServerOperate": {
"properties": {
"id": {
@@ -40383,6 +40630,17 @@ const docTemplate = `{
],
"type": "object"
},
"request.McpServerStatusSync": {
"properties": {
"ids": {
"items": {
"type": "integer"
},
"type": "array"
}
},
"type": "object"
},
"request.McpServerUpdate": {
"properties": {
"baseUrl": {
@@ -40400,6 +40658,9 @@ const docTemplate = `{
},
"type": "array"
},
"gatewayImage": {
"type": "string"
},
"hostIP": {
"type": "string"
},
@@ -40415,12 +40676,18 @@ const docTemplate = `{
"port": {
"type": "integer"
},
"protocolVersion": {
"type": "string"
},
"ssePath": {
"type": "string"
},
"streamableHttpPath": {
"type": "string"
},
"taskID": {
"type": "string"
},
"type": {
"type": "string"
},
@@ -41281,6 +41548,9 @@ const docTemplate = `{
"source": {
"type": "string"
},
"taskID": {
"type": "string"
},
"type": {
"type": "string"
},
@@ -41298,11 +41568,17 @@ const docTemplate = `{
},
"request.RuntimeDelete": {
"properties": {
"deleteImage": {
"type": "boolean"
},
"forceDelete": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"taskID": {
"type": "string"
}
},
"type": "object"
@@ -41691,6 +41967,9 @@ const docTemplate = `{
},
"request.Volume": {
"properties": {
"mode": {
"type": "string"
},
"source": {
"type": "string"
},
@@ -41852,9 +42131,15 @@ const docTemplate = `{
],
"type": "string"
},
"nodes": {
"type": "string"
},
"pushDir": {
"type": "boolean"
},
"pushNode": {
"type": "boolean"
},
"renew": {
"type": "boolean"
},
@@ -42726,6 +43011,30 @@ const docTemplate = `{
},
"type": "object"
},
"request.WebsiteSSLPush": {
"properties": {
"id": {
"type": "integer"
},
"nodes": {
"type": "string"
},
"pushNode": {
"type": "boolean"
},
"sync": {
"type": "boolean"
},
"taskID": {
"type": "string"
}
},
"required": [
"id",
"taskID"
],
"type": "object"
},
"request.WebsiteSSLSearch": {
"properties": {
"acmeAccountID": {
@@ -42745,6 +43054,7 @@ const docTemplate = `{
"orderBy": {
"enum": [
"created_at",
"updated_at",
"expire_date"
],
"type": "string"
@@ -42843,12 +43153,18 @@ const docTemplate = `{
"description": {
"type": "string"
},
"nodes": {
"type": "string"
},
"privateKey": {
"type": "string"
},
"privateKeyPath": {
"type": "string"
},
"pushNode": {
"type": "boolean"
},
"sslID": {
"type": "integer"
},
@@ -43976,6 +44292,26 @@ const docTemplate = `{
},
"type": "object"
},
"response.McpServerConnectionTestRes": {
"properties": {
"endpoint": {
"type": "string"
},
"message": {
"type": "string"
},
"outputTransport": {
"type": "string"
},
"protocolVersion": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"type": "object"
},
"response.McpServerDTO": {
"properties": {
"baseUrl": {
@@ -44005,6 +44341,9 @@ const docTemplate = `{
},
"type": "array"
},
"gatewayImage": {
"type": "string"
},
"hostIP": {
"type": "string"
},
@@ -44023,6 +44362,9 @@ const docTemplate = `{
"port": {
"type": "integer"
},
"protocolVersion": {
"type": "string"
},
"ssePath": {
"type": "string"
},
@@ -44050,6 +44392,20 @@ const docTemplate = `{
},
"type": "object"
},
"response.McpServerStatusDTO": {
"properties": {
"id": {
"type": "integer"
},
"message": {
"type": "string"
},
"status": {
"type": "string"
}
},
"type": "object"
},
"response.McpServersRes": {
"properties": {
"items": {
@@ -45145,4 +45501,4 @@ var SwaggerInfo = &swag.Spec{
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}
}
+358 -2
View File
@@ -70,6 +70,7 @@
"description": "OK",
"schema": {
"additionalProperties": {
"format": "int64",
"type": "integer"
},
"type": "object"
@@ -2879,6 +2880,44 @@
]
}
},
"/ai/mcp/server/connection/test": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/request.McpServerConnectionTest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.McpServerConnectionTestRes"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Test mcp server connection",
"tags": [
"McpServer"
]
}
},
"/ai/mcp/server/del": {
"post": {
"consumes": [
@@ -2914,6 +2953,44 @@
]
}
},
"/ai/mcp/server/detail": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/request.McpServerDetail"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.McpServerDTO"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Load mcp server detail",
"tags": [
"McpServer"
]
}
},
"/ai/mcp/server/op": {
"post": {
"consumes": [
@@ -2949,6 +3026,47 @@
]
}
},
"/ai/mcp/server/status/sync": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/request.McpServerStatusSync"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"items": {
"$ref": "#/definitions/response.McpServerStatusDTO"
},
"type": "array"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Sync mcp server status",
"tags": [
"McpServer"
]
}
},
"/ai/mcp/server/update": {
"post": {
"consumes": [
@@ -27584,6 +27702,59 @@
}
}
},
"/websites/ssl/push": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteSSLPush"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Push ssl to nodes",
"tags": [
"Website SSL"
],
"x-panel-log": {
"BeforeFunctions": [
{
"db": "website_ssls",
"input_column": "id",
"input_value": "id",
"isList": false,
"output_column": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Push ssl to nodes [domain]",
"formatZH": "推送证书到节点 [domain]",
"paramKeys": []
}
}
},
"/websites/ssl/resolve": {
"post": {
"consumes": [
@@ -28343,6 +28514,12 @@
"cpuQuota": {
"type": "number"
},
"dashboardPassword": {
"type": "string"
},
"dashboardUsername": {
"type": "string"
},
"dockerCompose": {
"type": "string"
},
@@ -28716,6 +28893,12 @@
"cpuQuota": {
"type": "number"
},
"dashboardPassword": {
"type": "string"
},
"dashboardUsername": {
"type": "string"
},
"dockerCompose": {
"type": "string"
},
@@ -29290,6 +29473,12 @@
"createdAt": {
"type": "string"
},
"dashboardPassword": {
"type": "string"
},
"dashboardUsername": {
"type": "string"
},
"id": {
"type": "integer"
},
@@ -29391,6 +29580,12 @@
"browserEnabled": {
"type": "boolean"
},
"dashboardPassword": {
"type": "string"
},
"dashboardUsername": {
"type": "string"
},
"npmRegistry": {
"type": "string"
},
@@ -29408,6 +29603,12 @@
"browserEnabled": {
"type": "boolean"
},
"dashboardPassword": {
"type": "string"
},
"dashboardUsername": {
"type": "string"
},
"npmRegistry": {
"type": "string"
},
@@ -30872,6 +31073,9 @@
},
"upgradeBackup": {
"type": "string"
},
"upgradeDeleteImage": {
"type": "string"
}
},
"type": "object"
@@ -30882,6 +31086,7 @@
"enum": [
"UninstallDeleteImage",
"UpgradeBackup",
"UpgradeDeleteImage",
"UninstallDeleteBackup",
"InstallAllowPort"
],
@@ -31527,6 +31732,9 @@
"fileName": {
"type": "string"
},
"isImmediate": {
"type": "boolean"
},
"name": {
"type": "string"
},
@@ -31709,7 +31917,8 @@
"restart",
"stop",
"down",
"delete"
"delete",
"rebuild"
],
"type": "string"
},
@@ -34454,6 +34663,9 @@
"es-es": {
"type": "string"
},
"fa": {
"type": "string"
},
"ja": {
"type": "string"
},
@@ -34511,7 +34723,8 @@
"ms",
"pt-BR",
"tr",
"es-ES"
"es-ES",
"fa"
],
"type": "string"
},
@@ -39392,6 +39605,9 @@
},
"hostPort": {
"type": "integer"
},
"protocol": {
"type": "string"
}
},
"type": "object"
@@ -40278,6 +40494,17 @@
],
"type": "object"
},
"request.McpServerConnectionTest": {
"properties": {
"id": {
"type": "integer"
}
},
"required": [
"id"
],
"type": "object"
},
"request.McpServerCreate": {
"properties": {
"baseUrl": {
@@ -40295,6 +40522,9 @@
},
"type": "array"
},
"gatewayImage": {
"type": "string"
},
"hostIP": {
"type": "string"
},
@@ -40307,12 +40537,18 @@
"port": {
"type": "integer"
},
"protocolVersion": {
"type": "string"
},
"ssePath": {
"type": "string"
},
"streamableHttpPath": {
"type": "string"
},
"taskID": {
"type": "string"
},
"type": {
"type": "string"
},
@@ -40343,6 +40579,17 @@
],
"type": "object"
},
"request.McpServerDetail": {
"properties": {
"id": {
"type": "integer"
}
},
"required": [
"id"
],
"type": "object"
},
"request.McpServerOperate": {
"properties": {
"id": {
@@ -40379,6 +40626,17 @@
],
"type": "object"
},
"request.McpServerStatusSync": {
"properties": {
"ids": {
"items": {
"type": "integer"
},
"type": "array"
}
},
"type": "object"
},
"request.McpServerUpdate": {
"properties": {
"baseUrl": {
@@ -40396,6 +40654,9 @@
},
"type": "array"
},
"gatewayImage": {
"type": "string"
},
"hostIP": {
"type": "string"
},
@@ -40411,12 +40672,18 @@
"port": {
"type": "integer"
},
"protocolVersion": {
"type": "string"
},
"ssePath": {
"type": "string"
},
"streamableHttpPath": {
"type": "string"
},
"taskID": {
"type": "string"
},
"type": {
"type": "string"
},
@@ -41277,6 +41544,9 @@
"source": {
"type": "string"
},
"taskID": {
"type": "string"
},
"type": {
"type": "string"
},
@@ -41294,11 +41564,17 @@
},
"request.RuntimeDelete": {
"properties": {
"deleteImage": {
"type": "boolean"
},
"forceDelete": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"taskID": {
"type": "string"
}
},
"type": "object"
@@ -41687,6 +41963,9 @@
},
"request.Volume": {
"properties": {
"mode": {
"type": "string"
},
"source": {
"type": "string"
},
@@ -41848,9 +42127,15 @@
],
"type": "string"
},
"nodes": {
"type": "string"
},
"pushDir": {
"type": "boolean"
},
"pushNode": {
"type": "boolean"
},
"renew": {
"type": "boolean"
},
@@ -42722,6 +43007,30 @@
},
"type": "object"
},
"request.WebsiteSSLPush": {
"properties": {
"id": {
"type": "integer"
},
"nodes": {
"type": "string"
},
"pushNode": {
"type": "boolean"
},
"sync": {
"type": "boolean"
},
"taskID": {
"type": "string"
}
},
"required": [
"id",
"taskID"
],
"type": "object"
},
"request.WebsiteSSLSearch": {
"properties": {
"acmeAccountID": {
@@ -42741,6 +43050,7 @@
"orderBy": {
"enum": [
"created_at",
"updated_at",
"expire_date"
],
"type": "string"
@@ -42839,12 +43149,18 @@
"description": {
"type": "string"
},
"nodes": {
"type": "string"
},
"privateKey": {
"type": "string"
},
"privateKeyPath": {
"type": "string"
},
"pushNode": {
"type": "boolean"
},
"sslID": {
"type": "integer"
},
@@ -43972,6 +44288,26 @@
},
"type": "object"
},
"response.McpServerConnectionTestRes": {
"properties": {
"endpoint": {
"type": "string"
},
"message": {
"type": "string"
},
"outputTransport": {
"type": "string"
},
"protocolVersion": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"type": "object"
},
"response.McpServerDTO": {
"properties": {
"baseUrl": {
@@ -44001,6 +44337,9 @@
},
"type": "array"
},
"gatewayImage": {
"type": "string"
},
"hostIP": {
"type": "string"
},
@@ -44019,6 +44358,9 @@
"port": {
"type": "integer"
},
"protocolVersion": {
"type": "string"
},
"ssePath": {
"type": "string"
},
@@ -44046,6 +44388,20 @@
},
"type": "object"
},
"response.McpServerStatusDTO": {
"properties": {
"id": {
"type": "integer"
},
"message": {
"type": "string"
},
"status": {
"type": "string"
}
},
"type": "object"
},
"response.McpServersRes": {
"properties": {
"items": {
+358 -53
View File
@@ -1247,6 +1247,33 @@
"formatZH": "更新 AI 网关分组 [name]",
"formatEN": "update AI proxy group [name]"
},
"/core/enterprise/ai-proxy/model-groups/create": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建 AI 网关模型组 [name]",
"formatEN": "create AI proxy model group [name]"
},
"/core/enterprise/ai-proxy/model-groups/delete": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除 AI 网关模型组 [id]",
"formatEN": "delete AI proxy model group [id]"
},
"/core/enterprise/ai-proxy/model-groups/update": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新 AI 网关模型组 [name]",
"formatEN": "update AI proxy model group [name]"
},
"/core/enterprise/ai-proxy/service/operate": {
"bodyKeys": [
"operate"
@@ -1368,13 +1395,14 @@
"/core/enterprise/skills-hub/import": {
"bodyKeys": [
"type",
"version",
"repoURL",
"packageURL"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "导入 Skills Hub 技能 [type]",
"formatEN": "import Skills Hub skill [type]"
"formatZH": "导入 Skills Hub 技能版本 [version]",
"formatEN": "import Skills Hub skill version [version]"
},
"/core/enterprise/skills-hub/status": {
"bodyKeys": [
@@ -1478,11 +1506,13 @@
"formatEN": "update Skills Hub install target [name]"
},
"/core/enterprise/skills-hub/upload": {
"bodyKeys": [],
"bodyKeys": [
"version"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "上传 Skills Hub 技能包",
"formatEN": "upload Skills Hub package"
"formatZH": "上传 Skills Hub 技能包版本 [version]",
"formatEN": "upload Skills Hub package version [version]"
},
"/core/enterprise/users": {
"bodyKeys": [
@@ -1493,6 +1523,25 @@
"formatZH": "添加用户 [name]",
"formatEN": "add user [name]"
},
"/core/enterprise/users/api/update": {
"bodyKeys": [
"id",
"ipWhiteList"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "users",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新用户 [name] API 接口配置 =\u003e IP 白名单: [ipWhiteList]",
"formatEN": "update user [name] api config =\u003e IP White List: [ipWhiteList]"
},
"/core/enterprise/users/del": {
"bodyKeys": [
"ids"
@@ -1529,6 +1578,255 @@
"formatZH": "更新 [name]",
"formatEN": "update user [name]"
},
"/core/enterprise/vms": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name]",
"formatEN": "create VM [name]"
},
"/core/enterprise/vms/del": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除虚拟机 [name]",
"formatEN": "delete VM [name]"
},
"/core/enterprise/vms/iso": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机镜像 [name] [type]",
"formatEN": "create VM ISO [name] [type]"
},
"/core/enterprise/vms/iso/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_isos",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机镜像 [name]",
"formatEN": "delete VM ISO [name]"
},
"/core/enterprise/vms/iso/update": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新虚拟机镜像 [name] [type]",
"formatEN": "update VM ISO [name] [type]"
},
"/core/enterprise/vms/networks": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机网络 [name] [type]",
"formatEN": "create VM network [name] [type]"
},
"/core/enterprise/vms/networks/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_networks",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机网络 [name]",
"formatEN": "delete VM network [name]"
},
"/core/enterprise/vms/networks/sync": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "同步虚拟机网络",
"formatEN": "sync VM networks"
},
"/core/enterprise/vms/networks/update": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_networks",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新虚拟机网络 [name]",
"formatEN": "update VM network [name]"
},
"/core/enterprise/vms/operate": {
"bodyKeys": [
"name",
"operate"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "操作虚拟机 [name] [operate]",
"formatEN": "operate VM [name] [operate]"
},
"/core/enterprise/vms/rename": {
"bodyKeys": [
"name",
"newName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "重命名虚拟机 [name] 为 [newName]",
"formatEN": "rename VM [name] to [newName]"
},
"/core/enterprise/vms/snapshots": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name] 快照 [snapshotName]",
"formatEN": "create VM [name] snapshot [snapshotName]"
},
"/core/enterprise/vms/snapshots/del": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除虚拟机 [name] 快照 [snapshotName]",
"formatEN": "delete VM [name] snapshot [snapshotName]"
},
"/core/enterprise/vms/snapshots/recover": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "恢复虚拟机 [name] 快照 [snapshotName]",
"formatEN": "recover VM [name] snapshot [snapshotName]"
},
"/core/enterprise/vms/storages": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机存储 [name] [type]",
"formatEN": "create VM storage [name] [type]"
},
"/core/enterprise/vms/storages/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_storages",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机存储 [name]",
"formatEN": "delete VM storage [name]"
},
"/core/enterprise/vms/storages/sync": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "同步虚拟机存储",
"formatEN": "sync VM storages"
},
"/core/enterprise/vms/storages/update": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_storages",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新虚拟机存储 [name]",
"formatEN": "update VM storage [name]"
},
"/core/enterprise/vms/templates": {
"bodyKeys": [
"name",
"templateName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name] 模板 [templateName]",
"formatEN": "create VM [name] template [templateName]"
},
"/core/enterprise/vms/templates/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_templates",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机模板 [name]",
"formatEN": "delete VM template [name]"
},
"/core/enterprise/vms/update": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新虚拟机 [name]",
"formatEN": "update VM [name]"
},
"/core/groups": {
"bodyKeys": [
"name",
@@ -1853,15 +2151,22 @@
},
"/core/xpack/exchange/file": {
"bodyKeys": [
"source",
"dest",
"sourceNode",
"targetNode",
"sourcePath",
"destPath"
"targetPath"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "交换文件 [sourcePath] 到 [destPath]",
"formatEN": "exchange file [sourcePath] to [destPath]"
"formatZH": "交换文件 [sourcePath] 到 [targetPath]",
"formatEN": "exchange file [sourcePath] to [targetPath]"
},
"/core/xpack/exchange/image": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "同步节点镜像",
"formatEN": "sync images"
},
"/core/xpack/nodes": {
"bodyKeys": [
@@ -2166,6 +2471,15 @@
"formatZH": "更新自定义应用配置 [status]",
"formatEN": "update custom app config [status]"
},
"/core/xpack/settings/app/image-prefix": {
"bodyKeys": [
"imagePrefix"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新应用镜像前缀 [imagePrefix]",
"formatEN": "update app image prefix [imagePrefix]"
},
"/core/xpack/settings/reset": {
"bodyKeys": [],
"paramKeys": [],
@@ -2191,27 +2505,11 @@
"formatEN": "update xpack setting [key]"
},
"/core/xpack/sync/app": {
"bodyKeys": [
"sourceNode",
"targetNode",
"appInstallID",
"appBackupID"
],
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "同步应用备份 [appInstallID] 到节点 [targetNode]",
"formatEN": "sync app backup [appInstallID] to node [targetNode]"
},
"/core/xpack/sync/app/install": {
"bodyKeys": [
"appKey",
"version",
"nodes"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "安装应用 [appKey] [version] 到节点 [nodes]",
"formatEN": "install app [appKey] [version] to nodes [nodes]"
"formatZH": "同步节点应用",
"formatEN": "sync app"
},
"/core/xpack/sync/ssl": {
"bodyKeys": [
@@ -2352,34 +2650,14 @@
"formatZH": "更新自定义应用配置 [status]",
"formatEN": "update custom app config [status]"
},
"/custom/app/install": {
"/custom/app/image-prefix": {
"bodyKeys": [
"name",
"appKey"
"imagePrefix"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "从节点安装应用 [name]",
"formatEN": "install app [name] from node"
},
"/custom/app/install/check": {
"bodyKeys": [
"name",
"appKey"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "检查应用安装 [name]",
"formatEN": "check app install [name]"
},
"/custom/app/push": {
"bodyKeys": [
"appBackupID"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "推送应用备份 [appBackupID] 到节点",
"formatEN": "push app backup [appBackupID] to node"
"formatZH": "更新应用镜像前缀 [imagePrefix]",
"formatEN": "update app image prefix [imagePrefix]"
},
"/custom/app/sync": {
"bodyKeys": [],
@@ -4440,6 +4718,24 @@
"formatZH": "申请证书 [domain]",
"formatEN": "apply ssl [domain]"
},
"/websites/ssl/push": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "website_ssls",
"output_column": "primary_domain",
"output_value": "domain"
}
],
"formatZH": "推送证书到节点 [domain]",
"formatEN": "Push ssl to nodes [domain]"
},
"/websites/ssl/update": {
"bodyKeys": [
"id"
@@ -4935,6 +5231,15 @@
"formatZH": "更新[scope]频率访问限制",
"formatEN": "update [scope] CC config"
},
"/xpack/waf/rule/common/apply": {
"bodyKeys": [
"scope"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "应用规则 [scope] 到网站",
"formatEN": "apply rule [scope] to websites"
},
"/xpack/waf/rule/common/create": {
"bodyKeys": [
"scope"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+27 -18
View File
@@ -57,23 +57,24 @@ var WebUrlMap = map[string]struct{}{
"/apps/upgrade": {},
"/apps/setting": {},
"/ai": {},
"/ai/ai-proxy": {},
"/ai/ai-proxy/model-pool": {},
"/ai/ai-proxy/api-keys": {},
"/ai/ai-proxy/groups": {},
"/ai/ai-proxy/usage": {},
"/ai/ai-proxy/content": {},
"/ai/skills-hub": {},
"/ai/skills-hub/targets": {},
"/ai/benchmark": {},
"/ai/model/account": {},
"/ai/model/local": {},
"/ai/gpu": {},
"/ai/gpu/current": {},
"/ai/gpu/history": {},
"/ai/mcp": {},
"/ai/agents/agent": {},
"/ai": {},
"/ai/ai-proxy": {},
"/ai/ai-proxy/model-pool": {},
"/ai/ai-proxy/api-keys": {},
"/ai/ai-proxy/groups": {},
"/ai/ai-proxy/model-groups": {},
"/ai/ai-proxy/usage": {},
"/ai/ai-proxy/content": {},
"/ai/skills-hub": {},
"/ai/skills-hub/targets": {},
"/ai/benchmark": {},
"/ai/model/account": {},
"/ai/model/local": {},
"/ai/gpu": {},
"/ai/gpu/current": {},
"/ai/gpu/history": {},
"/ai/mcp": {},
"/ai/agents/agent": {},
"/containers": {},
"/containers/container/operate": {},
@@ -182,7 +183,10 @@ var WebUrlMap = map[string]struct{}{
"/xpack/node/dashboard": {},
"/xpack/node": {},
"/xpack/simple-node": {},
"/xpack/exchange/file": {},
"/xpack/sync/file": {},
"/xpack/sync/image": {},
"/xpack/sync/ssl": {},
"/xpack/sync/app": {},
"/xpack/app": {},
"/xpack/app-upgrade": {},
@@ -204,6 +208,11 @@ var WebUrlMap = map[string]struct{}{
"/enterprise/ops-report/alert": {},
"/enterprise/ops-report/history": {},
"/enterprise/ops-report/settings": {},
"/enterprise/vm/list": {},
"/enterprise/vm/iso": {},
"/enterprise/vm/templates": {},
"/enterprise/vm/networks": {},
"/enterprise/vm/storage-pools": {},
}
var DynamicRoutes = []string{
+14 -5
View File
@@ -1,6 +1,6 @@
module github.com/1Panel-dev/1Panel/core
go 1.25.7
go 1.26.1
require (
github.com/1panel-dev/base64Captcha v1.3.8
@@ -16,13 +16,13 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/jinzhu/copier v0.4.0
github.com/johnfercher/maroto/v2 v2.4.0
github.com/nicksnyder/go-i18n/v2 v2.6.1
github.com/oschwald/maxminddb-golang v1.13.1
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
github.com/pkg/sftp v1.13.10
github.com/robfig/cron/v3 v3.0.1
github.com/signintech/gopdf v0.36.1
github.com/sirupsen/logrus v1.9.4
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/soheilhy/cmux v0.1.5
@@ -32,22 +32,25 @@ require (
github.com/swaggo/swag v1.16.6
github.com/xlzd/gotp v0.1.0
golang.org/x/crypto v0.53.0
golang.org/x/net v0.55.0
golang.org/x/net v0.56.0
golang.org/x/sys v0.46.0
golang.org/x/term v0.44.0
golang.org/x/text v0.38.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/gorm v1.31.1
gorm.io/gorm v1.31.2
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bytedance/gopkg v0.1.4 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.1 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/f-amaral/go-async v0.3.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.2 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/sse v1.1.1 // indirect
@@ -71,20 +74,26 @@ require (
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/hhrutter/lzw v1.0.0 // indirect
github.com/hhrutter/pkcs7 v0.2.0 // indirect
github.com/hhrutter/tiff v1.0.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/johnfercher/go-tree v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.21 // indirect
github.com/mattn/go-runewidth v0.0.21 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/pdfcpu/pdfcpu v0.11.1 // indirect
github.com/pelletier/go-toml/v2 v2.3.0 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 // indirect
github.com/phpdave11/gofpdf v1.4.3 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+38 -8
View File
@@ -4,12 +4,17 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@@ -20,6 +25,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/f-amaral/go-async v0.3.0 h1:h4kLsX7aKfdWaHvV0lf+/EE3OIeCzyeDYJDb/vDZUyg=
github.com/f-amaral/go-async v0.3.0/go.mod h1:Hz5Qr6DAWpbTTUjytnrg1WIsDgS7NtOei5y8SipYS7U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
@@ -106,6 +113,12 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0=
github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo=
github.com/hhrutter/pkcs7 v0.2.0 h1:i4HN2XMbGQpZRnKBLsUwO3dSckzgX142TNqY/KfXg+I=
github.com/hhrutter/pkcs7 v0.2.0/go.mod h1:aEzKz0+ZAlz7YaEMY47jDHL14hVWD6iXt0AgqgAvWgE=
github.com/hhrutter/tiff v1.0.2 h1:7H3FQQpKu/i5WaSChoD1nnJbGx4MxU5TlNqqpxw55z8=
github.com/hhrutter/tiff v1.0.2/go.mod h1:pcOeuK5loFUE7Y/WnzGw20YxUdnqjY1P0Jlcieb/cCw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
@@ -114,8 +127,13 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/johnfercher/go-tree v1.1.0 h1:L0Fs5jLR1uA2e/CwfHjNdO/Lt4IGQ46QgxarAC1yeXs=
github.com/johnfercher/go-tree v1.1.0/go.mod h1:DUO6QkXIFh1K7jeGBIkLCZaeUgnkdQAsB64FDSoHswg=
github.com/johnfercher/maroto/v2 v2.4.0 h1:Nc/jA2RCZvNZESrQj41HJOgtkwmerSHd5FUbP4dRrIE=
github.com/johnfercher/maroto/v2 v2.4.0/go.mod h1:Nnxa3g4f+vzdx/u/dUgx/52HnrCOCt5QBPSdeSlkFZQ=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
@@ -128,6 +146,10 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -141,12 +163,15 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pdfcpu/pdfcpu v0.11.1 h1:htHBSkGH5jMKWC6e0sihBFbcKZ8vG1M67c8/dJxhjas=
github.com/pdfcpu/pdfcpu v0.11.1/go.mod h1:pP3aGga7pRvwFWAm9WwFvo+V68DfANi9kxSQYioNYcw=
github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 h1:zyWXQ6vu27ETMpYsEMAsisQ+GqJ4e1TPvSNfdOPF0no=
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/phpdave11/gofpdf v1.4.3 h1:M/zHvS8FO3zh9tUd2RCOPEjyuVcs281FCyF22Qlz/IA=
github.com/phpdave11/gofpdf v1.4.3/go.mod h1:MAwzoUIgD3J55u0rxIG2eu37c+XWhBtXSpPAhnQXf/o=
github.com/phpdave11/gofpdi v1.0.15/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -165,10 +190,9 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/signintech/gopdf v0.36.1 h1:cGpvEKvvqCV+ZXB9R2SQoWgouW91JpwsgoQEhLxIdp0=
github.com/signintech/gopdf v0.36.1/go.mod h1:d23eO35GpEliSrF22eJ4bsM3wVeQJTjXTHq5x5qGKjA=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
@@ -190,6 +214,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@@ -225,14 +252,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww=
golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA=
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -261,8 +289,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
+1
View File
@@ -28,6 +28,7 @@ var langFiles = map[string]string{
"ko": "lang/ko.yaml",
"tr": "lang/tr.yaml",
"es-ES": "lang/es-ES.yaml",
"fa": "lang/fa.yaml",
}
func GetMsgWithMap(key string, maps map[string]interface{}) string {
+157 -101
View File
@@ -1,16 +1,36 @@
# base errors
ErrInvalidParams: "Invalid request parameters: {{ .detail }}"
ErrTokenParse: "Token generation error: {{ .detail }}"
ErrInitialPassword: "Incorrect initial password"
ErrInternalServer: "Internal server error: {{ .detail }}"
ErrInternalServerKey: "Internal server error:"
ErrRecordExist: "Record already exists"
ErrRecordNotFound: "Record not found"
ErrStructTransform: "Type conversion failed: {{ .detail }}"
ErrNotSupportType: "Unsupported type: {{ .name }}"
ErrDemoEnvironment: "Demo server, this operation is prohibited!"
ErrCmdTimeout: "Command execution timeout!"
ErrPortInUsed: "The {{ .name }} port is already in use!"
ErrFileNotFound: "{{ .name }} file does not exist"
ErrReqFailed: "{{.name}} request failed: {{ .err }}"
# request and entrance
ErrNoSuchHost: "Host resolution failed: {{ .err }}"
ErrHttpReqNotFound: "Requested resource not found: {{ .err }}"
ErrHttpReqFailed: "Request failed {{ .err }}"
ErrHttpReqTimeOut: "Request timed out {{ .err }}"
ErrCreateHttpClient: "Failed to create request {{ .err }}"
ErrProxy: "Request failed; check this node status: {{ .detail }}"
ErrProxySetting: "Proxy server information unavailable {{ .err }}, check and try again!"
ErrEntrance: "Security entrance information error, check and try again!"
ErrEntranceFormat: "Security entry {{ .name }} is not supported. Check and try again."
# login and security
ErrNotLogin: "User not logged in: {{ .detail }}"
ErrSessionDataNotFound: "Session expired"
ErrSessionDataFormat: "Invalid session data format"
ErrPasswordExpired: "Password expired: {{ .detail }}"
ErrNotSupportType: "Unsupported type: {{ .detail }}"
ErrProxy: "Request failed; check this node status: {{ .detail }}"
ErrLoginLocked: "Too many failed attempts. Login is temporarily locked for 5 minutes."
ErrApiConfigStatusInvalid: "API access disabled: {{ .detail }}"
ErrApiConfigKeyInvalid: "Invalid API key: {{ .detail }}"
ErrApiConfigIPInvalid: "The API request IP is not on the whitelist: {{ .detail }}"
@@ -22,53 +42,24 @@ ErrPasskeyLimit: "Passkey limit reached (max 5)"
ErrPasskeySession: "Passkey session expired or invalid"
ErrPasskeyDuplicate: "Passkey already exists"
ErrPasskeyVerify: "Passkey verification failed"
ErrLoginLocked: "Too many failed attempts. Login is temporarily locked for 5 minutes."
# request
ErrNoSuchHost: "Host resolution failed: {{ .err }}"
ErrHttpReqNotFound: "Requested resource not found: {{ .err }}"
ErrHttpReqFailed: "Request failed {{ .err }}"
ErrHttpReqTimeOut: "Request timed out {{ .err }}"
ErrCreateHttpClient: "Failed to create request {{ .err }}"
ErrProxySetting: "Proxy server information unavailable {{ .err }}, check and try again!"
ErrEntranceFormat: "Security entry {{ .name }} is not supported. Check and try again."
# common
ErrDemoEnvironment: "Demo server, this operation is prohibited!"
ErrCmdTimeout: "Command execution timeout!"
ErrEntrance: "Security entrance information error, check and try again!"
# user and permission
ErrNoSuchUser: "User information not found, check and try again!"
ErrToMaster: "This user does not have permission to perform this operation on the master node. Check and try again!"
ErrToNode: "This user does not have permission to perform this operation on this node. Check and try again!"
ErrRBAC: "Insufficient user permissions. This operation cannot be performed. Check and try again!"
ErrNoneNode: "This user has no node permissions. Contact an administrator or check and try again!"
ErrOnlySuperAdmin: "Only super admins can use this feature!"
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
ErrRolePresetCannotUpdate: "System preset roles cannot be updated"
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
ErrRolePresetCannotBind: "System preset roles cannot be assigned to users"
ErrNodeAdminMasterForbidden: "Node Admin permissions cannot be added to the master node"
ErrSuperAdminCannotDelete: "Super admin users cannot be deleted"
ErrGroupIsDefault: "Default group, unable to delete"
ErrGroupIsInUse: "The group is in use and cannot be deleted."
ErrAIProxyUserGroupIsDefault: "Default user group cannot be deleted."
ErrAIProxyUserGroupInUse: "The user group is in use and cannot be deleted."
ErrAIProxyUserGroupExists: "The user group name already exists."
ErrAIProxyBackendAccountExists: "This model account has already been imported into the AI Gateway account pool."
ErrAIProxyModelGroupInUse: "The model group is in use and cannot be deleted."
ErrAIProxyModelGroupExists: "The model group name already exists."
ErrAIProxySensitiveGroupInUse: "The sensitive word group is in use and cannot be deleted."
ErrAIProxySensitiveGroupExists: "The sensitive word group name already exists."
ErrLocalDelete: "Cannot delete the local node!"
ErrPortInUsed: "The {{ .name }} port is already in use!"
ErrInternalServerKey: "Internal server error:"
MasterNode: "Master Node"
# app
CustomAppStoreFileValid: "Application store package requires .tar.gz format"
ErrFileNotFound: "{{ .name }} file does not exist"
AppBackup: 'Application backup'
AppBackupPush: 'Transfer application backup file {{.file}} to node {{ .name }}'
ErrSourceTargetSame: 'Source node and target node cannot be the same!'
AppInstall: 'Install application {{ .name }} on node {{ .targetNode }}'
AppInstallCheck: 'Check application installation environment'
# backup
ErrBackupInUsed: "This backup account is used in scheduled tasks and cannot be deleted"
ErrBackupCheck: "Backup account connection test failed {{ .err }}"
ErrBackupLocal: "Local server backup account does not support this operation!"
ErrBackupPublic: "Detected that this backup account is not public, check and try again!"
ErrOSSConn: "Unable to get the latest version. Check server internet connectivity."
#license
# license
LicenseCheck: 'Check if the license is available'
ErrLicenseInUsed: 'The license is already bound. Check and try again!'
ErrLicenseExpired: 'The license has expired. Check and try again!'
@@ -98,34 +89,73 @@ ErrNodeUnbind: "This node is not within the license binding range, check and ret
ErrNodeBind: "This node is already bound to a license, check and retry!"
ErrNodeLocalRollback: "The primary node does not support direct rollback. Run '1pctl restore' manually to roll back."
ErrIntlLicense: "The current version does not support importing international licenses yet. Stay tuned!"
InvalidRequestBodyType: "Invalid request body format. Ensure content matches the required format and retry."
InvalidLicenseCodeType: "Invalid license code format provided, check and try again!"
LicenseNotFoundType: "License not found, no matching record exists in the system for the provided license. Check and try again!"
LicenseRevokedType: "The requested license has been revoked and is no longer usable. Check and try again!"
LicenseExpiredType: "License expired. Renew it or re-import it in Panel Settings - License before retrying."
LicenseProductMismatchType: "The license does not match the requested product or service!"
InvalidAssigneeType: "Invalid target user or device information for license assignment. Check and try again!"
LicenseUsageNotFoundType: "No usage records found. This license has not been activated or used yet. Check and try again!"
LicenseUsageLimitExceededType: "This license is already bound to another node. Check and try again!"
# user
ErrNoSuchUser: "User information not found, check and try again!"
ErrToMaster: "This user does not have permission to perform this operation on the master node. Check and try again!"
ErrToNode: "This user does not have permission to perform this operation on this node. Check and try again!"
ErrRBAC: "Insufficient user permissions. This operation cannot be performed. Check and try again!"
ErrNoneNode: "This user has no node permissions. Contact an administrator or check and try again!"
ErrOnlySuperAdmin: "Only super admins can use this feature!"
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
ErrRolePresetCannotUpdate: "System preset roles cannot be updated"
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
ErrRolePresetCannotBind: "System preset roles cannot be assigned to users"
ErrNodeAdminMasterForbidden: "Node Admin permissions cannot be added to the master node"
ErrSuperAdminCannotDelete: "Super admin users cannot be deleted"
# node and cluster
MasterNode: "Master Node"
Node: "Node"
ErrLocalDelete: "Cannot delete the local node!"
ErrMasterDelete: "Unable to delete the master node. Delete slave nodes first."
ClusterNameIsExist: "Cluster name already exists."
AppStatusUnHealthy: "Failed to fetch application status. Check installation node status in the node list."
MasterNodePortNotAvailable: "Node {{ .name }} port {{ .port }} connectivity check failed. Check firewall/security group settings and master node status."
ClusterMasterNotExist: "Cluster master node is disconnected. Delete child nodes."
InvalidRequestBodyType: "Invalid request body format. Ensure content matches the required format and retry."
InvalidLicenseCodeType: "Invalid license code format provided, check and try again!"
LicenseNotFoundType: "License not found, no matching record exists in the system for the provided license. Check and try again!"
LicenseRevokedType: "The requested license has been revoked and is no longer usable. Check and try again!"
LicenseExpiredType: "License expired. Renew it or re-import it in Panel Settings - License before retrying."
LicenseProductMismatchType: "The license does not match the requested product or service!"
InvalidAssigneeType: "Invalid target user or device information for license assignment. Check and try again!"
LicenseUsageNotFoundType: "No usage records found. This license has not been activated or used yet. Check and try again!"
LicenseUsageLimitExceededType: "This license is already bound to another node. Check and try again!"
# virtual machine
ErrVMNotFound: "Virtual machine does not exist"
ErrVMAlreadyExists: "Virtual machine already exists"
ErrVMRunning: "Virtual machine is running"
ErrVMLibvirtConnect: "Failed to connect to libvirt"
ErrVMPermission: "No permission to operate virtual machine: {{ .detail }}"
ErrVMNetworkNotFound: "Virtual machine network does not exist"
ErrVMInvalidPath: "Invalid virtual machine path"
ErrVMInvalidInput: "Invalid virtual machine parameter"
ErrVMCommandFailed: "Virtual machine command failed: {{ .err }}"
VMSnapshotCreateTask: "Create snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]"
VMSnapshotCreateStep: "Create virtual machine snapshot [{{ .snapshot }}]"
VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]"
VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]"
VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]"
VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]"
# app
CustomAppStoreFileValid: "Application store package requires .tar.gz format"
AppBackup: 'Application backup'
AppBackupPush: 'Transfer application backup file {{.file}} to node {{ .name }}'
ErrSourceTargetSame: 'Source node and target node cannot be the same!'
AppInstall: 'Install application {{ .name }} on node {{ .targetNode }}'
AppInstallCheck: 'Check application installation environment'
# backup
ErrBackupInUsed: "This backup account is used in scheduled tasks and cannot be deleted"
ErrBackupCheck: "Backup account connection test failed {{ .err }}"
ErrBackupLocal: "Local server backup account does not support this operation!"
ErrBackupPublic: "Detected that this backup account is not public, check and try again!"
ErrOSSConn: "Unable to get the latest version. Check server internet connectivity."
MvBackup: "Move data to backup directory"
# alert
ErrAlertSync: "Alert information sync error, check and retry!"
# AI gateway
ErrAIProxyUserGroupIsDefault: "Default user group cannot be deleted."
ErrAIProxyUserGroupInUse: "The user group is in use and cannot be deleted."
ErrAIProxyUserGroupExists: "The user group name already exists."
ErrAIProxyBackendAccountExists: "This model account has already been imported into the AI Gateway account pool."
ErrAIProxyModelGroupInUse: "The model group is in use and cannot be deleted."
ErrAIProxyModelGroupExists: "The model group name already exists."
ErrAIProxySensitiveGroupInUse: "The sensitive word group is in use and cannot be deleted."
ErrAIProxySensitiveGroupExists: "The sensitive word group name already exists."
AIBenchmarkRun: "Run AI benchmark"
# task
TaskStart: "{{ .name }} task started [START]"
TaskEnd: "{{ .name }} task ended [COMPLETED]"
@@ -140,6 +170,16 @@ TaskUpgrade: "Upgrade"
TaskSync: 'Sync'
TaskSyncForNode: "Sync node data"
TaskBackup: "Backup"
TaskPush: "Push"
TaskRsync: "Sync"
SuccessStatus: "{{ .name }} succeeded"
FailedStatus: "{{ .name }} failed {{ .err }}"
Start: "Start"
SubTask: "Subtask"
Skip: "Ignore errors and continue"
Success: "Success"
# agent batch task
BatchInstallAgent: "Batch install agent"
DispatchAgentInstallTasks: "Dispatch agent install tasks"
BatchUpgradeAgent: "Batch upgrade agent"
@@ -173,21 +213,15 @@ AgentOperateStart: "Start"
AgentOperateStop: "Stop"
AgentOperateRestart: "Restart"
AgentOperateDelete: "Delete"
AIBenchmarkRun: "Run AI benchmark"
SuccessStatus: "{{ .name }} succeeded"
FailedStatus: "{{ .name }} failed {{ .err }}"
Start: "Start"
SubTask: "Subtask"
Skip: "Ignore errors and continue"
# app and skill task
PushAppInstallTaskToNode: "Push app installation task to node [{{ .name }}]"
TaskPush: "Push"
AppInstallTask: "App installation task"
PushAppFailed: "Failed to push app installation task"
SkillHubInstallTask: "Install skill [{{ .name }}]"
SkillHubInstallToTarget: "Install skill to target [{{ .name }}]"
Success: "Success"
#script
# script library
ScriptLibrary: "Script Library"
RemoteScriptLibrary: "Remote Script Library"
ScriptSyncSkip: "Script library is already up to date"
@@ -195,7 +229,7 @@ DownloadData: "Downloading script library file data.yaml"
DownloadPackage: "Downloading script library package"
AnalyticCompletion: "Analysis completed, now syncing to database..."
Node: "Node"
# node sync
SyncNode: "Sync data to node {{ .name }}"
LocalName: "'local' name is only used for system local identification"
SyncPackageData: "Package sync data"
@@ -211,7 +245,7 @@ SyncCustomApp: "Custom App"
SyncLanguage: "System Language"
SyncEdition: "Region"
#upgrade node
# node upgrade
NodeUpgrade: "Upgrade node {{ .name }}"
UpgradeCheck: "Check for node updates"
UpgradeCheckLocal: "Local nodes do not support batch upgrades, skipping..."
@@ -224,12 +258,11 @@ LoadAutoUpgradeNodesFailed: "Load auto upgrade nodes failed, err: {{ .err }}"
AutoUpgradeNodes: "Auto upgrade nodes: {{ .nodes }}"
UpdateAutoUpgradeNodesStatusFailed: "Update status of auto upgrade nodes failed, err: {{ .err }}"
#add node
# node add
MasterData: "Master Node Data"
LoadSftpClient: "Load SFTP Client"
PackageMasterData: "Generate master node backup package"
UploadBackup: "Upload backup data"
MvBackup: "Move data to backup directory"
TaskAddNode: "Add node"
LoadNodeArch: "Get node architecture info"
LoadNodeArchDetail: "Detected master node architecture: {{ .local }}, child node architecture: {{ .node }}"
@@ -242,7 +275,35 @@ SendAgent: "Distribute node installation package"
StartService: "Start service"
NoBackupNode: "Backup node is empty. Select one and save first"
#cmd
# node file and image sync
LocalNodeIpFailed: "Failed to get master node IP. Edit the master node and add IP address and SSH authentication information."
HandlePrivateKey: "Handle node private key"
HandlePublicKey: "Handle node public key"
ExchangeFile: "Start syncing {{ .sourcePath }} from node {{ .source }} to node {{ .dest }}:{{ .destPath }}"
NodeFile: "Node file"
ImageSyncTaskName: "Image sync {{ .count }} images"
SSLSyncTaskName: "Certificate sync {{ .count }} certificates"
AppSyncTaskName: "App sync {{ .name }}"
SSLSyncToNode: "Push certificate [{{ .cert }}] to node [{{ .node }}]"
ImageSyncSave: "Save image {{ .image }}"
ImageSyncSaveTags: "Save image tags {{ .tags }}"
ImageSyncToNode: "Sync image {{ .image }} to node {{ .node }}"
ImageSyncLoad: "Load image {{ .image }} on node {{ .node }}"
ImageSyncClean: "Clean temporary files for image {{ .image }}"
ImageSyncNoTag: "Image {{ .image }} has no tag and will be saved by image ID"
ImageSyncFrontendTagsIgnored: "No tags were found for image {{ .image }} on the source node. Frontend tags were ignored."
ImageSyncHandlePrivateKey: "Handle image sync node private key"
ImageSyncHandlePublicKey: "Handle image sync node public key"
ImageSyncTransfer: "Start transferring image temporary file: {{ .source }}:{{ .sourcePath }} -> {{ .dest }}:{{ .destPath }}"
# command line
SudoHelper: "Use {{ .cmd }} or switch to root user"
UsernameNeed: "Enterprise edition must specify --username"
DBConnErr: "Error: Failed to initialize database connection, {{ .err }}"
SystemVersion: "version: "
SystemMode: "mode: "
# command line - app
AppVersion: "App version"
AppCommands: "App related commands"
AppInit: "Initialize app"
@@ -254,12 +315,14 @@ AppMissVersion: "App version missing, use -v to specify"
AppVersionExist: "Version already exists!"
AppCreateSuccessful: "Creation successful!"
AppWriteErr: "File {{ .name }} write failed {{ .err }}"
SudoHelper: "Use {{ .cmd }} or switch to root user"
UsernameNeed: "Enterprise edition must specify --username"
# command line - listen IP
ListenIPCommands: "Switch listening ip"
ListenIPv4: "Listen on IPv4"
ListenIPv6: "Listen on IPv6"
ListenChangeSuccessful: "Switch successful! Now listening on {{ .value }}"
# command line - reset
ResetCommands: "Reset system info"
ResetMFA: "Cancel 1Panel two-factor authentication"
ResetHttps: "Cancel 1Panel https login"
@@ -267,12 +330,19 @@ ResetEntrance: "Cancel 1Panel secure entrance"
ResetIPs: "Cancel 1Panel authorized ip restrictions"
ResetDomain: "Cancel 1Panel domain binding"
ResetPasskey: "Clear 1Panel passkeys"
# command line - user
UserList: "Get 1Panel user list"
UserTableName: "Name"
UserTableMFAStatus: "MFA Status"
UserTableSuperAdmin: "Super Admin"
UserTableCreatedAt: "Created At"
UserEmptyList: "No users found"
UserInfo: "Get panel information"
UserInfoAddr: "Panel address: "
UserInfoPassHelp: "Tip: To change the password, you can execute the command: "
# command line - restore
RestoreCommands: "Roll back 1Panel service and data"
RestoreNoSuchFile: "No rollback files found"
RestoreStep1: "(1/5) Starting rollback of 1Panel service and data from {{ .name }} directory..."
@@ -281,6 +351,8 @@ RestoreStep3: "(3/5) 1Panel script rollback completed"
RestoreStep4: "(4/5) 1Panel service rollback completed"
RestoreStep5: "(5/5) 1Panel data rollback completed"
RestoreSuccessful: "Rollback completed. Reverted to {{ .version }}. Restarting service..."
# command line - update
UpdateCommands: "Update panel info"
UpdateUser: "Update user information"
UpdatePassword: "Update user password"
@@ -307,28 +379,12 @@ UpdatePortResult: "Panel Port: {{ .name }}"
UpdatePortFirewallAdd: "Failed to add firewall port rule, {{ .err }}, manually add the {{ .name }} port to the firewall rules."
UpdatePortFirewallDel: "Error: Failed to delete firewall port, {{ .err }}"
UpdatePortFirewallReload: "Failed to reload the firewall, {{ .err }}, manually reload the firewall."
UserInfo: "Get panel information"
UserInfoAddr: "Panel address: "
UserInfoPassHelp: "Tip: To change the password, you can execute the command: "
DBConnErr: "Error: Failed to initialize database connection, {{ .err }}"
SystemVersion: "version: "
SystemMode: "mode: "
#mobile app
# mobile app
ErrVerifyToken: 'Token verification failed. Reset and scan again.'
ErrInvalidToken: 'Invalid token. Reset and scan again.'
ErrExpiredToken: 'Token expired. Reset and scan again.'
#cluster
ErrMasterDelete: "Unable to delete the master node. Delete slave nodes first."
ClusterNameIsExist: "Cluster name already exists."
AppStatusUnHealthy: "Failed to fetch application status. Check installation node status in the node list."
MasterNodePortNotAvailable: "Node {{ .name }} port {{ .port }} connectivity check failed. Check firewall/security group settings and master node status."
ClusterMasterNotExist: "Cluster master node is disconnected. Delete child nodes."
#ssl
ErrReqFailed: "{{.name}} request failed: {{ .err }}"
#command
# command export
Name: "Name"
Command: "Command"
+161 -99
View File
@@ -1,73 +1,65 @@
# errores básicos
ErrInvalidParams: 'Solicitud inválida: {{ .detail }}'
ErrTokenParse: 'Token inválido: {{ .detail }}'
ErrInitialPassword: 'Contraseña original incorrecta'
ErrInternalServer: 'Error interno: {{ .detail }}'
ErrInternalServerKey: "Error interno del servidor:"
ErrRecordExist: 'Registro ya existe'
ErrRecordNotFound: 'Registro no encontrado'
ErrStructTransform: 'Error de conversión: {{ .err }}'
ErrNotLogin: 'No has iniciado sesión: {{ .detail }}'
ErrSessionDataNotFound: "La sesión actual ha caducado"
ErrSessionDataFormat: "El formato de los datos de la sesión actual es anómalo"
ErrPasswordExpired: 'Contraseña expirada: {{ .detail }}'
ErrNotSupportType: 'Tipo no soportado: {{ .name }}'
ErrProxy: 'Solicitud fallida: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API desactivada: {{ .detail }}'
ErrApiConfigKeyInvalid: 'Clave API inválida: {{ .detail }}'
ErrApiConfigIPInvalid: 'IP no permitida: {{ .detail }}'
ErrApiConfigDisable: 'API no permitida: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'Timestamp inválido: {{ .detail }}'
ErrLoginLocked: "Demasiados intentos fallidos. El inicio de sesión se ha bloqueado temporalmente durante 5 minutos."
PushAppInstallTaskToNode: "Enviar tarea de instalación de aplicación al nodo [{{ .name }}]"
TaskPush: "Enviar"
AppInstallTask: "Tarea de instalación de aplicación"
PushAppFailed: "Error al enviar tarea de instalación de aplicación"
Success: "Éxito"
ErrDemoEnvironment: 'No disponible en demo'
ErrCmdTimeout: 'Comando agotó tiempo'
ErrPortInUsed: "El puerto {{ .name }} ya está en uso"
ErrFileNotFound: "El archivo {{ .name }} no existe"
ErrReqFailed: "{{.name}} petición fallida: {{ .err }}"
# request
# solicitud y entrada de seguridad
ErrNoSuchHost: "No se pudo encontrar el servidor solicitado {{ .err }}"
ErrHttpReqNotFound: "No se pudo encontrar el recurso solicitado {{ .err }}"
ErrHttpReqFailed: "Solicitud fallida {{ .err }}"
ErrHttpReqTimeOut: "La solicitud ha expirado {{ .err }}"
ErrCreateHttpClient: "Error al crear la solicitud {{ .err }}"
ErrProxy: 'Solicitud fallida: {{ .detail }}'
ErrProxySetting: "Información del servidor proxy no disponible {{ .err }}, compruebe e inténtelo de nuevo"
ErrEntrance: "Error en la información de entrada de seguridad, por favor revise e intente de nuevo"
ErrEntranceFormat: "La entrada de seguridad {{ .name }} no está actualmente soportada. Por favor verifique e inténtelo de nuevo"
# common
ErrDemoEnvironment: 'No disponible en demo'
ErrCmdTimeout: 'Comando agotó tiempo'
ErrEntrance: "Error en la información de entrada de seguridad, por favor revise e intente de nuevo"
# inicio de sesión y seguridad
ErrNotLogin: 'No has iniciado sesión: {{ .detail }}'
ErrSessionDataNotFound: "La sesión actual ha caducado"
ErrSessionDataFormat: "El formato de los datos de la sesión actual es anómalo"
ErrPasswordExpired: 'Contraseña expirada: {{ .detail }}'
ErrLoginLocked: "Demasiados intentos fallidos. El inicio de sesión se ha bloqueado temporalmente durante 5 minutos."
ErrApiConfigStatusInvalid: 'API desactivada: {{ .detail }}'
ErrApiConfigKeyInvalid: 'Clave API inválida: {{ .detail }}'
ErrApiConfigIPInvalid: 'IP no permitida: {{ .detail }}'
ErrApiConfigDisable: 'API no permitida: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'Timestamp inválido: {{ .detail }}'
ErrPasskeyDisabled: "Passkey requiere que HTTPS esté habilitado"
ErrPasskeyNotConfigured: "No hay Passkey configurado"
ErrPasskeyLimit: "Límite de Passkey alcanzado (máximo 5)"
ErrPasskeySession: "La sesión de Passkey ha expirado o no es válida"
ErrPasskeyDuplicate: "Passkey ya existe"
ErrPasskeyVerify: "Error al verificar Passkey"
# usuario y permisos
ErrNoSuchUser: "No se encontró la información del usuario. Verifique e intente de nuevo."
ErrToMaster: "Este usuario no tiene permiso para realizar esta operación en el nodo principal. Verifique e intente de nuevo."
ErrToNode: "Este usuario no tiene permiso para realizar esta operación en este nodo. Verifique e intente de nuevo."
ErrRBAC: "Permisos de usuario insuficientes. No se puede realizar esta operación. Verifique e intente de nuevo."
ErrNoneNode: "Este usuario no tiene permisos sobre ningún nodo. Contacte al administrador o verifique e intente de nuevo."
ErrOnlySuperAdmin: "Solo los superadministradores pueden usar esta función."
ErrRolePresetCannotDelete: "No se pueden eliminar los roles predefinidos del sistema"
ErrRolePresetCannotUpdate: "No se pueden actualizar los roles predefinidos del sistema"
ErrRoleBoundToUser: "Este rol ya está vinculado a usuarios y no se puede eliminar"
ErrRolePresetCannotBind: "Los roles predefinidos del sistema no se pueden asignar a usuarios"
ErrNodeAdminMasterForbidden: "No se pueden agregar permisos de administrador de nodo al nodo principal"
ErrSuperAdminCannotDelete: "No se pueden eliminar usuarios superadministradores"
ErrGroupIsDefault: "Grupo predeterminado, no se puede eliminar"
ErrGroupIsInUse: "El grupo está en uso y no se puede eliminar."
ErrAIProxyUserGroupIsDefault: "No se puede eliminar el grupo de usuarios predeterminado."
ErrAIProxyUserGroupInUse: "El grupo de usuarios está en uso y no se puede eliminar."
ErrAIProxyUserGroupExists: "El nombre del grupo de usuarios ya existe."
ErrAIProxyBackendAccountExists: "Esta cuenta de modelo ya se importó al grupo de cuentas de AI Gateway."
ErrAIProxyModelGroupInUse: "El grupo de modelos está en uso y no se puede eliminar."
ErrAIProxyModelGroupExists: "El nombre del grupo de modelos ya existe."
ErrAIProxySensitiveGroupInUse: "El grupo de palabras sensibles está en uso y no se puede eliminar."
ErrAIProxySensitiveGroupExists: "El nombre del grupo de palabras sensibles ya existe."
ErrLocalDelete: "No se puede eliminar el nodo local"
ErrPortInUsed: "El puerto {{ .name }} ya está en uso"
ErrInternalServerKey: "Error interno del servidor:"
MasterNode: "Nodo Maestro"
# app
CustomAppStoreFileValid: "El paquete de la tienda de aplicaciones debe tener formato .tar.gz"
ErrFileNotFound: "El archivo {{ .name }} no existe"
AppBackup: 'Copia de seguridad de aplicación'
AppBackupPush: 'Transferir archivo de copia de seguridad de aplicación {{.file}} al nodo {{ .name }}'
ErrSourceTargetSame: 'El nodo de origen y el nodo de destino no pueden ser el mismo'
AppInstall: 'Instalar aplicación {{ .name }} en nodo {{ .targetNode }}'
AppInstallCheck: 'Verificar entorno de instalación de aplicación'
# backup
ErrBackupInUsed: 'Cuenta de respaldo en uso por tarea programada'
ErrBackupCheck: 'Conexión de respaldo falló: {{ .err }}'
ErrBackupLocal: "La cuenta de respaldo del servidor local no admite esta operación"
ErrBackupPublic: "Se detectó que esta cuenta de respaldo no es pública, verifique e intente de nuevo"
ErrOSSConn: "No se pudo obtener la última versión, por favor revise la conectividad de red externa del servidor."
#license
# licencia
LicenseCheck: 'Comprobar si la licencia está disponible'
ErrLicenseInUsed: 'La licencia ya está vinculada. Verifique e intente de nuevo'
ErrLicenseExpired: 'La licencia ha expirado. Verifique e intente de nuevo'
@@ -79,7 +71,6 @@ ErrLicenseSync: "Sincronización de licencia fallida, no se detectó informació
ErrLicenseExist: "Este registro de licencia ya existe. Puede ir directamente a la página de licencia para la vinculación del nodo."
ErrXpackNotFound: "Esta sección requiere la edición Business. Importa una licencia en Configuración del panel > Licencia."
ErrXpackExceptional: "Esta sección requiere la edición Business. Sincroniza el estado de la licencia en Configuración del panel > Licencia."
ErrXpackOutOfDate: "La licencia actual ha expirado, importe nuevamente la licencia en Panel > Ajustes > Licencia"
ErrXpackLost: "La licencia alcanzó el límite de reintentos. Ve a Configuración del panel > Licencia y ejecuta una sincronización manual."
ErrEnterpriseLicenseRequired: "La licencia de la edición Enterprise no se ha importado. Importa primero una licencia."
ErrDeviceLost: "Faltan archivos necesarios para la verificación de licencia, verifique e intente de nuevo"
@@ -98,21 +89,6 @@ ErrNodeUnbind: "Este nodo no está dentro del rango de vinculación de la licenc
ErrNodeBind: "Este nodo ya está vinculado a una licencia, verifique e intente de nuevo"
ErrNodeLocalRollback: "El nodo principal no admite la reversión directa. Ejecuta manualmente '1pctl restore' para revertir."
ErrIntlLicense: "La versión actual aún no admite importar licencias de la edición internacional. Próximamente"
# user
ErrNoSuchUser: "No se encontró la información del usuario. Verifique e intente de nuevo."
ErrToMaster: "Este usuario no tiene permiso para realizar esta operación en el nodo principal. Verifique e intente de nuevo."
ErrToNode: "Este usuario no tiene permiso para realizar esta operación en este nodo. Verifique e intente de nuevo."
ErrRBAC: "Permisos de usuario insuficientes. No se puede realizar esta operación. Verifique e intente de nuevo."
ErrNoneNode: "Este usuario no tiene permisos sobre ningún nodo. Contacte al administrador o verifique e intente de nuevo."
ErrOnlySuperAdmin: "Solo los superadministradores pueden usar esta función."
ErrRolePresetCannotDelete: "No se pueden eliminar los roles predefinidos del sistema"
ErrRolePresetCannotUpdate: "No se pueden actualizar los roles predefinidos del sistema"
ErrRoleBoundToUser: "Este rol ya está vinculado a usuarios y no se puede eliminar"
ErrRolePresetCannotBind: "Los roles predefinidos del sistema no se pueden asignar a usuarios"
ErrNodeAdminMasterForbidden: "No se pueden agregar permisos de administrador de nodo al nodo principal"
ErrSuperAdminCannotDelete: "No se pueden eliminar usuarios superadministradores"
InvalidRequestBodyType: "Formato del cuerpo de la solicitud no válido, por favor revisa y asegúrate de que el contenido cumpla con el formato requerido antes de reintentar."
InvalidLicenseCodeType: "Formato de código de licencia no válido, por favor revisa e inténtalo de nuevo."
LicenseNotFoundType: "Licencia no encontrada, no existe ningún registro coincidente en el sistema para la licencia proporcionada. Por favor revisa e inténtalo de nuevo."
@@ -123,10 +99,64 @@ InvalidAssigneeType: "Información de usuario o dispositivo de destino no válid
LicenseUsageNotFoundType: "No se encontraron registros de uso. Esta licencia no ha sido activada o utilizada aún. Por favor revisa e inténtalo de nuevo."
LicenseUsageLimitExceededType: "Esta licencia ya está vinculada a otro nodo. Por favor revisa e inténtalo de nuevo."
# alert
# nodo y clúster
MasterNode: "Nodo Maestro"
Node: "Nodo"
ErrLocalDelete: "No se puede eliminar el nodo local"
ErrMasterDelete: "No se puede eliminar el nodo principal, elimine primero los nodos secundarios."
ClusterNameIsExist: "El nombre del clúster ya existe."
AppStatusUnHealthy: "Adquisición anormal del estado de la aplicación, revise el estado del nodo en la lista de nodos."
MasterNodePortNotAvailable: "Verificación de conectividad del puerto {{ .port }} del nodo {{ .name }} fallida, verifique la configuración del firewall/grupo de seguridad y el estado del nodo principal."
ClusterMasterNotExist: "El nodo principal del clúster está desconectado, elimine los nodos secundarios."
# máquina virtual
ErrVMNotFound: "La máquina virtual no existe"
ErrVMAlreadyExists: "La máquina virtual ya existe"
ErrVMRunning: "La máquina virtual está en ejecución"
ErrVMLibvirtConnect: "Error al conectar con libvirt: {{ .detail }}"
ErrVMPermission: "Sin permiso para operar la máquina virtual: {{ .detail }}"
ErrVMNetworkNotFound: "La red de la máquina virtual no existe: {{ .detail }}"
ErrVMInvalidPath: "Ruta de máquina virtual no válida: {{ .detail }}"
ErrVMInvalidInput: "Parámetro de máquina virtual no válido: {{ .detail }}"
ErrVMCommandFailed: "Error al ejecutar el comando de la máquina virtual: {{ .err }}"
VMSnapshotCreateTask: "Create snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]"
VMSnapshotCreateStep: "Create virtual machine snapshot [{{ .snapshot }}]"
VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]"
VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]"
VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]"
VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]"
# aplicación
CustomAppStoreFileValid: "El paquete de la tienda de aplicaciones debe tener formato .tar.gz"
AppBackup: 'Copia de seguridad de aplicación'
AppBackupPush: 'Transferir archivo de copia de seguridad de aplicación {{.file}} al nodo {{ .name }}'
ErrSourceTargetSame: 'El nodo de origen y el nodo de destino no pueden ser el mismo'
AppInstall: 'Instalar aplicación {{ .name }} en nodo {{ .targetNode }}'
AppInstallCheck: 'Verificar entorno de instalación de aplicación'
# copia de seguridad
ErrBackupInUsed: 'Cuenta de respaldo en uso por tarea programada'
ErrBackupCheck: 'Conexión de respaldo falló: {{ .err }}'
ErrBackupLocal: "La cuenta de respaldo del servidor local no admite esta operación"
ErrBackupPublic: "Se detectó que esta cuenta de respaldo no es pública, verifique e intente de nuevo"
ErrOSSConn: "No se pudo obtener la última versión, por favor revise la conectividad de red externa del servidor."
MvBackup: "Mover datos al directorio de respaldos"
# alerta
ErrAlertSync: "Error al sincronizar la información de alertas, verifique e intente de nuevo"
# task
# puerta de enlace de IA
ErrAIProxyUserGroupIsDefault: "No se puede eliminar el grupo de usuarios predeterminado."
ErrAIProxyUserGroupInUse: "El grupo de usuarios está en uso y no se puede eliminar."
ErrAIProxyUserGroupExists: "El nombre del grupo de usuarios ya existe."
ErrAIProxyBackendAccountExists: "Esta cuenta de modelo ya se importó al grupo de cuentas de AI Gateway."
ErrAIProxyModelGroupInUse: "El grupo de modelos está en uso y no se puede eliminar."
ErrAIProxyModelGroupExists: "El nombre del grupo de modelos ya existe."
ErrAIProxySensitiveGroupInUse: "El grupo de palabras sensibles está en uso y no se puede eliminar."
ErrAIProxySensitiveGroupExists: "El nombre del grupo de palabras sensibles ya existe."
AIBenchmarkRun: "Ejecutar prueba de referencia de IA"
# tarea
TaskStart: "Tarea {{ .name }} iniciada [INICIO]"
TaskEnd: "Tarea {{ .name }} finalizada [COMPLETADA]"
TaskFailed: "Tarea {{ .name }} fallida"
@@ -140,6 +170,16 @@ TaskUpgrade: "Actualizar"
TaskSync: 'Sincronizar'
TaskSyncForNode: "Sincronizar Datos del Nodo"
TaskBackup: "Respaldar"
TaskPush: "Enviar"
TaskRsync: "Sync"
SuccessStatus: "{{ .name }} correcta"
FailedStatus: "{{ .name }} fallida {{ .err }}"
Start: "Iniciar"
SubTask: "Subtarea"
Skip: "Omitir errores y continuar..."
Success: "Éxito"
# tarea por lotes de agentes
BatchInstallAgent: "Instalación masiva de agentes"
DispatchAgentInstallTasks: "Enviar tareas de instalación de agentes"
BatchUpgradeAgent: "Actualización masiva de agentes"
@@ -173,15 +213,15 @@ AgentOperateStart: "Iniciar"
AgentOperateStop: "Detener"
AgentOperateRestart: "Reiniciar"
AgentOperateDelete: "Eliminar"
SuccessStatus: "{{ .name }} correcta"
FailedStatus: "{{ .name }} fallida {{ .err }}"
Start: "Iniciar"
SubTask: "Subtarea"
Skip: "Omitir errores y continuar..."
# tarea de aplicación y habilidad
PushAppInstallTaskToNode: "Enviar tarea de instalación de aplicación al nodo [{{ .name }}]"
AppInstallTask: "Tarea de instalación de aplicación"
PushAppFailed: "Error al enviar tarea de instalación de aplicación"
SkillHubInstallTask: "Instalar skill [{{ .name }}]"
SkillHubInstallToTarget: "Instalar skill en destino [{{ .name }}]"
#script
# biblioteca de scripts
ScriptLibrary: "Biblioteca de scripts"
RemoteScriptLibrary: "Biblioteca de Scripts Remota"
ScriptSyncSkip: "La biblioteca de scripts ya está en la última versión"
@@ -189,7 +229,7 @@ DownloadData: "Descargando archivo de datos de la biblioteca de scripts data.yam
DownloadPackage: "Descargando paquete de la biblioteca de scripts"
AnalyticCompletion: "Análisis completado, sincronizando con la base de datos..."
Node: "Nodo"
# sincronización de nodos
SyncNode: "Sincronizar datos del nodo"
LocalName: "El nombre 'local' solo se utiliza para la identificación local del sistema"
SyncPackageData: "Sincronizar datos del paquete [{{ .detail }}]"
@@ -205,7 +245,7 @@ SyncCustomApp: "Aplicación personalizada"
SyncLanguage: "Idioma del sistema"
SyncEdition: "Región"
#upgrade node
# actualización de nodos
NodeUpgrade: "Actualizar nodo {{ .name }}"
UpgradeCheck: "Comprobar actualizaciones de nodos"
UpgradeCheckLocal: "Los nodos locales no admiten actualizaciones en lote, omitiendo..."
@@ -218,12 +258,11 @@ LoadAutoUpgradeNodesFailed: "Error al cargar nodos de actualización automática
AutoUpgradeNodes: "Nodos de actualización automática: {{ .nodes }}"
UpdateAutoUpgradeNodesStatusFailed: "Error al actualizar el estado de los nodos de actualización automática, error: {{ .err }}"
#add node
# agregar nodo
MasterData: "Datos del nodo principal"
LoadSftpClient: "Cargar cliente SFTP"
PackageMasterData: "Generar paquete de respaldo del nodo principal"
UploadBackup: "Subir datos de respaldo"
MvBackup: "Mover datos al directorio de respaldos"
TaskAddNode: "Agregar nodo"
LoadNodeArch: "Obtener información de arquitectura del nodo"
LoadNodeArchDetail: "Arquitectura detectada - principal: {{ .local }}, nodo secundario: {{ .node }}"
@@ -236,7 +275,35 @@ SendAgent: "Distribuir paquete de instalación del nodo"
StartService: "Iniciar servicio"
NoBackupNode: "Actualmente no hay nodo de respaldo. Seleccione uno para guardar e intente de nuevo."
#cmd
# sincronización de archivos e imágenes de nodo
LocalNodeIpFailed: "Failed to get master node IP. Edit the master node and add IP address and SSH authentication information."
HandlePrivateKey: "Handle node private key"
HandlePublicKey: "Handle node public key"
ExchangeFile: "Start syncing {{ .sourcePath }} from node {{ .source }} to node {{ .dest }}:{{ .destPath }}"
NodeFile: "Node file"
ImageSyncTaskName: "Image sync {{ .count }} images"
SSLSyncTaskName: "Certificate sync {{ .count }} certificates"
AppSyncTaskName: "App sync {{ .name }}"
SSLSyncToNode: "Push certificate [{{ .cert }}] to node [{{ .node }}]"
ImageSyncSave: "Save image {{ .image }}"
ImageSyncSaveTags: "Save image tags {{ .tags }}"
ImageSyncToNode: "Sync image {{ .image }} to node {{ .node }}"
ImageSyncLoad: "Load image {{ .image }} on node {{ .node }}"
ImageSyncClean: "Clean temporary files for image {{ .image }}"
ImageSyncNoTag: "Image {{ .image }} has no tag and will be saved by image ID"
ImageSyncFrontendTagsIgnored: "No tags were found for image {{ .image }} on the source node. Frontend tags were ignored."
ImageSyncHandlePrivateKey: "Handle image sync node private key"
ImageSyncHandlePublicKey: "Handle image sync node public key"
ImageSyncTransfer: "Start transferring image temporary file: {{ .source }}:{{ .sourcePath }} -> {{ .dest }}:{{ .destPath }}"
# línea de comandos
SudoHelper: "Por favor use {{ .cmd }} o cambie al usuario root"
UsernameNeed: "La edición Enterprise debe especificar --username"
DBConnErr: "Error: Fallo al inicializar la conexión a la base de datos, {{ .err }}"
SystemVersion: "versión: "
SystemMode: "modo: "
# línea de comandos - aplicación
AppVersion: "Versión de la aplicación"
AppCommands: "Comandos relacionados con la app"
AppInit: "Inicializar aplicación"
@@ -248,12 +315,14 @@ AppMissVersion: "Falta la versión de la app, use -v para especificar"
AppVersionExist: "La versión ya existe"
AppCreateSuccessful: "Creación finalizada con éxito"
AppWriteErr: "Error al escribir el archivo {{ .name }} {{ .err }}"
SudoHelper: "Por favor use {{ .cmd }} o cambie al usuario root"
UsernameNeed: "La edición Enterprise debe especificar --username"
# línea de comandos - IP de escucha
ListenIPCommands: "Cambiar ip de escucha"
ListenIPv4: "Escuchar en IPv4"
ListenIPv6: "Escuchar en IPv6"
ListenChangeSuccessful: "Cambio exitoso Ahora escuchando en {{ .value }}"
# línea de comandos - restablecer
ResetCommands: "Restablecer información del sistema"
ResetMFA: "Cancelar autenticación de dos factores de 1Panel"
ResetHttps: "Cancelar inicio de sesión https de 1Panel"
@@ -261,12 +330,19 @@ ResetEntrance: "Cancelar entrada segura de 1Panel"
ResetIPs: "Cancelar restricciones de IP autorizadas de 1Panel"
ResetDomain: "Cancelar vinculación de dominio de 1Panel"
ResetPasskey: "Eliminar passkeys de 1Panel"
# línea de comandos - usuario
UserList: "Obtener la lista de usuarios de 1Panel"
UserTableName: "Nombre"
UserTableMFAStatus: "Estado MFA"
UserTableSuperAdmin: "Superadministrador"
UserTableCreatedAt: "Creado en"
UserEmptyList: "No se encontraron usuarios"
UserInfo: "Obtener información del panel"
UserInfoAddr: "Dirección del panel: "
UserInfoPassHelp: "Consejo: para cambiar la contraseña, ejecute el comando: "
# línea de comandos - restaurar
RestoreCommands: "Revertir el servicio y los datos de 1Panel"
RestoreNoSuchFile: "No se encontraron archivos de reversión"
RestoreStep1: "(1/5) Iniciando la reversión del servicio y los datos de 1Panel desde el directorio {{ .name }}..."
@@ -275,6 +351,8 @@ RestoreStep3: "(3/5) Reversión del script de 1Panel completada"
RestoreStep4: "(4/5) Reversión del servicio de 1Panel completada"
RestoreStep5: "(5/5) Reversión de los datos de 1Panel completada"
RestoreSuccessful: "Reversión completada. Se volvió a {{ .version }}. Reiniciando el servicio..."
# línea de comandos - actualizar
UpdateCommands: "Actualizar información del panel"
UpdateUser: "Actualizar información del usuario"
UpdatePassword: "Actualizar contraseña del usuario"
@@ -301,28 +379,12 @@ UpdatePortResult: "Puerto del panel: {{ .name }}"
UpdatePortFirewallAdd: "Fallo al agregar la regla de puerto al firewall, {{ .err }}, agregue manualmente el puerto {{ .name }} al firewall."
UpdatePortFirewallDel: "Error: Fallo al eliminar el puerto del firewall, {{ .err }}"
UpdatePortFirewallReload: "Fallo al recargar el firewall, {{ .err }}, recargue el firewall manualmente."
UserInfo: "Obtener información del panel"
UserInfoAddr: "Dirección del panel: "
UserInfoPassHelp: "Consejo: para cambiar la contraseña, ejecute el comando: "
DBConnErr: "Error: Fallo al inicializar la conexión a la base de datos, {{ .err }}"
SystemVersion: "versión: "
SystemMode: "modo: "
#mobile app
# aplicación móvil
ErrVerifyToken: 'Error al verificar el token, por favor restablezca y escanee de nuevo.'
ErrInvalidToken: 'Token inválido, por favor restablezca y escanee de nuevo.'
ErrExpiredToken: 'El token ha expirado, por favor restablezca y escanee de nuevo.'
#cluster
ErrMasterDelete: "No se puede eliminar el nodo principal, elimine primero los nodos secundarios."
ClusterNameIsExist: "El nombre del clúster ya existe."
AppStatusUnHealthy: "Adquisición anormal del estado de la aplicación, revise el estado del nodo en la lista de nodos."
MasterNodePortNotAvailable: "Verificación de conectividad del puerto {{ .port }} del nodo {{ .name }} fallida, verifique la configuración del firewall/grupo de seguridad y el estado del nodo principal."
ClusterMasterNotExist: "El nodo principal del clúster está desconectado, elimine los nodos secundarios."
#ssl
ErrReqFailed: "{{.name}} petición fallida: {{ .err }}"
#command
# exportación de comandos
Name: "Nombre"
Command: "Comando"
+390
View File
@@ -0,0 +1,390 @@
# خطاهای پایه
ErrInvalidParams: "پارامترهای درخواست نامعتبر: {{ .detail }}"
ErrTokenParse: "خطا در تولید توکن: {{ .detail }}"
ErrInitialPassword: "رمز عبور اولیه اشتباه است"
ErrInternalServer: "خطای داخلی سرور: {{ .detail }}"
ErrInternalServerKey: "خطای داخلی سرور:"
ErrRecordExist: "رکورد از قبل وجود دارد"
ErrRecordNotFound: "رکورد یافت نشد"
ErrStructTransform: "تبدیل نوع ناموفق بود: {{ .detail }}"
ErrNotSupportType: "نوع پشتیبانی نمی‌شود: {{ .name }}"
ErrDemoEnvironment: "سرور آزمایشی، این عملیات ممنوع است!"
ErrCmdTimeout: "زمان اجرای دستور به پایان رسید!"
ErrPortInUsed: "پورت {{ .name }} قبلاً در حال استفاده است!"
ErrFileNotFound: "فایل {{ .name }} وجود ندارد"
ErrReqFailed: "درخواست {{.name}} ناموفق بود: {{ .err }}"
# درخواست و ورودی امنیتی
ErrNoSuchHost: "تفکیک میزبان ناموفق بود: {{ .err }}"
ErrHttpReqNotFound: "منبع درخواستی یافت نشد: {{ .err }}"
ErrHttpReqFailed: "درخواست ناموفق بود {{ .err }}"
ErrHttpReqTimeOut: "زمان درخواست به پایان رسید {{ .err }}"
ErrCreateHttpClient: "ایجاد درخواست ناموفق بود {{ .err }}"
ErrProxy: "درخواست ناموفق بود؛ وضعیت این گره را بررسی کنید: {{ .detail }}"
ErrProxySetting: "اطلاعات سرور پروکسی در دسترس نیست {{ .err }}، بررسی و دوباره تلاش کنید!"
ErrEntrance: "خطا در اطلاعات ورودی امنیتی، بررسی و دوباره تلاش کنید!"
ErrEntranceFormat: "ورودی امنیتی {{ .name }} پشتیبانی نمی‌شود. بررسی و دوباره تلاش کنید."
# ورود و امنیت
ErrNotLogin: "کاربر وارد نشده است: {{ .detail }}"
ErrSessionDataNotFound: "جلسه منقضی شده است"
ErrSessionDataFormat: "فرمت داده جلسه نامعتبر است"
ErrPasswordExpired: "رمز عبور منقضی شده است: {{ .detail }}"
ErrLoginLocked: "تعداد تلاش‌های ناموفق زیاد است. ورود به مدت ۵ دقیقه به طور موقت قفل شده است."
ErrApiConfigStatusInvalid: "دسترسی API غیرفعال است: {{ .detail }}"
ErrApiConfigKeyInvalid: "کلید API نامعتبر است: {{ .detail }}"
ErrApiConfigIPInvalid: "IP درخواست API در لیست سفید نیست: {{ .detail }}"
ErrApiConfigDisable: "این رابط فراخوانی API را ممنوع می‌کند: {{ .detail }}"
ErrApiConfigKeyTimeInvalid: "مهر زمانی API نامعتبر است: {{ .detail }}"
ErrPasskeyDisabled: "کلید عبور نیاز به فعال بودن HTTPS دارد"
ErrPasskeyNotConfigured: "هیچ کلید عبوری پیکربندی نشده است"
ErrPasskeyLimit: "محدودیت کلید عبور رسیده است (حداکثر ۵)"
ErrPasskeySession: "جلسه کلید عبور منقضی یا نامعتبر است"
ErrPasskeyDuplicate: "کلید عبور از قبل وجود دارد"
ErrPasskeyVerify: "تأیید کلید عبور ناموفق بود"
# کاربر و مجوزها
ErrNoSuchUser: "اطلاعات کاربر یافت نشد، بررسی و دوباره تلاش کنید!"
ErrToMaster: "این کاربر مجوز انجام این عملیات روی گره اصلی را ندارد. بررسی و دوباره تلاش کنید!"
ErrToNode: "این کاربر مجوز انجام این عملیات روی این گره را ندارد. بررسی و دوباره تلاش کنید!"
ErrRBAC: "مجوزهای کاربر ناکافی است. این عملیات قابل انجام نیست. بررسی و دوباره تلاش کنید!"
ErrNoneNode: "این کاربر هیچ مجوز گره‌ای ندارد. با مدیر تماس بگیرید یا بررسی و دوباره تلاش کنید!"
ErrOnlySuperAdmin: "فقط مدیران کل می‌توانند از این ویژگی استفاده کنند!"
ErrRolePresetCannotDelete: "نقش‌های از پیش تعیین شده سیستم قابل حذف نیستند"
ErrRolePresetCannotUpdate: "نقش‌های از پیش تعیین شده سیستم قابل به‌روزرسانی نیستند"
ErrRoleBoundToUser: "این نقش قبلاً به کاربران متصل است و قابل حذف نیست"
ErrRolePresetCannotBind: "نقش‌های از پیش تعیین شده سیستم قابل انتساب به کاربران نیستند"
ErrNodeAdminMasterForbidden: "مجوزهای مدیر گره نمی‌توانند به گره اصلی اضافه شوند"
ErrSuperAdminCannotDelete: "کاربران مدیر کل قابل حذف نیستند"
ErrGroupIsDefault: "گروه پیش‌فرض، قابل حذف نیست"
ErrGroupIsInUse: "گروه در حال استفاده است و قابل حذف نیست."
# مجوز
LicenseCheck: 'بررسی در دسترس بودن مجوز'
ErrLicenseInUsed: 'مجوز قبلاً متصل شده است. بررسی و دوباره تلاش کنید!'
ErrLicenseExpired: 'مجوز منقضی شده است. بررسی و دوباره تلاش کنید!'
ErrLicense: "خطا در فرمت مجوز، بررسی و دوباره تلاش کنید!"
ErrLicenseCheck: "تأیید مجوز ناموفق بود، بررسی و دوباره تلاش کنید!"
ErrXpackVersion: "تأیید مجوز ناموفق بود، این مجوز محدودیت نسخه دارد، قابل واردات نیست، بررسی و دوباره تلاش کنید!"
ErrLicenseSave: "ذخیره اطلاعات مجوز ناموفق بود، خطا {{ .err }}، دوباره تلاش کنید!"
ErrLicenseSync: "همگام‌سازی مجوز ناموفق بود، هیچ اطلاعات مجوزی در پایگاه داده شناسایی نشد!"
ErrLicenseExist: "این رکورد مجوز از قبل وجود دارد. می‌توانید مستقیماً به صفحه مجوز برای اتصال گره بروید."
ErrXpackNotFound: "این بخش نیاز به نسخه تجاری دارد. در تنظیمات پنل > مجوز یک مجوز وارد کنید."
ErrXpackExceptional: "این بخش نیاز به نسخه تجاری دارد. وضعیت مجوز را در تنظیمات پنل > مجوز همگام‌سازی کنید."
ErrXpackLost: "محدودیت تلاش مجدد مجوز رسیده است. به تنظیمات پنل > مجوز بروید و همگام‌سازی دستی را اجرا کنید."
ErrEnterpriseLicenseRequired: "مجوز نسخه سازمانی وارد نشده است. ابتدا یک مجوز وارد کنید."
ErrDeviceLost: "فایل‌های مورد نیاز برای تأیید مجوز وجود ندارند، بررسی و دوباره تلاش کنید!"
ErrDeviceErr: "محیط فعلی با محیط واردات مجوز مطابقت ندارد. مجوز را ویرایش کرده و دوباره وارد کنید."
ErrXpackTimeout: "زمان درخواست به پایان رسید، ممکن است اتصال شبکه ناپایدار باشد، بعداً دوباره تلاش کنید!"
ErrUnbindMaster: "گره‌هایی در مدیریت گره وجود دارند. ابتدا گره‌ها را حذف کنید، سپس مجوز فعلی را قطع اتصال کنید."
ErrFreeNodeLimit: "تعداد گره‌های قابل افزودن به حد مجاز رسیده است. خرید کرده و دوباره تلاش کنید."
ErrNodeBound: "این مجوز به گره دیگری متصل است، بررسی و دوباره تلاش کنید!"
ErrNodeBoundDelete: "این مجوز متصل است و از عملیات حذف پشتیبانی نمی‌کند. بررسی و دوباره تلاش کنید!"
ErrNodeBoundLimit: "گره رایگان فعلی به حد مجاز رسیده است، بررسی و دوباره تلاش کنید!"
ErrLicenseFree: "گره‌های رایگان فقط زمانی در دسترس هستند که مجوز به درستی به یک گره متصل باشد. تأیید کرده و دوباره تلاش کنید."
ErrLicenseUnbind: "گره‌های نسخه انجمن هنوز به این مجوز متصل هستند. آنها را در تنظیمات پنل > مجوز قطع اتصال کرده و دوباره تلاش کنید."
ErrLicenseBindCount: "تعداد گره‌های رایگان مجوز جدید کمتر از تعداد گره‌های نسخه انجمن متصل فعلی است. بررسی و دوباره تلاش کنید."
ErrNoSuchNode: "اطلاعات گره یافت نشد، بررسی و دوباره تلاش کنید!"
ErrNodeUnbind: "این گره در محدوده اتصال مجوز نیست، بررسی و دوباره تلاش کنید!"
ErrNodeBind: "این گره قبلاً به یک مجوز متصل است، بررسی و دوباره تلاش کنید!"
ErrNodeLocalRollback: "گره اصلی از بازگشت مستقیم پشتیبانی نمی‌کند. برای بازگشت به صورت دستی '1pctl restore' را اجرا کنید."
ErrIntlLicense: "نسخه فعلی از واردات مجوزهای بین‌المللی پشتیبانی نمی‌کند. منتظر به‌روزرسانی‌های بعدی باشید!"
InvalidRequestBodyType: "فرمت بدنه درخواست نامعتبر است. اطمینان حاصل کنید که محتوا با فرمت مورد نیاز مطابقت دارد و دوباره تلاش کنید."
InvalidLicenseCodeType: "فرمت کد مجوز ارائه شده نامعتبر است، بررسی و دوباره تلاش کنید!"
LicenseNotFoundType: "مجوز یافت نشد، هیچ رکورد منطبقی در سیستم برای مجوز ارائه شده وجود ندارد. بررسی و دوباره تلاش کنید!"
LicenseRevokedType: "مجوز درخواستی باطل شده است و دیگر قابل استفاده نیست. بررسی و دوباره تلاش کنید!"
LicenseExpiredType: "مجوز منقضی شده است. آن را تمدید کنید یا دوباره در تنظیمات پنل - مجوز وارد کنید و سپس دوباره تلاش کنید."
LicenseProductMismatchType: "مجوز با محصول یا سرویس درخواستی مطابقت ندارد!"
InvalidAssigneeType: "اطلاعات کاربر یا دستگاه هدف برای انتساب مجوز نامعتبر است. بررسی و دوباره تلاش کنید!"
LicenseUsageNotFoundType: "هیچ رکورد استفاده‌ای یافت نشد. این مجوز هنوز فعال یا استفاده نشده است. بررسی و دوباره تلاش کنید!"
LicenseUsageLimitExceededType: "این مجوز قبلاً به گره دیگری متصل است. بررسی و دوباره تلاش کنید!"
# گره و خوشه
MasterNode: "گره اصلی"
Node: "گره"
ErrLocalDelete: "گره محلی قابل حذف نیست!"
ErrMasterDelete: "گره اصلی قابل حذف نیست. ابتدا گره‌های برده را حذف کنید."
ClusterNameIsExist: "نام خوشه از قبل وجود دارد."
AppStatusUnHealthy: "دریافت وضعیت برنامه ناموفق بود. وضعیت گره نصب را در لیست گره‌ها بررسی کنید."
MasterNodePortNotAvailable: "بررسی اتصال پورت {{ .port }} گره {{ .name }} ناموفق بود. تنظیمات دیواره آتش/گروه امنیتی و وضعیت گره اصلی را بررسی کنید."
ClusterMasterNotExist: "گره اصلی خوشه قطع شده است. گره‌های زیرین را حذف کنید."
# ماشین مجازی
ErrVMNotFound: "ماشین مجازی وجود ندارد"
ErrVMAlreadyExists: "ماشین مجازی از قبل وجود دارد"
ErrVMRunning: "ماشین مجازی در حال اجرا است"
ErrVMLibvirtConnect: "اتصال به libvirt ناموفق بود: {{ .detail }}"
ErrVMPermission: "مجوز عملیات ماشین مجازی وجود ندارد: {{ .detail }}"
ErrVMNetworkNotFound: "شبکه ماشین مجازی وجود ندارد: {{ .detail }}"
ErrVMInvalidPath: "مسیر ماشین مجازی نامعتبر است: {{ .detail }}"
ErrVMInvalidInput: "پارامتر ماشین مجازی نامعتبر است: {{ .detail }}"
ErrVMCommandFailed: "اجرای دستور ماشین مجازی ناموفق بود: {{ .err }}"
VMSnapshotCreateTask: "ایجاد اسنپ‌شات [{{ .snapshot }}] برای ماشین مجازی [{{ .vm }}]"
VMSnapshotCreateStep: "ایجاد اسنپ‌شات ماشین مجازی [{{ .snapshot }}]"
VMSnapshotRecoverTask: "بازیابی اسنپ‌شات [{{ .snapshot }}] برای ماشین مجازی [{{ .vm }}]"
VMSnapshotRecoverStep: "بازیابی اسنپ‌شات ماشین مجازی [{{ .snapshot }}]"
VMSnapshotDeleteTask: "حذف اسنپ‌شات [{{ .snapshot }}] برای ماشین مجازی [{{ .vm }}]"
VMSnapshotDeleteStep: "حذف اسنپ‌شات ماشین مجازی [{{ .snapshot }}]"
# برنامه
CustomAppStoreFileValid: "بسته فروشگاه برنامه باید با فرمت .tar.gz باشد"
AppBackup: 'پشتیبان‌گیری از برنامه'
AppBackupPush: 'انتقال فایل پشتیبان برنامه {{.file}} به گره {{ .name }}'
ErrSourceTargetSame: 'گره منبع و گره هدف نمی‌توانند یکسان باشند!'
AppInstall: 'نصب برنامه {{ .name }} روی گره {{ .targetNode }}'
AppInstallCheck: 'بررسی محیط نصب برنامه'
# پشتیبان‌گیری
ErrBackupInUsed: "این حساب پشتیبان در وظایف زمان‌بندی استفاده می‌شود و قابل حذف نیست"
ErrBackupCheck: "تست اتصال حساب پشتیبان ناموفق بود {{ .err }}"
ErrBackupLocal: "حساب پشتیبان سرور محلی از این عملیات پشتیبانی نمی‌کند!"
ErrBackupPublic: "تشخیص داده شد که این حساب پشتیبان عمومی نیست، بررسی و دوباره تلاش کنید!"
ErrOSSConn: "امکان دریافت آخرین نسخه وجود ندارد. اتصال اینترنت سرور را بررسی کنید."
MvBackup: "انتقال داده‌ها به دایرکتوری پشتیبان"
# هشدار
ErrAlertSync: "خطا در همگام‌سازی اطلاعات هشدار، بررسی و دوباره تلاش کنید!"
# دروازه هوش مصنوعی
ErrAIProxyUserGroupIsDefault: "گروه کاربری پیش‌فرض قابل حذف نیست."
ErrAIProxyUserGroupInUse: "گروه کاربری در حال استفاده است و قابل حذف نیست."
ErrAIProxyUserGroupExists: "نام گروه کاربری از قبل وجود دارد."
ErrAIProxyBackendAccountExists: "این حساب مدل قبلاً به مجموعه حساب‌های دروازه AI وارد شده است."
ErrAIProxyModelGroupInUse: "گروه مدل در حال استفاده است و قابل حذف نیست."
ErrAIProxyModelGroupExists: "نام گروه مدل از قبل وجود دارد."
ErrAIProxySensitiveGroupInUse: "گروه کلمات حساس در حال استفاده است و قابل حذف نیست."
ErrAIProxySensitiveGroupExists: "نام گروه کلمات حساس از قبل وجود دارد."
AIBenchmarkRun: "اجرای معیار سنجش هوش مصنوعی"
# وظیفه
TaskStart: "وظیفه {{ .name }} شروع شد [شروع]"
TaskEnd: "وظیفه {{ .name }} پایان یافت [تکمیل شد]"
TaskFailed: "وظیفه {{ .name }} ناموفق بود"
TaskTimeout: "زمان وظیفه {{ .name }} به پایان رسید"
TaskSuccess: "وظیفه {{ .name }} با موفقیت انجام شد"
TaskRetry: "تلاش مجدد {{ .name }}"
SubTaskSuccess: "{{ .name }} با موفقیت انجام شد"
SubTaskFailed: "{{ .name }} ناموفق بود: {{ .err }}"
TaskInstall: "نصب"
TaskUpgrade: "ارتقاء"
TaskSync: 'همگام‌سازی'
TaskSyncForNode: "همگام‌سازی داده‌های گره"
TaskBackup: "پشتیبان‌گیری"
TaskPush: "ارسال"
TaskRsync: "همگام‌سازی"
SuccessStatus: "{{ .name }} با موفقیت انجام شد"
FailedStatus: "{{ .name }} ناموفق بود {{ .err }}"
Start: "شروع"
SubTask: "وظیفه فرعی"
Skip: "نادیده گرفتن خطاها و ادامه"
Success: "موفق"
# وظیفه دسته‌ای عامل
BatchInstallAgent: "نصب دسته‌ای عامل"
DispatchAgentInstallTasks: "ارسال وظایف نصب عامل"
BatchUpgradeAgent: "ارتقاء دسته‌ای عامل"
DispatchAgentUpgradeTasks: "ارسال وظایف ارتقاء عامل"
BatchAgentInstallDispatchFailed: "گره {{ .node }} در ارسال وظیفه نصب {{ .agentType }} ناموفق بود پیام: {{ .err }}"
BatchAgentInstallSubmitted: "گره {{ .node }} وظیفه نصب {{ .agentType }} را با موفقیت ارسال کرد!"
BatchAgentUpgradeDispatchFailed: "گره {{ .node }} در ارسال وظیفه ارتقاء {{ .agentType }} ناموفق بود پیام: {{ .err }}"
BatchAgentUpgradeAlreadyLatest: "گره {{ .node }} {{ .agent }} از قبل در نسخه هدف است، رد شد"
BatchAgentUpgradeSubmitted: "گره {{ .node }} وظیفه ارتقاء {{ .agent }} را با موفقیت ارسال کرد!"
BatchAgentUpgradeFailed: "گره {{ .node }} در ارسال وظیفه ارتقاء {{ .agent }} ناموفق بود پیام: {{ .err }}"
BatchInstallAgentSkill: "توزیع دسته‌ای مهارت"
DispatchAgentSkillInstallTasks: "ارسال وظایف نصب مهارت"
BatchAgentSkillDispatchFailed: "گره {{ .node }} در ارسال {{ .skill }} به {{ .target }} ناموفق بود پیام: {{ .err }}"
BatchAgentNoAgentsFound: "گره {{ .node }} هیچ عامل {{ .agentType }} پیدا نکرد، رد شد"
BatchAgentSkillSkipped: "گره {{ .node }} {{ .agent }} رد شد پیام: {{ .msg }}"
BatchAgentSkillInstallSubmitted: "گره {{ .node }} وظیفه نصب {{ .skill }} را به {{ .agent }} با موفقیت ارسال کرد!"
BatchAgentSkillInstallFailed: "گره {{ .node }} در ارسال وظیفه نصب {{ .skill }} به {{ .agent }} ناموفق بود پیام: {{ .err }}"
BatchAgentOperateDispatchFailed: "گره {{ .node }} در ارسال وظیفه {{ .operate }} {{ .agentType }} ناموفق بود پیام: {{ .err }}"
BatchAgentOperateSkipped: "گره {{ .node }} {{ .agent }} رد شد پیام: {{ .msg }}"
BatchAgentOperateSubmitted: "گره {{ .node }} وظیفه {{ .operate }} {{ .agent }} را با موفقیت ارسال کرد!"
BatchAgentOperateFailed: "گره {{ .node }} در ارسال وظیفه {{ .operate }} {{ .agent }} ناموفق بود پیام: {{ .err }}"
BatchStartAgent: "شروع دسته‌ای عامل"
DispatchAgentStartTasks: "ارسال وظایف شروع عامل"
BatchStopAgent: "توقف دسته‌ای عامل"
DispatchAgentStopTasks: "ارسال وظایف توقف عامل"
BatchRestartAgent: "راه‌اندازی مجدد دسته‌ای عامل"
DispatchAgentRestartTasks: "ارسال وظایف راه‌اندازی مجدد عامل"
BatchDeleteAgent: "حذف دسته‌ای عامل"
DispatchAgentDeleteTasks: "ارسال وظایف حذف عامل"
AgentOperateStart: "شروع"
AgentOperateStop: "توقف"
AgentOperateRestart: "راه‌اندازی مجدد"
AgentOperateDelete: "حذف"
# وظیفه برنامه و مهارت
PushAppInstallTaskToNode: "ارسال وظیفه نصب برنامه به گره [{{ .name }}]"
AppInstallTask: "وظیفه نصب برنامه"
PushAppFailed: "ارسال وظیفه نصب برنامه ناموفق بود"
SkillHubInstallTask: "نصب مهارت [{{ .name }}]"
SkillHubInstallToTarget: "نصب مهارت به هدف [{{ .name }}]"
# کتابخانه اسکریپت
ScriptLibrary: "کتابخانه اسکریپت"
RemoteScriptLibrary: "کتابخانه اسکریپت از راه دور"
ScriptSyncSkip: "کتابخانه اسکریپت از قبل به‌روز است"
DownloadData: "دانلود فایل داده‌های کتابخانه اسکریپت data.yaml"
DownloadPackage: "دانلود بسته کتابخانه اسکریپت"
AnalyticCompletion: "تحلیل کامل شد، در حال همگام‌سازی با پایگاه داده..."
# همگام‌سازی گره
SyncNode: "همگام‌سازی داده‌ها با گره {{ .name }}"
LocalName: "نام 'local' فقط برای شناسایی محلی سیستم استفاده می‌شود"
SyncPackageData: "همگام‌سازی داده‌های بسته"
SyncPackageEncrypt: "رمزگذاری بسته داده"
SyncRequest: "درخواست API همگام‌سازی گره"
SyncFailedRetry: "زمان همگام‌سازی داده‌های گره به پایان رسید (تلاش {{ .index }})، در حال تلاش مجدد..."
SyncFailed: "همگام‌سازی ناموفق بود، به صورت دستی در لیست گره‌ها همگام‌سازی کنید!"
SyncSystemProxy: "تنظیمات پراکسی سیستم"
SyncScripts: "کتابخانه اسکریپت"
SyncBackupAccounts: "حساب‌های پشتیبان"
SyncAlertSetting: "تنظیمات هشدار"
SyncCustomApp: "برنامه سفارشی"
SyncLanguage: "زبان سیستم"
SyncEdition: "منطقه"
# ارتقای گره
NodeUpgrade: "ارتقاء گره {{ .name }}"
UpgradeCheck: "بررسی به‌روزرسانی گره"
UpgradeCheckLocal: "گره‌های محلی از ارتقاء دسته‌ای پشتیبانی نمی‌کنند، رد شد..."
UpgradeCheckLatest: "گره از قبل آخرین نسخه است، رد شد..."
NewSSHClient: "در حال راه‌اندازی اتصال SSH"
BackupBeforeUpgrade: "پشتیبان‌گیری از داده‌ها قبل از ارتقاء"
UploadUpgradeFile: "توزیع فایل‌های ارتقاء"
RestartAfterUpgrade: "شروع سرویس پس از ارتقاء"
LoadAutoUpgradeNodesFailed: "بارگذاری گره‌های ارتقاء خودکار ناموفق بود، خطا: {{ .err }}"
AutoUpgradeNodes: "گره‌های ارتقاء خودکار: {{ .nodes }}"
UpdateAutoUpgradeNodesStatusFailed: "به‌روزرسانی وضعیت گره‌های ارتقاء خودکار ناموفق بود، خطا: {{ .err }}"
# افزودن گره
MasterData: "داده‌های گره اصلی"
LoadSftpClient: "بارگذاری کلاینت SFTP"
PackageMasterData: "تولید بسته پشتیبان گره اصلی"
UploadBackup: "بارگذاری داده‌های پشتیبان"
TaskAddNode: "افزودن گره"
LoadNodeArch: "دریافت اطلاعات معماری گره"
LoadNodeArchDetail: "معماری گره اصلی شناسایی شد: {{ .local }}، معماری گره زیرین: {{ .node }}"
LoadNodeUpgradeDetail: "استفاده از دایرکتوری نصب تاریخی نسخه v1: {{ .baseDir }}، پورت شنود سرویس: {{ .port }}"
SyncAgentBaseInfo: "همگام‌سازی اطلاعات پایه گره"
GenerateSSLInfo: "تولید اطلاعات SSL گره"
ConnInfoNotMatch: "اطلاعات اتصال مطابقت ندارد"
MakeAgentPackage: "تولید بسته نصب گره"
SendAgent: "توزیع بسته نصب گره"
StartService: "شروع سرویس"
NoBackupNode: "گره پشتیبان خالی است. ابتدا یکی را انتخاب کرده و ذخیره کنید"
# همگام‌سازی فایل و تصویر گره
LocalNodeIpFailed: "دریافت IP گره اصلی ناموفق بود. گره اصلی را ویرایش کرده و آدرس IP و اطلاعات احراز هویت SSH را اضافه کنید."
HandlePrivateKey: "مدیریت کلید خصوصی گره"
HandlePublicKey: "مدیریت کلید عمومی گره"
ExchangeFile: "شروع همگام‌سازی {{ .sourcePath }} از گره {{ .source }} به گره {{ .dest }}:{{ .destPath }}"
NodeFile: "فایل گره"
ImageSyncTaskName: "Image sync {{ .count }} images"
SSLSyncTaskName: "Certificate sync {{ .count }} certificates"
AppSyncTaskName: "App sync {{ .name }}"
SSLSyncToNode: "Push certificate [{{ .cert }}] to node [{{ .node }}]"
ImageSyncSave: "ذخیره تصویر {{ .image }}"
ImageSyncSaveTags: "ذخیره برچسب‌های تصویر {{ .tags }}"
ImageSyncToNode: "همگام‌سازی تصویر {{ .image }} به گره {{ .node }}"
ImageSyncLoad: "بارگذاری تصویر {{ .image }} روی گره {{ .node }}"
ImageSyncClean: "پاک‌سازی فایل‌های موقت تصویر {{ .image }}"
ImageSyncNoTag: "تصویر {{ .image }} هیچ برچسبی ندارد و با شناسه تصویر ذخیره می‌شود"
ImageSyncFrontendTagsIgnored: "هیچ برچسبی برای تصویر {{ .image }} در گره منبع یافت نشد. برچسب‌های فرانت‌اند نادیده گرفته شدند."
ImageSyncHandlePrivateKey: "مدیریت کلید خصوصی گره همگام‌سازی تصویر"
ImageSyncHandlePublicKey: "مدیریت کلید عمومی گره همگام‌سازی تصویر"
ImageSyncTransfer: "شروع انتقال فایل موقت تصویر: {{ .source }}:{{ .sourcePath }} -> {{ .dest }}:{{ .destPath }}"
# خط فرمان
SudoHelper: "از {{ .cmd }} استفاده کنید یا به کاربر root تغییر دهید"
UsernameNeed: "نسخه سازمانی باید --username را مشخص کند"
DBConnErr: "خطا: راه‌اندازی اتصال پایگاه داده ناموفق بود، {{ .err }}"
SystemVersion: "نسخه: "
SystemMode: "حالت: "
# خط فرمان - برنامه
AppVersion: "نسخه برنامه"
AppCommands: "دستورات مرتبط با برنامه"
AppInit: "راه‌اندازی برنامه"
AppKeyVal: "کلید برنامه (فقط انگلیسی پشتیبانی می‌شود)"
AppCreateFileErr: "ایجاد فایل {{ .name }} ناموفق بود {{ .err }}"
AppCreateDirErr: "ایجاد پوشه {{ .name }} ناموفق بود {{ .err }}"
AppMissKey: "کلید برنامه وجود ندارد، از -k برای مشخص کردن استفاده کنید"
AppMissVersion: "نسخه برنامه وجود ندارد، از -v برای مشخص کردن استفاده کنید"
AppVersionExist: "نسخه از قبل وجود دارد!"
AppCreateSuccessful: "ایجاد با موفقیت انجام شد!"
AppWriteErr: "نوشتن فایل {{ .name }} ناموفق بود {{ .err }}"
# خط فرمان - IP شنود
ListenIPCommands: "تغییر IP شنود"
ListenIPv4: "شنود روی IPv4"
ListenIPv6: "شنود روی IPv6"
ListenChangeSuccessful: "تغییر با موفقیت انجام شد! در حال شنود روی {{ .value }}"
# خط فرمان - بازنشانی
ResetCommands: "بازنشانی اطلاعات سیستم"
ResetMFA: "لغو احراز هویت دو مرحله‌ای 1Panel"
ResetHttps: "لغو ورود https 1Panel"
ResetEntrance: "لغو ورودی امنیتی 1Panel"
ResetIPs: "لغو محدودیت‌های IP مجاز 1Panel"
ResetDomain: "لغو اتصال دامنه 1Panel"
ResetPasskey: "پاک‌سازی کلیدهای عبور 1Panel"
# خط فرمان - کاربر
UserList: "دریافت لیست کاربران 1Panel"
UserTableName: "نام"
UserTableMFAStatus: "وضعیت MFA"
UserTableSuperAdmin: "مدیر کل"
UserTableCreatedAt: "تاریخ ایجاد"
UserEmptyList: "هیچ کاربری یافت نشد"
UserInfo: "دریافت اطلاعات پنل"
UserInfoAddr: "آدرس پنل: "
UserInfoPassHelp: "نکته: برای تغییر رمز عبور، می‌توانید دستور را اجرا کنید: "
# خط فرمان - بازیابی
RestoreCommands: "بازگشت سرویس و داده‌های 1Panel"
RestoreNoSuchFile: "هیچ فایل بازگشتی یافت نشد"
RestoreStep1: "(۱/۵) شروع بازگشت سرویس و داده‌های 1Panel از دایرکتوری {{ .name }}..."
RestoreStep2: "(۲/۵) بازگشت باینری 1Panel تکمیل شد"
RestoreStep3: "(۳/۵) بازگشت اسکریپت 1Panel تکمیل شد"
RestoreStep4: "(۴/۵) بازگشت سرویس 1Panel تکمیل شد"
RestoreStep5: "(۵/۵) بازگشت داده‌های 1Panel تکمیل شد"
RestoreSuccessful: "بازگشت تکمیل شد. به نسخه {{ .version }} بازگردانده شد. در حال راه‌اندازی مجدد سرویس..."
# خط فرمان - به‌روزرسانی
UpdateCommands: "به‌روزرسانی اطلاعات پنل"
UpdateUser: "به‌روزرسانی اطلاعات کاربر"
UpdatePassword: "به‌روزرسانی رمز عبور کاربر"
UpdatePort: "به‌روزرسانی پورت پنل"
UpdateUserNull: "خطا: کاربر خالی است!"
UpdateUserBlank: "خطا: کاربر شامل فاصله است!"
UpdateUserFormat: "خطا: فرمت کاربر نامعتبر است! فقط انگلیسی، چینی، اعداد و _ با طول ۳-۳۰ پشتیبانی می‌شود"
UpdateUserErr: "خطا: به‌روزرسانی کاربر ناموفق بود، {{ .err }}"
UpdateSuccessful: "به‌روزرسانی با موفقیت انجام شد!"
UpdateUserResult: "کاربر: {{ .name }}"
UpdatePasswordRead: "خطا: خواندن اطلاعات رمز عبور کاربر ناموفق بود، {{ .err }}"
UpdatePasswordNull: "خطا: رمز عبور کاربر خالی است!"
UpdateUPasswordBlank: "خطا: رمز عبور کاربر شامل فاصله است!"
UpdatePasswordFormat: "خطا: رمز عبور کاربر فقط از حروف، اعداد و کاراکترهای ویژه (!@#$%*_,.?) با طول ۸-۳۰ پشتیبانی می‌کند."
UpdatePasswordLen: "خطا: رمز عبور بیشتر از ۶ کاراکتر وارد کنید"
UpdatePasswordRe: "تکرار رمز عبور:"
UpdatePasswordErr: "خطا: به‌روزرسانی رمز عبور کاربر ناموفق بود، {{ .err }}"
UpdatePasswordSame: "خطا: دو رمز عبور مطابقت ندارند، بررسی و دوباره تلاش کنید!"
UpdatePasswordResult: "رمز عبور کاربر: {{ .name }}"
UpdatePortFormat: "خطا: شماره پورت ورودی باید بین ۱ تا ۶۵۵۳۵ باشد!"
UpdatePortUsed: "خطا: شماره پورت قبلاً در حال استفاده است، بررسی و دوباره تلاش کنید!"
UpdatePortErr: "خطا: به‌روزرسانی پورت پنل ناموفق بود، {{ .err }}"
UpdatePortResult: "پورت پنل: {{ .name }}"
UpdatePortFirewallAdd: "افزودن قانون پورت دیواره آتش ناموفق بود، {{ .err }}، پورت {{ .name }} را به صورت دستی به قوانین دیواره آتش اضافه کنید."
UpdatePortFirewallDel: "خطا: حذف پورت دیواره آتش ناموفق بود، {{ .err }}"
UpdatePortFirewallReload: "بارگذاری مجدد دیواره آتش ناموفق بود، {{ .err }}، دیواره آتش را به صورت دستی بارگذاری مجدد کنید."
# برنامه موبایل
ErrVerifyToken: 'تأیید توکن ناموفق بود. بازنشانی کرده و دوباره اسکن کنید.'
ErrInvalidToken: 'توکن نامعتبر است. بازنشانی کرده و دوباره اسکن کنید.'
ErrExpiredToken: 'توکن منقضی شده است. بازنشانی کرده و دوباره اسکن کنید.'
# خروجی فرمان
Name: "نام"
Command: "دستور"
+158 -96
View File
@@ -1,69 +1,65 @@
# 基本エラー
ErrInvalidParams: '不正なリクエスト: {{ .detail }}'
ErrTokenParse: 'トークンエラー: {{ .detail }}'
ErrInitialPassword: '元のパスワードが違います'
ErrInternalServer: '内部エラー: {{ .detail }}'
ErrInternalServerKey: "サーバー内部エラー: "
ErrRecordExist: 'レコードが既に存在します'
ErrRecordNotFound: 'レコードが見つかりません'
ErrStructTransform: '型変換エラー: {{ .err }}'
ErrNotLogin: 'ログインしていません: {{ .detail }}'
ErrSessionDataNotFound: "現在のセッションは期限切れです!"
ErrSessionDataFormat: "現在のセッションデータ形式が異常です!"
ErrPasswordExpired: 'パスワード期限切れ: {{ .detail }}'
ErrNotSupportType: '未対応のタイプ: {{ .name }}'
ErrProxy: 'リクエストに失敗しました: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API利用不可: {{ .detail }}'
ErrApiConfigKeyInvalid: 'APIキーが無効です: {{ .detail }}'
ErrApiConfigIPInvalid: 'IPが許可されていません: {{ .detail }}'
ErrApiConfigDisable: 'APIは無効です: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'タイムスタンプが無効です: {{ .detail }}'
ErrLoginLocked: "失敗回数が多すぎます。ログインは 5 分間一時的にロックされています。"
ErrDemoEnvironment: 'デモ環境では利用できません'
ErrCmdTimeout: 'コマンドがタイムアウトしました'
ErrPortInUsed: "{{ .name }} ポートはすでに使用されています!"
ErrFileNotFound: "{{ .name }} ファイルが存在しません"
ErrReqFailed: "{{.name}} リクエスト失敗: {{ .err }}"
# request
# リクエストとセキュリティ入口
ErrNoSuchHost: "要求されたサーバーが見つかりません {{ .err }}"
ErrHttpReqNotFound: "要求されたリソースが見つかりません {{ .err }}"
ErrHttpReqFailed: "リクエスト失敗 {{ .err }}"
ErrHttpReqTimeOut: "リクエストタイムアウト {{ .err }}"
ErrCreateHttpClient: "リクエストの作成に失敗しました {{ .err }}"
ErrProxy: 'リクエストに失敗しました: {{ .detail }}'
ErrProxySetting: "プロキシサーバー情報が利用できません {{ .err }}、確認して再試行してください!"
ErrEntrance: "セキュリティ情報エラー、再確認してください!"
ErrEntranceFormat: "セキュリティエントランス {{ .name }} は現在サポートされていません。確認して再試行してください!"
# common
ErrDemoEnvironment: 'デモ環境では利用できません'
ErrCmdTimeout: 'コマンドがタイムアウトしました'
ErrEntrance: "セキュリティ情報エラー、再確認してください!"
# ログインとセキュリティ
ErrNotLogin: 'ログインしていません: {{ .detail }}'
ErrSessionDataNotFound: "現在のセッションは期限切れです!"
ErrSessionDataFormat: "現在のセッションデータ形式が異常です!"
ErrPasswordExpired: 'パスワード期限切れ: {{ .detail }}'
ErrLoginLocked: "失敗回数が多すぎます。ログインは 5 分間一時的にロックされています。"
ErrApiConfigStatusInvalid: 'API利用不可: {{ .detail }}'
ErrApiConfigKeyInvalid: 'APIキーが無効です: {{ .detail }}'
ErrApiConfigIPInvalid: 'IPが許可されていません: {{ .detail }}'
ErrApiConfigDisable: 'APIは無効です: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'タイムスタンプが無効です: {{ .detail }}'
ErrPasskeyDisabled: "Passkeyを使用するにはHTTPSを有効にする必要があります"
ErrPasskeyNotConfigured: "Passkeyが設定されていません"
ErrPasskeyLimit: "Passkeyの上限に達しました(最大5個)"
ErrPasskeySession: "Passkeyセッションの有効期限が切れているか無効です"
ErrPasskeyDuplicate: "Passkeyは既に存在します"
ErrPasskeyVerify: "Passkeyの検証に失敗しました"
# ユーザーと権限
ErrNoSuchUser: "ユーザー情報が見つかりません。確認して再試行してください!"
ErrToMaster: "このユーザーにはマスターノードでこの操作を実行する権限がありません。確認して再試行してください!"
ErrToNode: "このユーザーにはこのノードでこの操作を実行する権限がありません。確認して再試行してください!"
ErrRBAC: "ユーザー権限が不足しているため、この操作を実行できません。確認して再試行してください!"
ErrNoneNode: "このユーザーにはノード権限がありません。管理者に連絡するか、確認して再試行してください!"
ErrOnlySuperAdmin: "この機能はスーパー管理者のみ使用できます!"
ErrRolePresetCannotDelete: "システム既定ロールは削除できません"
ErrRolePresetCannotUpdate: "システム既定ロールは更新できません"
ErrRoleBoundToUser: "このロールはすでにユーザーに紐付いているため削除できません"
ErrRolePresetCannotBind: "システム既定ロールはユーザーに割り当てできません"
ErrNodeAdminMasterForbidden: "マスターノードにノード管理者権限を追加することはできません"
ErrSuperAdminCannotDelete: "スーパー管理者ユーザーは削除できません"
ErrGroupIsDefault: "デフォルトグループの削除はできません"
ErrGroupIsInUse: "グループは使用中のため、削除できません。"
ErrAIProxyUserGroupIsDefault: "デフォルトユーザーグループは削除できません"
ErrAIProxyUserGroupInUse: "ユーザーグループは使用中のため、削除できません。"
ErrAIProxyUserGroupExists: "ユーザーグループ名は既に存在します。"
ErrAIProxyBackendAccountExists: "このモデルアカウントは既に AI ゲートウェイのアカウントプールにインポートされています。"
ErrAIProxyModelGroupInUse: "モデルグループは使用中のため、削除できません。"
ErrAIProxyModelGroupExists: "モデルグループ名は既に存在します。"
ErrAIProxySensitiveGroupInUse: "センシティブワードグループは使用中のため削除できません。"
ErrAIProxySensitiveGroupExists: "センシティブワードグループ名は既に存在します。"
ErrLocalDelete: "ローカルノードは削除できません!"
ErrPortInUsed: "{{ .name }} ポートはすでに使用されています!"
ErrInternalServerKey: "サーバー内部エラー: "
MasterNode: "マスターノード"
# app
CustomAppStoreFileValid: "アプリストアパッケージは .tar.gz 形式である必要があります"
ErrFileNotFound: "{{ .name }} ファイルが存在しません"
AppBackup: 'アプリケーションバックアップ'
AppBackupPush: 'アプリケーションバックアップファイル {{.file}} をノード {{ .name }} に転送'
ErrSourceTargetSame: 'ソースノードとターゲットノードは同じにできません!'
AppInstall: 'ノード {{ .targetNode }} にアプリケーション {{ .name }} をインストール'
AppInstallCheck: 'アプリケーションインストール環境を確認'
# backup
ErrBackupInUsed: 'バックアップアカウントがスケジュールで使用中'
ErrBackupCheck: '接続テストに失敗しました: {{ .err }}'
ErrBackupLocal: "ローカルサーバーバックアップアカウントはこの操作をサポートしていません!"
ErrBackupPublic: "このバックアップアカウントが公開されていないと検出されました。再確認してください!"
ErrOSSConn: "最新バージョンを取得できません。外部ネットワーク接続を確認してください。"
#license
# ライセンス
LicenseCheck: 'ライセンスが利用可能か確認する'
ErrLicenseInUsed: 'このライセンスは既に紐付けられています。確認して再試行してください!'
ErrLicenseExpired: 'このライセンスは期限切れです。確認して再試行してください!'
@@ -93,21 +89,6 @@ ErrNodeUnbind: "そのノードはライセンスのバインド範囲内では
ErrNodeBind: "そのノードはライセンスにバインドされています、確認して再試行してください"
ErrNodeLocalRollback: "マスターノードでは直接ロールバックできません。'1pctl restore' を手動で実行してロールバックしてください"
ErrIntlLicense: "現在のバージョンでは国際版ライセンスのインポートはまだサポートされていません。しばらくお待ちください!"
# user
ErrNoSuchUser: "ユーザー情報が見つかりません。確認して再試行してください!"
ErrToMaster: "このユーザーにはマスターノードでこの操作を実行する権限がありません。確認して再試行してください!"
ErrToNode: "このユーザーにはこのノードでこの操作を実行する権限がありません。確認して再試行してください!"
ErrRBAC: "ユーザー権限が不足しているため、この操作を実行できません。確認して再試行してください!"
ErrNoneNode: "このユーザーにはノード権限がありません。管理者に連絡するか、確認して再試行してください!"
ErrOnlySuperAdmin: "この機能はスーパー管理者のみ使用できます!"
ErrRolePresetCannotDelete: "システム既定ロールは削除できません"
ErrRolePresetCannotUpdate: "システム既定ロールは更新できません"
ErrRoleBoundToUser: "このロールはすでにユーザーに紐付いているため削除できません"
ErrRolePresetCannotBind: "システム既定ロールはユーザーに割り当てできません"
ErrNodeAdminMasterForbidden: "マスターノードにノード管理者権限を追加することはできません"
ErrSuperAdminCannotDelete: "スーパー管理者ユーザーは削除できません"
InvalidRequestBodyType: "リクエストボディの形式が無効です。内容が要求された形式に準拠しているか確認してから再試行してください!"
InvalidLicenseCodeType: "提供されたライセンスコードの形式が無効です。確認してから再試行してください!"
LicenseNotFoundType: "ライセンスが見つかりません。提供されたライセンスに一致する記録がシステム内に存在しません。確認してから再試行してください!"
@@ -118,10 +99,64 @@ InvalidAssigneeType: "ライセンス割り当ての対象ユーザーまたは
LicenseUsageNotFoundType: "使用記録がありません。このライセンスはまだ有効化または使用されていません。確認してから再試行してください!"
LicenseUsageLimitExceededType: "このライセンスは既に他のノードにバインドされています。確認してから再試行してください!"
# alert
# ノードとクラスター
MasterNode: "マスターノード"
Node: "ノード"
ErrLocalDelete: "ローカルノードは削除できません!"
ErrMasterDelete: "マスターノードを削除できません。スレーブノードを先に削除してください。"
ClusterNameIsExist: "クラスタ名は既に存在します。"
AppStatusUnHealthy: "アプリケーションのステータス取得が異常です。ノードリストでインストールノードのステータスを確認してください。"
MasterNodePortNotAvailable: "ノード {{ .name }} のポート {{ .port }} の接続性検証が失敗しました。ファイアウォール/セキュリティグループの設定とマスターノードのステータスを確認してください。"
ClusterMasterNotExist: "クラスタのマスターノードが切断されています。子ノードを削除してください。"
# 仮想マシン
ErrVMNotFound: "仮想マシンが存在しません"
ErrVMAlreadyExists: "仮想マシンは既に存在します"
ErrVMRunning: "仮想マシンは実行中です"
ErrVMLibvirtConnect: "libvirt への接続に失敗しました: {{ .detail }}"
ErrVMPermission: "仮想マシンを操作する権限がありません: {{ .detail }}"
ErrVMNetworkNotFound: "仮想マシンネットワークが存在しません: {{ .detail }}"
ErrVMInvalidPath: "仮想マシンのパスが無効です: {{ .detail }}"
ErrVMInvalidInput: "仮想マシンのパラメータが無効です: {{ .detail }}"
ErrVMCommandFailed: "仮想マシンコマンドの実行に失敗しました: {{ .err }}"
VMSnapshotCreateTask: "Create snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]"
VMSnapshotCreateStep: "Create virtual machine snapshot [{{ .snapshot }}]"
VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]"
VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]"
VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]"
VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]"
# アプリ
CustomAppStoreFileValid: "アプリストアパッケージは .tar.gz 形式である必要があります"
AppBackup: 'アプリケーションバックアップ'
AppBackupPush: 'アプリケーションバックアップファイル {{.file}} をノード {{ .name }} に転送'
ErrSourceTargetSame: 'ソースノードとターゲットノードは同じにできません!'
AppInstall: 'ノード {{ .targetNode }} にアプリケーション {{ .name }} をインストール'
AppInstallCheck: 'アプリケーションインストール環境を確認'
# バックアップ
ErrBackupInUsed: 'バックアップアカウントがスケジュールで使用中'
ErrBackupCheck: '接続テストに失敗しました: {{ .err }}'
ErrBackupLocal: "ローカルサーバーバックアップアカウントはこの操作をサポートしていません!"
ErrBackupPublic: "このバックアップアカウントが公開されていないと検出されました。再確認してください!"
ErrOSSConn: "最新バージョンを取得できません。外部ネットワーク接続を確認してください。"
MvBackup: "データをバックアップディレクトリに移動"
# アラート
ErrAlertSync: "アラート情報の同期エラーです。後で再試行してください!"
#task
# AIゲートウェイ
ErrAIProxyUserGroupIsDefault: "デフォルトユーザーグループは削除できません"
ErrAIProxyUserGroupInUse: "ユーザーグループは使用中のため、削除できません。"
ErrAIProxyUserGroupExists: "ユーザーグループ名は既に存在します。"
ErrAIProxyBackendAccountExists: "このモデルアカウントは既に AI ゲートウェイのアカウントプールにインポートされています。"
ErrAIProxyModelGroupInUse: "モデルグループは使用中のため、削除できません。"
ErrAIProxyModelGroupExists: "モデルグループ名は既に存在します。"
ErrAIProxySensitiveGroupInUse: "センシティブワードグループは使用中のため削除できません。"
ErrAIProxySensitiveGroupExists: "センシティブワードグループ名は既に存在します。"
AIBenchmarkRun: "AIベンチマークを実行"
# タスク
TaskStart: "{{ .name }} タスク開始 [START]"
TaskEnd: "{{ .name }} タスク終了 [COMPLETED]"
TaskFailed: "{{ .name }} タスク失敗"
@@ -135,6 +170,16 @@ TaskUpgrade: "アップグレード"
TaskSync: '同期'
TaskSyncForNode: "ノードデータを同期"
TaskBackup: "バックアップ"
TaskPush: "送信"
TaskRsync: "Sync"
SuccessStatus: "{{ .name }} 成功"
FailedStatus: "{{ .name }} 失敗 {{ .err }}"
Start: "開始"
SubTask: "サブタスク"
Skip: "エラーを無視して続行..."
Success: "成功"
# エージェント一括タスク
BatchInstallAgent: "エージェントの一括インストール"
DispatchAgentInstallTasks: "エージェントインストールタスクを配信"
BatchUpgradeAgent: "エージェントの一括アップグレード"
@@ -168,20 +213,15 @@ AgentOperateStart: "開始"
AgentOperateStop: "停止"
AgentOperateRestart: "再起動"
AgentOperateDelete: "削除"
SuccessStatus: "{{ .name }} 成功"
FailedStatus: "{{ .name }} 失敗 {{ .err }}"
Start: "開始"
SubTask: "サブタスク"
Skip: "エラーを無視して続行..."
# アプリとスキルのタスク
PushAppInstallTaskToNode: "ノード [{{ .name }}] にアプリインストールタスクを送信"
TaskPush: "送信"
AppInstallTask: "アプリインストールタスク"
PushAppFailed: "アプリインストールタスクの送信に失敗しました"
SkillHubInstallTask: "スキルをインストール [{{ .name }}]"
SkillHubInstallToTarget: "ターゲットにスキルをインストール [{{ .name }}]"
Success: "成功"
#script
# スクリプトライブラリ
ScriptLibrary: "スクリプトライブラリ"
RemoteScriptLibrary: "リモートスクリプトライブラリ"
ScriptSyncSkip: "現在のスクリプトライブラリは最新バージョンです!"
@@ -189,7 +229,7 @@ DownloadData: "スクリプトライブラリファイル data.yaml をダウン
DownloadPackage: "スクリプトライブラリパッケージをダウンロード中"
AnalyticCompletion: "解析が完了しました、データベースに同期しています..."
Node: "ノード"
# ノード同期
SyncNode: "データをノード {{ .name }} に同期"
LocalName: "'local'名称はシステムのローカル識別のみに使用されます"
SyncPackageData: "同期データのパッケージ化"
@@ -205,7 +245,7 @@ SyncCustomApp: "カスタムアプリ"
SyncLanguage: "システム言語"
SyncEdition: "実行リージョン"
#upgrade node
# ノードアップグレード
NodeUpgrade: "{{ .name }} ノードのアップグレード"
UpgradeCheck: "ノードの更新を確認"
UpgradeCheckLocal: "ローカルノードは一括アップグレードをサポートしていないため、スキップします..."
@@ -218,12 +258,11 @@ LoadAutoUpgradeNodesFailed: "自動アップグレードノードの読み込み
AutoUpgradeNodes: "自動アップグレードノード: {{ .nodes }}"
UpdateAutoUpgradeNodesStatusFailed: "自動アップグレードノードのステータス更新に失敗しました、エラー: {{ .err }}"
#add node
# ノード追加
MasterData: "マスターノードデータ"
LoadSftpClient: "SFTPクライアントの取得"
PackageMasterData: "マスターノードのバックアップパッケージを生成"
UploadBackup: "バックアップデータをアップロード"
MvBackup: "データをバックアップディレクトリに移動"
TaskAddNode: "ノードを追加"
LoadNodeArch: "ノードアーキテクチャ情報を取得"
LoadNodeArchDetail: "検出されたマスターノードアーキテクチャ: {{ .local }}, 子ノードアーキテクチャ: {{ .node }}"
@@ -236,7 +275,35 @@ SendAgent: "ノードインストールパッケージを送信"
StartService: "サービスを開始"
NoBackupNode: "現在バックアップノードが空です。保存するバックアップノードを選択して再試行してください!"
#cmd
# ノードファイルとイメージ同期
LocalNodeIpFailed: "Failed to get master node IP. Edit the master node and add IP address and SSH authentication information."
HandlePrivateKey: "Handle node private key"
HandlePublicKey: "Handle node public key"
ExchangeFile: "Start syncing {{ .sourcePath }} from node {{ .source }} to node {{ .dest }}:{{ .destPath }}"
NodeFile: "Node file"
ImageSyncTaskName: "Image sync {{ .count }} images"
SSLSyncTaskName: "Certificate sync {{ .count }} certificates"
AppSyncTaskName: "App sync {{ .name }}"
SSLSyncToNode: "Push certificate [{{ .cert }}] to node [{{ .node }}]"
ImageSyncSave: "Save image {{ .image }}"
ImageSyncSaveTags: "Save image tags {{ .tags }}"
ImageSyncToNode: "Sync image {{ .image }} to node {{ .node }}"
ImageSyncLoad: "Load image {{ .image }} on node {{ .node }}"
ImageSyncClean: "Clean temporary files for image {{ .image }}"
ImageSyncNoTag: "Image {{ .image }} has no tag and will be saved by image ID"
ImageSyncFrontendTagsIgnored: "No tags were found for image {{ .image }} on the source node. Frontend tags were ignored."
ImageSyncHandlePrivateKey: "Handle image sync node private key"
ImageSyncHandlePublicKey: "Handle image sync node public key"
ImageSyncTransfer: "Start transferring image temporary file: {{ .source }}:{{ .sourcePath }} -> {{ .dest }}:{{ .destPath }}"
# コマンドライン
SudoHelper: "{{.cmd}}を使用するか、ルートユーザーに切り替えてください"
UsernameNeed: "Enterprise版では --username を指定する必要があります"
DBConnErr: "エラー: データベース接続の初期化に失敗しました、{{ .err }}"
SystemVersion: "バージョン: "
SystemMode: "モード: "
# コマンドライン - アプリ
AppVersion: "アプリバージョン"
AppCommands: "アプリ関連のコマンド"
AppInit: "アプリを初期化します"
@@ -248,12 +315,14 @@ AppMissVersion: "アプリバージョンがありません、-vを使用して
AppVersionExist: "バージョンはすでに存在します!"
AppCreateSuccessful: "創造は成功しました!"
AppWriteErr: "file {{ .name }} write failed {{ .err }}"
SudoHelper: "{{.cmd}}を使用するか、ルートユーザーに切り替えてください"
UsernameNeed: "Enterprise版では --username を指定する必要があります"
# コマンドライン - 監視IP
ListenIPCommands: "リスニングIPを切り替えます"
ListenIPv4: "IPv4で聞いてください"
ListenIPv6: "IPv6で聞く"
ListenChangeSuccessful: "切り替え成功!{{.Value}}で聞いています"
# コマンドライン - リセット
ResetCommands: "システム情報をリセットします"
ResetMFA: "1パネルの2要素認証をキャンセルします"
ResetHttps: "1Panel HTTPSログインをキャンセルします"
@@ -261,12 +330,19 @@ ResetEntrance: "1パネルの安全な入り口をキャンセルします"
ResetIPs: "1パネル認定IP制限をキャンセルします"
ResetDomain: "1パネルドメインバインディングをキャンセルします"
ResetPasskey: "1Panel のパスキーをクリアします"
# コマンドライン - ユーザー
UserList: "1Panel ユーザー一覧を取得します"
UserTableName: "名前"
UserTableMFAStatus: "MFA ステータス"
UserTableSuperAdmin: "スーパー管理者"
UserTableCreatedAt: "作成日時"
UserEmptyList: "ユーザーが見つかりません"
UserInfo: "パネル情報を取得します"
UserInfoAddr: "パネルアドレス: "
UserInfoPassHelp: "ヒント: パスワードを変更するには、コマンドを実行できます。"
# コマンドライン - 復元
RestoreCommands: "1Panel のサービスとデータをロールバック"
RestoreNoSuchFile: "ロールバックに使えるファイルが見つかりません"
RestoreStep1: "(1/5){{ .name }} ディレクトリから 1Panel のサービスとデータのロールバックを開始しています..."
@@ -275,6 +351,8 @@ RestoreStep3: "(3/5)1Panel スクリプトのロールバックが完了し
RestoreStep4: "(4/5)1Panel サービスのロールバックが完了しました"
RestoreStep5: "(5/5)1Panel データのロールバックが完了しました"
RestoreSuccessful: "ロールバックが完了しました。{{ .version }} に戻し、サービスを再起動しています..."
# コマンドライン - 更新
UpdateCommands: "パネル情報を更新します"
UpdateUser: "ユーザー情報を更新します"
UpdatePassword: "ユーザーパスワードを更新します"
@@ -301,28 +379,12 @@ UpdatePortResult: "パネルポート: {{ .name }}"
UpdatePortFirewallAdd: "ファイアウォールポートルールの追加に失敗した{{ .err }}、ファイアウォールルールに{{ .name }}ポートを手動で追加してください。"
UpdatePortFirewallDel: "エラー: ファイアウォールポートの削除に失敗しました、{{ .err }}"
UpdatePortFirewallReload: "ファイアウォールのリロードに失敗しました{{ .err }}、ファイアウォールを手動でリロードしてください。"
UserInfo: "パネル情報を取得します"
UserInfoAddr: "パネルアドレス: "
UserInfoPassHelp: "ヒント: パスワードを変更するには、コマンドを実行できます。"
DBConnErr: "エラー: データベース接続の初期化に失敗しました、{{ .err }}"
SystemVersion: "バージョン: "
SystemMode: "モード: "
#mobile app
# モバイルアプリ
ErrVerifyToken: 'トークンの検証エラーです。リセット後、再度QRコードをスキャンしてください。'
ErrInvalidToken: '無効なトークンです。リセット後、再度QRコードをスキャンしてください。'
ErrExpiredToken: 'トークンの有効期限が切れました。リセット後、再度QRコードをスキャンしてください。'
#cluster
ErrMasterDelete: "マスターノードを削除できません。スレーブノードを先に削除してください。"
ClusterNameIsExist: "クラスタ名は既に存在します。"
AppStatusUnHealthy: "アプリケーションのステータス取得が異常です。ノードリストでインストールノードのステータスを確認してください。"
MasterNodePortNotAvailable: "ノード {{ .name }} のポート {{ .port }} の接続性検証が失敗しました。ファイアウォール/セキュリティグループの設定とマスターノードのステータスを確認してください。"
ClusterMasterNotExist: "クラスタのマスターノードが切断されています。子ノードを削除してください。"
#ssl
ErrReqFailed: "{{.name}} リクエスト失敗: {{ .err }}"
#command
# コマンドエクスポート
Name: "名前"
Command: "コマンド"

Some files were not shown because too many files have changed in this diff Show More