mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-12 08:00:34 +00:00
Compare commits
62
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c04848a92a | ||
|
|
748f5f070a | ||
|
|
2dcb367622 | ||
|
|
26bd2d08be | ||
|
|
739686ff89 | ||
|
|
68a871a5d7 | ||
|
|
b47bba4827 | ||
|
|
3b91859a49 | ||
|
|
c417bc9c2b | ||
|
|
7c95becfeb | ||
|
|
6b1480c5fa | ||
|
|
7f450544e1 | ||
|
|
0c046edfe5 | ||
|
|
2a2e6607b8 | ||
|
|
f6ed11ac55 | ||
|
|
d330f60b2d | ||
|
|
10c8cafcee | ||
|
|
d1534c88aa | ||
|
|
eb6b4c7cb7 | ||
|
|
c77ddcfebe | ||
|
|
af12526a6c | ||
|
|
b347abe100 | ||
|
|
092b57413b | ||
|
|
e8f35e4e06 | ||
|
|
b019e7526d | ||
|
|
1b06467521 | ||
|
|
7f3fd0cb1d | ||
|
|
3e9848554b | ||
|
|
b02cdbc5f9 | ||
|
|
d7723fd42a | ||
|
|
8b265bb81e | ||
|
|
89b6c94e42 | ||
|
|
219ac160b6 | ||
|
|
51ca1eab18 | ||
|
|
1ab3da1fab | ||
|
|
1d1b12c2fa | ||
|
|
07a658582d | ||
|
|
61da74ae50 | ||
|
|
90334f3cb0 | ||
|
|
fb9278bfc6 | ||
|
|
7750936af6 | ||
|
|
7b695af996 | ||
|
|
2874849557 | ||
|
|
9fd1f3e661 | ||
|
|
85182d9164 | ||
|
|
d9b2ea051f | ||
|
|
097fe6dcfb | ||
|
|
d5beca6d96 | ||
|
|
e3a356f893 | ||
|
|
8be2a9ab02 | ||
|
|
3d7f0f5143 | ||
|
|
1fd62fbaf7 | ||
|
|
f1372dda85 | ||
|
|
64c354adb2 | ||
|
|
a379e1f840 | ||
|
|
270ba799f7 | ||
|
|
60dcfacb88 | ||
|
|
b474d720e9 | ||
|
|
b2192b7252 | ||
|
|
6a988ad96a | ||
|
|
029ac5efee | ||
|
|
582cf1f533 |
@@ -1,25 +0,0 @@
|
|||||||
name: LLM Code Review
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened, reopened, synchronize]
|
|
||||||
jobs:
|
|
||||||
llm-code-review:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: fit2cloud/LLM-CodeReview-Action@main
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.FIT2CLOUDRD_LLM_CODE_REVIEW_TOKEN }}
|
|
||||||
OPENAI_API_KEY: ${{ secrets.ALIYUN_LLM_API_KEY }}
|
|
||||||
LANGUAGE: English
|
|
||||||
OPENAI_API_ENDPOINT: https://dashscope.aliyuncs.com/compatible-mode/v1
|
|
||||||
MODEL: qwen2.5-coder-3b-instruct
|
|
||||||
PROMPT: "Please check the following code differences for any irregularities, potential issues, or optimization suggestions, and provide your answers in English."
|
|
||||||
top_p: 1
|
|
||||||
temperature: 1
|
|
||||||
# max_tokens: 10000
|
|
||||||
MAX_PATCH_LENGTH: 10000
|
|
||||||
IGNORE_PATTERNS: "/node_modules,*.md,/dist,/.github"
|
|
||||||
FILE_PATTERNS: "*.java,*.go,*.py,*.vue,*.ts,*.js,*.css,*.scss,*.html"
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
name: SonarCloud Scan
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened]
|
|
||||||
jobs:
|
|
||||||
sonarcloud:
|
|
||||||
name: SonarCloud
|
|
||||||
if: github.repository == '1Panel-dev/1Panel'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: SonarCloud Scan
|
|
||||||
uses: SonarSource/sonarcloud-github-action@master
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUBTOKEN }}
|
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
||||||
@@ -6,7 +6,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Mirror the Github organization repos to Gitee.
|
- name: Mirror the Github organization repos to Gitee.
|
||||||
uses: Yikun/hub-mirror-action@master
|
uses: Yikun/hub-mirror-action@ba51c01b28a6c9f95a25d4f1bcf6af2a147c0e18 # master
|
||||||
with:
|
with:
|
||||||
src: 'github/1Panel-dev'
|
src: 'github/1Panel-dev'
|
||||||
dst: 'gitee/fit2cloud-feizhiyun'
|
dst: 'gitee/fit2cloud-feizhiyun'
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
name: Typos Check
|
|
||||||
on: pull_request
|
|
||||||
jobs:
|
|
||||||
run:
|
|
||||||
name: Spell Check with Typos
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout Actions Repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Check spelling
|
|
||||||
uses: crate-ci/typos@master
|
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
<a href="/docs/README.ru.md"><img alt="Русский" src="https://img.shields.io/badge/Русский-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.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>
|
<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>
|
||||||
|
<a href="/docs/README.lo.md"><img alt="ພາສາລາວ" src="https://img.shields.io/badge/%E0%BA%9E%E0%BA%B2%E0%BA%AA%E0%BA%B2%E0%BA%A5%E0%BA%B2%E0%BA%A7-d9d9d9"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -500,6 +500,27 @@ func (b *BaseApi) GetAgentAccountModels(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, list)
|
helper.SuccessWithData(c, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags AI
|
||||||
|
// @Summary Discover custom provider models
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.AgentAccountModelDiscoverReq true "request"
|
||||||
|
// @Success 200 {array} dto.AgentAccountModel
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /ai/accounts/models/discover [post]
|
||||||
|
func (b *BaseApi) DiscoverAgentAccountModels(c *gin.Context) {
|
||||||
|
var req dto.AgentAccountModelDiscoverReq
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, err := agentService.DiscoverAccountModels(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.BadRequest(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, list)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags AI
|
// @Tags AI
|
||||||
// @Summary Create model account model
|
// @Summary Create model account model
|
||||||
// @Accept json
|
// @Accept json
|
||||||
|
|||||||
@@ -41,20 +41,40 @@ func (b *BaseApi) CreateMysql(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Database Mysql
|
// @Tags Database Mysql
|
||||||
// @Summary Bind user of mysql database
|
// @Summary List mysql users
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.BindUser true "request"
|
// @Param request body dto.MysqlUserSearch true "request"
|
||||||
// @Success 200
|
// @Success 200 {array} dto.MysqlUser
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /databases/bind [post]
|
// @Router /databases/users/search [post]
|
||||||
// @x-panel-log {"bodyKeys":["database", "username"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"绑定 mysql 数据库名 [database] [username]","formatEN":"bind mysql database [database] [username]"}
|
func (b *BaseApi) ListMysqlUsers(c *gin.Context) {
|
||||||
func (b *BaseApi) BindUser(c *gin.Context) {
|
var req dto.MysqlUserSearch
|
||||||
var req dto.BindUser
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
data, err := mysqlService.ListUsers(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Database Mysql
|
||||||
|
// @Summary Create mysql user
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.MysqlUserCreate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /databases/users [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["database","username","host"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 mysql 数据库 [database] 用户 [username]@[host]","formatEN":"create mysql database [database] user [username]@[host]"}
|
||||||
|
func (b *BaseApi) CreateMysqlUser(c *gin.Context) {
|
||||||
|
var req dto.MysqlUserCreate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if len(req.Password) != 0 {
|
if len(req.Password) != 0 {
|
||||||
password, err := base64.StdEncoding.DecodeString(req.Password)
|
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -63,8 +83,191 @@ func (b *BaseApi) BindUser(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
req.Password = string(password)
|
req.Password = string(password)
|
||||||
}
|
}
|
||||||
|
if err := mysqlService.CreateUser(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
if err := mysqlService.BindUser(req); err != nil {
|
// @Tags Database Mysql
|
||||||
|
// @Summary Delete mysql user
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.MysqlUserDelete true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /databases/users/del [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["database","username","host"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除 mysql 数据库 [database] 用户 [username]@[host]","formatEN":"delete mysql database [database] user [username]@[host]"}
|
||||||
|
func (b *BaseApi) DeleteMysqlUser(c *gin.Context) {
|
||||||
|
var req dto.MysqlUserDelete
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := mysqlService.DeleteUser(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Database Mysql
|
||||||
|
// @Summary Update mysql user
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.MysqlUserUpdate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /databases/users/update [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["database","username","host","newHost","description"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 mysql 数据库 [database] 用户 [username] 访问权限 [host] => [newHost] 描述 [description]","formatEN":"update mysql database [database] user [username] access [host] => [newHost] description [description]"}
|
||||||
|
func (b *BaseApi) UpdateMysqlUser(c *gin.Context) {
|
||||||
|
var req dto.MysqlUserUpdate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := mysqlService.UpdateUser(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Database Mysql
|
||||||
|
// @Summary Change mysql user password
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.MysqlUserPassword true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /databases/users/password [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["database","username","host"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 mysql 数据库 [database] 用户 [username]@[host] 密码","formatEN":"update mysql database [database] user [username]@[host] password"}
|
||||||
|
func (b *BaseApi) ChangeMysqlUserPassword(c *gin.Context) {
|
||||||
|
var req dto.MysqlUserPassword
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.Password) != 0 {
|
||||||
|
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||||
|
if err != nil {
|
||||||
|
helper.BadRequest(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Password = string(password)
|
||||||
|
}
|
||||||
|
if err := mysqlService.ChangeUserPassword(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Database Mysql
|
||||||
|
// @Summary Save mysql user password locally
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.MysqlUserPassword true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /databases/users/password/save [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["database","username","host"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"补充 mysql 数据库 [database] 用户 [username]@[host] 密码","formatEN":"save mysql database [database] user [username]@[host] password locally"}
|
||||||
|
func (b *BaseApi) SaveMysqlUserPassword(c *gin.Context) {
|
||||||
|
var req dto.MysqlUserPassword
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.Password) != 0 {
|
||||||
|
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||||
|
if err != nil {
|
||||||
|
helper.BadRequest(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Password = string(password)
|
||||||
|
}
|
||||||
|
if err := mysqlService.SaveUserPassword(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Database Mysql
|
||||||
|
// @Summary List mysql grants
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.MysqlUserSearch true "request"
|
||||||
|
// @Success 200 {array} dto.MysqlGrant
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /databases/grants/search [post]
|
||||||
|
func (b *BaseApi) ListMysqlGrants(c *gin.Context) {
|
||||||
|
var req dto.MysqlUserSearch
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := mysqlService.ListGrants(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Database Mysql
|
||||||
|
// @Summary List mysql grant summary
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.MysqlGrantSummarySearch true "request"
|
||||||
|
// @Success 200 {object} map[string][]dto.MysqlUser
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /databases/grants/summary [post]
|
||||||
|
func (b *BaseApi) ListMysqlGrantSummary(c *gin.Context) {
|
||||||
|
var req dto.MysqlGrantSummarySearch
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := mysqlService.ListGrantSummary(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Database Mysql
|
||||||
|
// @Summary Grant mysql user
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.MysqlGrantCreate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /databases/grants [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["database","db","username","host"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"授权 mysql 数据库 [database] 用户 [username]@[host] 访问 [db]","formatEN":"grant mysql database [database] user [username]@[host] access to [db]"}
|
||||||
|
func (b *BaseApi) GrantMysqlUser(c *gin.Context) {
|
||||||
|
var req dto.MysqlGrantCreate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := mysqlService.GrantUser(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Database Mysql
|
||||||
|
// @Summary Revoke mysql grant
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.MysqlGrantDelete true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /databases/grants/del [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["database","db","username","host"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"取消 mysql 数据库 [database] 用户 [username]@[host] 对 [db] 的授权","formatEN":"revoke mysql database [database] user [username]@[host] access to [db]"}
|
||||||
|
func (b *BaseApi) RevokeMysqlGrant(c *gin.Context) {
|
||||||
|
var req dto.MysqlGrantDelete
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := mysqlService.RevokeGrant(req); err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -94,14 +297,14 @@ func (b *BaseApi) UpdateMysqlDescription(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Database Mysql
|
// @Tags Database Mysql
|
||||||
// @Summary Change mysql password
|
// @Summary Change mysql root password
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.ChangeDBInfo true "request"
|
// @Param request body dto.ChangeDBInfo true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /databases/change/password [post]
|
// @Router /databases/change/password [post]
|
||||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 密码","formatEN":"Update database [name] password"}
|
// @x-panel-log {"bodyKeys":["database"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新数据库 [database] root 密码","formatEN":"Update database [database] root password"}
|
||||||
func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) {
|
func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) {
|
||||||
var req dto.ChangeDBInfo
|
var req dto.ChangeDBInfo
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
@@ -125,14 +328,14 @@ func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Database Mysql
|
// @Tags Database Mysql
|
||||||
// @Summary Change mysql access
|
// @Summary Change mysql root access
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.ChangeDBInfo true "request"
|
// @Param request body dto.ChangeDBInfo true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /databases/change/access [post]
|
// @Router /databases/change/access [post]
|
||||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 访问权限","formatEN":"Update database [name] access"}
|
// @x-panel-log {"bodyKeys":["database"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新数据库 [database] root 访问权限","formatEN":"Update database [database] root access"}
|
||||||
func (b *BaseApi) ChangeMysqlAccess(c *gin.Context) {
|
func (b *BaseApi) ChangeMysqlAccess(c *gin.Context) {
|
||||||
var req dto.ChangeDBInfo
|
var req dto.ChangeDBInfo
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
|||||||
@@ -37,14 +37,15 @@ var (
|
|||||||
|
|
||||||
cronjobService = service.NewICronjobService()
|
cronjobService = service.NewICronjobService()
|
||||||
|
|
||||||
fileService = service.NewIFileService()
|
fileService = service.NewIFileService()
|
||||||
fileHistoryService = service.NewIFileHistoryService()
|
fileHistoryService = service.NewIFileHistoryService()
|
||||||
fileShareService = service.NewIFileShareService()
|
fileShareService = service.NewIFileShareService()
|
||||||
sshService = service.NewISSHService()
|
sshService = service.NewISSHService()
|
||||||
firewallService = service.NewIFirewallService()
|
firewallService = service.NewIFirewallService()
|
||||||
iptablesService = service.NewIIptablesService()
|
iptablesService = service.NewIIptablesService()
|
||||||
monitorService = service.NewIMonitorService()
|
monitorService = service.NewIMonitorService()
|
||||||
systemService = service.NewISystemService()
|
systemService = service.NewISystemService()
|
||||||
|
runtimeDiagnosticsService = service.NewIRuntimeDiagnosticsService()
|
||||||
|
|
||||||
deviceService = service.NewIDeviceService()
|
deviceService = service.NewIDeviceService()
|
||||||
fail2banService = service.NewIFail2BanService()
|
fail2banService = service.NewIFail2BanService()
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
@@ -28,6 +30,11 @@ import (
|
|||||||
qrcode "github.com/skip2/go-qrcode"
|
qrcode "github.com/skip2/go-qrcode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var cancelledChunkUploads = struct {
|
||||||
|
sync.RWMutex
|
||||||
|
ids map[string]struct{}
|
||||||
|
}{ids: make(map[string]struct{})}
|
||||||
|
|
||||||
// @Tags File
|
// @Tags File
|
||||||
// @Summary List files
|
// @Summary List files
|
||||||
// @Accept json
|
// @Accept json
|
||||||
@@ -873,7 +880,24 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
filename := c.PostForm("filename")
|
filename := c.PostForm("filename")
|
||||||
fileDir := filepath.Join(tmpDir, filename)
|
if filename == "" || filepath.Base(filename) != filename || strings.ContainsAny(filename, `/\\`) {
|
||||||
|
helper.BadRequest(c, errors.New("invalid filename"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uploadID := strings.TrimSpace(c.PostForm("uploadID"))
|
||||||
|
cancellable := uploadID != ""
|
||||||
|
if cancellable && (filepath.Base(uploadID) != uploadID || strings.ContainsAny(uploadID, `/\\`)) {
|
||||||
|
helper.BadRequest(c, errors.New("invalid upload ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !cancellable {
|
||||||
|
uploadID = filename
|
||||||
|
}
|
||||||
|
fileDir := filepath.Join(tmpDir, uploadID)
|
||||||
|
if cancellable && chunkUploadCancelled(uploadID) {
|
||||||
|
helper.BadRequest(c, errors.New("upload cancelled"))
|
||||||
|
return
|
||||||
|
}
|
||||||
if chunkIndex == 0 {
|
if chunkIndex == 0 {
|
||||||
if fileOp.Stat(fileDir) {
|
if fileOp.Stat(fileDir) {
|
||||||
_ = fileOp.DeleteDir(fileDir)
|
_ = fileOp.DeleteDir(fileDir)
|
||||||
@@ -884,7 +908,7 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
|||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = os.Remove(fileDir)
|
_ = os.RemoveAll(fileDir)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
var (
|
var (
|
||||||
@@ -904,6 +928,11 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
|||||||
helper.InternalServer(c, buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err))
|
helper.InternalServer(c, buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if cancellable && chunkUploadCancelled(uploadID) {
|
||||||
|
err = errors.New("upload cancelled")
|
||||||
|
helper.BadRequest(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex))
|
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex))
|
||||||
err = os.WriteFile(chunkPath, chunkData, constant.DirPerm)
|
err = os.WriteFile(chunkPath, chunkData, constant.DirPerm)
|
||||||
@@ -922,12 +951,50 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
|||||||
helper.InternalServer(c, buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err))
|
helper.InternalServer(c, buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if cancellable {
|
||||||
|
cancelledChunkUploads.Lock()
|
||||||
|
delete(cancelledChunkUploads.ids, uploadID)
|
||||||
|
cancelledChunkUploads.Unlock()
|
||||||
|
}
|
||||||
helper.SuccessWithData(c, true)
|
helper.SuccessWithData(c, true)
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StopChunkUpload removes temporary chunks left by a cancelled upload.
|
||||||
|
func (b *BaseApi) StopChunkUpload(c *gin.Context) {
|
||||||
|
var req request.FileProcessReq
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uploadID := strings.TrimSpace(req.Key)
|
||||||
|
if uploadID == "" || filepath.Base(uploadID) != uploadID || strings.ContainsAny(uploadID, `/\\`) {
|
||||||
|
helper.BadRequest(c, errors.New("invalid upload ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cancelledChunkUploads.Lock()
|
||||||
|
cancelledChunkUploads.ids[uploadID] = struct{}{}
|
||||||
|
cancelledChunkUploads.Unlock()
|
||||||
|
time.AfterFunc(10*time.Minute, func() {
|
||||||
|
cancelledChunkUploads.Lock()
|
||||||
|
delete(cancelledChunkUploads.ids, uploadID)
|
||||||
|
cancelledChunkUploads.Unlock()
|
||||||
|
})
|
||||||
|
if err := os.RemoveAll(filepath.Join(global.Dir.TmpDir, "upload", uploadID)); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func chunkUploadCancelled(uploadID string) bool {
|
||||||
|
cancelledChunkUploads.RLock()
|
||||||
|
_, ok := cancelledChunkUploads.ids[uploadID]
|
||||||
|
cancelledChunkUploads.RUnlock()
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
var wsUpgrade = websocket.Upgrader{
|
var wsUpgrade = websocket.Upgrader{
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package v2
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,3 +21,57 @@ func (b *BaseApi) GetSystemFiles(c *gin.Context) {
|
|||||||
|
|
||||||
helper.SuccessWithData(c, data)
|
helper.SuccessWithData(c, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags Logs
|
||||||
|
// @Summary Get host system log status
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} dto.SystemLogStatus
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /logs/system/status [get]
|
||||||
|
func (b *BaseApi) GetSystemLogStatus(c *gin.Context) {
|
||||||
|
data, err := logService.GetSystemLogStatus()
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Logs
|
||||||
|
// @Summary Read host logs
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.SystemLogReq true "request"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} dto.SystemLogRes
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /logs/system/read [post]
|
||||||
|
func (b *BaseApi) ReadSystemLog(c *gin.Context) {
|
||||||
|
var req dto.SystemLogReq
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := logService.ReadSystemLog(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Logs
|
||||||
|
// @Summary List running host services
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} string
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /logs/system/services [get]
|
||||||
|
func (b *BaseApi) ListRunningServices(c *gin.Context) {
|
||||||
|
data, err := logService.ListRunningServices()
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Tags RuntimeDiagnostics
|
||||||
|
// @Summary Load runtime diagnostics summary
|
||||||
|
// @Success 200 {object} dto.RuntimeDiagnosticsSummary
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/diagnostics/summary [get]
|
||||||
|
func (b *BaseApi) LoadRuntimeDiagnosticsSummary(c *gin.Context) {
|
||||||
|
data, err := runtimeDiagnosticsService.Summary()
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags RuntimeDiagnostics
|
||||||
|
// @Summary Load grouped goroutine snapshot
|
||||||
|
// @Success 200 {object} dto.RuntimeGoroutineSnapshot
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/diagnostics/goroutines [get]
|
||||||
|
func (b *BaseApi) LoadRuntimeGoroutines(c *gin.Context) {
|
||||||
|
data, err := runtimeDiagnosticsService.Goroutines()
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithDataGzipped(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags RuntimeDiagnostics
|
||||||
|
// @Summary Capture runtime profile
|
||||||
|
// @Param request body dto.RuntimeProfileCreate true "request"
|
||||||
|
// @Success 200 {file} file
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/diagnostics/profiles [post]
|
||||||
|
func (b *BaseApi) CreateRuntimeProfile(c *gin.Context) {
|
||||||
|
var req dto.RuntimeProfileCreate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
profile, err := runtimeDiagnosticsService.CreateProfile(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.BadRequest(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(profile.Path)
|
||||||
|
c.Header("Content-Disposition", `attachment; filename="`+profile.Name+`"`)
|
||||||
|
c.Header("Content-Type", "application/octet-stream")
|
||||||
|
c.File(profile.Path)
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
@@ -224,6 +224,21 @@ func (b *BaseApi) ExportSSHLogs(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, tmpFile)
|
helper.SuccessWithData(c, tmpFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags SSH
|
||||||
|
// @Summary Clean host SSH logs
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/ssh/log/clean [post]
|
||||||
|
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空 SSH 登录日志","formatEN":"clean SSH login logs"}
|
||||||
|
func (b *BaseApi) CleanSSHLogs(c *gin.Context) {
|
||||||
|
if err := sshService.CleanLog(); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags SSH
|
// @Tags SSH
|
||||||
// @Summary Load host SSH conf
|
// @Summary Load host SSH conf
|
||||||
// @Accept json
|
// @Accept json
|
||||||
|
|||||||
+36
-20
@@ -118,8 +118,6 @@ type AgentItem struct {
|
|||||||
ProviderName string `json:"providerName"`
|
ProviderName string `json:"providerName"`
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
APIType string `json:"apiType"`
|
APIType string `json:"apiType"`
|
||||||
MaxTokens int `json:"maxTokens"`
|
|
||||||
ContextWindow int `json:"contextWindow"`
|
|
||||||
BaseURL string `json:"baseUrl"`
|
BaseURL string `json:"baseUrl"`
|
||||||
APIKey string `json:"apiKey"`
|
APIKey string `json:"apiKey"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
@@ -292,19 +290,22 @@ type AgentOverviewSnapshot struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AgentAccountModel struct {
|
type AgentAccountModel struct {
|
||||||
RecordID uint `json:"recordId"`
|
RecordID uint `json:"recordId"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ContextWindow int `json:"contextWindow"`
|
|
||||||
MaxTokens int `json:"maxTokens"`
|
|
||||||
Reasoning bool `json:"reasoning"`
|
|
||||||
Input []string `json:"input"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentAccountModelReq struct {
|
type AgentAccountModelReq struct {
|
||||||
AccountID uint `json:"accountId" validate:"required"`
|
AccountID uint `json:"accountId" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AgentAccountModelDiscoverReq struct {
|
||||||
|
Provider string `json:"provider" validate:"required"`
|
||||||
|
BaseURL string `json:"baseURL" validate:"required"`
|
||||||
|
APIKey string `json:"apiKey" validate:"required"`
|
||||||
|
APIType string `json:"apiType" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type AgentAccountModelCreateReq struct {
|
type AgentAccountModelCreateReq struct {
|
||||||
AccountID uint `json:"accountId" validate:"required"`
|
AccountID uint `json:"accountId" validate:"required"`
|
||||||
Model AgentAccountModel `json:"model" validate:"required"`
|
Model AgentAccountModel `json:"model" validate:"required"`
|
||||||
@@ -328,6 +329,8 @@ type AgentAccountCreateReq struct {
|
|||||||
BaseURL string `json:"baseURL"`
|
BaseURL string `json:"baseURL"`
|
||||||
Models []AgentAccountModel `json:"models"`
|
Models []AgentAccountModel `json:"models"`
|
||||||
APIType string `json:"apiType" validate:"required"`
|
APIType string `json:"apiType" validate:"required"`
|
||||||
|
AuthMode string `json:"authMode"`
|
||||||
|
VerifyModel string `json:"verifyModel"`
|
||||||
Remark string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,6 +341,8 @@ type AgentAccountUpdateReq struct {
|
|||||||
RememberAPIKey bool `json:"rememberApiKey"`
|
RememberAPIKey bool `json:"rememberApiKey"`
|
||||||
BaseURL string `json:"baseURL"`
|
BaseURL string `json:"baseURL"`
|
||||||
APIType string `json:"apiType" validate:"required"`
|
APIType string `json:"apiType" validate:"required"`
|
||||||
|
AuthMode string `json:"authMode"`
|
||||||
|
VerifyModel string `json:"verifyModel"`
|
||||||
Remark string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
SyncAgents bool `json:"syncAgents"`
|
SyncAgents bool `json:"syncAgents"`
|
||||||
}
|
}
|
||||||
@@ -346,6 +351,9 @@ type AgentAccountVerifyReq struct {
|
|||||||
Provider string `json:"provider" validate:"required"`
|
Provider string `json:"provider" validate:"required"`
|
||||||
APIKey string `json:"apiKey" validate:"required"`
|
APIKey string `json:"apiKey" validate:"required"`
|
||||||
BaseURL string `json:"baseURL"`
|
BaseURL string `json:"baseURL"`
|
||||||
|
APIType string `json:"apiType" validate:"required"`
|
||||||
|
AuthMode string `json:"authMode"`
|
||||||
|
Model string `json:"model"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentAccountDeleteReq struct {
|
type AgentAccountDeleteReq struct {
|
||||||
@@ -373,26 +381,34 @@ type AgentAccountInfo struct {
|
|||||||
BaseURL string `json:"baseUrl"`
|
BaseURL string `json:"baseUrl"`
|
||||||
Models []AgentAccountModel `json:"models"`
|
Models []AgentAccountModel `json:"models"`
|
||||||
APIType string `json:"apiType"`
|
APIType string `json:"apiType"`
|
||||||
|
AuthMode string `json:"authMode"`
|
||||||
|
VerifyModel string `json:"verifyModel"`
|
||||||
Verified bool `json:"verified"`
|
Verified bool `json:"verified"`
|
||||||
Remark string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderModelInfo struct {
|
type ProviderModelInfo struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ContextWindow int `json:"contextWindow"`
|
}
|
||||||
MaxTokens int `json:"maxTokens"`
|
|
||||||
Reasoning bool `json:"reasoning"`
|
type ProviderAPIInfo struct {
|
||||||
Input []string `json:"input"`
|
APIType string `json:"apiType"`
|
||||||
|
BaseURL string `json:"baseUrl"`
|
||||||
|
EditableBaseURL bool `json:"editableBaseUrl"`
|
||||||
|
DefaultAuthMode string `json:"defaultAuthMode"`
|
||||||
|
AuthModes []string `json:"authModes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderInfo struct {
|
type ProviderInfo struct {
|
||||||
Sort uint `json:"-"`
|
Sort uint `json:"-"`
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
DisplayName string `json:"displayName"`
|
DisplayName string `json:"displayName"`
|
||||||
BaseURL string `json:"baseUrl"`
|
BaseURL string `json:"baseUrl"`
|
||||||
Models []ProviderModelInfo `json:"models"`
|
DefaultAPIType string `json:"defaultApiType"`
|
||||||
|
APITypes []ProviderAPIInfo `json:"apiTypes"`
|
||||||
|
Models []ProviderModelInfo `json:"models"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentFeishuConfigReq struct {
|
type AgentFeishuConfigReq struct {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package dto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
)
|
)
|
||||||
@@ -133,6 +134,7 @@ type Locale struct {
|
|||||||
Tr string `json:"tr"`
|
Tr string `json:"tr"`
|
||||||
Es string `json:"es-es" yaml:"es-es"`
|
Es string `json:"es-es" yaml:"es-es"`
|
||||||
Fa string `json:"fa"`
|
Fa string `json:"fa"`
|
||||||
|
Lo string `json:"lo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppForm struct {
|
type AppForm struct {
|
||||||
|
|||||||
@@ -134,10 +134,15 @@ type ExtraHost struct {
|
|||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
}
|
}
|
||||||
type ContainerNetwork struct {
|
type ContainerNetwork struct {
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
Ipv4 string `json:"ipv4"`
|
Ipv4 string `json:"ipv4"`
|
||||||
Ipv6 string `json:"ipv6"`
|
Ipv6 string `json:"ipv6"`
|
||||||
MacAddr string `json:"macAddr"`
|
MacAddr string `json:"macAddr"`
|
||||||
|
Links []string `json:"links"`
|
||||||
|
Aliases []string `json:"aliases"`
|
||||||
|
DriverOpts map[string]string `json:"driverOpts"`
|
||||||
|
GwPriority int `json:"gwPriority"`
|
||||||
|
LinkLocalIPs []string `json:"linkLocalIPs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContainerCreateByCommand struct {
|
type ContainerCreateByCommand struct {
|
||||||
|
|||||||
+69
-10
@@ -60,25 +60,84 @@ type MysqlDBCreate struct {
|
|||||||
Database string `json:"database" validate:"required"`
|
Database string `json:"database" validate:"required"`
|
||||||
Format string `json:"format" validate:"required"`
|
Format string `json:"format" validate:"required"`
|
||||||
Collation string `json:"collation"`
|
Collation string `json:"collation"`
|
||||||
Username string `json:"username" validate:"required"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password"`
|
||||||
Permission string `json:"permission" validate:"required"`
|
Permission string `json:"permission" validate:"required"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MysqlUser struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
IsDelete bool `json:"isDelete"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MysqlGrant struct {
|
||||||
|
Database string `json:"database"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MysqlGrantSummarySearch struct {
|
||||||
|
Database string `json:"database" validate:"required"`
|
||||||
|
DBs []string `json:"dbs" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MysqlUserSearch struct {
|
||||||
|
Database string `json:"database" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MysqlUserCreate struct {
|
||||||
|
Database string `json:"database" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Password string `json:"password" validate:"required"`
|
||||||
|
Host string `json:"host" validate:"required"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
DBs []string `json:"dbs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MysqlUserDelete struct {
|
||||||
|
Database string `json:"database" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Host string `json:"host" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MysqlUserUpdate struct {
|
||||||
|
Database string `json:"database" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Host string `json:"host" validate:"required"`
|
||||||
|
NewHost string `json:"newHost" validate:"required"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MysqlUserPassword struct {
|
||||||
|
Database string `json:"database" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Host string `json:"host" validate:"required"`
|
||||||
|
Password string `json:"password" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MysqlGrantCreate struct {
|
||||||
|
Database string `json:"database" validate:"required"`
|
||||||
|
DB string `json:"db" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Host string `json:"host" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MysqlGrantDelete struct {
|
||||||
|
Database string `json:"database" validate:"required"`
|
||||||
|
DB string `json:"db" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Host string `json:"host" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type MysqlFormatCollationOption struct {
|
type MysqlFormatCollationOption struct {
|
||||||
Format string `json:"format"`
|
Format string `json:"format"`
|
||||||
Collations []string `json:"collations"`
|
Collations []string `json:"collations"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BindUser struct {
|
|
||||||
Database string `json:"database" validate:"required"`
|
|
||||||
DB string `json:"db" validate:"required"`
|
|
||||||
Username string `json:"username" validate:"required"`
|
|
||||||
Password string `json:"password" validate:"required"`
|
|
||||||
Permission string `json:"permission" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MysqlLoadDB struct {
|
type MysqlLoadDB struct {
|
||||||
From string `json:"from" validate:"required,oneof=local remote"`
|
From string `json:"from" validate:"required,oneof=local remote"`
|
||||||
Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"`
|
Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"`
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,3 +16,37 @@ type SearchTaskLogReq struct {
|
|||||||
type TaskDTO struct {
|
type TaskDTO struct {
|
||||||
model.Task
|
model.Task
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SystemLogReq struct {
|
||||||
|
PageSize int `json:"pageSize" validate:"omitempty,min=1,max=500"`
|
||||||
|
Cursor string `json:"cursor"`
|
||||||
|
StartTime time.Time `json:"startTime"`
|
||||||
|
EndTime time.Time `json:"endTime"`
|
||||||
|
Keyword string `json:"keyword"`
|
||||||
|
Priority string `json:"priority"`
|
||||||
|
Service string `json:"service"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SystemLogRes struct {
|
||||||
|
Source string `json:"source"`
|
||||||
|
Items []SystemLogItem `json:"items"`
|
||||||
|
HasMore bool `json:"hasMore"`
|
||||||
|
NextCursor string `json:"nextCursor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SystemLogStatus struct {
|
||||||
|
Source string `json:"source"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
KeywordFilterSupported bool `json:"keywordFilterSupported"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SystemLogItem struct {
|
||||||
|
Timestamp int64 `json:"-"`
|
||||||
|
Cursor string `json:"-"`
|
||||||
|
Time string `json:"time"`
|
||||||
|
Priority string `json:"priority"`
|
||||||
|
Service string `json:"service"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Raw string `json:"raw"`
|
||||||
|
}
|
||||||
|
|||||||
+39
-5
@@ -1,6 +1,8 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
||||||
)
|
)
|
||||||
@@ -87,9 +89,41 @@ var LBAlgorithms = map[string]struct{}{"ip_hash": {}, "least_conn": {}}
|
|||||||
var RealIPKeys = map[string]struct{}{"X-Forwarded-For": {}, "X-Real-IP": {}, "CF-Connecting-IP": {}}
|
var RealIPKeys = map[string]struct{}{"X-Forwarded-For": {}, "X-Real-IP": {}, "CF-Connecting-IP": {}}
|
||||||
|
|
||||||
type NginxModule struct {
|
type NginxModule struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Script string `json:"script"`
|
Custom bool `json:"custom,omitempty"`
|
||||||
Packages []string `json:"packages"`
|
Script string `json:"script"`
|
||||||
Params string `json:"params"`
|
Packages []string `json:"packages"`
|
||||||
Enable bool `json:"enable"`
|
Params string `json:"params"`
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
BuildMode string `json:"buildMode,omitempty"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
|
LoadOrder int `json:"loadOrder,omitempty"`
|
||||||
|
Builds []NginxModuleBuild `json:"builds,omitempty"`
|
||||||
|
LastError string `json:"lastError,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NginxModuleBuild struct {
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
BuildMode string `json:"buildMode"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Target NginxModuleTarget `json:"target"`
|
||||||
|
Artifacts []NginxModuleArtifact `json:"artifacts,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
BuiltAt time.Time `json:"builtAt,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NginxModuleTarget struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
OpenRestyVersion string `json:"openrestyVersion"`
|
||||||
|
Architecture string `json:"architecture"`
|
||||||
|
Image string `json:"image,omitempty"`
|
||||||
|
ImageDigest string `json:"imageDigest,omitempty"`
|
||||||
|
BuilderDigest string `json:"builderDigest,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NginxModuleArtifact struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Checksum string `json:"checksum"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ type McpServerCreate struct {
|
|||||||
Type string `json:"type" validate:"required"`
|
Type string `json:"type" validate:"required"`
|
||||||
GatewayImage string `json:"gatewayImage"`
|
GatewayImage string `json:"gatewayImage"`
|
||||||
ProtocolVersion string `json:"protocolVersion"`
|
ProtocolVersion string `json:"protocolVersion"`
|
||||||
|
GatewayArgs string `json:"gatewayArgs" validate:"max=4096"`
|
||||||
TaskID string `json:"taskID"`
|
TaskID string `json:"taskID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,17 +114,22 @@ type NginxRedirectUpdate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NginxBuildReq struct {
|
type NginxBuildReq struct {
|
||||||
TaskID string `json:"taskID" validate:"required"`
|
TaskID string `json:"taskID" validate:"required"`
|
||||||
Mirror string `json:"mirror" validate:"required"`
|
Mirror string `json:"mirror" validate:"required"`
|
||||||
|
Modules []string `json:"modules"`
|
||||||
|
Force bool `json:"force"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NginxModuleUpdate struct {
|
type NginxModuleUpdate struct {
|
||||||
Operate string `json:"operate" validate:"required,oneof=create delete update"`
|
Operate string `json:"operate" validate:"required,oneof=create delete update"`
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
Packages string `json:"packages"`
|
Packages string `json:"packages"`
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
Params string `json:"params"`
|
Params string `json:"params"`
|
||||||
|
BuildMode string `json:"buildMode" validate:"omitempty,oneof=dynamic static"`
|
||||||
|
Provider string `json:"provider" validate:"omitempty,oneof=local prebuilt"`
|
||||||
|
LoadOrder int `json:"loadOrder" validate:"omitempty,min=0,max=9999"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NginxOperateReq struct {
|
type NginxOperateReq struct {
|
||||||
|
|||||||
@@ -69,16 +69,25 @@ type NginxProxyCache struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NginxModule struct {
|
type NginxModule struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Script string `json:"script"`
|
Custom bool `json:"custom"`
|
||||||
Packages string `json:"packages"`
|
Script string `json:"script"`
|
||||||
Params string `json:"params"`
|
Packages string `json:"packages"`
|
||||||
Enable bool `json:"enable"`
|
Params string `json:"params"`
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
BuildMode string `json:"buildMode"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
LoadOrder int `json:"loadOrder"`
|
||||||
|
BuildStatus string `json:"buildStatus"`
|
||||||
|
LoadStatus string `json:"loadStatus"`
|
||||||
|
Artifacts []dto.NginxModuleArtifact `json:"artifacts"`
|
||||||
|
LastError string `json:"lastError"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NginxBuildConfig struct {
|
type NginxBuildConfig struct {
|
||||||
Mirror string `json:"mirror"`
|
Mirror string `json:"mirror"`
|
||||||
Modules []NginxModule `json:"modules"`
|
DynamicSupported bool `json:"dynamicSupported"`
|
||||||
|
Modules []NginxModule `json:"modules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NginxConfigRes struct {
|
type NginxConfigRes struct {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type RuntimeDiagnosticsSummary struct {
|
||||||
|
RSS uint64 `json:"rss"`
|
||||||
|
HeapAlloc uint64 `json:"heapAlloc"`
|
||||||
|
HeapObjects uint64 `json:"heapObjects"`
|
||||||
|
Goroutines int `json:"goroutines"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuntimeGoroutineGroup struct {
|
||||||
|
State string `json:"state"`
|
||||||
|
Top string `json:"top"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
Stack []string `json:"stack"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuntimeGoroutineSnapshot struct {
|
||||||
|
Total int `json:"total"`
|
||||||
|
GroupCount int `json:"groupCount"`
|
||||||
|
Truncated bool `json:"truncated"`
|
||||||
|
CapturedAt time.Time `json:"capturedAt"`
|
||||||
|
Goroutines []RuntimeGoroutineGroup `json:"goroutines"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuntimeProfileCreate struct {
|
||||||
|
Type string `json:"type" validate:"required,oneof=cpu heap goroutine mutex block"`
|
||||||
|
Duration int `json:"duration" validate:"omitempty,min=5,max=30"`
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ type AgentAccount struct {
|
|||||||
APIKey string `json:"apiKey"`
|
APIKey string `json:"apiKey"`
|
||||||
BaseURL string `json:"baseUrl"`
|
BaseURL string `json:"baseUrl"`
|
||||||
APIType string `json:"apiType"`
|
APIType string `json:"apiType"`
|
||||||
|
AuthMode string `json:"authMode"`
|
||||||
|
VerifyModel string `json:"verifyModel"`
|
||||||
RememberAPIKey bool `json:"rememberApiKey"`
|
RememberAPIKey bool `json:"rememberApiKey"`
|
||||||
Verified bool `json:"verified"`
|
Verified bool `json:"verified"`
|
||||||
Remark string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
|
|||||||
@@ -2,14 +2,10 @@ package model
|
|||||||
|
|
||||||
type AgentAccountModel struct {
|
type AgentAccountModel struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
AccountID uint `json:"accountId" gorm:"index"`
|
AccountID uint `json:"accountId" gorm:"index"`
|
||||||
Model string `json:"model" gorm:"index"`
|
Model string `json:"model" gorm:"index"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ContextWindow int `json:"contextWindow"`
|
SortOrder int `json:"sortOrder" gorm:"index"`
|
||||||
MaxTokens int `json:"maxTokens"`
|
|
||||||
Reasoning bool `json:"reasoning"`
|
|
||||||
Input string `json:"input" gorm:"type:text"`
|
|
||||||
SortOrder int `json:"sortOrder" gorm:"index"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (AgentAccountModel) TableName() string {
|
func (AgentAccountModel) TableName() string {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type DatabaseUser struct {
|
||||||
|
BaseModel
|
||||||
|
Type string `json:"type" gorm:"not null;uniqueIndex:idx_database_user"`
|
||||||
|
Database string `json:"database" gorm:"not null;uniqueIndex:idx_database_user"`
|
||||||
|
Username string `json:"username" gorm:"not null;uniqueIndex:idx_database_user"`
|
||||||
|
Host string `json:"host" gorm:"uniqueIndex:idx_database_user"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
IsDelete bool `json:"isDelete"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatabaseUserGrant struct {
|
||||||
|
BaseModel
|
||||||
|
Type string `json:"type" gorm:"not null;uniqueIndex:idx_database_user_grant"`
|
||||||
|
Database string `json:"database" gorm:"not null;uniqueIndex:idx_database_user_grant"`
|
||||||
|
DBName string `json:"dbName" gorm:"not null;uniqueIndex:idx_database_user_grant"`
|
||||||
|
Username string `json:"username" gorm:"not null;uniqueIndex:idx_database_user_grant"`
|
||||||
|
Host string `json:"host" gorm:"not null;uniqueIndex:idx_database_user_grant"`
|
||||||
|
}
|
||||||
@@ -20,4 +20,5 @@ type McpServer struct {
|
|||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
GatewayImage string `json:"gatewayImage"`
|
GatewayImage string `json:"gatewayImage"`
|
||||||
ProtocolVersion string `json:"protocolVersion"`
|
ProtocolVersion string `json:"protocolVersion"`
|
||||||
|
GatewayArgs string `json:"gatewayArgs"`
|
||||||
}
|
}
|
||||||
|
|||||||
+242
-329
@@ -1,320 +1,181 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Model struct {
|
type APIConfig struct {
|
||||||
ID string
|
APIType string
|
||||||
Name string
|
BaseURL string
|
||||||
ContextWindow int
|
EditableBaseURL bool
|
||||||
MaxTokens int
|
DefaultAuthMode string
|
||||||
Reasoning bool
|
AuthModes []string
|
||||||
Input []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuntimeDefault struct {
|
const (
|
||||||
APIType string
|
AuthModeBearer = "bearer"
|
||||||
ContextWindow int
|
AuthModeXAPIKey = "x-api-key"
|
||||||
MaxTokens int
|
)
|
||||||
Input []string
|
|
||||||
|
type Model struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
Key string
|
Key string
|
||||||
DisplayName string
|
DisplayName string
|
||||||
Sort uint
|
Sort uint
|
||||||
DefaultBaseURL string
|
DefaultAPIType string
|
||||||
|
APIConfigs []APIConfig
|
||||||
EnvKey string
|
EnvKey string
|
||||||
Default RuntimeDefault
|
|
||||||
Models []Model
|
Models []Model
|
||||||
}
|
}
|
||||||
|
|
||||||
var catalog = map[string]Meta{
|
var catalog = map[string]Meta{
|
||||||
"custom": {
|
"custom": {
|
||||||
Key: "custom",
|
Key: "custom", DisplayName: "Custom", Sort: 10, DefaultAPIType: "openai-completions", EnvKey: "CUSTOM_API_KEY",
|
||||||
DisplayName: "Custom",
|
APIConfigs: editableAPIConfigs("openai-completions", "openai-responses", "anthropic-messages"),
|
||||||
Sort: 10,
|
|
||||||
DefaultBaseURL: "",
|
|
||||||
EnvKey: "CUSTOM_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "openai-completions",
|
|
||||||
ContextWindow: 128000,
|
|
||||||
MaxTokens: 8192,
|
|
||||||
Input: []string{"text"},
|
|
||||||
},
|
|
||||||
Models: []Model{},
|
|
||||||
},
|
},
|
||||||
"ollama": {
|
"ollama": {
|
||||||
Key: "ollama",
|
Key: "ollama", DisplayName: "Ollama", Sort: 15, DefaultAPIType: "openai-responses",
|
||||||
DisplayName: "Ollama",
|
APIConfigs: editableAPIConfigs("openai-responses", "openai-completions"),
|
||||||
Sort: 15,
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "openai-responses",
|
|
||||||
ContextWindow: 160000,
|
|
||||||
MaxTokens: 8192,
|
|
||||||
Input: []string{"text"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"vllm": {
|
"vllm": {
|
||||||
Key: "vllm",
|
Key: "vllm", DisplayName: "vLLM", Sort: 20, DefaultAPIType: "openai-completions", EnvKey: "VLLM_API_KEY",
|
||||||
DisplayName: "vLLM",
|
APIConfigs: editableAPIConfigs("openai-completions", "openai-responses", "anthropic-messages"),
|
||||||
Sort: 20,
|
|
||||||
DefaultBaseURL: "",
|
|
||||||
EnvKey: "VLLM_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "openai-completions",
|
|
||||||
ContextWindow: 128000,
|
|
||||||
MaxTokens: 8192,
|
|
||||||
Input: []string{"text"},
|
|
||||||
},
|
|
||||||
Models: []Model{},
|
|
||||||
},
|
},
|
||||||
"deepseek": {
|
"deepseek": {
|
||||||
Key: "deepseek",
|
Key: "deepseek", DisplayName: "DeepSeek", Sort: 25, DefaultAPIType: "openai-completions", EnvKey: "DEEPSEEK_API_KEY",
|
||||||
DisplayName: "DeepSeek",
|
APIConfigs: []APIConfig{
|
||||||
Sort: 25,
|
{APIType: "openai-completions", BaseURL: "https://api.deepseek.com/v1"},
|
||||||
DefaultBaseURL: "https://api.deepseek.com/v1",
|
anthropicAPIConfig("https://api.deepseek.com/anthropic", AuthModeXAPIKey),
|
||||||
EnvKey: "DEEPSEEK_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "openai-completions",
|
|
||||||
ContextWindow: 131072,
|
|
||||||
MaxTokens: 8192,
|
|
||||||
Input: []string{"text"},
|
|
||||||
},
|
|
||||||
Models: []Model{
|
|
||||||
{ID: "deepseek/deepseek-v4-flash", Name: "deepseek-v4-flash"},
|
|
||||||
{ID: "deepseek/deepseek-v4-pro", Name: "deepseek-v4-pro"},
|
|
||||||
},
|
},
|
||||||
|
Models: []Model{{ID: "deepseek-v4-flash", Name: "deepseek-v4-flash"}, {ID: "deepseek-v4-pro", Name: "deepseek-v4-pro"}},
|
||||||
},
|
},
|
||||||
"bailian-coding-plan": {
|
"bailian-coding-plan": {
|
||||||
Key: "bailian-coding-plan",
|
Key: "bailian-coding-plan", DisplayName: "阿里云百炼 Coding Plan", Sort: 30, DefaultAPIType: "openai-completions", EnvKey: "QWEN_API_KEY",
|
||||||
DisplayName: "阿里云百炼 Coding Plan",
|
APIConfigs: []APIConfig{
|
||||||
Sort: 30,
|
{APIType: "openai-completions", BaseURL: "https://coding.dashscope.aliyuncs.com/v1"},
|
||||||
DefaultBaseURL: "https://coding.dashscope.aliyuncs.com/v1",
|
anthropicAPIConfig("https://coding.dashscope.aliyuncs.com/apps/anthropic", AuthModeBearer),
|
||||||
EnvKey: "QWEN_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "openai-completions",
|
|
||||||
ContextWindow: 256000,
|
|
||||||
MaxTokens: 8192,
|
|
||||||
Input: []string{"text"},
|
|
||||||
},
|
},
|
||||||
Models: []Model{
|
Models: []Model{
|
||||||
{ID: "bailian-coding-plan/qwen3.5-plus", Name: "Qwen3.5-Plus", Reasoning: true},
|
{ID: "qwen3-coder-plus", Name: "Qwen3-Coder-Plus"},
|
||||||
{ID: "bailian-coding-plan/qwen3-max", Name: "Qwen3-Max", Reasoning: true},
|
{ID: "qwen3-max-2026-01-23", Name: "Qwen3-Max-2026-01-23"},
|
||||||
{ID: "bailian-coding-plan/qwen3-coder-next", Name: "Qwen3-Coder-Next", Reasoning: true},
|
{ID: "qwen3-coder-next", Name: "Qwen3-Coder-Next"},
|
||||||
{ID: "bailian-coding-plan/qwen3-coder-plus", Name: "Qwen3-Coder-Plus", Reasoning: true},
|
{ID: "glm-4.7", Name: "GLM-4.7"},
|
||||||
{ID: "bailian-coding-plan/minimax-m2.5", Name: "MiniMax M2.5", Reasoning: true},
|
{ID: "kimi-k2.5", Name: "Kimi K2.5"},
|
||||||
{ID: "bailian-coding-plan/glm-5", Name: "GLM-5", Reasoning: true},
|
{ID: "qwen3.5-plus", Name: "Qwen3.5-Plus"},
|
||||||
{ID: "bailian-coding-plan/kimi-k2.5", Name: "Kimi-k2.5", Reasoning: true},
|
{ID: "glm-5", Name: "GLM-5"},
|
||||||
{ID: "bailian-coding-plan/glm-4.7", Name: "GLM-4.7", Reasoning: true},
|
{ID: "MiniMax-M2.5", Name: "MiniMax M2.5"},
|
||||||
|
{ID: "qwen3.6-plus", Name: "Qwen3.6-Plus"},
|
||||||
|
{ID: "qwen3.7-plus", Name: "Qwen3.7-Plus"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"ark-coding-plan": {
|
"ark-coding-plan": {
|
||||||
Key: "ark-coding-plan",
|
Key: "ark-coding-plan", DisplayName: "方舟 Coding Plan", Sort: 35, DefaultAPIType: "openai-completions", EnvKey: "ARK_API_KEY",
|
||||||
DisplayName: "方舟 Coding Plan",
|
APIConfigs: []APIConfig{
|
||||||
Sort: 35,
|
{APIType: "openai-completions", BaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3"},
|
||||||
DefaultBaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3",
|
anthropicAPIConfig("https://ark.cn-beijing.volces.com/api/coding", AuthModeBearer),
|
||||||
EnvKey: "ARK_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "openai-completions",
|
|
||||||
ContextWindow: 256000,
|
|
||||||
MaxTokens: 4096,
|
|
||||||
Input: []string{"text"},
|
|
||||||
},
|
},
|
||||||
Models: []Model{
|
Models: []Model{
|
||||||
{ID: "ark-coding-plan/ark-code-latest", Name: "Ark Coding Plan", ContextWindow: 256000, MaxTokens: 4096},
|
{ID: "ark-code-latest", Name: "Ark Coding Plan"}, {ID: "doubao-seed-code", Name: "Doubao Seed Code"},
|
||||||
{ID: "ark-coding-plan/doubao-seed-code", Name: "Doubao Seed Code", ContextWindow: 256000, MaxTokens: 4096},
|
{ID: "glm-4.7", Name: "GLM 4.7 Coding"}, {ID: "kimi-k2-thinking", Name: "Kimi K2 Thinking"},
|
||||||
{ID: "ark-coding-plan/glm-4.7", Name: "GLM 4.7 Coding", ContextWindow: 200000, MaxTokens: 4096},
|
{ID: "kimi-k2.5", Name: "Kimi K2.5 Coding"}, {ID: "doubao-seed-code-preview-251028", Name: "Doubao Seed Code Preview"},
|
||||||
{ID: "ark-coding-plan/kimi-k2-thinking", Name: "Kimi K2 Thinking", ContextWindow: 256000, MaxTokens: 4096},
|
|
||||||
{ID: "ark-coding-plan/kimi-k2.5", Name: "Kimi K2.5 Coding", ContextWindow: 256000, MaxTokens: 4096},
|
|
||||||
{ID: "ark-coding-plan/doubao-seed-code-preview-251028", Name: "Doubao Seed Code Preview", ContextWindow: 256000, MaxTokens: 4096},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"zai": {
|
"zai": {
|
||||||
Key: "zai",
|
Key: "zai", DisplayName: "Z.ai", Sort: 40, DefaultAPIType: "openai-completions", EnvKey: "ZAI_API_KEY",
|
||||||
DisplayName: "Z.ai",
|
APIConfigs: []APIConfig{{APIType: "openai-completions", BaseURL: "https://open.bigmodel.cn/api/paas/v4", EditableBaseURL: true}},
|
||||||
Sort: 40,
|
Models: []Model{{ID: "glm-5", Name: "GLM-5"}, {ID: "glm-4.7", Name: "GLM-4.7"}, {ID: "glm-4.7-flash", Name: "GLM-4.7-Flash"}, {ID: "glm-4.7-flashx", Name: "GLM-4.7-FlashX"}},
|
||||||
DefaultBaseURL: "https://open.bigmodel.cn/api/paas/v4",
|
|
||||||
EnvKey: "ZAI_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "openai-completions",
|
|
||||||
ContextWindow: 204800,
|
|
||||||
MaxTokens: 131072,
|
|
||||||
Input: []string{"text"},
|
|
||||||
},
|
|
||||||
Models: []Model{
|
|
||||||
{ID: "zai/glm-5", Name: "GLM-5", Reasoning: true},
|
|
||||||
{ID: "zai/glm-4.7", Name: "GLM-4.7", Reasoning: true},
|
|
||||||
{ID: "zai/glm-4.7-flash", Name: "GLM-4.7-Flash", Reasoning: true},
|
|
||||||
{ID: "zai/glm-4.7-flashx", Name: "GLM-4.7-FlashX", Reasoning: true},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"minimax": {
|
"minimax": {
|
||||||
Key: "minimax",
|
Key: "minimax", DisplayName: "MiniMax (CN)", Sort: 45, DefaultAPIType: "anthropic-messages", EnvKey: "MINIMAX_API_KEY",
|
||||||
DisplayName: "MiniMax (CN)",
|
APIConfigs: []APIConfig{
|
||||||
Sort: 45,
|
anthropicAPIConfig("https://api.minimaxi.com/anthropic", AuthModeXAPIKey, AuthModeBearer),
|
||||||
DefaultBaseURL: "https://api.minimaxi.com/anthropic",
|
{APIType: "openai-completions", BaseURL: "https://api.minimaxi.com/v1"},
|
||||||
EnvKey: "MINIMAX_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "anthropic-messages",
|
|
||||||
ContextWindow: 200000,
|
|
||||||
MaxTokens: 8192,
|
|
||||||
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"},
|
|
||||||
},
|
},
|
||||||
|
Models: []Model{{ID: "MiniMax-M3", Name: "MiniMax M3"}, {ID: "MiniMax-M2.7", Name: "MiniMax M2.7"}, {ID: "MiniMax-M2.7-highspeed", Name: "MiniMax M2.7 highspeed"}},
|
||||||
},
|
},
|
||||||
"xiaomi": {
|
"xiaomi": {
|
||||||
Key: "xiaomi",
|
Key: "xiaomi", DisplayName: "Xiaomi", Sort: 46, DefaultAPIType: "openai-completions", EnvKey: "XIAOMI_API_KEY",
|
||||||
DisplayName: "Xiaomi",
|
APIConfigs: []APIConfig{
|
||||||
Sort: 46,
|
{APIType: "openai-completions", BaseURL: "https://api.xiaomimimo.com/v1"},
|
||||||
DefaultBaseURL: "https://api.xiaomimimo.com/v1",
|
anthropicAPIConfig("https://api.xiaomimimo.com/anthropic", AuthModeBearer),
|
||||||
EnvKey: "XIAOMI_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "openai-completions",
|
|
||||||
ContextWindow: 262144,
|
|
||||||
MaxTokens: 8192,
|
|
||||||
Input: []string{"text"},
|
|
||||||
},
|
|
||||||
Models: []Model{
|
|
||||||
{ID: "xiaomi/mimo-v2-flash", Name: "Xiaomi MiMo V2 Flash", ContextWindow: 262144, MaxTokens: 8192, Input: []string{"text"}},
|
|
||||||
{ID: "xiaomi/mimo-v2-pro", Name: "Xiaomi MiMo V2 Pro", ContextWindow: 1048576, MaxTokens: 32000, Reasoning: true, Input: []string{"text"}},
|
|
||||||
{ID: "xiaomi/mimo-v2-omni", Name: "Xiaomi MiMo V2 Omni", ContextWindow: 262144, MaxTokens: 32000, Reasoning: true, Input: []string{"text", "image"}},
|
|
||||||
},
|
},
|
||||||
|
Models: []Model{{ID: "mimo-v2-flash", Name: "Xiaomi MiMo V2 Flash"}, {ID: "mimo-v2-pro", Name: "Xiaomi MiMo V2 Pro"}, {ID: "mimo-v2-omni", Name: "Xiaomi MiMo V2 Omni"}},
|
||||||
},
|
},
|
||||||
"kimi": {
|
"kimi": {
|
||||||
Key: "kimi",
|
Key: "kimi", DisplayName: "Kimi (CN)", Sort: 50, DefaultAPIType: "openai-completions", EnvKey: "KIMI_API_KEY",
|
||||||
DisplayName: "Kimi (CN)",
|
APIConfigs: []APIConfig{{APIType: "openai-completions", BaseURL: "https://api.moonshot.cn/v1"}},
|
||||||
Sort: 50,
|
Models: []Model{{ID: "kimi-k2.5", Name: "Kimi K2.5"}, {ID: "kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"}, {ID: "kimi-k2-thinking", Name: "Kimi K2 Thinking"}},
|
||||||
DefaultBaseURL: "https://api.moonshot.cn/v1",
|
|
||||||
EnvKey: "KIMI_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "openai-completions",
|
|
||||||
ContextWindow: 256000,
|
|
||||||
MaxTokens: 8192,
|
|
||||||
Input: []string{"text", "image"},
|
|
||||||
},
|
|
||||||
Models: []Model{
|
|
||||||
{ID: "kimi/kimi-k2.5", Name: "Kimi K2.5", Reasoning: true},
|
|
||||||
{ID: "kimi/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
|
|
||||||
{ID: "kimi/kimi-k2-thinking", Name: "Kimi K2 Thinking", Reasoning: true},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"kimi-coding": {
|
"kimi-coding": {
|
||||||
Key: "kimi-coding",
|
Key: "kimi-coding", DisplayName: "Kimi Coding", Sort: 51, DefaultAPIType: "anthropic-messages", EnvKey: "KIMI_API_KEY",
|
||||||
DisplayName: "Kimi Coding",
|
APIConfigs: []APIConfig{anthropicAPIConfig("https://api.kimi.com/coding/", AuthModeXAPIKey)},
|
||||||
Sort: 51,
|
Models: []Model{{ID: "kimi-code", Name: "Kimi Code"}, {ID: "k2p5", Name: "Kimi K2.5"}},
|
||||||
DefaultBaseURL: "https://api.kimi.com/coding/",
|
|
||||||
EnvKey: "KIMI_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "anthropic-messages",
|
|
||||||
ContextWindow: 262144,
|
|
||||||
MaxTokens: 32768,
|
|
||||||
Input: []string{"text", "image"},
|
|
||||||
},
|
|
||||||
Models: []Model{
|
|
||||||
{ID: "kimi-coding/kimi-code", Name: "Kimi Code", ContextWindow: 262144, MaxTokens: 32768, Reasoning: true, Input: []string{"text", "image"}},
|
|
||||||
{ID: "kimi-coding/k2p5", Name: "Kimi K2.5", ContextWindow: 262144, MaxTokens: 32768, Reasoning: true, Input: []string{"text", "image"}},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"openai": {
|
"openai": {
|
||||||
Key: "openai",
|
Key: "openai", DisplayName: "OpenAI", Sort: 55, DefaultAPIType: "openai-responses", EnvKey: "OPENAI_API_KEY",
|
||||||
DisplayName: "OpenAI",
|
APIConfigs: []APIConfig{
|
||||||
Sort: 55,
|
{APIType: "openai-responses", BaseURL: "https://api.openai.com/v1"},
|
||||||
DefaultBaseURL: "https://api.openai.com/v1",
|
{APIType: "openai-completions", BaseURL: "https://api.openai.com/v1"},
|
||||||
EnvKey: "OPENAI_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "openai-responses",
|
|
||||||
ContextWindow: 272000,
|
|
||||||
MaxTokens: 128000,
|
|
||||||
Input: []string{"text", "image"},
|
|
||||||
},
|
|
||||||
Models: []Model{
|
|
||||||
{ID: "openai/gpt-5.4", Name: "gpt-5.4", ContextWindow: 272000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}},
|
|
||||||
{ID: "openai/gpt-5.4-pro", Name: "gpt-5.4-pro", ContextWindow: 1050000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}},
|
|
||||||
{ID: "openai/gpt-5.4-mini", Name: "gpt-5.4-mini", ContextWindow: 400000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}},
|
|
||||||
{ID: "openai/gpt-5.4-nano", Name: "gpt-5.4-nano", ContextWindow: 400000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}},
|
|
||||||
},
|
},
|
||||||
|
Models: []Model{{ID: "gpt-5.4", Name: "gpt-5.4"}, {ID: "gpt-5.4-pro", Name: "gpt-5.4-pro"}, {ID: "gpt-5.4-mini", Name: "gpt-5.4-mini"}, {ID: "gpt-5.4-nano", Name: "gpt-5.4-nano"}},
|
||||||
},
|
},
|
||||||
"openrouter": {
|
"openrouter": {
|
||||||
Key: "openrouter",
|
Key: "openrouter", DisplayName: "OpenRouter", Sort: 56, DefaultAPIType: "openai-completions", EnvKey: "OPENROUTER_API_KEY",
|
||||||
DisplayName: "OpenRouter",
|
APIConfigs: []APIConfig{{APIType: "openai-completions", BaseURL: "https://openrouter.ai/api/v1"}},
|
||||||
Sort: 56,
|
Models: []Model{{ID: "openrouter/free", Name: "openrouter/free"}, {ID: "openrouter/auto", Name: "openrouter/auto"}},
|
||||||
DefaultBaseURL: "https://openrouter.ai/api/v1",
|
|
||||||
EnvKey: "OPENROUTER_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "openai-completions",
|
|
||||||
ContextWindow: 128000,
|
|
||||||
MaxTokens: 8192,
|
|
||||||
Input: []string{"text"},
|
|
||||||
},
|
|
||||||
Models: []Model{
|
|
||||||
{ID: "openrouter/free", Name: "openrouter/free"},
|
|
||||||
{ID: "openrouter/auto", Name: "openrouter/auto"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"anthropic": {
|
"anthropic": {
|
||||||
Key: "anthropic",
|
Key: "anthropic", DisplayName: "Anthropic", Sort: 60, DefaultAPIType: "anthropic-messages", EnvKey: "ANTHROPIC_API_KEY",
|
||||||
DisplayName: "Anthropic",
|
APIConfigs: []APIConfig{anthropicAPIConfig("https://api.anthropic.com", AuthModeXAPIKey)},
|
||||||
Sort: 60,
|
Models: []Model{{ID: "claude-sonnet-4-6", Name: "Claude Sonnet 4.6"}, {ID: "claude-opus-4-6", Name: "Claude Opus 4.6"}, {ID: "claude-opus-4-5", Name: "Claude Opus 4.5"}, {ID: "claude-sonnet-4-5", Name: "Claude Sonnet 4.5"}, {ID: "claude-haiku-4-5", Name: "Claude Haiku 4.5"}},
|
||||||
DefaultBaseURL: "https://api.anthropic.com",
|
|
||||||
EnvKey: "ANTHROPIC_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "anthropic-messages",
|
|
||||||
ContextWindow: 256000,
|
|
||||||
MaxTokens: 8192,
|
|
||||||
Input: []string{"text", "image"},
|
|
||||||
},
|
|
||||||
Models: []Model{
|
|
||||||
{ID: "anthropic/claude-sonnet-4-6", Name: "Claude Sonnet 4.6", Reasoning: true},
|
|
||||||
{ID: "anthropic/claude-opus-4-6", Name: "Claude Opus 4.6", Reasoning: true},
|
|
||||||
{ID: "anthropic/claude-opus-4-5", Name: "Claude Opus 4.5"},
|
|
||||||
{ID: "anthropic/claude-sonnet-4-5", Name: "Claude Sonnet 4.5"},
|
|
||||||
{ID: "anthropic/claude-haiku-4-5", Name: "Claude Haiku 4.5"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"gemini": {
|
"gemini": {
|
||||||
Key: "gemini",
|
Key: "gemini", DisplayName: "Gemini", Sort: 65, DefaultAPIType: "gemini-generate-content", EnvKey: "GEMINI_API_KEY",
|
||||||
DisplayName: "Gemini",
|
APIConfigs: []APIConfig{{APIType: "gemini-generate-content", BaseURL: "https://generativelanguage.googleapis.com"}},
|
||||||
Sort: 65,
|
Models: []Model{{ID: "gemini-3-flash-preview", Name: "Gemini 3 Flash Preview"}, {ID: "gemini-flash-latest", Name: "Gemini Flash Latest"}, {ID: "gemini-3-pro-preview", Name: "Gemini 3 Pro Preview"}},
|
||||||
DefaultBaseURL: "https://generativelanguage.googleapis.com",
|
|
||||||
EnvKey: "GEMINI_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "openai-completions",
|
|
||||||
ContextWindow: 256000,
|
|
||||||
MaxTokens: 8192,
|
|
||||||
Input: []string{"text", "image"},
|
|
||||||
},
|
|
||||||
Models: []Model{
|
|
||||||
{ID: "google/gemini-3-flash-preview", Name: "Gemini 3 Flash Preview", Reasoning: true},
|
|
||||||
{ID: "google/gemini-flash-latest", Name: "Gemini Flash Latest"},
|
|
||||||
{ID: "google/gemini-3-pro-preview", Name: "Gemini 3 Pro Preview", Reasoning: true},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"moonshot": {
|
"moonshot": {
|
||||||
Key: "moonshot",
|
Key: "moonshot", DisplayName: "Moonshot (Global)", Sort: 70, DefaultAPIType: "openai-completions", EnvKey: "MOONSHOT_API_KEY",
|
||||||
DisplayName: "Moonshot (Global)",
|
APIConfigs: []APIConfig{{APIType: "openai-completions", BaseURL: "https://api.moonshot.ai/v1"}},
|
||||||
Sort: 70,
|
Models: []Model{{ID: "kimi-k2.5", Name: "Kimi K2.5"}, {ID: "kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"}, {ID: "kimi-k2-thinking", Name: "Kimi K2 Thinking"}},
|
||||||
DefaultBaseURL: "https://api.moonshot.ai/v1",
|
|
||||||
EnvKey: "MOONSHOT_API_KEY",
|
|
||||||
Default: RuntimeDefault{
|
|
||||||
APIType: "openai-completions",
|
|
||||||
ContextWindow: 256000,
|
|
||||||
MaxTokens: 8192,
|
|
||||||
Input: []string{"text"},
|
|
||||||
},
|
|
||||||
Models: []Model{
|
|
||||||
{ID: "moonshot/kimi-k2.5", Name: "Kimi K2.5", Reasoning: true},
|
|
||||||
{ID: "moonshot/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
|
|
||||||
{ID: "moonshot/kimi-k2-thinking", Name: "Kimi K2 Thinking", Reasoning: true},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func editableAPIConfigs(apiTypes ...string) []APIConfig {
|
||||||
|
configs := make([]APIConfig, 0, len(apiTypes))
|
||||||
|
for _, apiType := range apiTypes {
|
||||||
|
if apiType == "anthropic-messages" {
|
||||||
|
config := anthropicAPIConfig("", AuthModeXAPIKey, AuthModeBearer)
|
||||||
|
config.EditableBaseURL = true
|
||||||
|
configs = append(configs, config)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
configs = append(configs, APIConfig{APIType: apiType, EditableBaseURL: true})
|
||||||
|
}
|
||||||
|
return configs
|
||||||
|
}
|
||||||
|
|
||||||
|
func anthropicAPIConfig(baseURL, defaultAuthMode string, additionalAuthModes ...string) APIConfig {
|
||||||
|
authModes := []string{defaultAuthMode}
|
||||||
|
for _, authMode := range additionalAuthModes {
|
||||||
|
if authMode != defaultAuthMode {
|
||||||
|
authModes = append(authModes, authMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return APIConfig{
|
||||||
|
APIType: "anthropic-messages",
|
||||||
|
BaseURL: baseURL,
|
||||||
|
DefaultAuthMode: defaultAuthMode,
|
||||||
|
AuthModes: authModes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Get(key string) (Meta, bool) {
|
func Get(key string) (Meta, bool) {
|
||||||
meta, ok := catalog[key]
|
meta, ok := catalog[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -331,12 +192,101 @@ func All() map[string]Meta {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultBaseURL(key string) (string, bool) {
|
func FindAPIConfig(key, apiType string) (APIConfig, bool) {
|
||||||
meta, ok := catalog[key]
|
meta, ok := catalog[key]
|
||||||
if !ok || strings.TrimSpace(meta.DefaultBaseURL) == "" {
|
if !ok {
|
||||||
|
return APIConfig{}, false
|
||||||
|
}
|
||||||
|
target := strings.TrimSpace(apiType)
|
||||||
|
if target == "" {
|
||||||
|
target = meta.DefaultAPIType
|
||||||
|
}
|
||||||
|
for _, config := range meta.APIConfigs {
|
||||||
|
if config.APIType == target {
|
||||||
|
return config, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return APIConfig{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveAuthMode(provider, apiType, requested string) (string, error) {
|
||||||
|
config, ok := FindAPIConfig(provider, apiType)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("provider %s does not support api type %s", provider, apiType)
|
||||||
|
}
|
||||||
|
if config.APIType != "anthropic-messages" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
authMode := strings.TrimSpace(requested)
|
||||||
|
if authMode == "" {
|
||||||
|
authMode = config.DefaultAuthMode
|
||||||
|
}
|
||||||
|
for _, allowed := range config.AuthModes {
|
||||||
|
if authMode == allowed {
|
||||||
|
return authMode, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("provider %s does not support auth mode %s", provider, authMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultBaseURL(key string) (string, bool) {
|
||||||
|
config, ok := FindAPIConfig(key, "")
|
||||||
|
if !ok || strings.TrimSpace(config.BaseURL) == "" {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return meta.DefaultBaseURL, true
|
return config.BaseURL, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultAPIType(key string) string {
|
||||||
|
meta, ok := catalog[key]
|
||||||
|
if !ok || strings.TrimSpace(meta.DefaultAPIType) == "" {
|
||||||
|
return "openai-completions"
|
||||||
|
}
|
||||||
|
return meta.DefaultAPIType
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveBaseURL(key, apiType, requested string) (string, error) {
|
||||||
|
config, ok := FindAPIConfig(key, apiType)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("provider %s does not support api type %s", key, apiType)
|
||||||
|
}
|
||||||
|
if !config.EditableBaseURL {
|
||||||
|
return strings.TrimRight(config.BaseURL, "/"), nil
|
||||||
|
}
|
||||||
|
baseURL := strings.TrimSpace(requested)
|
||||||
|
if baseURL == "" {
|
||||||
|
baseURL = config.BaseURL
|
||||||
|
}
|
||||||
|
if baseURL == "" {
|
||||||
|
return "", fmt.Errorf("base url is required")
|
||||||
|
}
|
||||||
|
parsed, err := url.Parse(baseURL)
|
||||||
|
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||||||
|
return "", fmt.Errorf("invalid base url")
|
||||||
|
}
|
||||||
|
parsed.Path = normalizeEndpointPath(config.APIType, parsed.Path)
|
||||||
|
parsed.RawQuery = ""
|
||||||
|
parsed.Fragment = ""
|
||||||
|
return strings.TrimRight(parsed.String(), "/"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeEndpointPath(apiType, value string) string {
|
||||||
|
path := strings.TrimRight(value, "/")
|
||||||
|
suffixes := []string{}
|
||||||
|
switch apiType {
|
||||||
|
case "openai-completions":
|
||||||
|
suffixes = []string{"/chat/completions"}
|
||||||
|
case "openai-responses":
|
||||||
|
suffixes = []string{"/responses"}
|
||||||
|
case "anthropic-messages":
|
||||||
|
suffixes = []string{"/v1/messages", "/messages"}
|
||||||
|
}
|
||||||
|
for _, suffix := range suffixes {
|
||||||
|
if strings.HasSuffix(strings.ToLower(path), suffix) {
|
||||||
|
return path[:len(path)-len(suffix)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnvKey(key string) string {
|
func EnvKey(key string) string {
|
||||||
@@ -349,89 +299,52 @@ func EnvKey(key string) string {
|
|||||||
|
|
||||||
func DisplayName(key string) string {
|
func DisplayName(key string) string {
|
||||||
meta, ok := catalog[key]
|
meta, ok := catalog[key]
|
||||||
if !ok {
|
if !ok || strings.TrimSpace(meta.DisplayName) == "" {
|
||||||
return key
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(meta.DisplayName) == "" {
|
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
return meta.DisplayName
|
return meta.DisplayName
|
||||||
}
|
}
|
||||||
|
|
||||||
func FindModel(key, modelID string) (Model, bool) {
|
func NormalizeModelID(provider, modelID string) string {
|
||||||
meta, ok := Get(key)
|
target := strings.TrimLeft(strings.TrimSpace(modelID), "/")
|
||||||
if !ok {
|
for _, prefix := range legacyModelPrefixes[provider] {
|
||||||
return Model{}, false
|
legacyPrefix := prefix + "/"
|
||||||
}
|
if !strings.HasPrefix(target, legacyPrefix) {
|
||||||
for _, item := range meta.Models {
|
continue
|
||||||
if item.ID == modelID {
|
}
|
||||||
return item, true
|
candidate := strings.TrimSpace(strings.TrimPrefix(target, legacyPrefix))
|
||||||
|
if candidate != "" {
|
||||||
|
return candidate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Model{}, false
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
var legacyModelPrefixes = map[string][]string{
|
||||||
|
"custom": {"custom"},
|
||||||
|
"vllm": {"custom"},
|
||||||
|
"ollama": {"ollama"},
|
||||||
|
"deepseek": {"deepseek"},
|
||||||
|
"bailian-coding-plan": {"bailian-coding-plan"},
|
||||||
|
"ark-coding-plan": {"ark-coding-plan"},
|
||||||
|
"zai": {"zai"},
|
||||||
|
"minimax": {"minimax"},
|
||||||
|
"xiaomi": {"xiaomi"},
|
||||||
|
"kimi": {"kimi", "moonshot"},
|
||||||
|
"kimi-coding": {"kimi-coding"},
|
||||||
|
"openai": {"openai"},
|
||||||
|
"anthropic": {"anthropic"},
|
||||||
|
"gemini": {"google", "gemini"},
|
||||||
|
"moonshot": {"moonshot"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneMeta(meta Meta) Meta {
|
func cloneMeta(meta Meta) Meta {
|
||||||
clone := meta
|
clone := meta
|
||||||
if len(meta.Default.Input) > 0 {
|
clone.APIConfigs = make([]APIConfig, len(meta.APIConfigs))
|
||||||
clone.Default.Input = make([]string, len(meta.Default.Input))
|
for index, config := range meta.APIConfigs {
|
||||||
copy(clone.Default.Input, meta.Default.Input)
|
clone.APIConfigs[index] = config
|
||||||
}
|
clone.APIConfigs[index].AuthModes = append([]string(nil), config.AuthModes...)
|
||||||
if len(meta.Models) > 0 {
|
|
||||||
clone.Models = make([]Model, len(meta.Models))
|
|
||||||
for i, item := range meta.Models {
|
|
||||||
clone.Models[i] = normalizeModel(meta, item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
clone.Models = append([]Model(nil), meta.Models...)
|
||||||
return clone
|
return clone
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeModel(meta Meta, model Model) Model {
|
|
||||||
clone := model
|
|
||||||
clone.ID = strings.TrimSpace(clone.ID)
|
|
||||||
clone.Name = strings.TrimSpace(clone.Name)
|
|
||||||
if clone.Name == "" {
|
|
||||||
clone.Name = clone.ID
|
|
||||||
}
|
|
||||||
if clone.MaxTokens <= 0 {
|
|
||||||
clone.MaxTokens = meta.Default.MaxTokens
|
|
||||||
}
|
|
||||||
if clone.ContextWindow <= 0 {
|
|
||||||
clone.ContextWindow = meta.Default.ContextWindow
|
|
||||||
}
|
|
||||||
if len(clone.Input) == 0 && len(meta.Default.Input) > 0 {
|
|
||||||
clone.Input = make([]string, len(meta.Default.Input))
|
|
||||||
copy(clone.Input, meta.Default.Input)
|
|
||||||
}
|
|
||||||
return clone
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResolveRuntimeParams(provider, apiType string, maxTokens, contextWindow int) (string, int, int) {
|
|
||||||
defaultAPIType := "openai-completions"
|
|
||||||
defaultMaxTokens := 8192
|
|
||||||
defaultContextWindow := 256000
|
|
||||||
if meta, ok := Get(provider); ok {
|
|
||||||
if meta.Default.APIType != "" {
|
|
||||||
defaultAPIType = meta.Default.APIType
|
|
||||||
}
|
|
||||||
if meta.Default.MaxTokens > 0 {
|
|
||||||
defaultMaxTokens = meta.Default.MaxTokens
|
|
||||||
}
|
|
||||||
if meta.Default.ContextWindow > 0 {
|
|
||||||
defaultContextWindow = meta.Default.ContextWindow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolvedAPI := apiType
|
|
||||||
if strings.TrimSpace(apiType) == "" {
|
|
||||||
resolvedAPI = defaultAPIType
|
|
||||||
}
|
|
||||||
resolvedMaxTokens := defaultMaxTokens
|
|
||||||
resolvedContextWindow := defaultContextWindow
|
|
||||||
if maxTokens > 0 {
|
|
||||||
resolvedMaxTokens = maxTokens
|
|
||||||
}
|
|
||||||
if contextWindow > 0 {
|
|
||||||
resolvedContextWindow = contextWindow
|
|
||||||
}
|
|
||||||
return resolvedAPI, resolvedMaxTokens, resolvedContextWindow
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DiscoverModels(baseURL, apiKey string) ([]string, error) {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, buildModelDiscoveryURL(baseURL), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
|
resp, err := (&http.Client{Timeout: defaultVerifyTimeout}).Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode >= http.StatusBadRequest {
|
||||||
|
return nil, fmt.Errorf("request failed: %s", resp.Status)
|
||||||
|
}
|
||||||
|
var payload struct {
|
||||||
|
Data []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
models := make([]string, 0, len(payload.Data))
|
||||||
|
seen := make(map[string]struct{}, len(payload.Data))
|
||||||
|
for _, item := range payload.Data {
|
||||||
|
id := strings.TrimSpace(item.ID)
|
||||||
|
if id == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[id]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[id] = struct{}{}
|
||||||
|
models = append(models, id)
|
||||||
|
}
|
||||||
|
if len(models) == 0 {
|
||||||
|
return nil, fmt.Errorf("no models found")
|
||||||
|
}
|
||||||
|
return models, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildModelDiscoveryURL(baseURL string) string {
|
||||||
|
base := strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
||||||
|
for _, apiType := range []string{"openai-completions", "openai-responses", "anthropic-messages"} {
|
||||||
|
base = normalizeEndpointPath(apiType, base)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(base, "/v1/models"):
|
||||||
|
return base
|
||||||
|
case strings.HasSuffix(base, "/v1"):
|
||||||
|
return base + "/models"
|
||||||
|
default:
|
||||||
|
return base + "/v1/models"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,72 +15,49 @@ type OpenClawProviderPatch struct {
|
|||||||
AuthHeader bool
|
AuthHeader bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildOpenClawProviderPatch(provider, modelName, apiType, baseURL, apiKey string) (*OpenClawProviderPatch, error) {
|
func BuildOpenClawProviderPatch(provider, modelName, apiType, authMode, baseURL, apiKey string) (*OpenClawProviderPatch, error) {
|
||||||
if modelName == "" {
|
if modelName == "" {
|
||||||
return nil, fmt.Errorf("model is required")
|
return nil, fmt.Errorf("model is required")
|
||||||
}
|
}
|
||||||
resolvedAPIType, _, _ := ResolveRuntimeParams(provider, apiType, 0, 0)
|
resolvedAPIType := apiType
|
||||||
modelID := resolveOpenClawModelID(provider, modelName)
|
if _, ok := FindAPIConfig(provider, resolvedAPIType); !ok {
|
||||||
switch provider {
|
resolvedAPIType = DefaultAPIType(provider)
|
||||||
case "deepseek":
|
|
||||||
return newOpenClawProviderPatch(modelName, "deepseek", modelID, apiKey, baseURL, "openai-completions", false), nil
|
|
||||||
case "gemini":
|
|
||||||
return newOpenClawProviderPatch("google/"+modelID, "google", modelID, apiKey, baseURL, resolvedAPIType, false), nil
|
|
||||||
case "moonshot", "kimi":
|
|
||||||
return buildMoonshotProviderPatch(provider, modelName, modelID, baseURL, apiKey), nil
|
|
||||||
case "bailian-coding-plan":
|
|
||||||
return newOpenClawProviderPatch("bailian-coding-plan/"+modelID, "bailian-coding-plan", modelID, apiKey, baseURL, "openai-completions", false), nil
|
|
||||||
case "ark-coding-plan":
|
|
||||||
return newOpenClawProviderPatch("ark-coding-plan/"+modelID, "ark-coding-plan", modelID, apiKey, baseURL, "openai-completions", false), nil
|
|
||||||
case "minimax":
|
|
||||||
return newOpenClawProviderPatch("minimax/"+modelID, "minimax", modelID, apiKey, baseURL, "anthropic-messages", true), nil
|
|
||||||
case "xiaomi":
|
|
||||||
return newOpenClawProviderPatch("xiaomi/"+modelID, "xiaomi", modelID, apiKey, baseURL, "openai-completions", false), nil
|
|
||||||
case "custom", "vllm":
|
|
||||||
return newOpenClawProviderPatch(provider+"/"+modelID, provider, modelID, apiKey, baseURL, resolvedAPIType, false), nil
|
|
||||||
case "ollama":
|
|
||||||
return newOpenClawProviderPatch(modelName, "ollama", modelID, "ollama", baseURL, resolvedAPIType, false), nil
|
|
||||||
case "kimi-coding":
|
|
||||||
return newOpenClawProviderPatch(modelName, "kimi-coding", modelID, apiKey, baseURL, "anthropic-messages", false), nil
|
|
||||||
case "zai":
|
|
||||||
return newOpenClawProviderPatch("zai/"+modelID, "zai", modelID, apiKey, baseURL, "openai-completions", false), nil
|
|
||||||
default:
|
|
||||||
return newOpenClawProviderPatch(modelName, provider, modelID, apiKey, baseURL, resolvedAPIType, false), nil
|
|
||||||
}
|
}
|
||||||
}
|
resolvedAuthMode, err := ResolveAuthMode(provider, resolvedAPIType, authMode)
|
||||||
|
if err != nil {
|
||||||
func buildMoonshotProviderPatch(provider, modelName, modelID, baseURL, apiKey string) *OpenClawProviderPatch {
|
return nil, err
|
||||||
|
}
|
||||||
|
usesBearer := resolvedAuthMode == AuthModeBearer
|
||||||
|
modelID := NormalizeModelID(provider, modelName)
|
||||||
providerKey := provider
|
providerKey := provider
|
||||||
primaryModel := modelName
|
preserveQualifiedModel := false
|
||||||
if provider == "kimi" {
|
switch provider {
|
||||||
|
case "gemini":
|
||||||
|
providerKey = "google"
|
||||||
|
resolvedAPIType = "google-generative-ai"
|
||||||
|
usesBearer = false
|
||||||
|
case "moonshot", "kimi":
|
||||||
providerKey = "moonshot"
|
providerKey = "moonshot"
|
||||||
primaryModel = "moonshot/" + modelID
|
resolvedAPIType = "openai-completions"
|
||||||
|
usesBearer = false
|
||||||
|
case "ollama":
|
||||||
|
apiKey = "ollama"
|
||||||
|
usesBearer = false
|
||||||
|
case "openai", "openrouter", "anthropic":
|
||||||
|
preserveQualifiedModel = strings.Contains(modelName, "/")
|
||||||
}
|
}
|
||||||
return newOpenClawProviderPatch(primaryModel, providerKey, modelID, apiKey, baseURL, "openai-completions", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newOpenClawProviderPatch(primaryModel, providerKey, modelID, apiKey, baseURL, apiType string, authHeader bool) *OpenClawProviderPatch {
|
primaryModel := providerKey + "/" + modelID
|
||||||
|
if preserveQualifiedModel {
|
||||||
|
primaryModel = modelName
|
||||||
|
}
|
||||||
return &OpenClawProviderPatch{
|
return &OpenClawProviderPatch{
|
||||||
PrimaryModel: primaryModel,
|
PrimaryModel: primaryModel,
|
||||||
ProviderKey: providerKey,
|
ProviderKey: providerKey,
|
||||||
ModelID: modelID,
|
ModelID: modelID,
|
||||||
APIKey: apiKey,
|
APIKey: apiKey,
|
||||||
BaseURL: baseURL,
|
BaseURL: baseURL,
|
||||||
APIType: apiType,
|
APIType: resolvedAPIType,
|
||||||
AuthHeader: authHeader,
|
AuthHeader: usesBearer,
|
||||||
}
|
}, nil
|
||||||
}
|
|
||||||
|
|
||||||
func resolveOpenClawModelID(provider, modelName string) string {
|
|
||||||
if provider == "custom" || provider == "vllm" {
|
|
||||||
target := strings.TrimLeft(modelName, "/")
|
|
||||||
if parts := strings.SplitN(target, "/", 2); len(parts) == 2 && parts[0] == "custom" {
|
|
||||||
return strings.TrimLeft(parts[1], "/")
|
|
||||||
}
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
if parts := strings.SplitN(modelName, "/", 2); len(parts) == 2 {
|
|
||||||
return parts[1]
|
|
||||||
}
|
|
||||||
return modelName
|
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-119
@@ -3,7 +3,9 @@ package provider
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -18,160 +20,108 @@ type VerifyRequest struct {
|
|||||||
Body []byte
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
type verifyErrorResponse struct {
|
||||||
defaultVerifyTimeout = 30 * time.Second
|
Error struct {
|
||||||
)
|
Message string `json:"message"`
|
||||||
|
} `json:"error"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
func SkipVerification(key string) bool {
|
const defaultVerifyTimeout = 30 * time.Second
|
||||||
switch key {
|
|
||||||
case "custom", "vllm", "ollama", "kimi-coding":
|
func SkipVerification(provider string) bool {
|
||||||
|
switch provider {
|
||||||
|
case "vllm", "ollama", "kimi-coding":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func VerifyAccount(provider, baseURL, apiKey string) error {
|
func VerifyAccount(provider, apiType, authMode, baseURL, apiKey, model string) error {
|
||||||
req := BuildVerifyRequest(provider, baseURL, apiKey)
|
req := BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model)
|
||||||
var body *bytes.Buffer
|
httpReq, err := http.NewRequest(req.Method, req.URL, bytes.NewReader(req.Body))
|
||||||
if len(req.Body) > 0 {
|
|
||||||
body = bytes.NewBuffer(req.Body)
|
|
||||||
} else {
|
|
||||||
body = bytes.NewBuffer(nil)
|
|
||||||
}
|
|
||||||
httpReq, err := http.NewRequest(req.Method, req.URL, body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for key, value := range req.Headers {
|
for key, value := range req.Headers {
|
||||||
httpReq.Header.Set(key, value)
|
httpReq.Header.Set(key, value)
|
||||||
}
|
}
|
||||||
resp, err := (&http.Client{Timeout: verifyTimeout()}).Do(httpReq)
|
httpReq.Header.Set("Accept", "application/json")
|
||||||
|
resp, err := (&http.Client{Timeout: defaultVerifyTimeout}).Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return buserr.WithErr("ErrAgentAccountUnavailable", err)
|
return buserr.WithErr("ErrAgentAccountUnavailable", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode >= 400 {
|
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||||
return buserr.WithErr("ErrAgentAccountUnavailable", fmt.Errorf("verify failed: %s", resp.Status))
|
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1024*1024))
|
||||||
|
if readErr != nil {
|
||||||
|
return buserr.WithErr("ErrAgentAccountUnavailable", readErr)
|
||||||
|
}
|
||||||
|
return buserr.WithErr("ErrAgentAccountUnavailable", errors.New(verifyHTTPError(resp.StatusCode, body)))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyTimeout() time.Duration {
|
func BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model string) VerifyRequest {
|
||||||
return defaultVerifyTimeout
|
baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
||||||
}
|
headers := map[string]string{"Content-Type": "application/json"}
|
||||||
|
request := VerifyRequest{Method: http.MethodPost, Headers: headers}
|
||||||
|
|
||||||
func BuildVerifyRequest(provider, baseURL, apiKey string) VerifyRequest {
|
if provider == "gemini" {
|
||||||
base := strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
request.URL = baseURL + "/v1beta/models/" + strings.TrimSpace(model) + ":generateContent"
|
||||||
headers := map[string]string{}
|
|
||||||
request := VerifyRequest{Method: http.MethodGet, Headers: headers}
|
|
||||||
|
|
||||||
switch provider {
|
|
||||||
case "anthropic", "kimi-coding":
|
|
||||||
headers["x-api-key"] = apiKey
|
|
||||||
headers["anthropic-version"] = "2023-06-01"
|
|
||||||
if strings.Contains(base, "/v1") {
|
|
||||||
request.URL = base + "/models"
|
|
||||||
} else {
|
|
||||||
request.URL = base + "/v1/models"
|
|
||||||
}
|
|
||||||
case "gemini":
|
|
||||||
request.Method = http.MethodPost
|
|
||||||
if strings.Contains(base, "/v1beta") {
|
|
||||||
request.URL = base + "/models/gemini-3-flash-preview:generateContent"
|
|
||||||
} else {
|
|
||||||
request.URL = base + "/v1beta/models/gemini-3-flash-preview:generateContent"
|
|
||||||
}
|
|
||||||
headers["x-goog-api-key"] = apiKey
|
headers["x-goog-api-key"] = apiKey
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
request.Body = mustJSON(map[string]interface{}{
|
request.Body = mustJSON(map[string]interface{}{
|
||||||
"contents": []map[string]interface{}{{
|
"contents": []map[string]interface{}{{"parts": []map[string]string{{"text": "test"}}}},
|
||||||
"parts": []map[string]string{{
|
|
||||||
"text": "Explain how AI works in a few words",
|
|
||||||
}},
|
|
||||||
}},
|
|
||||||
})
|
})
|
||||||
case "zai":
|
return request
|
||||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
}
|
||||||
request.URL = base + "/models"
|
|
||||||
case "bailian-coding-plan":
|
switch apiType {
|
||||||
request.Method = http.MethodPost
|
case "anthropic-messages":
|
||||||
if !strings.Contains(base, "/v1") {
|
request.URL = baseURL + "/v1/messages"
|
||||||
base = base + "/v1"
|
if authMode == AuthModeBearer {
|
||||||
|
headers["Authorization"] = "Bearer " + apiKey
|
||||||
|
} else {
|
||||||
|
headers["x-api-key"] = apiKey
|
||||||
}
|
}
|
||||||
request.URL = base + "/chat/completions"
|
|
||||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
request.Body = mustJSON(map[string]interface{}{
|
|
||||||
"model": "qwen3.5-plus",
|
|
||||||
"messages": []map[string]string{{"role": "user", "content": "test"}},
|
|
||||||
"max_tokens": 1,
|
|
||||||
})
|
|
||||||
case "ark-coding-plan":
|
|
||||||
request.Method = http.MethodPost
|
|
||||||
if !strings.Contains(base, "/api/coding/v3") {
|
|
||||||
base = "https://ark.cn-beijing.volces.com/api/coding/v3"
|
|
||||||
}
|
|
||||||
request.URL = base + "/chat/completions"
|
|
||||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
request.Body = mustJSON(map[string]interface{}{
|
|
||||||
"model": "ark-code-latest",
|
|
||||||
"messages": []map[string]string{{"role": "user", "content": "test"}},
|
|
||||||
"max_tokens": 1,
|
|
||||||
})
|
|
||||||
case "minimax":
|
|
||||||
request.Method = http.MethodPost
|
|
||||||
headers["x-api-key"] = apiKey
|
|
||||||
headers["anthropic-version"] = "2023-06-01"
|
headers["anthropic-version"] = "2023-06-01"
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
if strings.Contains(base, "/v1") {
|
|
||||||
request.URL = base + "/messages"
|
|
||||||
} else {
|
|
||||||
request.URL = base + "/v1/messages"
|
|
||||||
}
|
|
||||||
request.Body = mustJSON(map[string]interface{}{
|
request.Body = mustJSON(map[string]interface{}{
|
||||||
"model": "MiniMax-M3",
|
"model": model, "max_tokens": 1, "stream": false,
|
||||||
"max_tokens": 1,
|
"messages": []map[string]interface{}{{"role": "user", "content": []map[string]string{{"type": "text", "text": "test"}}}},
|
||||||
"messages": []map[string]interface{}{{
|
|
||||||
"role": "user",
|
|
||||||
"content": []map[string]string{{
|
|
||||||
"type": "text",
|
|
||||||
"text": "test",
|
|
||||||
}},
|
|
||||||
}},
|
|
||||||
})
|
})
|
||||||
case "xiaomi":
|
case "openai-responses":
|
||||||
request.Method = http.MethodPost
|
request.URL = baseURL + "/responses"
|
||||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
headers["Authorization"] = "Bearer " + apiKey
|
||||||
headers["Content-Type"] = "application/json"
|
request.Body = mustJSON(map[string]interface{}{"model": model, "input": "test", "max_output_tokens": 1, "stream": false})
|
||||||
if !strings.Contains(base, "/v1") {
|
|
||||||
base = base + "/v1"
|
|
||||||
}
|
|
||||||
request.URL = base + "/chat/completions"
|
|
||||||
request.Body = mustJSON(map[string]interface{}{
|
|
||||||
"model": "mimo-v2-flash",
|
|
||||||
"max_tokens": 1,
|
|
||||||
"messages": []map[string]string{{"role": "user", "content": "test"}},
|
|
||||||
})
|
|
||||||
case "openrouter":
|
|
||||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
|
||||||
if strings.Contains(base, "/v1") {
|
|
||||||
request.URL = base + "/key"
|
|
||||||
} else {
|
|
||||||
request.URL = base + "/v1/key"
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
request.URL = baseURL + "/chat/completions"
|
||||||
if strings.Contains(base, "/v1") {
|
if provider != "ollama" || strings.TrimSpace(apiKey) != "" {
|
||||||
request.URL = base + "/models"
|
headers["Authorization"] = "Bearer " + apiKey
|
||||||
} else {
|
|
||||||
request.URL = base + "/v1/models"
|
|
||||||
}
|
}
|
||||||
|
request.Body = mustJSON(map[string]interface{}{
|
||||||
|
"model": model, "messages": []map[string]string{{"role": "user", "content": "test"}}, "max_tokens": 1, "stream": false,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func verifyHTTPError(statusCode int, body []byte) string {
|
||||||
|
message := strings.TrimSpace(string(body))
|
||||||
|
var payload verifyErrorResponse
|
||||||
|
if err := json.Unmarshal(body, &payload); err == nil {
|
||||||
|
if value := strings.TrimSpace(payload.Error.Message); value != "" {
|
||||||
|
message = value
|
||||||
|
} else if value := strings.TrimSpace(payload.Message); value != "" {
|
||||||
|
message = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if message == "" {
|
||||||
|
return fmt.Sprintf("validation request returned status %d", statusCode)
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
func mustJSON(value interface{}) []byte {
|
func mustJSON(value interface{}) []byte {
|
||||||
payload, err := json.Marshal(value)
|
payload, err := json.Marshal(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type IAppInstallResourceRpo interface {
|
|||||||
WithAppInstallId(appInstallId uint) DBOption
|
WithAppInstallId(appInstallId uint) DBOption
|
||||||
WithLinkId(linkId uint) DBOption
|
WithLinkId(linkId uint) DBOption
|
||||||
WithResourceId(resourceId uint) DBOption
|
WithResourceId(resourceId uint) DBOption
|
||||||
|
WithResourceIds(resourceIds []uint) DBOption
|
||||||
GetBy(opts ...DBOption) ([]model.AppInstallResource, error)
|
GetBy(opts ...DBOption) ([]model.AppInstallResource, error)
|
||||||
GetFirst(opts ...DBOption) (model.AppInstallResource, error)
|
GetFirst(opts ...DBOption) (model.AppInstallResource, error)
|
||||||
Create(ctx context.Context, resource *model.AppInstallResource) error
|
Create(ctx context.Context, resource *model.AppInstallResource) error
|
||||||
@@ -44,6 +45,12 @@ func (a AppInstallResourceRpo) WithResourceId(resourceId uint) DBOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a AppInstallResourceRpo) WithResourceIds(resourceIds []uint) DBOption {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("resource_id IN ?", resourceIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a AppInstallResourceRpo) GetBy(opts ...DBOption) ([]model.AppInstallResource, error) {
|
func (a AppInstallResourceRpo) GetBy(opts ...DBOption) ([]model.AppInstallResource, error) {
|
||||||
db := global.DB.Model(&model.AppInstallResource{})
|
db := global.DB.Model(&model.AppInstallResource{})
|
||||||
var resources []model.AppInstallResource
|
var resources []model.AppInstallResource
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DatabaseUserRepo struct{}
|
||||||
|
|
||||||
|
type IDatabaseUserRepo interface {
|
||||||
|
Get(opts ...DBOption) (model.DatabaseUser, error)
|
||||||
|
List(opts ...DBOption) ([]model.DatabaseUser, error)
|
||||||
|
Save(user *model.DatabaseUser) error
|
||||||
|
Delete(opts ...DBOption) error
|
||||||
|
DeleteBy(ctx context.Context, opts ...DBOption) error
|
||||||
|
Update(vars map[string]interface{}, opts ...DBOption) error
|
||||||
|
WithByDatabase(database string) DBOption
|
||||||
|
WithByUser(username, host string) DBOption
|
||||||
|
WithByUserList(users [][2]string) DBOption
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIDatabaseUserRepo() IDatabaseUserRepo {
|
||||||
|
return &DatabaseUserRepo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserRepo) Get(opts ...DBOption) (model.DatabaseUser, error) {
|
||||||
|
var user model.DatabaseUser
|
||||||
|
db := global.DB.Model(&model.DatabaseUser{})
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
if err := db.First(&user).Error; err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
password, err := encrypt.StringDecrypt(user.Password)
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("decrypt database user %s password failed, err: %v", user.Username, err)
|
||||||
|
}
|
||||||
|
user.Password = password
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserRepo) List(opts ...DBOption) ([]model.DatabaseUser, error) {
|
||||||
|
var users []model.DatabaseUser
|
||||||
|
db := global.DB.Model(&model.DatabaseUser{})
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
if err := db.Find(&users).Error; err != nil {
|
||||||
|
return users, err
|
||||||
|
}
|
||||||
|
for i := 0; i < len(users); i++ {
|
||||||
|
password, err := encrypt.StringDecrypt(users[i].Password)
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("decrypt database user %s password failed, err: %v", users[i].Username, err)
|
||||||
|
}
|
||||||
|
users[i].Password = password
|
||||||
|
}
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserRepo) Save(user *model.DatabaseUser) error {
|
||||||
|
if len(user.Password) != 0 {
|
||||||
|
password, err := encrypt.StringEncrypt(user.Password)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encrypt database user %s password failed, err: %v", user.Username, err)
|
||||||
|
}
|
||||||
|
user.Password = password
|
||||||
|
}
|
||||||
|
return global.DB.Save(user).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserRepo) Delete(opts ...DBOption) error {
|
||||||
|
db := global.DB
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
return db.Delete(&model.DatabaseUser{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserRepo) DeleteBy(ctx context.Context, opts ...DBOption) error {
|
||||||
|
return getTx(ctx, opts...).Delete(&model.DatabaseUser{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserRepo) Update(vars map[string]interface{}, opts ...DBOption) error {
|
||||||
|
db := global.DB.Model(&model.DatabaseUser{})
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
return db.Updates(vars).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserRepo) WithByDatabase(database string) DBOption {
|
||||||
|
return func(g *gorm.DB) *gorm.DB {
|
||||||
|
return g.Where("database = ?", database)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserRepo) WithByUser(username, host string) DBOption {
|
||||||
|
return func(g *gorm.DB) *gorm.DB {
|
||||||
|
return g.Where("username = ? AND host = ?", username, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserRepo) WithByUserList(users [][2]string) DBOption {
|
||||||
|
return func(g *gorm.DB) *gorm.DB {
|
||||||
|
if len(users) == 0 {
|
||||||
|
return g.Where("1 = 0")
|
||||||
|
}
|
||||||
|
values := make([][]interface{}, 0, len(users))
|
||||||
|
for _, user := range users {
|
||||||
|
values = append(values, []interface{}{user[0], user[1]})
|
||||||
|
}
|
||||||
|
return g.Where("(username, host) IN ?", values)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DatabaseUserGrantRepo struct{}
|
||||||
|
|
||||||
|
type IDatabaseUserGrantRepo interface {
|
||||||
|
Get(opts ...DBOption) (model.DatabaseUserGrant, error)
|
||||||
|
List(opts ...DBOption) ([]model.DatabaseUserGrant, error)
|
||||||
|
Save(grant *model.DatabaseUserGrant) error
|
||||||
|
Replace(dbType, database string, grants []model.DatabaseUserGrant) error
|
||||||
|
Delete(opts ...DBOption) error
|
||||||
|
DeleteBy(ctx context.Context, opts ...DBOption) error
|
||||||
|
Update(vars map[string]interface{}, opts ...DBOption) error
|
||||||
|
WithByDatabase(database string) DBOption
|
||||||
|
WithByDBName(dbName string) DBOption
|
||||||
|
WithByDBNames(dbNames []string) DBOption
|
||||||
|
WithByUser(username, host string) DBOption
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserGrantRepo) Get(opts ...DBOption) (model.DatabaseUserGrant, error) {
|
||||||
|
var grant model.DatabaseUserGrant
|
||||||
|
db := global.DB.Model(&model.DatabaseUserGrant{})
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
err := db.First(&grant).Error
|
||||||
|
return grant, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIDatabaseUserGrantRepo() IDatabaseUserGrantRepo {
|
||||||
|
return &DatabaseUserGrantRepo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserGrantRepo) List(opts ...DBOption) ([]model.DatabaseUserGrant, error) {
|
||||||
|
var grants []model.DatabaseUserGrant
|
||||||
|
db := global.DB.Model(&model.DatabaseUserGrant{})
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
err := db.Find(&grants).Error
|
||||||
|
return grants, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserGrantRepo) Save(grant *model.DatabaseUserGrant) error {
|
||||||
|
return global.DB.Save(grant).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserGrantRepo) Replace(dbType, database string, grants []model.DatabaseUserGrant) error {
|
||||||
|
return global.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
|
if err := tx.Where("`type` = ? AND database = ?", dbType, database).Delete(&model.DatabaseUserGrant{}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(grants) != 0 {
|
||||||
|
return tx.Create(&grants).Error
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserGrantRepo) Delete(opts ...DBOption) error {
|
||||||
|
db := global.DB
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
return db.Delete(&model.DatabaseUserGrant{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserGrantRepo) DeleteBy(ctx context.Context, opts ...DBOption) error {
|
||||||
|
return getTx(ctx, opts...).Delete(&model.DatabaseUserGrant{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserGrantRepo) Update(vars map[string]interface{}, opts ...DBOption) error {
|
||||||
|
db := global.DB.Model(&model.DatabaseUserGrant{})
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
return db.Updates(vars).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserGrantRepo) WithByDatabase(database string) DBOption {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("database = ?", database)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserGrantRepo) WithByDBName(dbName string) DBOption {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("db_name = ?", dbName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserGrantRepo) WithByDBNames(dbNames []string) DBOption {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("db_name IN ?", dbNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DatabaseUserGrantRepo) WithByUser(username, host string) DBOption {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("username = ? AND host = ?", username, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ type IWebsiteRepo interface {
|
|||||||
WithParentID(websiteID uint) DBOption
|
WithParentID(websiteID uint) DBOption
|
||||||
WithType(websiteType string) DBOption
|
WithType(websiteType string) DBOption
|
||||||
WithDBType(dbType string) DBOption
|
WithDBType(dbType string) DBOption
|
||||||
|
WithDBTypes(dbTypes []string) DBOption
|
||||||
WithDBID(dbID uint) DBOption
|
WithDBID(dbID uint) DBOption
|
||||||
|
|
||||||
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
|
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
|
||||||
@@ -132,6 +133,12 @@ func (w *WebsiteRepo) WithDBType(dbType string) DBOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *WebsiteRepo) WithDBTypes(dbTypes []string) DBOption {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("db_type IN ?", dbTypes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (w *WebsiteRepo) WithDBID(dbID uint) DBOption {
|
func (w *WebsiteRepo) WithDBID(dbID uint) DBOption {
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Where("db_id = ?", dbID)
|
return db.Where("db_id = ?", dbID)
|
||||||
|
|||||||
+180
-110
@@ -8,8 +8,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
@@ -30,6 +30,8 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var agentAccountMutationMu sync.Mutex
|
||||||
|
|
||||||
type IAgentService interface {
|
type IAgentService interface {
|
||||||
Create(req dto.AgentCreateReq) (*dto.AgentItem, error)
|
Create(req dto.AgentCreateReq) (*dto.AgentItem, error)
|
||||||
BatchInstall(req dto.AgentBatchInstallReq) (*dto.AgentItem, error)
|
BatchInstall(req dto.AgentBatchInstallReq) (*dto.AgentItem, error)
|
||||||
@@ -77,6 +79,7 @@ type IAgentService interface {
|
|||||||
PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error)
|
PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error)
|
||||||
CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error)
|
CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error)
|
||||||
GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error)
|
GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error)
|
||||||
|
DiscoverAccountModels(req dto.AgentAccountModelDiscoverReq) ([]dto.AgentAccountModel, error)
|
||||||
CreateAccountModel(req dto.AgentAccountModelCreateReq) error
|
CreateAccountModel(req dto.AgentAccountModelCreateReq) error
|
||||||
UpdateAccountModel(req dto.AgentAccountModelUpdateReq) error
|
UpdateAccountModel(req dto.AgentAccountModelUpdateReq) error
|
||||||
DeleteAccountModel(req dto.AgentAccountModelDeleteReq) error
|
DeleteAccountModel(req dto.AgentAccountModelDeleteReq) error
|
||||||
@@ -144,7 +147,7 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
|||||||
if installs, _ := appInstallRepo.ListBy(context.Background(), repo.WithByLowerName(req.Name)); len(installs) > 0 {
|
if installs, _ := appInstallRepo.ListBy(context.Background(), repo.WithByLowerName(req.Name)); len(installs) > 0 {
|
||||||
return nil, buserr.New("ErrNameIsExist")
|
return nil, buserr.New("ErrNameIsExist")
|
||||||
}
|
}
|
||||||
if !xpack.MultiNodeProvider.IsXpack() {
|
if !global.CONF.Base.IsEnterprise && !xpack.MultiNodeProvider.IsXpack() {
|
||||||
count, _, err := agentRepo.Page(1, 1)
|
count, _, err := agentRepo.Page(1, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -165,8 +168,6 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
|||||||
provider := ""
|
provider := ""
|
||||||
baseURL := ""
|
baseURL := ""
|
||||||
apiType := ""
|
apiType := ""
|
||||||
maxTokens := 0
|
|
||||||
contextWindow := 0
|
|
||||||
apiKey := ""
|
apiKey := ""
|
||||||
runtimeModel := ""
|
runtimeModel := ""
|
||||||
accountID := uint(0)
|
accountID := uint(0)
|
||||||
@@ -197,8 +198,6 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
|||||||
}
|
}
|
||||||
storedModel = resolvedRuntime.StoredModel
|
storedModel = resolvedRuntime.StoredModel
|
||||||
apiType = resolvedRuntime.APIType
|
apiType = resolvedRuntime.APIType
|
||||||
maxTokens = resolvedRuntime.MaxTokens
|
|
||||||
contextWindow = resolvedRuntime.ContextWindow
|
|
||||||
runtimeModel = resolvedRuntime.PrimaryModel
|
runtimeModel = resolvedRuntime.PrimaryModel
|
||||||
apiKey = account.APIKey
|
apiKey = account.APIKey
|
||||||
accountID = account.ID
|
accountID = account.ID
|
||||||
@@ -247,8 +246,6 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
|||||||
params["PROVIDER"] = provider
|
params["PROVIDER"] = provider
|
||||||
params["MODEL"] = runtimeModel
|
params["MODEL"] = runtimeModel
|
||||||
params["API_TYPE"] = apiType
|
params["API_TYPE"] = apiType
|
||||||
params["MAX_TOKENS"] = maxTokens
|
|
||||||
params["CONTEXT_WINDOW"] = contextWindow
|
|
||||||
params["BASE_URL"] = baseURL
|
params["BASE_URL"] = baseURL
|
||||||
params["API_KEY"] = apiKey
|
params["API_KEY"] = apiKey
|
||||||
params["OPENCLAW_GATEWAY_TOKEN"] = token
|
params["OPENCLAW_GATEWAY_TOKEN"] = token
|
||||||
@@ -291,22 +288,20 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
|||||||
configPath = path.Join(appInstall.GetPath(), "data", "config.yaml")
|
configPath = path.Join(appInstall.GetPath(), "data", "config.yaml")
|
||||||
}
|
}
|
||||||
agent := &model.Agent{
|
agent := &model.Agent{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Remark: req.Remark,
|
Remark: req.Remark,
|
||||||
AgentType: agentType,
|
AgentType: agentType,
|
||||||
Provider: provider,
|
Provider: provider,
|
||||||
Model: storedModel,
|
Model: storedModel,
|
||||||
APIType: apiType,
|
APIType: apiType,
|
||||||
MaxTokens: maxTokens,
|
BaseURL: baseURL,
|
||||||
ContextWindow: contextWindow,
|
APIKey: apiKey,
|
||||||
BaseURL: baseURL,
|
Token: token,
|
||||||
APIKey: apiKey,
|
Status: appInstall.Status,
|
||||||
Token: token,
|
Message: appInstall.Message,
|
||||||
Status: appInstall.Status,
|
AppInstallID: appInstall.ID,
|
||||||
Message: appInstall.Message,
|
AccountID: accountID,
|
||||||
AppInstallID: appInstall.ID,
|
ConfigPath: configPath,
|
||||||
AccountID: accountID,
|
|
||||||
ConfigPath: configPath,
|
|
||||||
}
|
}
|
||||||
if err := agentRepo.Create(agent); err != nil {
|
if err := agentRepo.Create(agent); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -404,7 +399,7 @@ func (a AgentService) BatchInstallSkill(req dto.AgentBatchSkillInstallReq) ([]dt
|
|||||||
results = append(results, result)
|
results = append(results, result)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
installTask, err := task.NewTaskWithOps(skillName, task.TaskInstall, task.TaskScopeAI, buildBatchSkillInstallTaskID(req.TaskID, agent.ID), agent.ID)
|
installTask, err := task.NewTaskWithOps(skillName, task.TaskInstall, task.TaskScopeAI, buildBatchTaskID(req.TaskID, "batch-skill-install", agent.ID), agent.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Message = err.Error()
|
result.Message = err.Error()
|
||||||
results = append(results, result)
|
results = append(results, result)
|
||||||
@@ -446,7 +441,7 @@ func (a AgentService) BatchOperate(req dto.AgentBatchOperateReq) ([]dto.AgentBat
|
|||||||
if operate == constant.Delete {
|
if operate == constant.Delete {
|
||||||
if err := a.Delete(dto.AgentDeleteReq{
|
if err := a.Delete(dto.AgentDeleteReq{
|
||||||
ID: agent.ID,
|
ID: agent.ID,
|
||||||
TaskID: buildBatchOperateTaskID(req.TaskID, agent.ID),
|
TaskID: buildBatchTaskID(req.TaskID, "batch-operate", agent.ID),
|
||||||
ForceDelete: req.ForceDelete,
|
ForceDelete: req.ForceDelete,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
result.Message = err.Error()
|
result.Message = err.Error()
|
||||||
@@ -474,7 +469,7 @@ func (a AgentService) BatchOperate(req dto.AgentBatchOperateReq) ([]dto.AgentBat
|
|||||||
if err := NewIAppInstalledService().Operate(request.AppInstalledOperate{
|
if err := NewIAppInstalledService().Operate(request.AppInstalledOperate{
|
||||||
InstallId: install.ID,
|
InstallId: install.ID,
|
||||||
Operate: operate,
|
Operate: operate,
|
||||||
TaskID: buildBatchOperateTaskID(req.TaskID, agent.ID),
|
TaskID: buildBatchTaskID(req.TaskID, "batch-operate", agent.ID),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
result.Message = err.Error()
|
result.Message = err.Error()
|
||||||
} else {
|
} else {
|
||||||
@@ -558,35 +553,19 @@ func buildBatchUpgradePlans(req dto.AgentBatchUpgradeReq) ([]batchUpgradePlan, [
|
|||||||
DetailID: detail.ID,
|
DetailID: detail.ID,
|
||||||
Backup: req.Backup,
|
Backup: req.Backup,
|
||||||
PullImage: req.PullImage,
|
PullImage: req.PullImage,
|
||||||
TaskID: buildBatchUpgradeTaskID(req.TaskID, install.ID),
|
TaskID: buildBatchTaskID(req.TaskID, "batch-upgrade", install.ID),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return plans, results, nil
|
return plans, results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildBatchUpgradeTaskID(taskID string, appInstallID uint) string {
|
func buildBatchTaskID(taskID, prefix string, id uint) string {
|
||||||
taskID = strings.TrimSpace(taskID)
|
taskID = strings.TrimSpace(taskID)
|
||||||
if taskID == "" {
|
if taskID == "" {
|
||||||
taskID = fmt.Sprintf("batch-upgrade-%d-%d", appInstallID, time.Now().UnixNano())
|
taskID = fmt.Sprintf("%s-%d-%d", prefix, id, time.Now().UnixNano())
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s-%d", taskID, appInstallID)
|
return fmt.Sprintf("%s-%d", taskID, id)
|
||||||
}
|
|
||||||
|
|
||||||
func buildBatchSkillInstallTaskID(taskID string, agentID uint) string {
|
|
||||||
taskID = strings.TrimSpace(taskID)
|
|
||||||
if taskID == "" {
|
|
||||||
taskID = fmt.Sprintf("batch-skill-install-%d-%d", agentID, time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s-%d", taskID, agentID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildBatchOperateTaskID(taskID string, agentID uint) string {
|
|
||||||
taskID = strings.TrimSpace(taskID)
|
|
||||||
if taskID == "" {
|
|
||||||
taskID = fmt.Sprintf("batch-operate-%d-%d", agentID, time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s-%d", taskID, agentID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func batchOperateSkipMessage(operate constant.AppOperate, status string) string {
|
func batchOperateSkipMessage(operate constant.AppOperate, status string) string {
|
||||||
@@ -715,6 +694,7 @@ func (a AgentService) ensureBatchInstallAccount(req dto.AgentBatchInstallReq) (u
|
|||||||
account.RememberAPIKey = snapshot.RememberAPIKey
|
account.RememberAPIKey = snapshot.RememberAPIKey
|
||||||
account.BaseURL = snapshot.BaseURL
|
account.BaseURL = snapshot.BaseURL
|
||||||
account.APIType = snapshot.APIType
|
account.APIType = snapshot.APIType
|
||||||
|
account.AuthMode = snapshot.AuthMode
|
||||||
account.Remark = snapshot.Remark
|
account.Remark = snapshot.Remark
|
||||||
account.Verified = true
|
account.Verified = true
|
||||||
|
|
||||||
@@ -722,6 +702,11 @@ func (a AgentService) ensureBatchInstallAccount(req dto.AgentBatchInstallReq) (u
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
verifyModel, err := resolveAgentAccountVerifyModel(account.Provider, snapshot.VerifyModel, initialModels)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
account.VerifyModel = verifyModel
|
||||||
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
if account.ID == 0 {
|
if account.ID == 0 {
|
||||||
if err := tx.Create(account).Error; err != nil {
|
if err := tx.Create(account).Error; err != nil {
|
||||||
@@ -959,7 +944,7 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
modelName := resolvedRuntime.StoredModel
|
modelName := resolvedRuntime.StoredModel
|
||||||
apiType, maxTokens, contextWindow := resolvedRuntime.APIType, resolvedRuntime.MaxTokens, resolvedRuntime.ContextWindow
|
apiType := resolvedRuntime.APIType
|
||||||
confDir := path.Dir(agent.ConfigPath)
|
confDir := path.Dir(agent.ConfigPath)
|
||||||
if agent.AgentType == constant.AppHermesAgent {
|
if agent.AgentType == constant.AppHermesAgent {
|
||||||
cfg, err := readHermesConfig(agent.ConfigPath)
|
cfg, err := readHermesConfig(agent.ConfigPath)
|
||||||
@@ -980,8 +965,6 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
|||||||
agent.Provider = account.Provider
|
agent.Provider = account.Provider
|
||||||
agent.Model = modelName
|
agent.Model = modelName
|
||||||
agent.APIType = apiType
|
agent.APIType = apiType
|
||||||
agent.MaxTokens = maxTokens
|
|
||||||
agent.ContextWindow = contextWindow
|
|
||||||
agent.BaseURL = account.BaseURL
|
agent.BaseURL = account.BaseURL
|
||||||
agent.APIKey = account.APIKey
|
agent.APIKey = account.APIKey
|
||||||
agent.AccountID = account.ID
|
agent.AccountID = account.ID
|
||||||
@@ -995,20 +978,29 @@ func (a AgentService) GetProviders() ([]dto.ProviderInfo, error) {
|
|||||||
models := make([]dto.ProviderModelInfo, 0, len(def.Models))
|
models := make([]dto.ProviderModelInfo, 0, len(def.Models))
|
||||||
for _, item := range def.Models {
|
for _, item := range def.Models {
|
||||||
models = append(models, dto.ProviderModelInfo{
|
models = append(models, dto.ProviderModelInfo{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
Name: item.Name,
|
Name: item.Name,
|
||||||
ContextWindow: item.ContextWindow,
|
|
||||||
MaxTokens: item.MaxTokens,
|
|
||||||
Reasoning: item.Reasoning,
|
|
||||||
Input: append([]string(nil), item.Input...),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
apiTypes := make([]dto.ProviderAPIInfo, 0, len(def.APIConfigs))
|
||||||
|
for _, item := range def.APIConfigs {
|
||||||
|
apiTypes = append(apiTypes, dto.ProviderAPIInfo{
|
||||||
|
APIType: item.APIType,
|
||||||
|
BaseURL: item.BaseURL,
|
||||||
|
EditableBaseURL: item.EditableBaseURL,
|
||||||
|
DefaultAuthMode: item.DefaultAuthMode,
|
||||||
|
AuthModes: item.AuthModes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
baseURL, _ := providercatalog.DefaultBaseURL(key)
|
||||||
providers = append(providers, dto.ProviderInfo{
|
providers = append(providers, dto.ProviderInfo{
|
||||||
Sort: def.Sort,
|
Sort: def.Sort,
|
||||||
Provider: key,
|
Provider: key,
|
||||||
DisplayName: def.DisplayName,
|
DisplayName: def.DisplayName,
|
||||||
BaseURL: def.DefaultBaseURL,
|
BaseURL: baseURL,
|
||||||
Models: models,
|
DefaultAPIType: def.DefaultAPIType,
|
||||||
|
APITypes: apiTypes,
|
||||||
|
Models: models,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sort.Slice(providers, func(i, j int) bool {
|
sort.Slice(providers, func(i, j int) bool {
|
||||||
@@ -1018,11 +1010,21 @@ func (a AgentService) GetProviders() ([]dto.ProviderInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
|
func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
|
||||||
|
agentAccountMutationMu.Lock()
|
||||||
|
defer agentAccountMutationMu.Unlock()
|
||||||
provider := req.Provider
|
provider := req.Provider
|
||||||
if exist, _ := agentAccountRepo.GetFirst(repo.WithByProvider(provider), repo.WithByName(req.Name)); exist != nil && exist.ID > 0 {
|
if err := ensureAgentAccountNameAvailable(provider, req.Name, 0); err != nil {
|
||||||
return buserr.New("ErrRecordExist")
|
return err
|
||||||
}
|
}
|
||||||
resolvedInput, err := resolveAgentAccountInput(provider, req.APIKey, req.BaseURL)
|
initialModels, err := buildInitialAgentAccountModels(&model.AgentAccount{Provider: provider}, req.Models)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verifyModel, err := resolveAgentAccountVerifyModel(provider, req.VerifyModel, initialModels)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resolvedInput, err := resolveAgentAccountInput(provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, verifyModel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1032,14 +1034,12 @@ func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
|
|||||||
APIKey: resolvedInput.APIKey,
|
APIKey: resolvedInput.APIKey,
|
||||||
RememberAPIKey: req.RememberAPIKey,
|
RememberAPIKey: req.RememberAPIKey,
|
||||||
BaseURL: resolvedInput.BaseURL,
|
BaseURL: resolvedInput.BaseURL,
|
||||||
APIType: req.APIType,
|
APIType: resolvedInput.APIType,
|
||||||
|
AuthMode: resolvedInput.AuthMode,
|
||||||
|
VerifyModel: verifyModel,
|
||||||
Verified: true,
|
Verified: true,
|
||||||
Remark: req.Remark,
|
Remark: req.Remark,
|
||||||
}
|
}
|
||||||
initialModels, err := buildInitialAgentAccountModels(account, req.Models)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
if err := tx.Create(account).Error; err != nil {
|
if err := tx.Create(account).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1056,12 +1056,29 @@ func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
||||||
|
agentAccountMutationMu.Lock()
|
||||||
|
defer agentAccountMutationMu.Unlock()
|
||||||
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.ID))
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.ID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
provider := account.Provider
|
provider := account.Provider
|
||||||
resolvedInput, err := resolveAgentAccountInput(provider, req.APIKey, req.BaseURL)
|
if err := ensureAgentAccountNameAvailable(provider, req.Name, account.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
models, err := loadAgentAccountModels(account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
requestedVerifyModel := req.VerifyModel
|
||||||
|
if strings.TrimSpace(requestedVerifyModel) == "" {
|
||||||
|
requestedVerifyModel = account.VerifyModel
|
||||||
|
}
|
||||||
|
verifyModel, err := resolveAgentAccountVerifyModel(provider, requestedVerifyModel, models)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resolvedInput, err := resolveAgentAccountInput(provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, verifyModel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1069,7 +1086,9 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
|||||||
account.APIKey = resolvedInput.APIKey
|
account.APIKey = resolvedInput.APIKey
|
||||||
account.RememberAPIKey = req.RememberAPIKey
|
account.RememberAPIKey = req.RememberAPIKey
|
||||||
account.BaseURL = resolvedInput.BaseURL
|
account.BaseURL = resolvedInput.BaseURL
|
||||||
account.APIType = req.APIType
|
account.APIType = resolvedInput.APIType
|
||||||
|
account.AuthMode = resolvedInput.AuthMode
|
||||||
|
account.VerifyModel = verifyModel
|
||||||
account.Remark = req.Remark
|
account.Remark = req.Remark
|
||||||
account.Verified = true
|
account.Verified = true
|
||||||
|
|
||||||
@@ -1115,17 +1134,33 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.Age
|
|||||||
BaseURL: item.BaseURL,
|
BaseURL: item.BaseURL,
|
||||||
Models: nil,
|
Models: nil,
|
||||||
APIType: item.APIType,
|
APIType: item.APIType,
|
||||||
|
AuthMode: item.AuthMode,
|
||||||
|
VerifyModel: item.VerifyModel,
|
||||||
Verified: item.Verified,
|
Verified: item.Verified,
|
||||||
Remark: item.Remark,
|
Remark: item.Remark,
|
||||||
CreatedAt: item.CreatedAt,
|
CreatedAt: item.CreatedAt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for i := range items {
|
if len(list) > 0 {
|
||||||
models, err := loadAgentAccountModels(&list[i])
|
accountIDs := make([]uint, 0, len(list))
|
||||||
if err != nil {
|
for _, account := range list {
|
||||||
|
accountIDs = append(accountIDs, account.ID)
|
||||||
|
}
|
||||||
|
var rows []model.AgentAccountModel
|
||||||
|
if err := global.DB.Where("account_id IN ?", accountIDs).Order("account_id ASC, sort_order ASC, id ASC").Find(&rows).Error; err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
items[i].Models = models
|
modelsByAccount := make(map[uint][]dto.AgentAccountModel, len(list))
|
||||||
|
for _, row := range rows {
|
||||||
|
modelsByAccount[row.AccountID] = append(modelsByAccount[row.AccountID], dto.AgentAccountModel{
|
||||||
|
RecordID: row.ID,
|
||||||
|
ID: strings.TrimSpace(row.Model),
|
||||||
|
Name: strings.TrimSpace(row.Name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for index, account := range list {
|
||||||
|
items[index].Models = modelsByAccount[account.ID]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return count, items, nil
|
return count, items, nil
|
||||||
}
|
}
|
||||||
@@ -1142,6 +1177,21 @@ func (a AgentService) GetAccountModels(req dto.AgentAccountModelReq) ([]dto.Agen
|
|||||||
return loadAgentAccountModels(account)
|
return loadAgentAccountModels(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a AgentService) DiscoverAccountModels(req dto.AgentAccountModelDiscoverReq) ([]dto.AgentAccountModel, error) {
|
||||||
|
if req.APIType != "openai-completions" && req.APIType != "openai-responses" {
|
||||||
|
return nil, buserr.New("ErrAgentAccountModelsRequired")
|
||||||
|
}
|
||||||
|
baseURL, err := providercatalog.ResolveBaseURL(req.Provider, req.APIType, req.BaseURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, buserr.WithErr("ErrAgentAccountUnavailable", err)
|
||||||
|
}
|
||||||
|
models, err := providercatalog.DiscoverModels(baseURL, req.APIKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, buserr.WithErr("ErrAgentAccountUnavailable", err)
|
||||||
|
}
|
||||||
|
return buildDiscoveredAgentAccountModels(models), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a AgentService) CreateAccountModel(req dto.AgentAccountModelCreateReq) error {
|
func (a AgentService) CreateAccountModel(req dto.AgentAccountModelCreateReq) error {
|
||||||
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1151,24 +1201,19 @@ func (a AgentService) CreateAccountModel(req dto.AgentAccountModelCreateReq) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
nextModel := cloneAgentAccountModel(req.Model)
|
nextModel, err := normalizeAgentAccountModel(account, req.Model)
|
||||||
if _, ok := findAgentAccountModelForProvider(account.Provider, models, nextModel.ID); ok {
|
|
||||||
return buserr.New("ErrRecordExist")
|
|
||||||
}
|
|
||||||
inputPayload, err := json.Marshal(nextModel.Input)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if _, ok := findAgentAccountModelForProvider(account.Provider, models, nextModel.ID); ok {
|
||||||
|
return buserr.New("ErrRecordExist")
|
||||||
|
}
|
||||||
sortOrder := len(models) + 1
|
sortOrder := len(models) + 1
|
||||||
record := &model.AgentAccountModel{
|
record := &model.AgentAccountModel{
|
||||||
AccountID: account.ID,
|
AccountID: account.ID,
|
||||||
Model: nextModel.ID,
|
Model: nextModel.ID,
|
||||||
Name: nextModel.Name,
|
Name: nextModel.Name,
|
||||||
ContextWindow: nextModel.ContextWindow,
|
SortOrder: sortOrder,
|
||||||
MaxTokens: nextModel.MaxTokens,
|
|
||||||
Reasoning: nextModel.Reasoning,
|
|
||||||
Input: string(inputPayload),
|
|
||||||
SortOrder: sortOrder,
|
|
||||||
}
|
}
|
||||||
if err := agentAccountModelRepo.Create(record); err != nil {
|
if err := agentAccountModelRepo.Create(record); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1189,7 +1234,11 @@ func (a AgentService) UpdateAccountModel(req dto.AgentAccountModelUpdateReq) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
nextModel := cloneAgentAccountModel(req.Model)
|
nextModel, err := normalizeAgentAccountModel(account, req.Model)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nextModel.RecordID = req.Model.RecordID
|
||||||
for _, item := range models {
|
for _, item := range models {
|
||||||
if item.RecordID == req.Model.RecordID {
|
if item.RecordID == req.Model.RecordID {
|
||||||
continue
|
continue
|
||||||
@@ -1209,19 +1258,18 @@ func (a AgentService) UpdateAccountModel(req dto.AgentAccountModelUpdateReq) err
|
|||||||
if err := ensureAccountModelsNotBound(account, nextModels); err != nil {
|
if err := ensureAccountModelsNotBound(account, nextModels); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
inputPayload, err := json.Marshal(nextModel.Input)
|
previousModelID := record.Model
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
record.Model = nextModel.ID
|
record.Model = nextModel.ID
|
||||||
record.Name = nextModel.Name
|
record.Name = nextModel.Name
|
||||||
record.ContextWindow = nextModel.ContextWindow
|
|
||||||
record.MaxTokens = nextModel.MaxTokens
|
|
||||||
record.Reasoning = nextModel.Reasoning
|
|
||||||
record.Input = string(inputPayload)
|
|
||||||
if err := agentAccountModelRepo.Save(record); err != nil {
|
if err := agentAccountModelRepo.Save(record); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if sameProviderModelID(account.Provider, account.VerifyModel, previousModelID) {
|
||||||
|
account.VerifyModel = nextModel.ID
|
||||||
|
if err := agentAccountRepo.Save(account); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
terminalai.InvalidateTerminalRuntimeCache()
|
terminalai.InvalidateTerminalRuntimeCache()
|
||||||
terminalai.InvalidateFileAIRuntimeCache()
|
terminalai.InvalidateFileAIRuntimeCache()
|
||||||
return a.syncAgentsByAccount(account)
|
return a.syncAgentsByAccount(account)
|
||||||
@@ -1232,9 +1280,13 @@ func (a AgentService) DeleteAccountModel(req dto.AgentAccountModelDeleteReq) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := agentAccountModelRepo.GetFirst(repo.WithByID(req.RecordID), repo.WithByAccountID(req.AccountID)); err != nil {
|
record, err := agentAccountModelRepo.GetFirst(repo.WithByID(req.RecordID), repo.WithByAccountID(req.AccountID))
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if sameProviderModelID(account.Provider, account.VerifyModel, record.Model) {
|
||||||
|
return buserr.New("ErrAgentVerifyModelInUse")
|
||||||
|
}
|
||||||
models, err := loadAgentAccountModels(account)
|
models, err := loadAgentAccountModels(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1268,25 +1320,45 @@ func (a AgentService) SyncAgentsByAccount(account *model.AgentAccount) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) VerifyAccount(req dto.AgentAccountVerifyReq) error {
|
func (a AgentService) VerifyAccount(req dto.AgentAccountVerifyReq) error {
|
||||||
_, err := resolveAgentAccountInput(req.Provider, req.APIKey, req.BaseURL)
|
_, err := resolveAgentAccountInput(req.Provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, req.Model)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) DeleteAccount(req dto.AgentAccountDeleteReq) error {
|
func (a AgentService) DeleteAccount(req dto.AgentAccountDeleteReq) error {
|
||||||
if exists, _ := agentRepo.GetFirst(repo.WithByAccountID(req.ID)); exists != nil && exists.ID > 0 {
|
agentAccountMutationMu.Lock()
|
||||||
return buserr.New("ErrAgentAccountBound")
|
defer agentAccountMutationMu.Unlock()
|
||||||
}
|
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
if aiStatus, _ := settingRepo.GetValueByKey("AIStatus"); strings.EqualFold(strings.TrimSpace(aiStatus), constant.StatusEnable) {
|
var agentCount int64
|
||||||
if aiAccountID, _ := settingRepo.GetValueByKey("AIAccountID"); strings.TrimSpace(aiAccountID) == strconv.FormatUint(uint64(req.ID), 10) {
|
if err := tx.Model(&model.Agent{}).Where("account_id = ?", req.ID).Count(&agentCount).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if agentCount > 0 {
|
||||||
|
return buserr.New("ErrAgentAccountBound")
|
||||||
|
}
|
||||||
|
used, err := agentAccountUsedBySetting(tx, req.ID, "AIStatus", "AIAccountID")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if used {
|
||||||
return buserr.New("ErrTerminalAIAccountInUse")
|
return buserr.New("ErrTerminalAIAccountInUse")
|
||||||
}
|
}
|
||||||
}
|
used, err = agentAccountUsedBySetting(tx, req.ID, "FileAIStatus", "FileAIAccountID")
|
||||||
if err := agentAccountModelRepo.Delete(repo.WithByAccountID(req.ID)); err != nil {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if used {
|
||||||
|
return buserr.New("ErrFileAIAccountInUse")
|
||||||
|
}
|
||||||
|
if err := tx.Where("account_id = ?", req.ID).Delete(&model.AgentAccountModel{}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Delete(&model.AgentAccount{}, req.ID).Error
|
||||||
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
terminalai.InvalidateTerminalRuntimeCache()
|
terminalai.InvalidateTerminalRuntimeCache()
|
||||||
terminalai.InvalidateFileAIRuntimeCache()
|
terminalai.InvalidateFileAIRuntimeCache()
|
||||||
return agentAccountRepo.DeleteByID(req.ID)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) GetSecurityConfig(req dto.AgentIDReq) (*dto.AgentSecurityConfig, error) {
|
func (a AgentService) GetSecurityConfig(req dto.AgentIDReq) (*dto.AgentSecurityConfig, error) {
|
||||||
@@ -1557,7 +1629,7 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
modelName := resolvedRuntime.StoredModel
|
modelName := resolvedRuntime.StoredModel
|
||||||
apiType, maxTokens, contextWindow := resolvedRuntime.APIType, resolvedRuntime.MaxTokens, resolvedRuntime.ContextWindow
|
apiType := resolvedRuntime.APIType
|
||||||
confDir := path.Dir(agent.ConfigPath)
|
confDir := path.Dir(agent.ConfigPath)
|
||||||
switch agent.AgentType {
|
switch agent.AgentType {
|
||||||
case constant.AppOpenclaw:
|
case constant.AppOpenclaw:
|
||||||
@@ -1585,8 +1657,6 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
|
|||||||
agent.Provider = account.Provider
|
agent.Provider = account.Provider
|
||||||
agent.Model = modelName
|
agent.Model = modelName
|
||||||
agent.APIType = apiType
|
agent.APIType = apiType
|
||||||
agent.MaxTokens = maxTokens
|
|
||||||
agent.ContextWindow = contextWindow
|
|
||||||
_ = agentRepo.Save(&agent)
|
_ = agentRepo.Save(&agent)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ func writeHermesConfig(confDir string, account *model.AgentAccount, modelName st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
provider := resolveHermesProvider(account.Provider)
|
provider := resolveHermesProvider(account.Provider, account.APIType)
|
||||||
configPath := path.Join(confDir, "config.yaml")
|
configPath := path.Join(confDir, "config.yaml")
|
||||||
cfg, err := readHermesConfigMap(configPath)
|
cfg, err := readHermesConfigMap(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -458,11 +458,18 @@ func normalizeHermesTimezone(timezone string) string {
|
|||||||
return timezone
|
return timezone
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveHermesProvider(provider string) string {
|
func resolveHermesProvider(provider, apiType string) string {
|
||||||
|
if apiType == "anthropic-messages" && (provider == "deepseek" || provider == "bailian-coding-plan" || provider == "ark-coding-plan" || provider == "xiaomi") {
|
||||||
|
return "anthropic"
|
||||||
|
}
|
||||||
switch provider {
|
switch provider {
|
||||||
case "":
|
case "":
|
||||||
return "custom"
|
return "custom"
|
||||||
case "openrouter", "anthropic", "gemini", "zai", "kimi-coding", "xiaomi":
|
case "moonshot":
|
||||||
|
return "kimi-coding"
|
||||||
|
case "kimi":
|
||||||
|
return "kimi-coding-cn"
|
||||||
|
case "openrouter", "anthropic", "deepseek", "gemini", "zai", "kimi-coding", "xiaomi":
|
||||||
return provider
|
return provider
|
||||||
case "minimax":
|
case "minimax":
|
||||||
return "minimax-cn"
|
return "minimax-cn"
|
||||||
@@ -479,17 +486,7 @@ func resolveHermesModel(sourceProvider, targetProvider, modelName string) string
|
|||||||
if targetProvider != "custom" {
|
if targetProvider != "custom" {
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
if sourceProvider == "custom" || sourceProvider == "vllm" {
|
return providercatalog.NormalizeModelID(sourceProvider, target)
|
||||||
return normalizeCustomModel(target)
|
|
||||||
}
|
|
||||||
if strings.Contains(target, "/") {
|
|
||||||
parts := strings.SplitN(target, "/", 2)
|
|
||||||
model := strings.TrimSpace(parts[1])
|
|
||||||
if model != "" {
|
|
||||||
return model
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return target
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveHermesConfiguredModelID(account *model.AgentAccount, accountModels []dto.AgentAccountModel, configuredModel string) (string, error) {
|
func resolveHermesConfiguredModelID(account *model.AgentAccount, accountModels []dto.AgentAccountModel, configuredModel string) (string, error) {
|
||||||
@@ -500,7 +497,7 @@ func resolveHermesConfiguredModelID(account *model.AgentAccount, accountModels [
|
|||||||
if configuredModel == "" {
|
if configuredModel == "" {
|
||||||
return "", buserr.New("ErrAgentModelNotInAccount")
|
return "", buserr.New("ErrAgentModelNotInAccount")
|
||||||
}
|
}
|
||||||
provider := resolveHermesProvider(account.Provider)
|
provider := resolveHermesProvider(account.Provider, account.APIType)
|
||||||
for _, item := range accountModels {
|
for _, item := range accountModels {
|
||||||
if resolveHermesModel(account.Provider, provider, item.ID) == configuredModel {
|
if resolveHermesModel(account.Provider, provider, item.ID) == configuredModel {
|
||||||
return item.ID, nil
|
return item.ID, nil
|
||||||
@@ -513,7 +510,8 @@ func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
|
|||||||
if account == nil {
|
if account == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if resolveHermesProvider(account.Provider) == "custom" {
|
resolvedProvider := resolveHermesProvider(account.Provider, account.APIType)
|
||||||
|
if resolvedProvider == "custom" {
|
||||||
if account.APIKey == "" {
|
if account.APIKey == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -531,6 +529,11 @@ func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
|
|||||||
}
|
}
|
||||||
entries = append(entries, hermesEnvEntry{Key: key, Value: value})
|
entries = append(entries, hermesEnvEntry{Key: key, Value: value})
|
||||||
}
|
}
|
||||||
|
if resolvedProvider == "anthropic" {
|
||||||
|
appendEntry("ANTHROPIC_API_KEY", apiKey)
|
||||||
|
appendEntry("ANTHROPIC_BASE_URL", baseURL)
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
switch account.Provider {
|
switch account.Provider {
|
||||||
case "openrouter":
|
case "openrouter":
|
||||||
@@ -538,6 +541,7 @@ func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
|
|||||||
appendEntry("OPENROUTER_BASE_URL", baseURL)
|
appendEntry("OPENROUTER_BASE_URL", baseURL)
|
||||||
case "anthropic":
|
case "anthropic":
|
||||||
appendEntry("ANTHROPIC_API_KEY", apiKey)
|
appendEntry("ANTHROPIC_API_KEY", apiKey)
|
||||||
|
appendEntry("ANTHROPIC_BASE_URL", baseURL)
|
||||||
case "gemini":
|
case "gemini":
|
||||||
appendEntry("GOOGLE_API_KEY", apiKey)
|
appendEntry("GOOGLE_API_KEY", apiKey)
|
||||||
appendEntry("GEMINI_API_KEY", apiKey)
|
appendEntry("GEMINI_API_KEY", apiKey)
|
||||||
@@ -607,6 +611,7 @@ func hermesManagedModelEnvKeys() []string {
|
|||||||
"OPENROUTER_API_KEY",
|
"OPENROUTER_API_KEY",
|
||||||
"OPENROUTER_BASE_URL",
|
"OPENROUTER_BASE_URL",
|
||||||
"ANTHROPIC_API_KEY",
|
"ANTHROPIC_API_KEY",
|
||||||
|
"ANTHROPIC_BASE_URL",
|
||||||
"GOOGLE_API_KEY",
|
"GOOGLE_API_KEY",
|
||||||
"GEMINI_API_KEY",
|
"GEMINI_API_KEY",
|
||||||
"GEMINI_BASE_URL",
|
"GEMINI_BASE_URL",
|
||||||
|
|||||||
+130
-262
@@ -4,10 +4,12 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -37,6 +39,39 @@ type resolvedAgentAccountInput struct {
|
|||||||
Provider string
|
Provider string
|
||||||
APIKey string
|
APIKey string
|
||||||
BaseURL string
|
BaseURL string
|
||||||
|
APIType string
|
||||||
|
AuthMode string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureAgentAccountNameAvailable(provider, name string, excludeID uint) error {
|
||||||
|
opts := []repo.DBOption{repo.WithByProvider(provider), repo.WithByName(name)}
|
||||||
|
if excludeID > 0 {
|
||||||
|
opts = append(opts, repo.WithByNOTID(excludeID))
|
||||||
|
}
|
||||||
|
account, err := agentAccountRepo.GetFirst(opts...)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if account != nil && account.ID > 0 {
|
||||||
|
return buserr.New("ErrRecordExist")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func agentAccountUsedBySetting(tx *gorm.DB, accountID uint, statusKey, accountIDKey string) (bool, error) {
|
||||||
|
var settings []model.Setting
|
||||||
|
if err := tx.Where("key IN ?", []string{statusKey, accountIDKey}).Find(&settings).Error; err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
values := make(map[string]string, len(settings))
|
||||||
|
for _, setting := range settings {
|
||||||
|
values[setting.Key] = setting.Value
|
||||||
|
}
|
||||||
|
return strings.EqualFold(strings.TrimSpace(values[statusKey]), constant.StatusEnable) &&
|
||||||
|
strings.TrimSpace(values[accountIDKey]) == strconv.FormatUint(uint64(accountID), 10), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadOpenclawAgentByID(agentID uint) (*model.Agent, error) {
|
func loadOpenclawAgentByID(agentID uint) (*model.Agent, error) {
|
||||||
@@ -61,19 +96,26 @@ func ensureContainerRunning(containerName string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveAgentAccountInput(provider, apiKey, baseURL string) (resolvedAgentAccountInput, error) {
|
func resolveAgentAccountInput(provider, apiType, authMode, apiKey, baseURL, modelID string) (resolvedAgentAccountInput, error) {
|
||||||
resolvedAPIKey := strings.TrimSpace(apiKey)
|
resolvedAPIKey := strings.TrimSpace(apiKey)
|
||||||
resolvedBaseURL := strings.TrimSpace(baseURL)
|
resolvedAPIType := strings.TrimSpace(apiType)
|
||||||
if resolvedBaseURL == "" {
|
resolvedAuthMode, err := providercatalog.ResolveAuthMode(provider, resolvedAPIType, authMode)
|
||||||
if requiresInitialAgentAccountModels(provider) {
|
if err != nil {
|
||||||
|
return resolvedAgentAccountInput{}, err
|
||||||
|
}
|
||||||
|
resolvedBaseURL, err := providercatalog.ResolveBaseURL(provider, resolvedAPIType, baseURL)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "base url is required") {
|
||||||
return resolvedAgentAccountInput{}, buserr.New("ErrAgentBaseURLRequired")
|
return resolvedAgentAccountInput{}, buserr.New("ErrAgentBaseURLRequired")
|
||||||
}
|
}
|
||||||
if defaultURL, ok := providercatalog.DefaultBaseURL(provider); ok {
|
return resolvedAgentAccountInput{}, err
|
||||||
resolvedBaseURL = defaultURL
|
}
|
||||||
}
|
modelID = strings.TrimSpace(modelID)
|
||||||
|
if modelID == "" {
|
||||||
|
return resolvedAgentAccountInput{}, buserr.New("ErrAgentAccountModelsRequired")
|
||||||
}
|
}
|
||||||
if !providercatalog.SkipVerification(provider) {
|
if !providercatalog.SkipVerification(provider) {
|
||||||
if err := providercatalog.VerifyAccount(provider, resolvedBaseURL, resolvedAPIKey); err != nil {
|
if err := providercatalog.VerifyAccount(provider, resolvedAPIType, resolvedAuthMode, resolvedBaseURL, resolvedAPIKey, modelID); err != nil {
|
||||||
return resolvedAgentAccountInput{}, err
|
return resolvedAgentAccountInput{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,6 +123,8 @@ func resolveAgentAccountInput(provider, apiKey, baseURL string) (resolvedAgentAc
|
|||||||
Provider: provider,
|
Provider: provider,
|
||||||
APIKey: resolvedAPIKey,
|
APIKey: resolvedAPIKey,
|
||||||
BaseURL: resolvedBaseURL,
|
BaseURL: resolvedBaseURL,
|
||||||
|
APIType: resolvedAPIType,
|
||||||
|
AuthMode: resolvedAuthMode,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,26 +381,24 @@ func setOtherConfig(conf map[string]interface{}, config dto.AgentOtherConfig) {
|
|||||||
func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map[string]interface{}) dto.AgentItem {
|
func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map[string]interface{}) dto.AgentItem {
|
||||||
agentType := agent.AgentType
|
agentType := agent.AgentType
|
||||||
item := dto.AgentItem{
|
item := dto.AgentItem{
|
||||||
ID: agent.ID,
|
ID: agent.ID,
|
||||||
Name: agent.Name,
|
Name: agent.Name,
|
||||||
Remark: agent.Remark,
|
Remark: agent.Remark,
|
||||||
AgentType: agentType,
|
AgentType: agentType,
|
||||||
Provider: agent.Provider,
|
Provider: agent.Provider,
|
||||||
ProviderName: providercatalog.DisplayName(agent.Provider),
|
ProviderName: providercatalog.DisplayName(agent.Provider),
|
||||||
Model: agent.Model,
|
Model: agent.Model,
|
||||||
APIType: agent.APIType,
|
APIType: agent.APIType,
|
||||||
MaxTokens: agent.MaxTokens,
|
BaseURL: agent.BaseURL,
|
||||||
ContextWindow: agent.ContextWindow,
|
APIKey: maskKey(agent.APIKey),
|
||||||
BaseURL: agent.BaseURL,
|
Token: agent.Token,
|
||||||
APIKey: maskKey(agent.APIKey),
|
Status: agent.Status,
|
||||||
Token: agent.Token,
|
Message: agent.Message,
|
||||||
Status: agent.Status,
|
AppInstallID: agent.AppInstallID,
|
||||||
Message: agent.Message,
|
WebsiteID: agent.WebsiteID,
|
||||||
AppInstallID: agent.AppInstallID,
|
AccountID: agent.AccountID,
|
||||||
WebsiteID: agent.WebsiteID,
|
ConfigPath: agent.ConfigPath,
|
||||||
AccountID: agent.AccountID,
|
CreatedAt: agent.CreatedAt,
|
||||||
ConfigPath: agent.ConfigPath,
|
|
||||||
CreatedAt: agent.CreatedAt,
|
|
||||||
}
|
}
|
||||||
if appInstall != nil && appInstall.ID > 0 {
|
if appInstall != nil && appInstall.ID > 0 {
|
||||||
item.Container = appInstall.ContainerName
|
item.Container = appInstall.ContainerName
|
||||||
@@ -684,13 +726,9 @@ type modelProvider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type modelEntry struct {
|
type modelEntry struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Reasoning bool `json:"reasoning"`
|
Input []string `json:"input,omitempty"`
|
||||||
Input []string `json:"input"`
|
|
||||||
ContextWindow int `json:"contextWindow"`
|
|
||||||
MaxTokens int `json:"maxTokens"`
|
|
||||||
Cost modelCost `json:"cost"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func requiresOpenclawProviderModels(provider string) bool {
|
func requiresOpenclawProviderModels(provider string) bool {
|
||||||
@@ -710,13 +748,6 @@ func applyOpenclawModelsConfig(conf map[string]interface{}, models *modelsConfig
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type modelCost struct {
|
|
||||||
Input float64 `json:"input"`
|
|
||||||
Output float64 `json:"output"`
|
|
||||||
CacheRead float64 `json:"cacheRead"`
|
|
||||||
CacheWrite float64 `json:"cacheWrite"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type browserConfig struct {
|
type browserConfig struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
ExecutablePath string `json:"executablePath"`
|
ExecutablePath string `json:"executablePath"`
|
||||||
@@ -1062,7 +1093,7 @@ func buildOpenclawModelsFromAccount(account *model.AgentAccount, selectedModel s
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildOpenclawAccountModelConfig(account *model.AgentAccount, model dto.AgentAccountModel) (string, modelEntry, string, modelProvider, error) {
|
func buildOpenclawAccountModelConfig(account *model.AgentAccount, model dto.AgentAccountModel) (string, modelEntry, string, modelProvider, error) {
|
||||||
providerPatch, err := providercatalog.BuildOpenClawProviderPatch(account.Provider, model.ID, account.APIType, account.BaseURL, account.APIKey)
|
providerPatch, err := providercatalog.BuildOpenClawProviderPatch(account.Provider, model.ID, account.APIType, account.AuthMode, account.BaseURL, account.APIKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", modelEntry{}, "", modelProvider{}, err
|
return "", modelEntry{}, "", modelProvider{}, err
|
||||||
}
|
}
|
||||||
@@ -1074,28 +1105,24 @@ func buildOpenclawAccountModelConfig(account *model.AgentAccount, model dto.Agen
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var openclawVisionModelPattern = regexp.MustCompile(`(?i)(\b(gpt-4o|gpt-4\.1|gpt-[5-9]|o[134])\b|\bclaude-(3|4|sonnet|opus|haiku)\b|\bgemini\b|\b(qwen[\w.-]*-?vl|qwen-vl|qwen3\.[5-9]-plus)\b|\b(kimi-k2\.(5|6)|kimi-k2\.7-code|minimax-m3)\b|\b(vision|llava|pixtral|internvl|mllama|minicpm-v|glm-4v|omni)\b|(^|[-_/])vl([-_/]|$))`)
|
||||||
|
|
||||||
func buildOpenclawModelEntry(modelID string, model dto.AgentAccountModel) modelEntry {
|
func buildOpenclawModelEntry(modelID string, model dto.AgentAccountModel) modelEntry {
|
||||||
name := strings.TrimSpace(model.Name)
|
name := strings.TrimSpace(model.Name)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = strings.TrimSpace(modelID)
|
name = strings.TrimSpace(modelID)
|
||||||
}
|
}
|
||||||
return modelEntry{
|
entry := modelEntry{ID: strings.TrimSpace(modelID), Name: name}
|
||||||
ID: strings.TrimSpace(modelID),
|
if openclawVisionModelPattern.MatchString(modelID) {
|
||||||
Name: name,
|
entry.Input = []string{"text", "image"}
|
||||||
Reasoning: model.Reasoning,
|
|
||||||
Input: sanitizeAgentAccountModelInputs(model.Input),
|
|
||||||
ContextWindow: model.ContextWindow,
|
|
||||||
MaxTokens: model.MaxTokens,
|
|
||||||
Cost: modelCost{},
|
|
||||||
}
|
}
|
||||||
|
return entry
|
||||||
}
|
}
|
||||||
|
|
||||||
type openclawAccountModelRuntime struct {
|
type openclawAccountModelRuntime struct {
|
||||||
StoredModel string
|
StoredModel string
|
||||||
PrimaryModel string
|
PrimaryModel string
|
||||||
APIType string
|
APIType string
|
||||||
MaxTokens int
|
|
||||||
ContextWindow int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildOpenclawAccountModelRuntime(account *model.AgentAccount, model dto.AgentAccountModel) (openclawAccountModelRuntime, error) {
|
func buildOpenclawAccountModelRuntime(account *model.AgentAccount, model dto.AgentAccountModel) (openclawAccountModelRuntime, error) {
|
||||||
@@ -1104,11 +1131,9 @@ func buildOpenclawAccountModelRuntime(account *model.AgentAccount, model dto.Age
|
|||||||
return openclawAccountModelRuntime{}, err
|
return openclawAccountModelRuntime{}, err
|
||||||
}
|
}
|
||||||
return openclawAccountModelRuntime{
|
return openclawAccountModelRuntime{
|
||||||
StoredModel: model.ID,
|
StoredModel: model.ID,
|
||||||
PrimaryModel: primaryModel,
|
PrimaryModel: primaryModel,
|
||||||
APIType: account.APIType,
|
APIType: account.APIType,
|
||||||
MaxTokens: model.MaxTokens,
|
|
||||||
ContextWindow: model.ContextWindow,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1128,15 +1153,11 @@ func buildInitialAgentAccountModels(account *model.AgentAccount, requested []dto
|
|||||||
if account == nil {
|
if account == nil {
|
||||||
return nil, fmt.Errorf("account is required")
|
return nil, fmt.Errorf("account is required")
|
||||||
}
|
}
|
||||||
if requiresInitialAgentAccountModels(account.Provider) && len(requested) > 1 {
|
if account.Provider != "custom" && requiresInitialAgentAccountModels(account.Provider) && len(requested) > 1 {
|
||||||
return nil, buserr.New("ErrAgentAccountSingleInitialModel")
|
return nil, buserr.New("ErrAgentAccountSingleInitialModel")
|
||||||
}
|
}
|
||||||
if len(requested) > 0 {
|
if len(requested) > 0 {
|
||||||
models := make([]dto.AgentAccountModel, 0, len(requested))
|
return normalizeAgentAccountModels(account, requested)
|
||||||
for _, item := range requested {
|
|
||||||
models = append(models, cloneAgentAccountModel(item))
|
|
||||||
}
|
|
||||||
return models, nil
|
|
||||||
}
|
}
|
||||||
meta, ok := providercatalog.Get(account.Provider)
|
meta, ok := providercatalog.Get(account.Provider)
|
||||||
if !ok || len(meta.Models) == 0 {
|
if !ok || len(meta.Models) == 0 {
|
||||||
@@ -1148,15 +1169,22 @@ func buildInitialAgentAccountModels(account *model.AgentAccount, requested []dto
|
|||||||
requested = make([]dto.AgentAccountModel, 0, len(meta.Models))
|
requested = make([]dto.AgentAccountModel, 0, len(meta.Models))
|
||||||
for _, item := range meta.Models {
|
for _, item := range meta.Models {
|
||||||
requested = append(requested, dto.AgentAccountModel{
|
requested = append(requested, dto.AgentAccountModel{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
Name: item.Name,
|
Name: item.Name,
|
||||||
ContextWindow: item.ContextWindow,
|
|
||||||
MaxTokens: item.MaxTokens,
|
|
||||||
Reasoning: item.Reasoning,
|
|
||||||
Input: append([]string(nil), item.Input...),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return requested, nil
|
return normalizeAgentAccountModels(account, requested)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDiscoveredAgentAccountModels(modelIDs []string) []dto.AgentAccountModel {
|
||||||
|
models := make([]dto.AgentAccountModel, 0, len(modelIDs))
|
||||||
|
for _, modelID := range modelIDs {
|
||||||
|
models = append(models, dto.AgentAccountModel{
|
||||||
|
ID: modelID,
|
||||||
|
Name: modelID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return models
|
||||||
}
|
}
|
||||||
|
|
||||||
func compactPersistedAgentAccountModelSortOrder(accountID uint) error {
|
func compactPersistedAgentAccountModelSortOrder(accountID uint) error {
|
||||||
@@ -1184,18 +1212,6 @@ func loadAgentAccountModels(account *model.AgentAccount) ([]dto.AgentAccountMode
|
|||||||
return listPersistedAgentAccountModels(account.ID)
|
return listPersistedAgentAccountModels(account.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneAgentAccountModel(model dto.AgentAccountModel) dto.AgentAccountModel {
|
|
||||||
return dto.AgentAccountModel{
|
|
||||||
RecordID: model.RecordID,
|
|
||||||
ID: model.ID,
|
|
||||||
Name: model.Name,
|
|
||||||
ContextWindow: model.ContextWindow,
|
|
||||||
MaxTokens: model.MaxTokens,
|
|
||||||
Reasoning: model.Reasoning,
|
|
||||||
Input: append([]string(nil), model.Input...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func MergeCatalogAgentAccountModelsForMigration(account *model.AgentAccount, existing []dto.AgentAccountModel) ([]dto.AgentAccountModel, error) {
|
func MergeCatalogAgentAccountModelsForMigration(account *model.AgentAccount, existing []dto.AgentAccountModel) ([]dto.AgentAccountModel, error) {
|
||||||
if account == nil {
|
if account == nil {
|
||||||
return nil, fmt.Errorf("account is required")
|
return nil, fmt.Errorf("account is required")
|
||||||
@@ -1219,12 +1235,8 @@ func MergeCatalogAgentAccountModelsForMigration(account *model.AgentAccount, exi
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
requested = append(requested, dto.AgentAccountModel{
|
requested = append(requested, dto.AgentAccountModel{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
Name: item.Name,
|
Name: item.Name,
|
||||||
ContextWindow: item.ContextWindow,
|
|
||||||
MaxTokens: item.MaxTokens,
|
|
||||||
Reasoning: item.Reasoning,
|
|
||||||
Input: append([]string(nil), item.Input...),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if len(requested) == len(existing) {
|
if len(requested) == len(existing) {
|
||||||
@@ -1243,18 +1255,10 @@ func listPersistedAgentAccountModels(accountID uint) ([]dto.AgentAccountModel, e
|
|||||||
}
|
}
|
||||||
result := make([]dto.AgentAccountModel, 0, len(rows))
|
result := make([]dto.AgentAccountModel, 0, len(rows))
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
inputs := []string{}
|
|
||||||
if strings.TrimSpace(row.Input) != "" {
|
|
||||||
_ = json.Unmarshal([]byte(row.Input), &inputs)
|
|
||||||
}
|
|
||||||
result = append(result, dto.AgentAccountModel{
|
result = append(result, dto.AgentAccountModel{
|
||||||
RecordID: row.ID,
|
RecordID: row.ID,
|
||||||
ID: strings.TrimSpace(row.Model),
|
ID: strings.TrimSpace(row.Model),
|
||||||
Name: strings.TrimSpace(row.Name),
|
Name: strings.TrimSpace(row.Name),
|
||||||
ContextWindow: row.ContextWindow,
|
|
||||||
MaxTokens: row.MaxTokens,
|
|
||||||
Reasoning: row.Reasoning,
|
|
||||||
Input: sanitizeAgentAccountModelInputs(inputs),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -1265,19 +1269,11 @@ func replacePersistedAgentAccountModelsWithTx(tx *gorm.DB, accountID uint, model
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for index, item := range models {
|
for index, item := range models {
|
||||||
inputPayload, err := json.Marshal(sanitizeAgentAccountModelInputs(item.Input))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
record := &model.AgentAccountModel{
|
record := &model.AgentAccountModel{
|
||||||
AccountID: accountID,
|
AccountID: accountID,
|
||||||
Model: strings.TrimSpace(item.ID),
|
Model: strings.TrimSpace(item.ID),
|
||||||
Name: strings.TrimSpace(item.Name),
|
Name: strings.TrimSpace(item.Name),
|
||||||
ContextWindow: item.ContextWindow,
|
SortOrder: index + 1,
|
||||||
MaxTokens: item.MaxTokens,
|
|
||||||
Reasoning: item.Reasoning,
|
|
||||||
Input: string(inputPayload),
|
|
||||||
SortOrder: index + 1,
|
|
||||||
}
|
}
|
||||||
if err := tx.Create(record).Error; err != nil {
|
if err := tx.Create(record).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1318,86 +1314,17 @@ func normalizeAgentAccountModel(account *model.AgentAccount, model dto.AgentAcco
|
|||||||
if modelID == "" {
|
if modelID == "" {
|
||||||
return dto.AgentAccountModel{}, fmt.Errorf("model is required")
|
return dto.AgentAccountModel{}, fmt.Errorf("model is required")
|
||||||
}
|
}
|
||||||
|
modelID = providercatalog.NormalizeModelID(account.Provider, modelID)
|
||||||
name := strings.TrimSpace(model.Name)
|
name := strings.TrimSpace(model.Name)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = modelID
|
name = modelID
|
||||||
}
|
}
|
||||||
inputs := sanitizeAgentAccountModelInputs(model.Input)
|
|
||||||
return dto.AgentAccountModel{
|
return dto.AgentAccountModel{
|
||||||
ID: normalizeAgentAccountModelID(account.Provider, modelID),
|
ID: modelID,
|
||||||
Name: name,
|
Name: name,
|
||||||
ContextWindow: model.ContextWindow,
|
|
||||||
MaxTokens: model.MaxTokens,
|
|
||||||
Reasoning: model.Reasoning,
|
|
||||||
Input: inputs,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeAgentAccountModelID(provider, requestedID string) string {
|
|
||||||
switch provider {
|
|
||||||
case "custom", "vllm":
|
|
||||||
return normalizeCustomModel(requestedID)
|
|
||||||
case "ollama":
|
|
||||||
target := strings.TrimSpace(requestedID)
|
|
||||||
if strings.HasPrefix(target, "ollama/") {
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
target = strings.TrimLeft(strings.TrimSpace(target), "/")
|
|
||||||
if target == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return "ollama/" + target
|
|
||||||
default:
|
|
||||||
target := strings.TrimSpace(requestedID)
|
|
||||||
if target == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
prefix := poolModelPrefix(provider)
|
|
||||||
if strings.Contains(target, "/") {
|
|
||||||
parts := strings.SplitN(target, "/", 2)
|
|
||||||
targetPrefix := parts[0]
|
|
||||||
targetModel := strings.TrimSpace(parts[1])
|
|
||||||
if targetModel == "" {
|
|
||||||
return strings.TrimSpace(target)
|
|
||||||
}
|
|
||||||
for _, item := range supportedProviderModelPrefixes(provider) {
|
|
||||||
if item == targetPrefix {
|
|
||||||
if prefix != "" {
|
|
||||||
return prefix + "/" + targetModel
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(target)
|
|
||||||
}
|
|
||||||
target = strings.TrimLeft(strings.TrimSpace(target), "/")
|
|
||||||
if prefix == "" {
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
return prefix + "/" + target
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeAgentAccountModelInputs(values []string) []string {
|
|
||||||
result := make([]string, 0, len(values))
|
|
||||||
seen := make(map[string]struct{}, len(values))
|
|
||||||
for _, value := range values {
|
|
||||||
normalized := value
|
|
||||||
if normalized != "text" && normalized != "image" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := seen[normalized]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[normalized] = struct{}{}
|
|
||||||
result = append(result, normalized)
|
|
||||||
}
|
|
||||||
if len(result) == 0 {
|
|
||||||
return []string{"text"}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func requiresInitialAgentAccountModels(provider string) bool {
|
func requiresInitialAgentAccountModels(provider string) bool {
|
||||||
switch provider {
|
switch provider {
|
||||||
case "custom", "vllm", "ollama":
|
case "custom", "vllm", "ollama":
|
||||||
@@ -1407,36 +1334,14 @@ func requiresInitialAgentAccountModels(provider string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeComparableProviderModelID(provider, modelID string) string {
|
|
||||||
target := strings.TrimSpace(modelID)
|
|
||||||
if target == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if !strings.Contains(target, "/") {
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
parts := strings.SplitN(target, "/", 2)
|
|
||||||
prefix := parts[0]
|
|
||||||
model := strings.TrimSpace(parts[1])
|
|
||||||
if model == "" {
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
for _, item := range supportedProviderModelPrefixes(provider) {
|
|
||||||
if item == prefix {
|
|
||||||
return model
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
|
|
||||||
func sameProviderModelID(provider, left, right string) bool {
|
func sameProviderModelID(provider, left, right string) bool {
|
||||||
leftTrimmed := strings.TrimSpace(left)
|
leftTrimmed := strings.TrimSpace(left)
|
||||||
rightTrimmed := strings.TrimSpace(right)
|
rightTrimmed := strings.TrimSpace(right)
|
||||||
if leftTrimmed == rightTrimmed {
|
if leftTrimmed == rightTrimmed {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
leftComparable := normalizeComparableProviderModelID(provider, leftTrimmed)
|
leftComparable := providercatalog.NormalizeModelID(provider, leftTrimmed)
|
||||||
rightComparable := normalizeComparableProviderModelID(provider, rightTrimmed)
|
rightComparable := providercatalog.NormalizeModelID(provider, rightTrimmed)
|
||||||
return leftComparable != "" && leftComparable == rightComparable
|
return leftComparable != "" && leftComparable == rightComparable
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1457,6 +1362,20 @@ func requireAgentAccountModelForProvider(provider string, models []dto.AgentAcco
|
|||||||
return selectedAccountModel, nil
|
return selectedAccountModel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveAgentAccountVerifyModel(provider, requested string, models []dto.AgentAccountModel) (string, error) {
|
||||||
|
if len(models) == 0 {
|
||||||
|
return "", buserr.New("ErrAgentAccountModelsRequired")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(requested) == "" {
|
||||||
|
return models[0].ID, nil
|
||||||
|
}
|
||||||
|
selected, ok := findAgentAccountModelForProvider(provider, models, requested)
|
||||||
|
if !ok {
|
||||||
|
return "", buserr.New("ErrAgentModelNotInAccount")
|
||||||
|
}
|
||||||
|
return selected.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ensureAccountModelsNotBound(account *model.AgentAccount, models []dto.AgentAccountModel) error {
|
func ensureAccountModelsNotBound(account *model.AgentAccount, models []dto.AgentAccountModel) error {
|
||||||
if account == nil || account.ID == 0 {
|
if account == nil || account.ID == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -1595,57 +1514,6 @@ func toInt(value interface{}) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeCustomModel(modelName string) string {
|
|
||||||
trim := strings.TrimSpace(modelName)
|
|
||||||
trim = strings.TrimLeft(trim, "/")
|
|
||||||
if parts := strings.SplitN(trim, "/", 2); len(parts) == 2 {
|
|
||||||
if strings.EqualFold(parts[0], "custom") {
|
|
||||||
return strings.TrimLeft(strings.TrimSpace(parts[1]), "/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return trim
|
|
||||||
}
|
|
||||||
|
|
||||||
func runtimeProviderModelPrefix(provider string) string {
|
|
||||||
switch provider {
|
|
||||||
case "gemini":
|
|
||||||
return "google"
|
|
||||||
case "kimi":
|
|
||||||
return "moonshot"
|
|
||||||
default:
|
|
||||||
return provider
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func poolModelPrefix(provider string) string {
|
|
||||||
meta, ok := providercatalog.Get(provider)
|
|
||||||
if ok && len(meta.Models) > 0 {
|
|
||||||
parts := strings.SplitN(strings.TrimSpace(meta.Models[0].ID), "/", 2)
|
|
||||||
if len(parts) == 2 && strings.TrimSpace(parts[0]) != "" {
|
|
||||||
return parts[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return provider
|
|
||||||
}
|
|
||||||
|
|
||||||
func supportedProviderModelPrefixes(provider string) []string {
|
|
||||||
values := []string{poolModelPrefix(provider), runtimeProviderModelPrefix(provider)}
|
|
||||||
result := make([]string, 0, len(values))
|
|
||||||
seen := make(map[string]struct{}, len(values))
|
|
||||||
for _, value := range values {
|
|
||||||
target := value
|
|
||||||
if target == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := seen[target]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[target] = struct{}{}
|
|
||||||
result = append(result, target)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateToken() string {
|
func generateToken() string {
|
||||||
bytes := make([]byte, 24)
|
bytes := make([]byte, 24)
|
||||||
if _, err := rand.Read(bytes); err != nil {
|
if _, err := rand.Read(bytes); err != nil {
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ func fillAgentWebsiteItems(items []dto.AgentItem, explicitWebsiteMap map[uint]mo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func uniqueDeploymentWebsiteMapByAppInstall(websites []model.Website) map[uint]model.Website {
|
func UniqueDeploymentWebsiteMapByAppInstall(websites []model.Website) map[uint]model.Website {
|
||||||
websiteMap := make(map[uint]model.Website)
|
websiteMap := make(map[uint]model.Website)
|
||||||
duplicateAppInstallIDs := make(map[uint]struct{})
|
duplicateAppInstallIDs := make(map[uint]struct{})
|
||||||
for _, website := range websites {
|
for _, website := range websites {
|
||||||
@@ -182,10 +182,6 @@ func uniqueDeploymentWebsiteMapByAppInstall(websites []model.Website) map[uint]m
|
|||||||
return websiteMap
|
return websiteMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func UniqueDeploymentWebsiteMapForMigration(websites []model.Website) map[uint]model.Website {
|
|
||||||
return uniqueDeploymentWebsiteMapByAppInstall(websites)
|
|
||||||
}
|
|
||||||
|
|
||||||
func uniqueUintList(items []uint) []uint {
|
func uniqueUintList(items []uint) []uint {
|
||||||
itemMap := make(map[uint]struct{}, len(items))
|
itemMap := make(map[uint]struct{}, len(items))
|
||||||
uniq := make([]uint, 0, len(items))
|
uniq := make([]uint, 0, len(items))
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
vllmAppKeyForUpgrade = "vllm"
|
||||||
|
vllmImageEnvKey = "IMAGE"
|
||||||
|
vllmImageTypeNvidia = "nvidia"
|
||||||
|
vllmImageTypeIntel = "intel"
|
||||||
|
vllmImageTypeAscend = "ascend"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resolveVllmVersionFamily(version, image string) string {
|
||||||
|
normalizedVersion := strings.ToLower(strings.TrimSpace(version))
|
||||||
|
if strings.HasPrefix(normalizedVersion, vllmImageTypeIntel+"-") {
|
||||||
|
return vllmImageTypeIntel
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(normalizedVersion, vllmImageTypeAscend+"-") {
|
||||||
|
return vllmImageTypeAscend
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(normalizedVersion, vllmImageTypeNvidia+"-") {
|
||||||
|
return vllmImageTypeNvidia
|
||||||
|
}
|
||||||
|
normalizedImage := strings.ToLower(strings.TrimSpace(image))
|
||||||
|
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, vllmImageTypeAscend} {
|
||||||
|
prefix := family + "-"
|
||||||
|
if strings.HasPrefix(normalized, prefix) {
|
||||||
|
return strings.TrimSpace(trimmed[len(prefix):])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDefaultVllmImageByVersion(version string) string {
|
||||||
|
tag := trimVllmVersionFamily(version)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func isVllmUpgradeVersionAllowed(currentVersion, targetVersion, currentImage string) bool {
|
||||||
|
currentFamily := resolveVllmVersionFamily(currentVersion, currentImage)
|
||||||
|
targetFamily := resolveVllmVersionFamily(targetVersion, "")
|
||||||
|
return currentFamily == targetFamily
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasVllmVersionFamilyPrefix(version string) bool {
|
||||||
|
normalized := strings.ToLower(strings.TrimSpace(version))
|
||||||
|
return strings.HasPrefix(normalized, vllmImageTypeNvidia+"-") ||
|
||||||
|
strings.HasPrefix(normalized, vllmImageTypeIntel+"-") ||
|
||||||
|
strings.HasPrefix(normalized, vllmImageTypeAscend+"-")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isVllmUpgradeCandidate(currentVersion, targetVersion, currentImage string) bool {
|
||||||
|
if strings.TrimSpace(currentVersion) == strings.TrimSpace(targetVersion) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !isVllmUpgradeVersionAllowed(currentVersion, targetVersion, currentImage) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if common.CompareVersion(targetVersion, currentVersion) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return !hasVllmVersionFamilyPrefix(currentVersion) &&
|
||||||
|
resolveVllmVersionFamily(targetVersion, "") == vllmImageTypeNvidia &&
|
||||||
|
trimVllmVersionFamily(currentVersion) == trimVllmVersionFamily(targetVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildVllmUpgradeImage(currentImage, currentVersion, targetVersion string) string {
|
||||||
|
trimmedImage := strings.TrimSpace(currentImage)
|
||||||
|
if trimmedImage == "" || trimmedImage == buildDefaultVllmImageByVersion(currentVersion) {
|
||||||
|
return buildDefaultVllmImageByVersion(targetVersion)
|
||||||
|
}
|
||||||
|
return trimmedImage
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadVllmImageFromEnv(raw string) string {
|
||||||
|
envs := make(map[string]interface{})
|
||||||
|
if strings.TrimSpace(raw) == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(raw), &envs); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if image, ok := envs[vllmImageEnvKey].(string); ok {
|
||||||
|
return strings.TrimSpace(image)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func setVllmImageInEnvContent(content []byte, image string) []byte {
|
||||||
|
normalizedImage := strings.TrimSpace(image)
|
||||||
|
if normalizedImage == "" {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(content), "\n")
|
||||||
|
replaced := false
|
||||||
|
for index, line := range lines {
|
||||||
|
if strings.HasPrefix(line, vllmImageEnvKey+"=") {
|
||||||
|
lines[index] = vllmImageEnvKey + "=" + normalizedImage
|
||||||
|
replaced = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !replaced {
|
||||||
|
if len(lines) > 0 && lines[len(lines)-1] == "" {
|
||||||
|
lines[len(lines)-1] = vllmImageEnvKey + "=" + normalizedImage
|
||||||
|
lines = append(lines, "")
|
||||||
|
} else {
|
||||||
|
lines = append(lines, vllmImageEnvKey+"="+normalizedImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []byte(strings.Join(lines, "\n"))
|
||||||
|
}
|
||||||
@@ -303,6 +303,9 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
|||||||
filename := filepath.Base(appDetailDTO.DownloadUrl)
|
filename := filepath.Base(appDetailDTO.DownloadUrl)
|
||||||
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(appDetailDTO.DownloadUrl, filename), "docker-compose.yml")
|
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(appDetailDTO.DownloadUrl, filename), "docker-compose.yml")
|
||||||
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||||
|
if statusCode == http.StatusNotFound {
|
||||||
|
return appDetailDTO, buserr.New("ErrAppVersionUnavailable")
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return appDetailDTO, buserr.WithDetail("ErrGetCompose", err.Error(), err)
|
return appDetailDTO, buserr.WithDetail("ErrGetCompose", err.Error(), err)
|
||||||
}
|
}
|
||||||
@@ -426,7 +429,12 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
|||||||
} else {
|
} else {
|
||||||
if appDetail.DockerCompose == "" {
|
if appDetail.DockerCompose == "" {
|
||||||
dockerComposeUrl := fmt.Sprintf("%s/%s/1panel/%s/%s/docker-compose.yml", global.AppRepoURL(), global.CONF.Base.Mode, app.Key, appDetail.Version)
|
dockerComposeUrl := fmt.Sprintf("%s/%s/1panel/%s/%s/docker-compose.yml", global.AppRepoURL(), global.CONF.Base.Mode, app.Key, appDetail.Version)
|
||||||
_, composeRes, err = req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
var statusCode int
|
||||||
|
statusCode, composeRes, err = req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||||
|
if statusCode == http.StatusNotFound {
|
||||||
|
err = buserr.New("ErrAppVersionUnavailable")
|
||||||
|
return
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -601,6 +601,9 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
|
|||||||
filename := filepath.Base(detail.DownloadUrl)
|
filename := filepath.Base(detail.DownloadUrl)
|
||||||
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(detail.DownloadUrl, filename), "docker-compose.yml")
|
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(detail.DownloadUrl, filename), "docker-compose.yml")
|
||||||
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||||
|
if statusCode == http.StatusNotFound {
|
||||||
|
return versions, buserr.New("ErrAppVersionUnavailable")
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return versions, err
|
return versions, err
|
||||||
}
|
}
|
||||||
|
|||||||
+257
-83
@@ -1,7 +1,6 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -299,8 +298,8 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
|
|||||||
}
|
}
|
||||||
resourceId = oldMysqlDb.ID
|
resourceId = oldMysqlDb.ID
|
||||||
if oldMysqlDb.ID > 0 {
|
if oldMysqlDb.ID > 0 {
|
||||||
if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password {
|
if err := ensureAppMysqlDBUser(database, dbConfig); err != nil {
|
||||||
return buserr.New("ErrDbUserNotValid")
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var createMysql dto.MysqlDBCreate
|
var createMysql dto.MysqlDBCreate
|
||||||
@@ -412,6 +411,20 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
|||||||
|
|
||||||
switch install.App.Key {
|
switch install.App.Key {
|
||||||
case constant.AppMysql, constant.AppMariaDB, constant.AppMysqlCluster:
|
case constant.AppMysql, constant.AppMariaDB, constant.AppMysqlCluster:
|
||||||
|
if err = databaseUserGrantRepo.DeleteBy(
|
||||||
|
ctx,
|
||||||
|
repo.WithByType(install.App.Key),
|
||||||
|
databaseUserGrantRepo.WithByDatabase(install.Name),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = databaseUserRepo.DeleteBy(
|
||||||
|
ctx,
|
||||||
|
repo.WithByType(install.App.Key),
|
||||||
|
databaseUserRepo.WithByDatabase(install.Name),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
_ = mysqlRepo.Delete(ctx, mysqlRepo.WithByMysqlName(install.Name))
|
_ = mysqlRepo.Delete(ctx, mysqlRepo.WithByMysqlName(install.Name))
|
||||||
case constant.AppMongodb:
|
case constant.AppMongodb:
|
||||||
_ = mongodbRepo.Delete(ctx, mongodbRepo.WithByMongodbName(install.Name))
|
_ = mongodbRepo.Delete(ctx, mongodbRepo.WithByMongodbName(install.Name))
|
||||||
@@ -519,6 +532,44 @@ func deleteAppImagesByIDs(t *task.Task, client docker.Client, imageIDs []appImag
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureAppMysqlDBUser(database model.Database, dbConfig dto.AppDatabase) error {
|
||||||
|
const host = "%"
|
||||||
|
mysqlService := NewIMysqlService()
|
||||||
|
users, err := mysqlService.ListUsers(dto.MysqlUserSearch{Database: database.Name})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
userExists := false
|
||||||
|
passwordValid := false
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Username != dbConfig.DbUser || user.Host != host || user.IsDelete {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
userExists = true
|
||||||
|
passwordValid = user.Password == dbConfig.Password
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !userExists || !passwordValid {
|
||||||
|
return buserr.New("ErrDbUserNotValid")
|
||||||
|
}
|
||||||
|
|
||||||
|
grants, err := mysqlService.ListGrants(dto.MysqlUserSearch{Database: database.Name})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, grant := range grants {
|
||||||
|
if grant.Database == dbConfig.DbName && grant.Username == dbConfig.DbUser && grant.Host == host {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mysqlService.GrantUser(dto.MysqlGrantCreate{
|
||||||
|
Database: database.Name,
|
||||||
|
DB: dbConfig.DbName,
|
||||||
|
Username: dbConfig.DbUser,
|
||||||
|
Host: host,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func deleteLink(del dto.DelAppLink) error {
|
func deleteLink(del dto.DelAppLink) error {
|
||||||
install := del.Install
|
install := del.Install
|
||||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||||
@@ -528,19 +579,20 @@ func deleteLink(del dto.DelAppLink) error {
|
|||||||
for _, re := range resources {
|
for _, re := range resources {
|
||||||
switch re.Key {
|
switch re.Key {
|
||||||
case constant.AppMysql, constant.AppMariaDB:
|
case constant.AppMysql, constant.AppMariaDB:
|
||||||
mysqlService := NewIMysqlService()
|
|
||||||
database, _ := mysqlRepo.Get(repo.WithByID(re.ResourceId))
|
database, _ := mysqlRepo.Get(repo.WithByID(re.ResourceId))
|
||||||
if reflect.DeepEqual(database, model.DatabaseMysql{}) {
|
if reflect.DeepEqual(database, model.DatabaseMysql{}) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := mysqlService.Delete(del.Ctx, dto.MysqlDBDelete{
|
if err := deleteMysqlDatabaseForResourceOwner(del.Ctx, dto.MysqlDBDelete{
|
||||||
ID: database.ID,
|
ID: database.ID,
|
||||||
ForceDelete: del.ForceDelete,
|
ForceDelete: del.ForceDelete,
|
||||||
DeleteBackup: true,
|
DeleteBackup: true,
|
||||||
Type: re.Key,
|
Type: re.Key,
|
||||||
Database: database.MysqlName,
|
Database: database.MysqlName,
|
||||||
}); err != nil && !del.ForceDelete {
|
}, dto.DBResource{Type: constant.TypeApp, Name: install.Name}); err != nil {
|
||||||
return err
|
if isMysqlDatabaseResourceInUseError(err) || !del.ForceDelete {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case constant.AppPostgresql:
|
case constant.AppPostgresql:
|
||||||
pgsqlService := NewIPostgresqlService()
|
pgsqlService := NewIPostgresqlService()
|
||||||
@@ -615,11 +667,58 @@ func handleUpgradeCompose(install model.AppInstall, detail model.AppDetail) (map
|
|||||||
if oldServiceValue["restart"] != nil {
|
if oldServiceValue["restart"] != nil {
|
||||||
serviceValue["restart"] = oldServiceValue["restart"]
|
serviceValue["restart"] = oldServiceValue["restart"]
|
||||||
}
|
}
|
||||||
|
if install.App.Key == constant.AppOpenresty {
|
||||||
|
mergeOpenrestyModuleVolumes(serviceValue, oldServiceValue)
|
||||||
|
}
|
||||||
servicesMap[install.ServiceName] = serviceValue
|
servicesMap[install.ServiceName] = serviceValue
|
||||||
composeMap["services"] = servicesMap
|
composeMap["services"] = servicesMap
|
||||||
return composeMap, nil
|
return composeMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mergeOpenrestyModuleVolumes carries the dynamic module mounts of the old
|
||||||
|
// compose over to the upgraded one when it does not declare them, so built
|
||||||
|
// module artifacts and their load configuration stay mounted across upgrades.
|
||||||
|
func mergeOpenrestyModuleVolumes(serviceValue, oldServiceValue map[string]interface{}) {
|
||||||
|
oldVolumes, ok := oldServiceValue["volumes"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newVolumes, _ := serviceValue["volumes"].([]interface{})
|
||||||
|
existing := make(map[string]struct{}, len(newVolumes))
|
||||||
|
for _, volume := range newVolumes {
|
||||||
|
if containerPath, ok := composeVolumeContainerPath(volume); ok {
|
||||||
|
existing[containerPath] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, volume := range oldVolumes {
|
||||||
|
containerPath, ok := composeVolumeContainerPath(volume)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.Contains(containerPath, nginxModuleEnabledConfDir) && !strings.Contains(containerPath, "nginx/modules/1panel") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok = existing[containerPath]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newVolumes = append(newVolumes, volume)
|
||||||
|
existing[containerPath] = struct{}{}
|
||||||
|
}
|
||||||
|
serviceValue["volumes"] = newVolumes
|
||||||
|
}
|
||||||
|
|
||||||
|
func composeVolumeContainerPath(volume interface{}) (string, bool) {
|
||||||
|
volumeStr, ok := volume.(string)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
parts := strings.Split(volumeStr, ":")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return parts[1], true
|
||||||
|
}
|
||||||
|
|
||||||
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
|
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
|
||||||
if detail.DockerCompose == "" {
|
if detail.DockerCompose == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
@@ -653,74 +752,35 @@ func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string
|
|||||||
return string(composeByte), nil
|
return string(composeByte), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildNginx(parentTask *task.Task) error {
|
func buildNginx(parentTask *task.Task, nginxInstall model.AppInstall, catalogPath string) error {
|
||||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
buildPath := path.Join(nginxInstall.GetPath(), "build")
|
buildPath := path.Join(nginxInstall.GetPath(), nginxModuleBuildDir)
|
||||||
if !fileOp.Stat(buildPath) {
|
if !fileOp.Stat(buildPath) {
|
||||||
return buserr.New("ErrBuildDirNotFound")
|
return buserr.New("ErrBuildDirNotFound")
|
||||||
}
|
}
|
||||||
moduleConfigPath := path.Join(buildPath, "module.json")
|
modules, err := loadNginxModulesWithCatalog(nginxInstall, catalogPath)
|
||||||
moduleContent, err := fileOp.GetContent(moduleConfigPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var (
|
previousModules := cloneNginxModules(modules)
|
||||||
modules []dto.NginxModule
|
staticBuild := hasEnabledStaticNginxModules(modules)
|
||||||
addModuleParams []string
|
if err = configureStaticNginxModules(nginxInstall, modules, ""); err != nil {
|
||||||
addPackages []string
|
return err
|
||||||
)
|
|
||||||
if len(moduleContent) > 0 {
|
|
||||||
_ = json.Unmarshal(moduleContent, &modules)
|
|
||||||
bashFile, err := os.OpenFile(path.Join(buildPath, "tmp", "pre.sh"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.DirPerm)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer bashFile.Close()
|
|
||||||
bashFileWriter := bufio.NewWriter(bashFile)
|
|
||||||
for _, module := range modules {
|
|
||||||
if !module.Enable {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, err = bashFileWriter.WriteString(module.Script + "\n")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
addModuleParams = append(addModuleParams, module.Params)
|
|
||||||
addPackages = append(addPackages, module.Packages...)
|
|
||||||
}
|
|
||||||
err = bashFileWriter.Flush()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
envs, err := gotenv.Read(nginxInstall.GetEnvPath())
|
if staticBuild {
|
||||||
|
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("TaskBuild"), i18n.GetMsgByKey("Image"))
|
||||||
|
parentTask.LogStart(logStr)
|
||||||
|
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*parentTask), cmd.WithTimeout(120*time.Minute))
|
||||||
|
if err = cmdMgr.Run("docker", "compose", "-f", nginxInstall.GetComposePath(), "build"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
parentTask.LogSuccess(logStr)
|
||||||
|
}
|
||||||
|
modules, err = buildDynamicNginxModules(nginxInstall, modules, nil, false, "", catalogPath, parentTask)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
envs["RESTY_CONFIG_OPTIONS_MORE"] = ""
|
return commitNginxModuleBuilds(nginxInstall, previousModules, modules, false, catalogPath)
|
||||||
envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = ""
|
|
||||||
if len(addModuleParams) > 0 {
|
|
||||||
envs["RESTY_CONFIG_OPTIONS_MORE"] = strings.Join(addModuleParams, " ")
|
|
||||||
}
|
|
||||||
if len(addPackages) > 0 {
|
|
||||||
envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = strings.Join(addPackages, " ")
|
|
||||||
}
|
|
||||||
_ = gotenv.Write(envs, nginxInstall.GetEnvPath())
|
|
||||||
if len(addModuleParams) == 0 && len(addPackages) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("TaskBuild"), i18n.GetMsgByKey("Image"))
|
|
||||||
parentTask.LogStart(logStr)
|
|
||||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*parentTask), cmd.WithTimeout(60*time.Minute))
|
|
||||||
if err = cmdMgr.Run("docker", "compose", "-f", nginxInstall.GetComposePath(), "build"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
parentTask.LogSuccess(logStr)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func upgradeInstall(req request.AppInstallUpgrade) error {
|
func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||||
@@ -728,6 +788,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
originalInstall := install
|
||||||
oldVersion := install.Version
|
oldVersion := install.Version
|
||||||
detail, err := appDetailRepo.GetFirst(repo.WithByID(req.DetailID))
|
detail, err := appDetailRepo.GetFirst(repo.WithByID(req.DetailID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -747,8 +808,9 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
install.Status = constant.StatusUpgrading
|
install.Status = constant.StatusUpgrading
|
||||||
|
|
||||||
var (
|
var (
|
||||||
upErr error
|
upErr error
|
||||||
backupFile string
|
backupFile string
|
||||||
|
nginxUpgradeSnapshot *openrestyUpgradeSnapshot
|
||||||
)
|
)
|
||||||
backUpApp := func(t *task.Task) error {
|
backUpApp := func(t *task.Task) error {
|
||||||
backupService := NewIBackupService()
|
backupService := NewIBackupService()
|
||||||
@@ -804,6 +866,13 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
}
|
}
|
||||||
oldEnvContent := append([]byte(nil), content...)
|
oldEnvContent := append([]byte(nil), content...)
|
||||||
oldDockerCompose := install.DockerCompose
|
oldDockerCompose := install.DockerCompose
|
||||||
|
targetNginxCatalogPath := ""
|
||||||
|
if install.App.Key == constant.AppOpenresty {
|
||||||
|
nginxUpgradeSnapshot, err = createOpenrestyUpgradeSnapshot(install.GetPath())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if install.App.Key == vllmAppKeyForUpgrade {
|
if install.App.Key == vllmAppKeyForUpgrade {
|
||||||
envs := make(map[string]interface{})
|
envs := make(map[string]interface{})
|
||||||
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
|
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
|
||||||
@@ -820,22 +889,33 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
}
|
}
|
||||||
_ = copyAppDetailMissing(fileOp, detailDir, install.GetPath())
|
_ = copyAppDetailMissing(fileOp, detailDir, install.GetPath())
|
||||||
if install.App.Key == constant.AppOpenresty {
|
if install.App.Key == constant.AppOpenresty {
|
||||||
installBuildDir := path.Join(install.GetPath(), "build")
|
installBuildDir := path.Join(install.GetPath(), nginxModuleBuildDir)
|
||||||
detailBuildDir := path.Join(detailDir, "build")
|
detailBuildDir := path.Join(detailDir, nginxModuleBuildDir)
|
||||||
if !fileOp.Stat(installBuildDir) {
|
if !fileOp.Stat(installBuildDir) {
|
||||||
if err := fileOp.CreateDir(installBuildDir, constant.DirPerm); err != nil {
|
if err := fileOp.CreateDir(installBuildDir, constant.DirPerm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := fileOp.DeleteDir(path.Join(installBuildDir, "tmp")); err != nil {
|
if err := fileOp.DeleteDir(path.Join(installBuildDir, nginxModuleTmpDir)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fileOp.CopyDir(path.Join(detailBuildDir, "tmp"), installBuildDir); err != nil {
|
if err := fileOp.CopyDir(path.Join(detailBuildDir, nginxModuleTmpDir), installBuildDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fileOp.CopyFile(path.Join(detailBuildDir, "Dockerfile"), installBuildDir); err != nil {
|
if err := fileOp.CopyFile(path.Join(detailBuildDir, "Dockerfile"), installBuildDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := syncNginxModuleBuilder(detailBuildDir, installBuildDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
targetCatalogSource := path.Join(detailBuildDir, nginxModuleCatalogFile)
|
||||||
|
if !fileOp.Stat(targetCatalogSource) {
|
||||||
|
return fmt.Errorf("target OpenResty module catalog not found: %s", targetCatalogSource)
|
||||||
|
}
|
||||||
|
targetNginxCatalogPath = path.Join(installBuildDir, nginxModuleCatalogPendingFile)
|
||||||
|
if err := stageNginxModuleCatalog(targetCatalogSource, targetNginxCatalogPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.conf"), installBuildDir); err != nil {
|
if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.conf"), installBuildDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -913,6 +993,26 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if install.App.Key == constant.AppOpenresty {
|
||||||
|
modules, moduleErr := loadNginxModulesWithCatalog(install, targetNginxCatalogPath)
|
||||||
|
if moduleErr != nil {
|
||||||
|
return moduleErr
|
||||||
|
}
|
||||||
|
// Build dynamic modules for the target version before stopping the
|
||||||
|
// current container. Static modules retain the full rebuild path.
|
||||||
|
if !hasEnabledStaticNginxModules(modules) {
|
||||||
|
previousModules := cloneNginxModules(modules)
|
||||||
|
modules, moduleErr = buildDynamicNginxModules(install, modules, nil, false, "", targetNginxCatalogPath, t)
|
||||||
|
if moduleErr != nil {
|
||||||
|
return moduleErr
|
||||||
|
}
|
||||||
|
if moduleErr = saveNginxModulesWithCatalog(install, modules, targetNginxCatalogPath); moduleErr != nil {
|
||||||
|
removeNginxModuleOutputsNotReferenced(install, modules, previousModules)
|
||||||
|
return moduleErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if out, err := compose.Down(install.GetComposePath()); err != nil {
|
if out, err := compose.Down(install.GetComposePath()); err != nil {
|
||||||
if out != "" {
|
if out != "" {
|
||||||
upErr = errors.New(out)
|
upErr = errors.New(out)
|
||||||
@@ -947,7 +1047,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if install.App.Key == constant.AppOpenresty {
|
if install.App.Key == constant.AppOpenresty {
|
||||||
if err = buildNginx(t); err != nil {
|
if err = buildNginx(t, install, targetNginxCatalogPath); err != nil {
|
||||||
t.Log(err.Error())
|
t.Log(err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -963,8 +1063,24 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
}
|
}
|
||||||
t.LogSuccess(logStr)
|
t.LogSuccess(logStr)
|
||||||
install.Status = constant.StatusRunning
|
install.Status = constant.StatusRunning
|
||||||
if err = appInstallRepo.Save(context.Background(), &install); err != nil {
|
if install.App.Key == constant.AppOpenresty {
|
||||||
return err
|
if err = commitStaticNginxModuleBuilds(install, targetNginxCatalogPath, t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
activeCatalogPath := path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleCatalogFile)
|
||||||
|
if err = activateNginxModuleCatalogAndCommit(targetNginxCatalogPath, activeCatalogPath, func() error {
|
||||||
|
return appInstallRepo.Save(context.Background(), &install)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = appInstallRepo.Save(context.Background(), &install); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nginxUpgradeSnapshot != nil {
|
||||||
|
nginxUpgradeSnapshot.Cleanup()
|
||||||
|
nginxUpgradeSnapshot = nil
|
||||||
}
|
}
|
||||||
if req.DeleteImage {
|
if req.DeleteImage {
|
||||||
newEnvContent, err := fileOp.GetContent(install.GetEnvPath())
|
newEnvContent, err := fileOp.GetContent(install.GetEnvPath())
|
||||||
@@ -993,30 +1109,88 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
rollBackApp := func(t *task.Task) {
|
rollBackApp := func(t *task.Task) {
|
||||||
if req.Backup {
|
if req.Backup {
|
||||||
t.Log(i18n.GetWithName("AppRecover", install.Name))
|
t.Log(i18n.GetWithName("AppRecover", install.Name))
|
||||||
if err := NewIBackupService().AppRecover(dto.CommonRecover{Name: install.App.Key, DetailName: install.Name, Type: "app", DownloadAccountID: 1, File: backupFile}); err != nil {
|
recoverErr := NewIBackupService().AppRecover(dto.CommonRecover{
|
||||||
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), err)
|
Name: install.App.Key, DetailName: install.Name, Type: "app", DownloadAccountID: 1, File: backupFile,
|
||||||
|
})
|
||||||
|
if recoverErr == nil {
|
||||||
|
if nginxUpgradeSnapshot != nil {
|
||||||
|
nginxUpgradeSnapshot.Cleanup()
|
||||||
|
nginxUpgradeSnapshot = nil
|
||||||
|
}
|
||||||
|
t.LogSuccess(i18n.GetWithName("AppRecover", install.Name))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), recoverErr)
|
||||||
|
if install.App.Key != constant.AppOpenresty {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if install.App.Key == constant.AppOpenresty && nginxUpgradeSnapshot != nil {
|
||||||
|
if out, rollbackErr := compose.Down(install.GetComposePath()); rollbackErr != nil {
|
||||||
|
if out != "" {
|
||||||
|
rollbackErr = fmt.Errorf("%s: %w", out, rollbackErr)
|
||||||
|
}
|
||||||
|
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
|
||||||
|
}
|
||||||
|
if rollbackErr := nginxUpgradeSnapshot.Restore(); rollbackErr != nil {
|
||||||
|
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nginxUpgradeSnapshot.Cleanup()
|
||||||
|
nginxUpgradeSnapshot = nil
|
||||||
|
if out, rollbackErr := compose.Up(originalInstall.GetComposePath()); rollbackErr != nil {
|
||||||
|
if out != "" {
|
||||||
|
rollbackErr = fmt.Errorf("%s: %w", out, rollbackErr)
|
||||||
|
}
|
||||||
|
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
originalInstall.Status = constant.StatusRunning
|
||||||
|
originalInstall.Message = ""
|
||||||
|
if rollbackErr := appInstallRepo.Save(context.Background(), &originalInstall); rollbackErr != nil {
|
||||||
|
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
install = originalInstall
|
||||||
t.LogSuccess(i18n.GetWithName("AppRecover", install.Name))
|
t.LogSuccess(i18n.GetWithName("AppRecover", install.Name))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if install.App.Key == constant.AppOpenresty {
|
||||||
|
if rollbackErr := appInstallRepo.Save(context.Background(), &originalInstall); rollbackErr != nil {
|
||||||
|
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
install = originalInstall
|
||||||
|
t.LogSuccess(i18n.GetWithName("AppRecover", install.Name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
upgradeTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskUpgrade, task.TaskScopeApp), upgradeApp, rollBackApp, 0, 1*time.Hour)
|
upgradeTimeout := 1 * time.Hour
|
||||||
|
if install.App.Key == constant.AppOpenresty {
|
||||||
|
// Dynamic modules are built serially and each Docker build has its own
|
||||||
|
// timeout. An outer deadline would start rollback while upgradeApp is
|
||||||
|
// still mutating the installation because SubTask does not stop its
|
||||||
|
// action goroutine on timeout.
|
||||||
|
upgradeTimeout = 0
|
||||||
|
}
|
||||||
|
upgradeTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskUpgrade, task.TaskScopeApp), upgradeApp, rollBackApp, 0, upgradeTimeout)
|
||||||
|
|
||||||
|
upgradingInstall := install
|
||||||
|
if err = appInstallRepo.Save(context.Background(), &upgradingInstall); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
err = upgradeTask.Execute()
|
if taskErr := upgradeTask.Execute(); taskErr != nil {
|
||||||
if err != nil {
|
|
||||||
existInstall, _ := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
|
existInstall, _ := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
|
||||||
if existInstall.ID > 0 && existInstall.Status != constant.StatusRunning {
|
if existInstall.ID > 0 && existInstall.Status != constant.StatusRunning {
|
||||||
existInstall.Status = constant.StatusUpgradeErr
|
existInstall.Status = constant.StatusUpgradeErr
|
||||||
existInstall.Message = err.Error()
|
existInstall.Message = taskErr.Error()
|
||||||
_ = appInstallRepo.Save(context.Background(), &existInstall)
|
_ = appInstallRepo.Save(context.Background(), &existInstall)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return appInstallRepo.Save(context.Background(), &install)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func skipCheckStatus(service types.ServiceConfig) bool {
|
func skipCheckStatus(service types.ServiceConfig) bool {
|
||||||
@@ -2247,7 +2421,7 @@ func handleOpenrestyFile(appInstall *model.AppInstall) error {
|
|||||||
|
|
||||||
func handleDefaultServer(appInstall *model.AppInstall) error {
|
func handleDefaultServer(appInstall *model.AppInstall) error {
|
||||||
installDir := appInstall.GetPath()
|
installDir := appInstall.GetPath()
|
||||||
defaultConfigPath := path.Join(installDir, "conf", "default", "00.default.conf")
|
defaultConfigPath := path.Join(installDir, nginxModuleConfDir, "default", "00.default.conf")
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
content, err := fileOp.GetContent(defaultConfigPath)
|
content, err := fileOp.GetContent(defaultConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2261,7 +2435,7 @@ func handleDefaultServer(appInstall *model.AppInstall) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleSSLConfig(appInstall *model.AppInstall, hasDefaultWebsite bool, sslRejectHandshake bool) error {
|
func handleSSLConfig(appInstall *model.AppInstall, hasDefaultWebsite bool, sslRejectHandshake bool) error {
|
||||||
sslDir := path.Join(appInstall.GetPath(), "conf", "ssl")
|
sslDir := path.Join(appInstall.GetPath(), nginxModuleConfDir, "ssl")
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
if !fileOp.Stat(sslDir) {
|
if !fileOp.Stat(sslDir) {
|
||||||
return errors.New("ssl dir not found")
|
return errors.New("ssl dir not found")
|
||||||
@@ -2291,7 +2465,7 @@ func handleSSLConfig(appInstall *model.AppInstall, hasDefaultWebsite bool, sslRe
|
|||||||
_ = NewIWebsiteSSLService().Delete([]uint{websiteSSL.ID})
|
_ = NewIWebsiteSSLService().Delete([]uint{websiteSSL.ID})
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
defaultConfigPath := path.Join(appInstall.GetPath(), "conf", "default", "00.default.conf")
|
defaultConfigPath := path.Join(appInstall.GetPath(), nginxModuleConfDir, "default", "00.default.conf")
|
||||||
content, err := os.ReadFile(defaultConfigPath)
|
content, err := os.ReadFile(defaultConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -372,7 +373,7 @@ func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, erro
|
|||||||
targetPath = strings.TrimPrefix(targetPath, "/")
|
targetPath = strings.TrimPrefix(targetPath, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := client.Upload(fileItem, targetPath); err != nil {
|
if _, err := client.Upload(context.Background(), fileItem, targetPath); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
_, _ = client.Delete(path.Join(backup.BackupPath, "test/1panel"))
|
_, _ = client.Delete(path.Join(backup.BackupPath, "test/1panel"))
|
||||||
@@ -437,6 +438,10 @@ type backupClientHelper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewBackupClientMap(ids []string) map[string]backupClientHelper {
|
func NewBackupClientMap(ids []string) map[string]backupClientHelper {
|
||||||
|
return NewBackupClientMapWithContext(context.Background(), ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBackupClientMapWithContext(ctx context.Context, ids []string) map[string]backupClientHelper {
|
||||||
var accounts []model.BackupAccount
|
var accounts []model.BackupAccount
|
||||||
var idItems []uint
|
var idItems []uint
|
||||||
for i := 0; i < len(ids); i++ {
|
for i := 0; i < len(ids); i++ {
|
||||||
@@ -446,7 +451,7 @@ func NewBackupClientMap(ids []string) map[string]backupClientHelper {
|
|||||||
accounts, _ = backupRepo.List(repo.WithByIDs(idItems))
|
accounts, _ = backupRepo.List(repo.WithByIDs(idItems))
|
||||||
clientMap := make(map[string]backupClientHelper)
|
clientMap := make(map[string]backupClientHelper)
|
||||||
for _, item := range accounts {
|
for _, item := range accounts {
|
||||||
backClient, err := newClient(&item, true)
|
backClient, err := newClientWithContext(ctx, &item, true)
|
||||||
itemHelper := backupClientHelper{
|
itemHelper := backupClientHelper{
|
||||||
client: backClient,
|
client: backClient,
|
||||||
name: item.Name,
|
name: item.Name,
|
||||||
@@ -463,7 +468,11 @@ func NewBackupClientMap(ids []string) map[string]backupClientHelper {
|
|||||||
return clientMap
|
return clientMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func uploadWithMap(taskItem task.Task, accountMap map[string]backupClientHelper, src, dst, accountIDs string, downloadAccountID, retry uint) error {
|
func uploadWithMap(taskItem task.Task, accountMap map[string]backupClientHelper, src, dst, accountIDs string, downloadAccountID, retry uint, cleanOnFailure bool) error {
|
||||||
|
return uploadWithMapWithContext(context.Background(), taskItem, accountMap, src, dst, accountIDs, downloadAccountID, retry, cleanOnFailure, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func uploadWithMapWithContext(ctx context.Context, taskItem task.Task, accountMap map[string]backupClientHelper, src, dst, accountIDs string, downloadAccountID, retry uint, cleanOnFailure, removeSrc bool) error {
|
||||||
accounts := strings.Split(accountIDs, ",")
|
accounts := strings.Split(accountIDs, ",")
|
||||||
for _, account := range accounts {
|
for _, account := range accounts {
|
||||||
if len(account) == 0 {
|
if len(account) == 0 {
|
||||||
@@ -489,10 +498,13 @@ func uploadWithMap(taskItem task.Task, accountMap map[string]backupClientHelper,
|
|||||||
"backup": name,
|
"backup": name,
|
||||||
}))
|
}))
|
||||||
for i := 0; i < int(retry)+1; i++ {
|
for i := 0; i < int(retry)+1; i++ {
|
||||||
_, err := itemBackup.client.Upload(src, path.Join(itemBackup.backupPath, dst))
|
_, err := itemBackup.client.Upload(ctx, src, path.Join(itemBackup.backupPath, dst))
|
||||||
taskItem.LogWithStatus(i18n.GetMsgByKey("Upload"), err)
|
taskItem.LogWithStatus(i18n.GetMsgByKey("Upload"), err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if account == fmt.Sprintf("%d", downloadAccountID) {
|
if account == fmt.Sprintf("%d", downloadAccountID) {
|
||||||
|
if cleanOnFailure {
|
||||||
|
cleanupCronjobBackupArtifacts(accountMap, src, dst)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -502,11 +514,71 @@ func uploadWithMap(taskItem task.Task, accountMap map[string]backupClientHelper,
|
|||||||
itemBackup.hasBackup = true
|
itemBackup.hasBackup = true
|
||||||
accountMap[account] = itemBackup
|
accountMap[account] = itemBackup
|
||||||
}
|
}
|
||||||
os.RemoveAll(src)
|
if removeSrc {
|
||||||
|
os.RemoveAll(src)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanupCronjobBackupArtifacts(accountMap map[string]backupClientHelper, src, dst string) {
|
||||||
|
if err := os.RemoveAll(src); err != nil {
|
||||||
|
global.LOG.Errorf("remove failed local cronjob backup file %s failed, err: %v", src, err)
|
||||||
|
}
|
||||||
|
for _, account := range accountMap {
|
||||||
|
if !account.isOk {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := account.client.Delete(path.Join(account.backupPath, dst)); err != nil {
|
||||||
|
global.LOG.Errorf("remove failed cronjob backup file %s failed, err: %v", dst, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func markBackupFailed(recordID uint, backupErr error) {
|
||||||
|
_ = backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": backupErr.Error()})
|
||||||
|
|
||||||
|
record, err := backupRepo.GetRecord(repo.WithByID(recordID))
|
||||||
|
if err != nil || record.ID == 0 {
|
||||||
|
global.LOG.Errorf("load failed backup record %d for cleanup failed, err: %v", recordID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := path.Join(record.FileDir, record.FileName)
|
||||||
|
if err := os.Remove(path.Join(global.Dir.LocalBackupDir, filePath)); err != nil && !os.IsNotExist(err) {
|
||||||
|
global.LOG.Errorf("remove failed local backup file %s failed, err: %v", filePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleaned := make(map[string]struct{})
|
||||||
|
for _, accountID := range strings.Split(record.SourceAccountIDs, ",") {
|
||||||
|
if accountID == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := cleaned[accountID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cleaned[accountID] = struct{}{}
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(accountID)
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("parse backup account %s for failed backup cleanup failed, err: %v", accountID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
account, storageClient, err := NewBackupClientWithID(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("new backup client for failed backup cleanup failed, err: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := storageClient.Delete(path.Join(account.BackupPath, filePath)); err != nil {
|
||||||
|
global.LOG.Errorf("remove failed backup file %s failed, err: %v", filePath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newClient(account *model.BackupAccount, isEncrypt bool) (cloud_storage.CloudStorageClient, error) {
|
func newClient(account *model.BackupAccount, isEncrypt bool) (cloud_storage.CloudStorageClient, error) {
|
||||||
|
return newClientWithContext(context.Background(), account, isEncrypt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClientWithContext(ctx context.Context, account *model.BackupAccount, isEncrypt bool) (cloud_storage.CloudStorageClient, error) {
|
||||||
varMap := make(map[string]interface{})
|
varMap := make(map[string]interface{})
|
||||||
if len(account.Vars) != 0 {
|
if len(account.Vars) != 0 {
|
||||||
if err := json.Unmarshal([]byte(account.Vars), &varMap); err != nil {
|
if err := json.Unmarshal([]byte(account.Vars), &varMap); err != nil {
|
||||||
@@ -531,7 +603,7 @@ func newClient(account *model.BackupAccount, isEncrypt bool) (cloud_storage.Clou
|
|||||||
varMap["password"] = account.Credential
|
varMap["password"] = account.Credential
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := cloud_storage.NewCloudStorageClient(account.Type, varMap)
|
client, err := cloud_storage.NewCloudStorageClientWithContext(ctx, account.Type, varMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er
|
|||||||
|
|
||||||
if !req.IsImmediate {
|
if !req.IsImmediate {
|
||||||
if err = handleAppBackup(&install, nil, record.ID, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {
|
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()})
|
markBackupFailed(record.ID, err)
|
||||||
global.LOG.Errorf("backup app %s failed, err: %v", req.DetailName, err)
|
global.LOG.Errorf("backup app %s failed, err: %v", req.DetailName, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -74,13 +74,13 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er
|
|||||||
|
|
||||||
backupTask, err := task.NewTaskWithOps(install.Name, task.TaskBackup, task.TaskScopeBackup, req.TaskID, install.ID)
|
backupTask, err := task.NewTaskWithOps(install.Name, task.TaskBackup, task.TaskScopeBackup, req.TaskID, install.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(record.ID, err)
|
||||||
record.Status = constant.StatusFailed
|
record.Status = constant.StatusFailed
|
||||||
record.Message = err.Error()
|
record.Message = err.Error()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = doAppBackup(&install, backupTask, backupDir, fileName, "", req.Secret); err != nil {
|
if err = doAppBackup(&install, backupTask, backupDir, fileName, "", req.Secret); err != nil {
|
||||||
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(record.ID, err)
|
||||||
record.Status = constant.StatusFailed
|
record.Status = constant.StatusFailed
|
||||||
record.Message = err.Error()
|
record.Message = err.Error()
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -162,7 +162,7 @@ func handleAppBackup(install *model.AppInstall, parentTask *task.Task, recordID
|
|||||||
backupTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 3, time.Hour)
|
backupTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 3, time.Hour)
|
||||||
go func() {
|
go func() {
|
||||||
if err := backupTask.Execute(); err != nil {
|
if err := backupTask.Execute(); err != nil {
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(recordID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||||
@@ -280,7 +280,7 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
newDB, err := reCreateDB(db.ID, database, backupEnvMap)
|
newDB, err := reCreateDB(db.ID, database, backupEnvMap, install.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -408,10 +408,16 @@ func doAppBackup(install *model.AppInstall, parentTask *task.Task, backupDir, fi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func reCreateDB(dbID uint, database model.Database, envMap map[string]interface{}) (*model.DatabaseMysql, error) {
|
func reCreateDB(dbID uint, database model.Database, envMap map[string]interface{}, appInstallName string) (*model.DatabaseMysql, error) {
|
||||||
mysqlService := NewIMysqlService()
|
mysqlService := NewIMysqlService()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
_ = mysqlService.Delete(ctx, dto.MysqlDBDelete{ID: dbID, Database: database.Name, Type: database.Type, DeleteBackup: false, ForceDelete: true})
|
if err := deleteMysqlDatabaseForResourceOwner(
|
||||||
|
ctx,
|
||||||
|
dto.MysqlDBDelete{ID: dbID, Database: database.Name, Type: database.Type, DeleteBackup: false, ForceDelete: true},
|
||||||
|
dto.DBResource{Type: constant.TypeApp, Name: appInstallName},
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
dbInfo := getDBCreateInfoFromEnv(envMap, "utf8mb4")
|
dbInfo := getDBCreateInfoFromEnv(envMap, "utf8mb4")
|
||||||
createDB, err := mysqlService.Create(context.Background(), dto.MysqlDBCreate{
|
createDB, err := mysqlService.Create(context.Background(), dto.MysqlDBCreate{
|
||||||
@@ -419,17 +425,57 @@ func reCreateDB(dbID uint, database model.Database, envMap map[string]interface{
|
|||||||
From: database.From,
|
From: database.From,
|
||||||
Database: database.Name,
|
Database: database.Name,
|
||||||
Format: dbInfo.Format,
|
Format: dbInfo.Format,
|
||||||
Username: dbInfo.User,
|
|
||||||
Password: dbInfo.Password,
|
|
||||||
Permission: "%",
|
Permission: "%",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(dbInfo.User) != 0 {
|
||||||
|
if err := ensureMysqlDBUser(mysqlService, database, dbInfo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
updateCronjobsDBRef(dbID, createDB.ID)
|
updateCronjobsDBRef(dbID, createDB.ID)
|
||||||
return createDB, nil
|
return createDB, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureMysqlDBUser(mysqlService IMysqlService, database model.Database, dbInfo dbRecreateInfo) error {
|
||||||
|
const host = "%"
|
||||||
|
users, err := mysqlService.ListUsers(dto.MysqlUserSearch{Database: database.Name})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var oldUser dto.MysqlUser
|
||||||
|
exists := false
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Username == dbInfo.User && user.Host == host && !user.IsDelete {
|
||||||
|
oldUser = user
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
if len(oldUser.Password) != 0 && oldUser.Password != dbInfo.Password {
|
||||||
|
return buserr.New("ErrDbUserNotValid")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := mysqlService.CreateUser(dto.MysqlUserCreate{
|
||||||
|
Database: database.Name,
|
||||||
|
Username: dbInfo.User,
|
||||||
|
Host: host,
|
||||||
|
Password: dbInfo.Password,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mysqlService.GrantUser(dto.MysqlGrantCreate{
|
||||||
|
Database: database.Name,
|
||||||
|
DB: dbInfo.Name,
|
||||||
|
Username: dbInfo.User,
|
||||||
|
Host: host,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func reCreatePostgresqlDB(dbID uint, database model.Database, envMap map[string]interface{}) (*model.DatabasePostgresql, error) {
|
func reCreatePostgresqlDB(dbID uint, database model.Database, envMap map[string]interface{}) (*model.DatabasePostgresql, error) {
|
||||||
postgresqlService := NewIPostgresqlService()
|
postgresqlService := NewIPostgresqlService()
|
||||||
_ = postgresqlService.Delete(context.Background(), dto.PostgresqlDBDelete{
|
_ = postgresqlService.Delete(context.Background(), dto.PostgresqlDBDelete{
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func (u *BackupService) ComposeBackup(req dto.CommonBackup) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := handleComposeBackup(req, nil, record.ID, backupDir, fileName); err != nil {
|
if err := handleComposeBackup(req, nil, record.ID, backupDir, fileName); err != nil {
|
||||||
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(record.ID, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -163,7 +163,7 @@ func handleComposeBackup(req dto.CommonBackup, parentTask *task.Task, recordID u
|
|||||||
go func() {
|
go func() {
|
||||||
defer composeCtx.close()
|
defer composeCtx.close()
|
||||||
if err := backupTask.Execute(); err != nil {
|
if err := backupTask.Execute(); err != nil {
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(recordID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func (u *BackupService) ContainerBackup(req dto.CommonBackup) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := handleContainerBackup(req.Name, nil, record.ID, backupDir, fileName, req.TaskID, req.Secret, req.StopBefore); err != nil {
|
if err := handleContainerBackup(req.Name, nil, record.ID, backupDir, fileName, req.TaskID, req.Secret, req.StopBefore); err != nil {
|
||||||
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(record.ID, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -158,7 +158,7 @@ func handleContainerBackup(containerName string, parentTask *task.Task, recordID
|
|||||||
go func() {
|
go func() {
|
||||||
defer backupCtx.close()
|
defer backupCtx.close()
|
||||||
if err := backupTask.Execute(); err != nil {
|
if err := backupTask.Execute(); err != nil {
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(recordID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||||
@@ -604,23 +604,40 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeUnsupportedEndpointStaticIPAM(cli *client.Client, primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) {
|
func normalizeContainerEndpointSettings(ctx context.Context, cli *client.Client, primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) {
|
||||||
|
if cli.NewVersionError(ctx, "1.44", "specify mac-address per network") != nil {
|
||||||
|
removeEndpointMacAddresses(primary, extras)
|
||||||
|
}
|
||||||
|
endpointGroups := []map[string]*network.EndpointSettings{extras}
|
||||||
if primary != nil {
|
if primary != nil {
|
||||||
removeUnsupportedEndpointStaticIPAMFromEndpoints(cli, primary.EndpointsConfig)
|
endpointGroups = append(endpointGroups, primary.EndpointsConfig)
|
||||||
|
}
|
||||||
|
for _, endpoints := range endpointGroups {
|
||||||
|
for netName, endpoint := range endpoints {
|
||||||
|
if endpoint == nil || endpoint.IPAMConfig == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
info, err := cli.NetworkInspect(ctx, netName, network.InspectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
removeUnsupportedEndpointStaticIP(netName, info, endpoint)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
removeUnsupportedEndpointStaticIPAMFromEndpoints(cli, extras)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeUnsupportedEndpointStaticIPAMFromEndpoints(cli *client.Client, endpoints map[string]*network.EndpointSettings) {
|
func removeEndpointMacAddresses(primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) {
|
||||||
for netName, endpoint := range endpoints {
|
if primary != nil {
|
||||||
if endpoint == nil || endpoint.IPAMConfig == nil {
|
for _, endpoint := range primary.EndpointsConfig {
|
||||||
continue
|
if endpoint != nil {
|
||||||
|
endpoint.MacAddress = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
info, err := cli.NetworkInspect(context.Background(), netName, network.InspectOptions{})
|
}
|
||||||
if err != nil {
|
for _, endpoint := range extras {
|
||||||
continue
|
if endpoint != nil {
|
||||||
|
endpoint.MacAddress = ""
|
||||||
}
|
}
|
||||||
removeUnsupportedEndpointStaticIP(netName, info, endpoint)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -639,7 +656,7 @@ func removeUnsupportedEndpointStaticIP(netName string, info network.Inspect, end
|
|||||||
if endpoint.IPAMConfig.IPv6Address != "" && !networkSupportsStaticIP(info, endpoint.IPAMConfig.IPv6Address, true) {
|
if endpoint.IPAMConfig.IPv6Address != "" && !networkSupportsStaticIP(info, endpoint.IPAMConfig.IPv6Address, true) {
|
||||||
endpoint.IPAMConfig.IPv6Address = ""
|
endpoint.IPAMConfig.IPv6Address = ""
|
||||||
}
|
}
|
||||||
if endpoint.IPAMConfig.IPv4Address == "" && endpoint.IPAMConfig.IPv6Address == "" {
|
if endpoint.IPAMConfig.IPv4Address == "" && endpoint.IPAMConfig.IPv6Address == "" && len(endpoint.IPAMConfig.LinkLocalIPs) == 0 {
|
||||||
endpoint.IPAMConfig = nil
|
endpoint.IPAMConfig = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -776,11 +793,30 @@ func buildContainerRecoverNetworkConfig(networkSettings *container.NetworkSettin
|
|||||||
if name == "host" || name == "none" {
|
if name == "host" || name == "none" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
endpointSetting := &network.EndpointSettings{Aliases: append([]string(nil), endpoint.Aliases...), MacAddress: endpoint.MacAddress}
|
if endpoint == nil {
|
||||||
|
if name == primaryName {
|
||||||
|
config.EndpointsConfig[name] = &network.EndpointSettings{}
|
||||||
|
} else {
|
||||||
|
extraNetworks[name] = &network.EndpointSettings{}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
endpointSetting := &network.EndpointSettings{
|
||||||
|
Links: append([]string(nil), endpoint.Links...),
|
||||||
|
Aliases: append([]string(nil), endpoint.Aliases...),
|
||||||
|
DriverOpts: cloneStringMap(endpoint.DriverOpts),
|
||||||
|
GwPriority: endpoint.GwPriority,
|
||||||
|
}
|
||||||
if endpoint.IPAMConfig != nil {
|
if endpoint.IPAMConfig != nil {
|
||||||
endpointSetting.IPAMConfig = &network.EndpointIPAMConfig{
|
endpointSetting.IPAMConfig = &network.EndpointIPAMConfig{
|
||||||
IPv4Address: endpoint.IPAMConfig.IPv4Address,
|
IPv4Address: endpoint.IPAMConfig.IPv4Address,
|
||||||
IPv6Address: endpoint.IPAMConfig.IPv6Address,
|
IPv6Address: endpoint.IPAMConfig.IPv6Address,
|
||||||
|
LinkLocalIPs: append([]string(nil), endpoint.IPAMConfig.LinkLocalIPs...),
|
||||||
|
}
|
||||||
|
} else if name != "bridge" && (endpoint.IPAddress != "" || endpoint.GlobalIPv6Address != "") {
|
||||||
|
endpointSetting.IPAMConfig = &network.EndpointIPAMConfig{
|
||||||
|
IPv4Address: endpoint.IPAddress,
|
||||||
|
IPv6Address: endpoint.GlobalIPv6Address,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if name == primaryName {
|
if name == primaryName {
|
||||||
@@ -795,6 +831,39 @@ func buildContainerRecoverNetworkConfig(networkSettings *container.NetworkSettin
|
|||||||
return config, extraNetworks
|
return config, extraNetworks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unsupportedUserSpecifiedIPAddress = "user specified IP address is supported only when connecting to networks with user configured subnets"
|
||||||
|
|
||||||
|
func clearUnsupportedDynamicEndpointIPAM(err error, endpoints map[string]*network.EndpointSettings, networkSettings *container.NetworkSettings) bool {
|
||||||
|
if err == nil || !strings.Contains(err.Error(), unsupportedUserSpecifiedIPAddress) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for name, endpoint := range endpoints {
|
||||||
|
if !isDynamicContainerNetwork(networkSettings, name) || endpoint == nil || endpoint.IPAMConfig == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(err.Error(), "network "+name+":") {
|
||||||
|
endpoint.IPAMConfig = nil
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cleared := false
|
||||||
|
for name, endpoint := range endpoints {
|
||||||
|
if isDynamicContainerNetwork(networkSettings, name) && endpoint != nil && endpoint.IPAMConfig != nil {
|
||||||
|
endpoint.IPAMConfig = nil
|
||||||
|
cleared = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cleared
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDynamicContainerNetwork(networkSettings *container.NetworkSettings, name string) bool {
|
||||||
|
if networkSettings == nil || name == "bridge" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
endpoint := networkSettings.Networks[name]
|
||||||
|
return endpoint != nil && endpoint.IPAMConfig == nil && (endpoint.IPAddress != "" || endpoint.GlobalIPv6Address != "")
|
||||||
|
}
|
||||||
|
|
||||||
func cloneContainerConfig(config *container.Config) *container.Config {
|
func cloneContainerConfig(config *container.Config) *container.Config {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return &container.Config{}
|
return &container.Config{}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (u *BackupService) MongodbBackup(req dto.CommonBackup) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := handleMongodbBackup(req, nil, record.ID, targetDir, fileName); err != nil {
|
if err := handleMongodbBackup(req, nil, record.ID, targetDir, fileName); err != nil {
|
||||||
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(record.ID, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -98,7 +98,7 @@ func handleMongodbBackup(req dto.CommonBackup, parentTask *task.Task, recordID u
|
|||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
if err := backupTask.Execute(); err != nil {
|
if err := backupTask.Execute(); err != nil {
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(recordID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
|
|||||||
|
|
||||||
databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName, Args: req.Args}
|
databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName, Args: req.Args}
|
||||||
if err := handleMysqlBackup(databaseHelper, nil, record.ID, targetDir, fileName, req.TaskID, req.Secret); err != nil {
|
if err := handleMysqlBackup(databaseHelper, nil, record.ID, targetDir, fileName, req.TaskID, req.Secret); err != nil {
|
||||||
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(record.ID, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -100,7 +100,7 @@ func handleMysqlBackup(db DatabaseHelper, parentTask *task.Task, recordID uint,
|
|||||||
backupTask.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 0, 3*time.Hour)
|
backupTask.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 0, 3*time.Hour)
|
||||||
go func() {
|
go func() {
|
||||||
if err := backupTask.Execute(); err != nil {
|
if err := backupTask.Execute(); err != nil {
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(recordID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||||
|
|||||||
@@ -39,10 +39,12 @@ func (u *BackupService) PostgresqlBackup(req dto.CommonBackup) error {
|
|||||||
}
|
}
|
||||||
if err := backupRepo.CreateRecord(record); err != nil {
|
if err := backupRepo.CreateRecord(record); err != nil {
|
||||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName}
|
databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName}
|
||||||
if err := handlePostgresqlBackup(databaseHelper, nil, record.ID, targetDir, fileName, req.TaskID, req.Secret); err != nil {
|
if err := handlePostgresqlBackup(databaseHelper, nil, record.ID, targetDir, fileName, req.TaskID, req.Secret); err != nil {
|
||||||
|
markBackupFailed(record.ID, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -88,7 +90,7 @@ func handlePostgresqlBackup(db DatabaseHelper, parentTask *task.Task, recordID u
|
|||||||
backupTask.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 0, 3*time.Hour)
|
backupTask.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 0, 3*time.Hour)
|
||||||
go func() {
|
go func() {
|
||||||
if err := backupTask.Execute(); err != nil {
|
if err := backupTask.Execute(); err != nil {
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(recordID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ func (u *BackupService) RedisBackup(req dto.CommonBackup) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := handleRedisBackup(redisInfo, nil, record.ID, backupDir, fileName, req.Secret, req.TaskID); err != nil {
|
if err := handleRedisBackup(redisInfo, nil, record.ID, backupDir, fileName, req.Secret, req.TaskID); err != nil {
|
||||||
|
markBackupFailed(record.ID, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -129,7 +130,7 @@ func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, recordID
|
|||||||
itemTask.AddSubTaskWithOps(i18n.GetMsgByKey("TaskBackup"), backupDatabase, nil, 3, time.Hour)
|
itemTask.AddSubTaskWithOps(i18n.GetMsgByKey("TaskBackup"), backupDatabase, nil, 3, time.Hour)
|
||||||
go func() {
|
go func() {
|
||||||
if err := itemTask.Execute(); err != nil {
|
if err := itemTask.Execute(); err != nil {
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(recordID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = handleWebsiteBackup(&website, nil, record.ID, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {
|
if err = handleWebsiteBackup(&website, nil, record.ID, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {
|
||||||
|
markBackupFailed(record.ID, err)
|
||||||
global.LOG.Errorf("backup website %s failed, err: %v", website.Alias, err)
|
global.LOG.Errorf("backup website %s failed, err: %v", website.Alias, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -235,7 +236,7 @@ func handleWebsiteBackup(website *model.Website, parentTask *task.Task, recordID
|
|||||||
backupTask.AddSubTaskWithOps(task.GetTaskName(website.Alias, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 3, time.Hour)
|
backupTask.AddSubTaskWithOps(task.GetTaskName(website.Alias, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 3, time.Hour)
|
||||||
go func() {
|
go func() {
|
||||||
if err := backupTask.Execute(); err != nil {
|
if err := backupTask.Execute(); err != nil {
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
markBackupFailed(recordID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||||
|
|||||||
+79
-207
@@ -485,15 +485,19 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
unlock := containerOperationLock.lock(req.Name)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
newContainer, _ := client.ContainerInspect(ctx, req.Name)
|
newContainer, _ := client.ContainerInspect(ctx, req.Name)
|
||||||
if newContainer.ContainerJSONBase != nil {
|
if newContainer.ContainerJSONBase != nil {
|
||||||
|
unlock()
|
||||||
|
_ = client.Close()
|
||||||
return buserr.New("ErrContainerName")
|
return buserr.New("ErrContainerName")
|
||||||
}
|
}
|
||||||
|
|
||||||
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
unlock()
|
||||||
|
_ = client.Close()
|
||||||
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -529,18 +533,20 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
removeUnsupportedEndpointStaticIPAM(client, networkConf, nil)
|
normalizeContainerEndpointSettings(ctx, client, networkConf, nil)
|
||||||
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
|
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
|
||||||
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
|
if con.ID != "" {
|
||||||
|
_ = client.ContainerRemove(ctx, con.ID, container.RemoveOptions{RemoveVolumes: true, Force: true})
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = client.ContainerStart(ctx, con.ID, container.StartOptions{})
|
err = client.ContainerStart(ctx, con.ID, container.StartOptions{})
|
||||||
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err)
|
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
|
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
|
||||||
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
|
_ = client.ContainerRemove(ctx, con.ID, container.RemoveOptions{RemoveVolumes: true, Force: true})
|
||||||
return fmt.Errorf("create successful but start failed, err: %v", err)
|
return fmt.Errorf("create successful but start failed, err: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -548,12 +554,16 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
|||||||
|
|
||||||
if inThread {
|
if inThread {
|
||||||
go func() {
|
go func() {
|
||||||
|
defer unlock()
|
||||||
|
defer client.Close()
|
||||||
if err := taskItem.Execute(); err != nil {
|
if err := taskItem.Execute(); err != nil {
|
||||||
global.LOG.Error(err.Error())
|
global.LOG.Error(err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
defer unlock()
|
||||||
|
defer client.Close()
|
||||||
return taskItem.Execute()
|
return taskItem.Execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,21 +584,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
|||||||
data.Image = oldContainer.Config.Image
|
data.Image = oldContainer.Config.Image
|
||||||
if oldContainer.NetworkSettings != nil {
|
if oldContainer.NetworkSettings != nil {
|
||||||
for net, val := range oldContainer.NetworkSettings.Networks {
|
for net, val := range oldContainer.NetworkSettings.Networks {
|
||||||
netItem := dto.ContainerNetwork{
|
data.Networks = append(data.Networks, loadContainerNetworkInfo(net, val))
|
||||||
Network: net,
|
|
||||||
MacAddr: val.MacAddress,
|
|
||||||
}
|
|
||||||
if val.IPAMConfig != nil {
|
|
||||||
if netItem.Network != "bridge" {
|
|
||||||
netItem.Ipv4 = val.IPAMConfig.IPv4Address
|
|
||||||
netItem.Ipv6 = val.IPAMConfig.IPv6Address
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if netItem.Network != "bridge" {
|
|
||||||
netItem.Ipv4 = val.IPAddress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.Networks = append(data.Networks, netItem)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,141 +630,40 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
|||||||
return &data, nil
|
return &data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
|
func loadContainerNetworkInfo(name string, endpoint *network.EndpointSettings) dto.ContainerNetwork {
|
||||||
client, err := docker.NewDockerClient()
|
item := dto.ContainerNetwork{Network: name}
|
||||||
if err != nil {
|
if endpoint == nil {
|
||||||
return err
|
return item
|
||||||
}
|
}
|
||||||
defer client.Close()
|
item.MacAddr = endpoint.MacAddress
|
||||||
ctx := context.Background()
|
item.Links = append([]string(nil), endpoint.Links...)
|
||||||
oldContainer, err := client.ContainerInspect(ctx, req.Name)
|
item.Aliases = append([]string(nil), endpoint.Aliases...)
|
||||||
if err != nil {
|
item.DriverOpts = cloneStringMap(endpoint.DriverOpts)
|
||||||
return err
|
item.GwPriority = endpoint.GwPriority
|
||||||
|
if endpoint.IPAMConfig != nil {
|
||||||
|
item.LinkLocalIPs = append([]string(nil), endpoint.IPAMConfig.LinkLocalIPs...)
|
||||||
}
|
}
|
||||||
|
if name != "bridge" {
|
||||||
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskUpdate, task.TaskScopeContainer, req.TaskID, 1)
|
if endpoint.IPAMConfig != nil {
|
||||||
if err != nil {
|
item.Ipv4 = endpoint.IPAMConfig.IPv4Address
|
||||||
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
item.Ipv6 = endpoint.IPAMConfig.IPv6Address
|
||||||
return err
|
} else {
|
||||||
}
|
item.Ipv4 = endpoint.IPAddress
|
||||||
go func() {
|
item.Ipv6 = endpoint.GlobalIPv6Address
|
||||||
taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error {
|
|
||||||
if !checkImageExist(client, req.Image) || req.ForcePull {
|
|
||||||
if err := pullImages(taskItem, client, req.Image); err != nil {
|
|
||||||
if !req.ForcePull {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf("pull image %s failed, err: %v", req.Image, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, nil)
|
|
||||||
|
|
||||||
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", req.Name), func(t *task.Task) error {
|
|
||||||
err := client.ContainerRemove(ctx, req.Name, container.RemoveOptions{Force: true})
|
|
||||||
taskItem.LogWithStatus(i18n.GetWithName("ContainerRemoveOld", req.Name), err)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
config, hostConf, networkConf, err := loadConfigInfo(false, req, &oldContainer)
|
|
||||||
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerLoadInfo"), err)
|
|
||||||
if err != nil {
|
|
||||||
taskItem.Log(i18n.GetMsgByKey("ContainerRecreate"))
|
|
||||||
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
removeUnsupportedEndpointStaticIPAM(client, networkConf, nil)
|
|
||||||
|
|
||||||
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
|
||||||
if err != nil {
|
|
||||||
taskItem.Log(i18n.GetMsgByKey("ContainerRecreate"))
|
|
||||||
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
|
|
||||||
return fmt.Errorf("update container failed, err: %v", err)
|
|
||||||
}
|
|
||||||
err = client.ContainerStart(ctx, con.ID, container.StartOptions{})
|
|
||||||
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("update successful but start failed, err: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, nil)
|
|
||||||
|
|
||||||
if err := taskItem.Execute(); err != nil {
|
|
||||||
global.LOG.Error(err.Error())
|
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
|
return item
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
|
func cloneStringMap(source map[string]string) map[string]string {
|
||||||
client, err := docker.NewDockerClient()
|
if len(source) == 0 {
|
||||||
if err != nil {
|
return nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
defer client.Close()
|
result := make(map[string]string, len(source))
|
||||||
ctx := context.Background()
|
for key, value := range source {
|
||||||
taskItem, err := task.NewTaskWithOps(req.Image, task.TaskUpgrade, task.TaskScopeImage, req.TaskID, 1)
|
result[key] = value
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
go func() {
|
return result
|
||||||
taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error {
|
|
||||||
taskItem.LogStart(i18n.GetWithName("ContainerImagePull", req.Image))
|
|
||||||
if !checkImageExist(client, req.Image) || req.ForcePull {
|
|
||||||
if err := pullImages(taskItem, client, req.Image); err != nil {
|
|
||||||
if !req.ForcePull {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf("pull image %s failed, err: %v", req.Image, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, nil)
|
|
||||||
for _, item := range req.Names {
|
|
||||||
var oldContainer container.InspectResponse
|
|
||||||
taskItem.AddSubTask(i18n.GetWithName("ContainerLoadInfo", item), func(t *task.Task) error {
|
|
||||||
taskItem.Logf("----------------- %s -----------------", item)
|
|
||||||
oldContainer, err = client.ContainerInspect(ctx, item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, nil)
|
|
||||||
|
|
||||||
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", item), func(t *task.Task) error {
|
|
||||||
config := oldContainer.Config
|
|
||||||
config.Image = req.Image
|
|
||||||
hostConf := oldContainer.HostConfig
|
|
||||||
err := client.ContainerRemove(ctx, item, container.RemoveOptions{Force: true})
|
|
||||||
taskItem.LogWithStatus(i18n.GetWithName("ContainerRemoveOld", item), err)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
con, err := createContainerWithOldNetworks(ctx, client, config, hostConf, oldContainer.NetworkSettings, item)
|
|
||||||
if err != nil {
|
|
||||||
taskItem.Log(i18n.GetMsgByKey("ContainerRecreate"))
|
|
||||||
reCreateAfterUpdate(item, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
|
|
||||||
return fmt.Errorf("upgrade container failed, err: %v", err)
|
|
||||||
}
|
|
||||||
err = client.ContainerStart(ctx, con.ID, container.StartOptions{})
|
|
||||||
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("upgrade successful but start failed, err: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, nil)
|
|
||||||
|
|
||||||
}
|
|
||||||
if err := taskItem.Execute(); err != nil {
|
|
||||||
global.LOG.Error(err.Error())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ContainerRename(req dto.ContainerRename) error {
|
func (u *ContainerService) ContainerRename(req dto.ContainerRename) error {
|
||||||
@@ -778,6 +673,8 @@ func (u *ContainerService) ContainerRename(req dto.ContainerRename) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
unlock := containerOperationLock.lock(req.Name, req.NewName)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
newContainer, _ := client.ContainerInspect(ctx, req.NewName)
|
newContainer, _ := client.ContainerInspect(ctx, req.NewName)
|
||||||
if newContainer.ContainerJSONBase != nil {
|
if newContainer.ContainerJSONBase != nil {
|
||||||
@@ -822,44 +719,48 @@ func (u *ContainerService) ContainerCommit(req dto.ContainerCommit) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
|
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
|
||||||
var err error
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
|
||||||
taskItem, err := task.NewTaskWithOps(strings.Join(req.Names, " "), req.Operation, task.TaskScopeContainer, req.TaskID, 1)
|
taskItem, err := task.NewTaskWithOps(strings.Join(req.Names, " "), req.Operation, task.TaskScopeContainer, req.TaskID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = client.Close()
|
||||||
return fmt.Errorf("new task for container commit failed, err: %v", err)
|
return fmt.Errorf("new task for container commit failed, err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, item := range req.Names {
|
for _, item := range req.Names {
|
||||||
|
item := item
|
||||||
taskItem.AddSubTask(item, func(t *task.Task) error {
|
taskItem.AddSubTask(item, func(t *task.Task) error {
|
||||||
|
unlock := containerOperationLock.lock(item)
|
||||||
|
defer unlock()
|
||||||
|
var operationErr error
|
||||||
switch req.Operation {
|
switch req.Operation {
|
||||||
case constant.ContainerOpStart:
|
case constant.ContainerOpStart:
|
||||||
err = client.ContainerStart(ctx, item, container.StartOptions{})
|
operationErr = client.ContainerStart(ctx, item, container.StartOptions{})
|
||||||
case constant.ContainerOpStop:
|
case constant.ContainerOpStop:
|
||||||
err = client.ContainerStop(ctx, item, container.StopOptions{})
|
operationErr = client.ContainerStop(ctx, item, container.StopOptions{})
|
||||||
case constant.ContainerOpRestart:
|
case constant.ContainerOpRestart:
|
||||||
err = client.ContainerRestart(ctx, item, container.StopOptions{})
|
operationErr = client.ContainerRestart(ctx, item, container.StopOptions{})
|
||||||
case constant.ContainerOpKill:
|
case constant.ContainerOpKill:
|
||||||
err = client.ContainerKill(ctx, item, "SIGKILL")
|
operationErr = client.ContainerKill(ctx, item, "SIGKILL")
|
||||||
case constant.ContainerOpPause:
|
case constant.ContainerOpPause:
|
||||||
err = client.ContainerPause(ctx, item)
|
operationErr = client.ContainerPause(ctx, item)
|
||||||
case constant.ContainerOpUnpause:
|
case constant.ContainerOpUnpause:
|
||||||
err = client.ContainerUnpause(ctx, item)
|
operationErr = client.ContainerUnpause(ctx, item)
|
||||||
case constant.ContainerOpRemove:
|
case constant.ContainerOpRemove:
|
||||||
err = client.ContainerRemove(ctx, item, container.RemoveOptions{RemoveVolumes: true, Force: true})
|
operationErr = client.ContainerRemove(ctx, item, container.RemoveOptions{RemoveVolumes: true, Force: true})
|
||||||
}
|
}
|
||||||
return err
|
return operationErr
|
||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
defer client.Close()
|
||||||
_ = taskItem.Execute()
|
_ = taskItem.Execute()
|
||||||
}()
|
}()
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
|
func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
|
||||||
@@ -868,6 +769,8 @@ func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
unlock := containerOperationLock.lock(req.Name)
|
||||||
|
defer unlock()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
containerItem, err := client.ContainerInspect(ctx, req.Name)
|
containerItem, err := client.ContainerInspect(ctx, req.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1832,7 +1735,7 @@ func loadCpuAndMem(client *client.Client, containerItem string) dto.ContainerLis
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) {
|
func checkPortStats(ports []dto.PortHelper, checkInUse bool) (nat.PortMap, error) {
|
||||||
portMap := make(nat.PortMap)
|
portMap := make(nat.PortMap)
|
||||||
if len(ports) == 0 {
|
if len(ports) == 0 {
|
||||||
return portMap, nil
|
return portMap, nil
|
||||||
@@ -1857,7 +1760,7 @@ func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) {
|
|||||||
portMap[nat.Port(fmt.Sprintf("%d/%s", containerStart+i, port.Protocol))] = []nat.PortBinding{bindItem}
|
portMap[nat.Port(fmt.Sprintf("%d/%s", containerStart+i, port.Protocol))] = []nat.PortBinding{bindItem}
|
||||||
}
|
}
|
||||||
for i := hostStart; i <= hostEnd; i++ {
|
for i := hostStart; i <= hostEnd; i++ {
|
||||||
if common.ScanPortWithIP(port.HostIP, i) {
|
if checkInUse && common.ScanPortWithIP(port.HostIP, i) {
|
||||||
return portMap, buserr.WithDetail("ErrPortInUsed", i, nil)
|
return portMap, buserr.WithDetail("ErrPortInUsed", i, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1868,7 +1771,7 @@ func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) {
|
|||||||
} else {
|
} else {
|
||||||
portItem, _ = strconv.Atoi(port.HostPort)
|
portItem, _ = strconv.Atoi(port.HostPort)
|
||||||
}
|
}
|
||||||
if common.ScanPortWithIP(port.HostIP, portItem) {
|
if checkInUse && common.ScanPortWithIP(port.HostIP, portItem) {
|
||||||
return portMap, buserr.WithDetail("ErrPortInUsed", portItem, nil)
|
return portMap, buserr.WithDetail("ErrPortInUsed", portItem, nil)
|
||||||
}
|
}
|
||||||
bindItem := nat.PortBinding{HostPort: strconv.Itoa(portItem), HostIP: port.HostIP}
|
bindItem := nat.PortBinding{HostPort: strconv.Itoa(portItem), HostIP: port.HostIP}
|
||||||
@@ -1887,7 +1790,7 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *conta
|
|||||||
}
|
}
|
||||||
var networkConf network.NetworkingConfig
|
var networkConf network.NetworkingConfig
|
||||||
|
|
||||||
portMap, err := checkPortStats(req.ExposedPorts)
|
portMap, err := checkPortStats(req.ExposedPorts, isCreate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -1915,15 +1818,21 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *conta
|
|||||||
case "host", "none", "bridge":
|
case "host", "none", "bridge":
|
||||||
hostConf.NetworkMode = container.NetworkMode(item.Network)
|
hostConf.NetworkMode = container.NetworkMode(item.Network)
|
||||||
}
|
}
|
||||||
if item.Ipv4 != "" || item.Ipv6 != "" {
|
endpoint := &network.EndpointSettings{
|
||||||
networkConf.EndpointsConfig[item.Network] = &network.EndpointSettings{
|
Links: append([]string(nil), item.Links...),
|
||||||
IPAMConfig: &network.EndpointIPAMConfig{
|
Aliases: append([]string(nil), item.Aliases...),
|
||||||
IPv4Address: item.Ipv4,
|
DriverOpts: cloneStringMap(item.DriverOpts),
|
||||||
IPv6Address: item.Ipv6,
|
GwPriority: item.GwPriority,
|
||||||
}, MacAddress: item.MacAddr}
|
MacAddress: item.MacAddr,
|
||||||
} else {
|
|
||||||
networkConf.EndpointsConfig[item.Network] = &network.EndpointSettings{}
|
|
||||||
}
|
}
|
||||||
|
if item.Ipv4 != "" || item.Ipv6 != "" || len(item.LinkLocalIPs) != 0 {
|
||||||
|
endpoint.IPAMConfig = &network.EndpointIPAMConfig{
|
||||||
|
IPv4Address: item.Ipv4,
|
||||||
|
IPv6Address: item.Ipv6,
|
||||||
|
LinkLocalIPs: append([]string(nil), item.LinkLocalIPs...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
networkConf.EndpointsConfig[item.Network] = endpoint
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, nil, nil, fmt.Errorf("please set up the network")
|
return nil, nil, nil, fmt.Errorf("please set up the network")
|
||||||
@@ -1970,43 +1879,6 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *conta
|
|||||||
return &config, &hostConf, &networkConf, nil
|
return &config, &hostConf, &networkConf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func reCreateAfterUpdate(name string, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkConf *container.NetworkSettings) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
oldContainer, err := createContainerWithOldNetworks(ctx, client, config, hostConf, networkConf, name)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("recreate after container update failed, err: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := client.ContainerStart(ctx, oldContainer.ID, container.StartOptions{}); err != nil {
|
|
||||||
global.LOG.Errorf("restart after container update failed, err: %v", err)
|
|
||||||
}
|
|
||||||
global.LOG.Info("recreate after container update successful")
|
|
||||||
}
|
|
||||||
|
|
||||||
func createContainerWithOldNetworks(ctx context.Context, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkSettings *container.NetworkSettings, name string) (container.CreateResponse, error) {
|
|
||||||
networkConf, extraNetworks := buildContainerRecoverNetworkConfig(networkSettings, hostConf)
|
|
||||||
removeUnsupportedEndpointStaticIPAM(client, networkConf, extraNetworks)
|
|
||||||
|
|
||||||
created, err := client.ContainerCreate(ctx, config, hostConf, networkConf, nil, name)
|
|
||||||
if err != nil {
|
|
||||||
return created, err
|
|
||||||
}
|
|
||||||
|
|
||||||
extraNames := make([]string, 0, len(extraNetworks))
|
|
||||||
for item := range extraNetworks {
|
|
||||||
extraNames = append(extraNames, item)
|
|
||||||
}
|
|
||||||
sort.Strings(extraNames)
|
|
||||||
for _, item := range extraNames {
|
|
||||||
if err := client.NetworkConnect(ctx, item, created.ID, extraNetworks[item]); err != nil {
|
|
||||||
_ = client.ContainerRemove(ctx, created.ID, container.RemoveOptions{Force: true})
|
|
||||||
return created, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return created, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadVolumeBinds(binds []container.MountPoint) []dto.VolumeHelper {
|
func loadVolumeBinds(binds []container.MountPoint) []dto.VolumeHelper {
|
||||||
var datas []dto.VolumeHelper
|
var datas []dto.VolumeHelper
|
||||||
for _, bind := range binds {
|
for _, bind := range binds {
|
||||||
|
|||||||
@@ -0,0 +1,657 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/mount"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskUpdate, task.TaskScopeContainer, req.TaskID, 1)
|
||||||
|
if err != nil {
|
||||||
|
_ = client.Close()
|
||||||
|
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
defer client.Close()
|
||||||
|
taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error {
|
||||||
|
if !checkImageExist(client, req.Image) || req.ForcePull {
|
||||||
|
if err := pullImages(taskItem, client, req.Image); err != nil {
|
||||||
|
if !req.ForcePull {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("pull image %s failed, err: %v", req.Image, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
taskItem.AddSubTaskWithOps(task.GetTaskName(req.Name, task.TaskUpdate, task.TaskScopeContainer), func(t *task.Task) error {
|
||||||
|
t.LogStart(i18n.GetWithName("ContainerAcquireLock", req.Name))
|
||||||
|
unlock := containerOperationLock.lock(req.Name)
|
||||||
|
defer unlock()
|
||||||
|
t.LogWithStatus(i18n.GetWithName("ContainerAcquireLock", req.Name), nil)
|
||||||
|
|
||||||
|
oldContainer, err := client.ContainerInspect(ctx, req.Name)
|
||||||
|
if err != nil {
|
||||||
|
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerLoadInfo"), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
config, hostConf, networkConf, err := loadConfigInfo(false, req, &oldContainer)
|
||||||
|
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerLoadInfo"), err)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
normalizeContainerEndpointSettings(ctx, client, networkConf, nil)
|
||||||
|
|
||||||
|
cleanupErr, err := switchContainer(ctx, client, req.Name, oldContainer, func() (container.CreateResponse, error) {
|
||||||
|
return createContainerWithDynamicIPFallback(func() (container.CreateResponse, error) {
|
||||||
|
return client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||||
|
}, networkConf.EndpointsConfig, oldContainer.NetworkSettings)
|
||||||
|
}, newContainerSwitchTaskLogger(t))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("update container failed, err: %v", err)
|
||||||
|
}
|
||||||
|
if cleanupErr != nil {
|
||||||
|
taskItem.Log(i18n.GetWithNameAndErr("ContainerCleanupWarning", containerSwitchBackupName(oldContainer.ID), cleanupErr))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, nil, 0, 0)
|
||||||
|
|
||||||
|
if err := taskItem.Execute(); err != nil {
|
||||||
|
global.LOG.Error(err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
taskItem, err := task.NewTaskWithOps(req.Image, task.TaskUpgrade, task.TaskScopeImage, req.TaskID, 1)
|
||||||
|
if err != nil {
|
||||||
|
_ = client.Close()
|
||||||
|
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
defer client.Close()
|
||||||
|
taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error {
|
||||||
|
taskItem.LogStart(i18n.GetWithName("ContainerImagePull", req.Image))
|
||||||
|
if !checkImageExist(client, req.Image) || req.ForcePull {
|
||||||
|
if err := pullImages(taskItem, client, req.Image); err != nil {
|
||||||
|
if !req.ForcePull {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("pull image %s failed, err: %v", req.Image, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, nil)
|
||||||
|
var upgradeErrors []error
|
||||||
|
for _, item := range req.Names {
|
||||||
|
item := item
|
||||||
|
taskItem.AddSubTaskWithIgnoreErr(i18n.GetWithName("ContainerUpgradeItem", item), func(t *task.Task) error {
|
||||||
|
t.Logf("----------------- %s -----------------", item)
|
||||||
|
t.LogStart(i18n.GetWithName("ContainerAcquireLock", item))
|
||||||
|
unlock := containerOperationLock.lock(item)
|
||||||
|
defer unlock()
|
||||||
|
t.LogWithStatus(i18n.GetWithName("ContainerAcquireLock", item), nil)
|
||||||
|
|
||||||
|
oldContainer, inspectErr := client.ContainerInspect(ctx, item)
|
||||||
|
t.LogWithStatus(i18n.GetWithName("ContainerLoadInfo", item), inspectErr)
|
||||||
|
if inspectErr != nil {
|
||||||
|
err := fmt.Errorf("reload container %s failed: %w", item, inspectErr)
|
||||||
|
upgradeErrors = append(upgradeErrors, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
config := cloneContainerConfig(oldContainer.Config)
|
||||||
|
config.Image = req.Image
|
||||||
|
hostConf := cloneContainerHostConfig(oldContainer.HostConfig)
|
||||||
|
preserveContainerVolumeMounts(hostConf, oldContainer.Mounts)
|
||||||
|
cleanupErr, err := switchContainer(ctx, client, item, oldContainer, func() (container.CreateResponse, error) {
|
||||||
|
return createContainerWithOldNetworks(ctx, client, config, hostConf, oldContainer.NetworkSettings, item)
|
||||||
|
}, newContainerSwitchTaskLogger(t))
|
||||||
|
if err != nil {
|
||||||
|
upgradeErr := fmt.Errorf("upgrade container %s failed: %w", item, err)
|
||||||
|
upgradeErrors = append(upgradeErrors, upgradeErr)
|
||||||
|
return upgradeErr
|
||||||
|
}
|
||||||
|
if cleanupErr != nil {
|
||||||
|
t.Log(i18n.GetWithNameAndErr("ContainerCleanupWarning", containerSwitchBackupName(oldContainer.ID), cleanupErr))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
taskItem.AddSubTask(i18n.GetMsgByKey("ContainerUpgradeSummary"), func(t *task.Task) error {
|
||||||
|
return errors.Join(upgradeErrors...)
|
||||||
|
}, nil)
|
||||||
|
if err := taskItem.Execute(); err != nil {
|
||||||
|
global.LOG.Error(err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type containerSwitchClient interface {
|
||||||
|
ContainerStop(context.Context, string, container.StopOptions) error
|
||||||
|
ContainerRename(context.Context, string, string) error
|
||||||
|
ContainerStart(context.Context, string, container.StartOptions) error
|
||||||
|
ContainerRemove(context.Context, string, container.RemoveOptions) error
|
||||||
|
ContainerInspect(context.Context, string) (container.InspectResponse, error)
|
||||||
|
NetworkConnect(context.Context, string, string, *network.EndpointSettings) error
|
||||||
|
NetworkDisconnect(context.Context, string, string, bool) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type containerOperationMutex struct {
|
||||||
|
mutex sync.Mutex
|
||||||
|
locks map[string]*containerOperationLockEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
var containerOperationLock containerOperationMutex
|
||||||
|
|
||||||
|
type containerOperationLockEntry struct {
|
||||||
|
mutex sync.Mutex
|
||||||
|
references int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *containerOperationMutex) lock(names ...string) func() {
|
||||||
|
nameSet := make(map[string]struct{}, len(names))
|
||||||
|
for _, name := range names {
|
||||||
|
if name != "" {
|
||||||
|
nameSet[name] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orderedNames := make([]string, 0, len(nameSet))
|
||||||
|
for name := range nameSet {
|
||||||
|
orderedNames = append(orderedNames, name)
|
||||||
|
}
|
||||||
|
sort.Strings(orderedNames)
|
||||||
|
if len(orderedNames) == 0 {
|
||||||
|
return func() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.mutex.Lock()
|
||||||
|
if l.locks == nil {
|
||||||
|
l.locks = make(map[string]*containerOperationLockEntry)
|
||||||
|
}
|
||||||
|
entries := make([]*containerOperationLockEntry, 0, len(orderedNames))
|
||||||
|
for _, name := range orderedNames {
|
||||||
|
entry := l.locks[name]
|
||||||
|
if entry == nil {
|
||||||
|
entry = &containerOperationLockEntry{}
|
||||||
|
l.locks[name] = entry
|
||||||
|
}
|
||||||
|
entry.references++
|
||||||
|
entries = append(entries, entry)
|
||||||
|
}
|
||||||
|
l.mutex.Unlock()
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
entry.mutex.Lock()
|
||||||
|
}
|
||||||
|
return func() {
|
||||||
|
for index := len(entries) - 1; index >= 0; index-- {
|
||||||
|
entries[index].mutex.Unlock()
|
||||||
|
}
|
||||||
|
l.mutex.Lock()
|
||||||
|
defer l.mutex.Unlock()
|
||||||
|
for index, name := range orderedNames {
|
||||||
|
entry := entries[index]
|
||||||
|
entry.references--
|
||||||
|
if entry.references == 0 {
|
||||||
|
delete(l.locks, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type containerNetworkAttachment struct {
|
||||||
|
name string
|
||||||
|
endpoint *network.EndpointSettings
|
||||||
|
isDynamic bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type containerSwitchLogFunc func(messageKey, containerName string, err error)
|
||||||
|
|
||||||
|
func newContainerSwitchTaskLogger(t *task.Task) containerSwitchLogFunc {
|
||||||
|
return func(messageKey, containerName string, err error) {
|
||||||
|
t.LogWithStatus(i18n.GetWithName(messageKey, containerName), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logContainerSwitchStep(logger containerSwitchLogFunc, messageKey, containerName string, err error) {
|
||||||
|
if logger != nil {
|
||||||
|
logger(messageKey, containerName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// switchContainer keeps the stopped original container as the rollback target until the replacement starts.
|
||||||
|
func switchContainer(
|
||||||
|
ctx context.Context,
|
||||||
|
cli containerSwitchClient,
|
||||||
|
name string,
|
||||||
|
oldContainer container.InspectResponse,
|
||||||
|
createNew func() (container.CreateResponse, error),
|
||||||
|
logger containerSwitchLogFunc,
|
||||||
|
) (cleanupErr error, err error) {
|
||||||
|
if oldContainer.ID == "" {
|
||||||
|
return nil, fmt.Errorf("original container ID is empty")
|
||||||
|
}
|
||||||
|
wasRunning := oldContainer.State != nil && oldContainer.State.Running
|
||||||
|
if wasRunning && oldContainer.HostConfig != nil && oldContainer.HostConfig.AutoRemove {
|
||||||
|
return nil, fmt.Errorf("cannot safely replace container %s with auto-remove enabled", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wasRunning {
|
||||||
|
if err := cli.ContainerStop(ctx, oldContainer.ID, container.StopOptions{}); err != nil {
|
||||||
|
logContainerSwitchStep(logger, "ContainerStopOld", name, err)
|
||||||
|
current, inspectErr := cli.ContainerInspect(ctx, oldContainer.ID)
|
||||||
|
if inspectErr == nil && current.State != nil && !current.State.Running {
|
||||||
|
restartErr := restartOriginalContainer(ctx, cli, oldContainer.ID)
|
||||||
|
logContainerSwitchStep(logger, "ContainerRollbackRestartOld", name, restartErr)
|
||||||
|
return nil, errors.Join(fmt.Errorf("stop original container failed: %w", err), restartErr)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("stop original container failed: %w", err)
|
||||||
|
}
|
||||||
|
logContainerSwitchStep(logger, "ContainerStopOld", name, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
backupName := containerSwitchBackupName(oldContainer.ID)
|
||||||
|
if err := cli.ContainerRename(ctx, oldContainer.ID, backupName); err != nil {
|
||||||
|
logContainerSwitchStep(logger, "ContainerRenameOld", name, err)
|
||||||
|
if wasRunning {
|
||||||
|
restartErr := restartOriginalContainer(ctx, cli, oldContainer.ID)
|
||||||
|
logContainerSwitchStep(logger, "ContainerRollbackRestartOld", name, restartErr)
|
||||||
|
return nil, errors.Join(fmt.Errorf("rename original container failed: %w", err), restartErr)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("rename original container failed: %w", err)
|
||||||
|
}
|
||||||
|
logContainerSwitchStep(logger, "ContainerRenameOld", name, nil)
|
||||||
|
disconnectedNetworks, disconnectErr := disconnectOriginalContainerNetworks(ctx, cli, oldContainer)
|
||||||
|
logContainerSwitchStep(logger, "ContainerDisconnectOld", backupName, disconnectErr)
|
||||||
|
if disconnectErr != nil {
|
||||||
|
rollbackErr := restoreOriginalContainer(ctx, cli, oldContainer.ID, name, wasRunning, "", disconnectedNetworks, logger)
|
||||||
|
return nil, errors.Join(disconnectErr, rollbackErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
created, createErr := createNew()
|
||||||
|
logContainerSwitchStep(logger, "ContainerCreateReplacement", name, createErr)
|
||||||
|
if createErr != nil {
|
||||||
|
rollbackErr := restoreOriginalContainer(ctx, cli, oldContainer.ID, name, wasRunning, created.ID, disconnectedNetworks, logger)
|
||||||
|
return nil, errors.Join(createErr, rollbackErr)
|
||||||
|
}
|
||||||
|
if err := cli.ContainerStart(ctx, created.ID, container.StartOptions{}); err != nil {
|
||||||
|
logContainerSwitchStep(logger, "ContainerStartReplacement", name, err)
|
||||||
|
rollbackErr := restoreOriginalContainer(ctx, cli, oldContainer.ID, name, wasRunning, created.ID, disconnectedNetworks, logger)
|
||||||
|
return nil, errors.Join(fmt.Errorf("start new container failed: %w", err), rollbackErr)
|
||||||
|
}
|
||||||
|
logContainerSwitchStep(logger, "ContainerStartReplacement", name, nil)
|
||||||
|
if wasRunning {
|
||||||
|
if err := waitContainerReady(ctx, cli, created.ID); err != nil {
|
||||||
|
logContainerSwitchStep(logger, "ContainerWaitReplacement", name, err)
|
||||||
|
rollbackErr := restoreOriginalContainer(ctx, cli, oldContainer.ID, name, wasRunning, created.ID, disconnectedNetworks, logger)
|
||||||
|
return nil, errors.Join(fmt.Errorf("new container readiness check failed: %w", err), rollbackErr)
|
||||||
|
}
|
||||||
|
logContainerSwitchStep(logger, "ContainerWaitReplacement", name, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupErr = cli.ContainerRemove(ctx, oldContainer.ID, container.RemoveOptions{Force: true, RemoveVolumes: false})
|
||||||
|
logContainerSwitchStep(logger, "ContainerRemoveOld", backupName, cleanupErr)
|
||||||
|
return cleanupErr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
containerStartStabilization = 10 * time.Second
|
||||||
|
containerStartPollInterval = time.Second
|
||||||
|
containerHealthCheckMinWait = 30 * time.Second
|
||||||
|
containerHealthCheckMaxWait = 10 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
func waitContainerReady(ctx context.Context, cli containerSwitchClient, containerID string) error {
|
||||||
|
info, err := cli.ContainerInspect(ctx, containerID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := checkContainerRunningState(info); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.State.Health == nil {
|
||||||
|
return waitContainerStable(ctx, cli, containerID, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := containerHealthCheckTimeout(info.Config)
|
||||||
|
deadline := time.NewTimer(timeout)
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
defer deadline.Stop()
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
if info.State.Restarting || info.RestartCount != 0 {
|
||||||
|
return fmt.Errorf("container restarted %d times during startup", info.RestartCount)
|
||||||
|
}
|
||||||
|
if info.State.Health == nil {
|
||||||
|
return fmt.Errorf("container health status is unavailable")
|
||||||
|
}
|
||||||
|
switch info.State.Health.Status {
|
||||||
|
case container.Healthy:
|
||||||
|
return nil
|
||||||
|
case container.Unhealthy:
|
||||||
|
return fmt.Errorf("container health status is unhealthy")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-deadline.C:
|
||||||
|
return fmt.Errorf("container health check timed out after %s", timeout)
|
||||||
|
case <-ticker.C:
|
||||||
|
info, err = cli.ContainerInspect(ctx, containerID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := checkContainerRunningState(info); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitContainerStable(ctx context.Context, cli containerSwitchClient, containerID string, initial container.InspectResponse) error {
|
||||||
|
startedAt := initial.State.StartedAt
|
||||||
|
if err := checkContainerStableState(initial, startedAt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
deadline := time.NewTimer(containerStartStabilization)
|
||||||
|
ticker := time.NewTicker(containerStartPollInterval)
|
||||||
|
defer deadline.Stop()
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-deadline.C:
|
||||||
|
info, err := cli.ContainerInspect(ctx, containerID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return checkContainerStableState(info, startedAt)
|
||||||
|
case <-ticker.C:
|
||||||
|
info, err := cli.ContainerInspect(ctx, containerID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := checkContainerStableState(info, startedAt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkContainerStableState(info container.InspectResponse, startedAt string) error {
|
||||||
|
if err := checkContainerRunningState(info); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.State.Restarting || info.RestartCount != 0 {
|
||||||
|
return fmt.Errorf("container restarted %d times during startup", info.RestartCount)
|
||||||
|
}
|
||||||
|
if startedAt != "" && info.State.StartedAt != startedAt {
|
||||||
|
return fmt.Errorf("container start time changed during startup")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkContainerRunningState(info container.InspectResponse) error {
|
||||||
|
if info.State == nil {
|
||||||
|
return fmt.Errorf("container state is unavailable")
|
||||||
|
}
|
||||||
|
if !info.State.Running {
|
||||||
|
return fmt.Errorf("container exited with code %d: %s", info.State.ExitCode, info.State.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerHealthCheckTimeout(config *container.Config) time.Duration {
|
||||||
|
if config == nil || config.Healthcheck == nil {
|
||||||
|
return containerHealthCheckMinWait
|
||||||
|
}
|
||||||
|
health := config.Healthcheck
|
||||||
|
interval := health.Interval
|
||||||
|
if interval <= 0 {
|
||||||
|
interval = 30 * time.Second
|
||||||
|
}
|
||||||
|
checkTimeout := health.Timeout
|
||||||
|
if checkTimeout <= 0 {
|
||||||
|
checkTimeout = 30 * time.Second
|
||||||
|
}
|
||||||
|
retries := health.Retries
|
||||||
|
if retries <= 0 {
|
||||||
|
retries = 3
|
||||||
|
}
|
||||||
|
timeout := health.StartPeriod + time.Duration(retries)*(interval+checkTimeout)
|
||||||
|
if timeout < containerHealthCheckMinWait {
|
||||||
|
return containerHealthCheckMinWait
|
||||||
|
}
|
||||||
|
if timeout > containerHealthCheckMaxWait {
|
||||||
|
return containerHealthCheckMaxWait
|
||||||
|
}
|
||||||
|
return timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func preserveContainerVolumeMounts(hostConfig *container.HostConfig, oldMounts []container.MountPoint) {
|
||||||
|
if hostConfig == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, oldMount := range oldMounts {
|
||||||
|
if oldMount.Type != mount.TypeVolume || oldMount.Name == "" || oldMount.Destination == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
preserved := false
|
||||||
|
for index := range hostConfig.Mounts {
|
||||||
|
if hostConfig.Mounts[index].Target == oldMount.Destination {
|
||||||
|
hostConfig.Mounts[index].Type = mount.TypeVolume
|
||||||
|
hostConfig.Mounts[index].Source = oldMount.Name
|
||||||
|
preserved = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for index, raw := range hostConfig.Binds {
|
||||||
|
destination, mode := containerBindDestinationAndMode(raw)
|
||||||
|
if destination != oldMount.Destination {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hostConfig.Binds[index] = oldMount.Name + ":" + oldMount.Destination
|
||||||
|
if mode != "" {
|
||||||
|
hostConfig.Binds[index] += ":" + mode
|
||||||
|
}
|
||||||
|
preserved = true
|
||||||
|
}
|
||||||
|
if !preserved {
|
||||||
|
hostConfig.Mounts = append(hostConfig.Mounts, mount.Mount{
|
||||||
|
Type: mount.TypeVolume,
|
||||||
|
Source: oldMount.Name,
|
||||||
|
Target: oldMount.Destination,
|
||||||
|
ReadOnly: !oldMount.RW,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerBindDestinationAndMode(raw string) (string, string) {
|
||||||
|
parts := strings.SplitN(raw, ":", 3)
|
||||||
|
switch len(parts) {
|
||||||
|
case 1:
|
||||||
|
return parts[0], ""
|
||||||
|
case 2:
|
||||||
|
return parts[1], ""
|
||||||
|
default:
|
||||||
|
return parts[1], parts[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerSwitchBackupName(containerID string) string {
|
||||||
|
if len(containerID) > 12 {
|
||||||
|
containerID = containerID[:12]
|
||||||
|
}
|
||||||
|
return "1panel-backup-" + containerID
|
||||||
|
}
|
||||||
|
|
||||||
|
func restartOriginalContainer(ctx context.Context, cli containerSwitchClient, containerID string) error {
|
||||||
|
if err := cli.ContainerStart(ctx, containerID, container.StartOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("restart original container failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func disconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitchClient, oldContainer container.InspectResponse) ([]containerNetworkAttachment, error) {
|
||||||
|
primary, extras := buildContainerRecoverNetworkConfig(oldContainer.NetworkSettings, oldContainer.HostConfig)
|
||||||
|
endpoints := make(map[string]*network.EndpointSettings, len(extras)+1)
|
||||||
|
if primary != nil {
|
||||||
|
for name, endpoint := range primary.EndpointsConfig {
|
||||||
|
if name != "bridge" && endpoint != nil && endpoint.IPAMConfig != nil {
|
||||||
|
endpoints[name] = endpoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name, endpoint := range extras {
|
||||||
|
if name != "bridge" && endpoint != nil && endpoint.IPAMConfig != nil {
|
||||||
|
endpoints[name] = endpoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
names := make([]string, 0, len(endpoints))
|
||||||
|
for name := range endpoints {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
disconnected := make([]containerNetworkAttachment, 0, len(names))
|
||||||
|
for _, name := range names {
|
||||||
|
if err := cli.NetworkDisconnect(ctx, name, oldContainer.ID, true); err != nil {
|
||||||
|
return disconnected, fmt.Errorf("disconnect original container from network %s failed: %w", name, err)
|
||||||
|
}
|
||||||
|
disconnected = append(disconnected, containerNetworkAttachment{
|
||||||
|
name: name,
|
||||||
|
endpoint: endpoints[name],
|
||||||
|
isDynamic: isDynamicContainerNetwork(oldContainer.NetworkSettings, name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return disconnected, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func reconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitchClient, containerID string, attachments []containerNetworkAttachment) error {
|
||||||
|
var reconnectErr error
|
||||||
|
for _, attachment := range attachments {
|
||||||
|
err := cli.NetworkConnect(ctx, attachment.name, containerID, attachment.endpoint)
|
||||||
|
if err != nil && attachment.isDynamic && strings.Contains(err.Error(), unsupportedUserSpecifiedIPAddress) {
|
||||||
|
attachment.endpoint.IPAMConfig = nil
|
||||||
|
err = cli.NetworkConnect(ctx, attachment.name, containerID, attachment.endpoint)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
reconnectErr = errors.Join(reconnectErr, fmt.Errorf("reconnect original container to network %s failed: %w", attachment.name, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reconnectErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreOriginalContainer(ctx context.Context, cli containerSwitchClient, oldContainerID, originalName string, wasRunning bool, newContainer string, disconnectedNetworks []containerNetworkAttachment, logger containerSwitchLogFunc) error {
|
||||||
|
var rollbackErr error
|
||||||
|
backupName := containerSwitchBackupName(oldContainerID)
|
||||||
|
if newContainer != "" {
|
||||||
|
removeErr := cli.ContainerRemove(ctx, newContainer, container.RemoveOptions{Force: true, RemoveVolumes: true})
|
||||||
|
if client.IsErrNotFound(removeErr) {
|
||||||
|
removeErr = nil
|
||||||
|
}
|
||||||
|
logContainerSwitchStep(logger, "ContainerRollbackRemoveReplacement", originalName, removeErr)
|
||||||
|
if removeErr != nil {
|
||||||
|
rollbackErr = errors.Join(rollbackErr, fmt.Errorf("remove failed replacement container failed: %w", removeErr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renameErr := cli.ContainerRename(ctx, oldContainerID, originalName)
|
||||||
|
logContainerSwitchStep(logger, "ContainerRollbackRenameOld", backupName, renameErr)
|
||||||
|
if renameErr != nil {
|
||||||
|
rollbackErr = errors.Join(rollbackErr, fmt.Errorf("restore original container name failed: %w", renameErr))
|
||||||
|
}
|
||||||
|
currentName := originalName
|
||||||
|
if renameErr != nil {
|
||||||
|
currentName = backupName
|
||||||
|
}
|
||||||
|
reconnectErr := reconnectOriginalContainerNetworks(ctx, cli, oldContainerID, disconnectedNetworks)
|
||||||
|
logContainerSwitchStep(logger, "ContainerRollbackReconnectOld", currentName, reconnectErr)
|
||||||
|
rollbackErr = errors.Join(rollbackErr, reconnectErr)
|
||||||
|
if wasRunning {
|
||||||
|
restartErr := restartOriginalContainer(ctx, cli, oldContainerID)
|
||||||
|
logContainerSwitchStep(logger, "ContainerRollbackRestartOld", currentName, restartErr)
|
||||||
|
rollbackErr = errors.Join(rollbackErr, restartErr)
|
||||||
|
}
|
||||||
|
return rollbackErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func createContainerWithOldNetworks(ctx context.Context, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkSettings *container.NetworkSettings, name string) (container.CreateResponse, error) {
|
||||||
|
networkConf, extraNetworks := buildContainerRecoverNetworkConfig(networkSettings, hostConf)
|
||||||
|
normalizeContainerEndpointSettings(ctx, client, networkConf, extraNetworks)
|
||||||
|
var primaryEndpoints map[string]*network.EndpointSettings
|
||||||
|
if networkConf != nil {
|
||||||
|
primaryEndpoints = networkConf.EndpointsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
created, err := createContainerWithDynamicIPFallback(func() (container.CreateResponse, error) {
|
||||||
|
return client.ContainerCreate(ctx, config, hostConf, networkConf, nil, name)
|
||||||
|
}, primaryEndpoints, networkSettings)
|
||||||
|
if err != nil {
|
||||||
|
return created, err
|
||||||
|
}
|
||||||
|
|
||||||
|
extraNames := make([]string, 0, len(extraNetworks))
|
||||||
|
for item := range extraNetworks {
|
||||||
|
extraNames = append(extraNames, item)
|
||||||
|
}
|
||||||
|
sort.Strings(extraNames)
|
||||||
|
for _, item := range extraNames {
|
||||||
|
err := client.NetworkConnect(ctx, item, created.ID, extraNetworks[item])
|
||||||
|
if clearUnsupportedDynamicEndpointIPAM(err, map[string]*network.EndpointSettings{item: extraNetworks[item]}, networkSettings) {
|
||||||
|
err = client.NetworkConnect(ctx, item, created.ID, extraNetworks[item])
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
_ = client.ContainerRemove(ctx, created.ID, container.RemoveOptions{Force: true})
|
||||||
|
return created, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return created, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createContainerWithDynamicIPFallback(
|
||||||
|
create func() (container.CreateResponse, error),
|
||||||
|
endpoints map[string]*network.EndpointSettings,
|
||||||
|
networkSettings *container.NetworkSettings,
|
||||||
|
) (container.CreateResponse, error) {
|
||||||
|
for {
|
||||||
|
created, err := create()
|
||||||
|
if err == nil || created.ID != "" || !clearUnsupportedDynamicEndpointIPAM(err, endpoints, networkSettings) {
|
||||||
|
return created, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -644,7 +644,7 @@ func (u *CronjobService) HandleStop(id uint) error {
|
|||||||
if len(record.TaskID) == 0 {
|
if len(record.TaskID) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if cancel, ok := global.TaskCtxMap[record.TaskID]; ok {
|
if cancel, ok := global.LoadTaskCancel(record.TaskID); ok {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, t
|
|||||||
|
|
||||||
src := path.Join(backupDir, record.FileName)
|
src := path.Join(backupDir, record.FileName)
|
||||||
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
|
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
|
||||||
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil {
|
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes, true); err != nil {
|
||||||
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
|
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
|
||||||
retry++
|
retry++
|
||||||
return err
|
return err
|
||||||
@@ -119,7 +119,7 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
|
|||||||
|
|
||||||
src := path.Join(backupDir, record.FileName)
|
src := path.Join(backupDir, record.FileName)
|
||||||
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
|
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
|
||||||
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil {
|
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes, true); err != nil {
|
||||||
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
|
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
|
||||||
retry++
|
retry++
|
||||||
return err
|
return err
|
||||||
@@ -216,7 +216,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
|
|||||||
|
|
||||||
src := path.Join(backupDir, record.FileName)
|
src := path.Join(backupDir, record.FileName)
|
||||||
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
|
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
|
||||||
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil {
|
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes, true); err != nil {
|
||||||
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
|
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
|
||||||
retry++
|
retry++
|
||||||
return err
|
return err
|
||||||
@@ -274,7 +274,7 @@ func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.T
|
|||||||
|
|
||||||
src := path.Join(backupDir, fileName)
|
src := path.Join(backupDir, fileName)
|
||||||
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
|
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
|
||||||
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil {
|
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
record.FileDir = path.Dir(dst)
|
record.FileDir = path.Dir(dst)
|
||||||
@@ -310,7 +310,7 @@ func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.T
|
|||||||
|
|
||||||
src := path.Join(path.Dir(backupDir), fileName)
|
src := path.Join(path.Dir(backupDir), fileName)
|
||||||
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
|
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
|
||||||
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil {
|
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
record.FileDir = path.Dir(dst)
|
record.FileDir = path.Dir(dst)
|
||||||
|
|||||||
@@ -229,13 +229,12 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
|
|||||||
|
|
||||||
if ioOption == "all" {
|
if ioOption == "all" {
|
||||||
diskInfo, _ := disk.IOCounters()
|
diskInfo, _ := disk.IOCounters()
|
||||||
for _, state := range diskInfo {
|
state := sumDiskIOCounters(diskInfo)
|
||||||
currentInfo.IOReadBytes += state.ReadBytes
|
currentInfo.IOReadBytes = state.ReadBytes
|
||||||
currentInfo.IOWriteBytes += state.WriteBytes
|
currentInfo.IOWriteBytes = state.WriteBytes
|
||||||
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
|
currentInfo.IOCount = state.ReadCount + state.WriteCount
|
||||||
currentInfo.IOReadTime += state.ReadTime
|
currentInfo.IOReadTime = state.ReadTime
|
||||||
currentInfo.IOWriteTime += state.WriteTime
|
currentInfo.IOWriteTime = state.WriteTime
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
diskInfo, _ := disk.IOCounters(ioOption)
|
diskInfo, _ := disk.IOCounters(ioOption)
|
||||||
for _, state := range diskInfo {
|
for _, state := range diskInfo {
|
||||||
|
|||||||
+984
-129
File diff suppressed because it is too large
Load Diff
@@ -19,10 +19,12 @@ var (
|
|||||||
agentAccountRepo = repo.NewIAgentAccountRepo()
|
agentAccountRepo = repo.NewIAgentAccountRepo()
|
||||||
agentAccountModelRepo = repo.NewIAgentAccountModelRepo()
|
agentAccountModelRepo = repo.NewIAgentAccountModelRepo()
|
||||||
|
|
||||||
mysqlRepo = repo.NewIMysqlRepo()
|
mysqlRepo = repo.NewIMysqlRepo()
|
||||||
postgresqlRepo = repo.NewIPostgresqlRepo()
|
postgresqlRepo = repo.NewIPostgresqlRepo()
|
||||||
mongodbRepo = repo.NewIMongodbRepo()
|
mongodbRepo = repo.NewIMongodbRepo()
|
||||||
databaseRepo = repo.NewIDatabaseRepo()
|
databaseRepo = repo.NewIDatabaseRepo()
|
||||||
|
databaseUserRepo = repo.NewIDatabaseUserRepo()
|
||||||
|
databaseUserGrantRepo = repo.NewIDatabaseUserGrantRepo()
|
||||||
|
|
||||||
imageRepoRepo = repo.NewIImageRepoRepo()
|
imageRepoRepo = repo.NewIImageRepoRepo()
|
||||||
composeRepo = repo.NewIComposeTemplateRepo()
|
composeRepo = repo.NewIComposeTemplateRepo()
|
||||||
|
|||||||
@@ -485,7 +485,7 @@ func preflightDecompressTool(decompressType files.CompressType) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileService) StopCompress(taskID string) error {
|
func (f *FileService) StopCompress(taskID string) error {
|
||||||
if cancel, ok := global.TaskCtxMap[taskID]; ok {
|
if cancel, ok := global.LoadTaskCancel(taskID); ok {
|
||||||
cancel()
|
cancel()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -493,7 +493,7 @@ func (f *FileService) StopCompress(taskID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileService) StopDeCompress(taskID string) error {
|
func (f *FileService) StopDeCompress(taskID string) error {
|
||||||
if cancel, ok := global.TaskCtxMap[taskID]; ok {
|
if cancel, ok := global.LoadTaskCancel(taskID); ok {
|
||||||
cancel()
|
cancel()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -621,9 +621,16 @@ func copyDecompressEntry(ctx context.Context, srcPath, dstPath string) (retErr e
|
|||||||
return os.Chtimes(dstPath, info.ModTime(), info.ModTime())
|
return os.Chtimes(dstPath, info.ModTime(), info.ModTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.RemoveAll(dstPath); err != nil {
|
dstInfo, err := os.Lstat(dstPath)
|
||||||
|
keepExistingFile := err == nil && dstInfo.Mode().IsRegular()
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !keepExistingFile {
|
||||||
|
if err := os.RemoveAll(dstPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := os.MkdirAll(filepath.Dir(dstPath), constant.DirPerm); err != nil {
|
if err := os.MkdirAll(filepath.Dir(dstPath), constant.DirPerm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -647,8 +654,10 @@ func copyDecompressEntry(ctx context.Context, srcPath, dstPath string) (retErr e
|
|||||||
if _, err := io.Copy(dstFile, srcFile); err != nil {
|
if _, err := io.Copy(dstFile, srcFile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := applyDecompressOwnership(srcPath, dstPath); err != nil {
|
if !keepExistingFile {
|
||||||
return err
|
if err := applyDecompressOwnership(srcPath, dstPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return os.Chtimes(dstPath, info.ModTime(), info.ModTime())
|
return os.Chtimes(dstPath, info.ModTime(), info.ModTime())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,6 +166,12 @@ func (u *FirewallService) OperateFirewall(req dto.FirewallOperation) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
fail2BanState := newFirewallFail2BanState()
|
||||||
|
if client.Name() == "firewalld" && req.Operation == "stop" {
|
||||||
|
if err := fail2BanState.rememberBeforeFirewallStop(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
needRestartDocker := false
|
needRestartDocker := false
|
||||||
switch req.Operation {
|
switch req.Operation {
|
||||||
case "start":
|
case "start":
|
||||||
@@ -210,6 +216,11 @@ func (u *FirewallService) OperateFirewall(req dto.FirewallOperation) error {
|
|||||||
return fmt.Errorf("failed to restart Docker: %v", err)
|
return fmt.Errorf("failed to restart Docker: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if client.Name() == "firewalld" && req.Operation == "start" {
|
||||||
|
if err := fail2BanState.restoreAfterFirewallStart(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
||||||
|
)
|
||||||
|
|
||||||
|
const fail2BanRestoreWithFirewallMarker = "/run/1panel_fail2ban_restore_with_firewall"
|
||||||
|
|
||||||
|
type firewallFail2BanState struct {
|
||||||
|
markerPath string
|
||||||
|
isExist func(string) bool
|
||||||
|
isActive func(string) bool
|
||||||
|
start func(string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFirewallFail2BanState() *firewallFail2BanState {
|
||||||
|
return &firewallFail2BanState{
|
||||||
|
markerPath: fail2BanRestoreWithFirewallMarker,
|
||||||
|
isExist: func(serviceName string) bool {
|
||||||
|
exists, err := controller.CheckExist(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Warnf("check %s installation before stopping the firewall failed: %v", serviceName, err)
|
||||||
|
}
|
||||||
|
return exists
|
||||||
|
},
|
||||||
|
isActive: func(serviceName string) bool {
|
||||||
|
active, err := controller.CheckActive(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Warnf("check %s status before stopping the firewall failed: %v", serviceName, err)
|
||||||
|
}
|
||||||
|
return active
|
||||||
|
},
|
||||||
|
start: controller.HandleStart,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *firewallFail2BanState) rememberBeforeFirewallStop() error {
|
||||||
|
if !s.isExist("fail2ban.service") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !s.isActive("fail2ban.service") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return s.markForRestore()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *firewallFail2BanState) markForRestore() error {
|
||||||
|
if err := os.WriteFile(s.markerPath, nil, 0600); err != nil {
|
||||||
|
return fmt.Errorf("mark Fail2Ban for restoration with the firewall: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *firewallFail2BanState) restoreAfterFirewallStart() error {
|
||||||
|
_, err := os.Stat(s.markerPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("load Fail2Ban restore marker after starting the firewall: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.start("fail2ban.service"); err != nil {
|
||||||
|
return fmt.Errorf("restore Fail2Ban after starting the firewall: %w", err)
|
||||||
|
}
|
||||||
|
return s.clearRestoreMarker()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *firewallFail2BanState) clearRestoreMarker() error {
|
||||||
|
if err := os.Remove(s.markerPath); err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("clear Fail2Ban firewall restore status: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,18 +1,483 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LogService struct{}
|
type LogService struct{}
|
||||||
|
|
||||||
|
const maxSystemLogCursorSkip = 100
|
||||||
|
const maxFileSystemLogLines = 1000
|
||||||
|
|
||||||
|
var (
|
||||||
|
syslogPriorityKeywords = []struct {
|
||||||
|
keyword string
|
||||||
|
priority string
|
||||||
|
}{
|
||||||
|
{"emerg", "0"}, {"panic", "0"}, {"alert", "1"}, {"crit", "2"}, {"fatal", "2"},
|
||||||
|
{"error", "3"}, {"err", "3"}, {"warn", "4"}, {"notice", "5"}, {"debug", "7"}, {"info", "6"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type systemLogCursor struct {
|
||||||
|
EndTime int64 `json:"endTime"`
|
||||||
|
Skipped int `json:"skipped"`
|
||||||
|
JournalCursor string `json:"journalCursor,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type ILogService interface {
|
type ILogService interface {
|
||||||
ListSystemLogFile() ([]string, error)
|
ListSystemLogFile() ([]string, error)
|
||||||
|
GetSystemLogStatus() (dto.SystemLogStatus, error)
|
||||||
|
ReadSystemLog(req dto.SystemLogReq) (dto.SystemLogRes, error)
|
||||||
|
ListRunningServices() ([]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *LogService) ReadSystemLog(req dto.SystemLogReq) (dto.SystemLogRes, error) {
|
||||||
|
pageSize := req.PageSize
|
||||||
|
if pageSize == 0 {
|
||||||
|
pageSize = 100
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
startTime := req.StartTime
|
||||||
|
if startTime.IsZero() {
|
||||||
|
startTime = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||||
|
}
|
||||||
|
endTime := req.EndTime
|
||||||
|
if endTime.IsZero() {
|
||||||
|
endTime = now
|
||||||
|
}
|
||||||
|
cursor, err := decodeSystemLogCursor(req.Cursor)
|
||||||
|
if err != nil {
|
||||||
|
return dto.SystemLogRes{}, err
|
||||||
|
}
|
||||||
|
if cursor != nil && cursor.JournalCursor == "" {
|
||||||
|
endTime = time.Unix(0, cursor.EndTime*int64(time.Microsecond))
|
||||||
|
}
|
||||||
|
journalctl, err := exec.LookPath("journalctl")
|
||||||
|
if err != nil {
|
||||||
|
return u.readFileSystemLog(req, startTime, endTime, pageSize, cursor)
|
||||||
|
}
|
||||||
|
queryArgs := buildJournalQueryArgs(req, startTime, endTime, pageSize, cursor)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
|
output, err := exec.CommandContext(ctx, journalctl, queryArgs...).CombinedOutput()
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
if journalctlGrepUnsupported(req.Keyword, string(output)) {
|
||||||
|
return dto.SystemLogRes{}, buserr.New("ErrSystemLogKeywordFilterUnsupported")
|
||||||
|
}
|
||||||
|
return dto.SystemLogRes{}, fmt.Errorf("read host system logs failed: %s", strings.TrimSpace(string(output)))
|
||||||
|
}
|
||||||
|
content := strings.TrimSpace(string(output))
|
||||||
|
if content == "" || strings.HasPrefix(content, "-- No entries --") {
|
||||||
|
return dto.SystemLogRes{Source: "journalctl", Items: []dto.SystemLogItem{}}, nil
|
||||||
|
}
|
||||||
|
items := parseJournalLogItems(content)
|
||||||
|
if cursor != nil && cursor.JournalCursor == "" {
|
||||||
|
items, err = skipSystemLogCursorItems(items, *cursor)
|
||||||
|
if err != nil {
|
||||||
|
return dto.SystemLogRes{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buildSystemLogResponse("journalctl", items, pageSize, cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *LogService) GetSystemLogStatus() (dto.SystemLogStatus, error) {
|
||||||
|
journalctl, err := exec.LookPath("journalctl")
|
||||||
|
if err != nil {
|
||||||
|
return dto.SystemLogStatus{
|
||||||
|
Source: "file",
|
||||||
|
KeywordFilterSupported: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
versionOutput, err := exec.CommandContext(ctx, journalctl, "--version").CombinedOutput()
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return dto.SystemLogStatus{}, fmt.Errorf("read journalctl version failed: %s", strings.TrimSpace(string(versionOutput)))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
helpOutput, err := exec.CommandContext(ctx, journalctl, "--help").CombinedOutput()
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return dto.SystemLogStatus{}, fmt.Errorf("read journalctl capabilities failed: %s", strings.TrimSpace(string(helpOutput)))
|
||||||
|
}
|
||||||
|
supported := journalctlHelpSupportsGrep(string(helpOutput))
|
||||||
|
status := dto.SystemLogStatus{
|
||||||
|
Source: "journalctl",
|
||||||
|
Version: firstOutputLine(string(versionOutput)),
|
||||||
|
KeywordFilterSupported: supported,
|
||||||
|
}
|
||||||
|
if !supported {
|
||||||
|
status.Message = i18n.Get("ErrSystemLogKeywordFilterUnsupported")
|
||||||
|
}
|
||||||
|
return status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func journalctlHelpSupportsGrep(help string) bool {
|
||||||
|
return strings.Contains(help, "--grep=")
|
||||||
|
}
|
||||||
|
|
||||||
|
func journalctlGrepUnsupported(keyword, output string) bool {
|
||||||
|
if strings.TrimSpace(keyword) == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
output = strings.ToLower(output)
|
||||||
|
return strings.Contains(output, "grep") &&
|
||||||
|
(strings.Contains(output, "unrecognized option") || strings.Contains(output, "unknown option"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstOutputLine(output string) string {
|
||||||
|
for _, line := range strings.Split(output, "\n") {
|
||||||
|
if line = strings.TrimSpace(line); line != "" {
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildJournalQueryArgs(req dto.SystemLogReq, startTime, endTime time.Time, pageSize int, cursor *systemLogCursor) []string {
|
||||||
|
args := []string{
|
||||||
|
"--no-pager", "--reverse", "--output=json",
|
||||||
|
"--since", formatJournalQueryTime(startTime),
|
||||||
|
}
|
||||||
|
if cursor != nil && cursor.JournalCursor != "" {
|
||||||
|
args = append(args, "--after-cursor="+cursor.JournalCursor)
|
||||||
|
} else {
|
||||||
|
args = append(args, "--until", formatJournalQueryTime(endTime))
|
||||||
|
}
|
||||||
|
if service := strings.TrimSpace(req.Service); service != "" {
|
||||||
|
args = append(args, "-u", service)
|
||||||
|
}
|
||||||
|
if priority := strings.TrimSpace(req.Priority); priority != "" {
|
||||||
|
args = append(args, "--priority", priority+".."+priority)
|
||||||
|
}
|
||||||
|
if keyword := strings.TrimSpace(req.Keyword); keyword != "" {
|
||||||
|
args = append(args, "--grep", keyword)
|
||||||
|
}
|
||||||
|
queryLines := pageSize + 1
|
||||||
|
if cursor != nil && cursor.JournalCursor == "" {
|
||||||
|
queryLines += cursor.Skipped
|
||||||
|
}
|
||||||
|
return append(args, "--lines="+strconv.Itoa(queryLines))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *LogService) readFileSystemLog(req dto.SystemLogReq, startTime, endTime time.Time, pageSize int, cursor *systemLogCursor) (dto.SystemLogRes, error) {
|
||||||
|
items := make([]dto.SystemLogItem, 0)
|
||||||
|
for _, logFile := range []string{"/var/log/syslog", "/var/log/messages", "/var/log/system.log"} {
|
||||||
|
if _, err := os.Stat(logFile); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
content, err := readLastSystemLogLines(logFile, maxFileSystemLogLines)
|
||||||
|
if err != nil {
|
||||||
|
return dto.SystemLogRes{}, err
|
||||||
|
}
|
||||||
|
for _, line := range strings.Split(content, "\n") {
|
||||||
|
item, ok := parseFileSystemLogItem(line)
|
||||||
|
if !ok || !matchSystemLogItem(item, req, startTime, endTime) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.SliceStable(items, func(i, j int) bool { return items[i].Timestamp > items[j].Timestamp })
|
||||||
|
if cursor != nil {
|
||||||
|
var err error
|
||||||
|
items, err = skipSystemLogCursorItems(items, *cursor)
|
||||||
|
if err != nil {
|
||||||
|
return dto.SystemLogRes{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buildSystemLogResponse("file", items, pageSize, cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLastSystemLogLines(logFile string, lines int) (string, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
output, err := exec.CommandContext(ctx, "tail", "-n", strconv.Itoa(lines), logFile).Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(output), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildSystemLogResponse(source string, items []dto.SystemLogItem, pageSize int, cursor *systemLogCursor) (dto.SystemLogRes, error) {
|
||||||
|
hasMore := len(items) > pageSize
|
||||||
|
if hasMore {
|
||||||
|
items = items[:pageSize]
|
||||||
|
}
|
||||||
|
res := dto.SystemLogRes{Source: source, Items: items, HasMore: hasMore}
|
||||||
|
if !hasMore || len(items) == 0 {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
lastItem := items[len(items)-1]
|
||||||
|
next := systemLogCursor{EndTime: lastItem.Timestamp}
|
||||||
|
if lastItem.Cursor != "" {
|
||||||
|
next.JournalCursor = lastItem.Cursor
|
||||||
|
} else {
|
||||||
|
next.Skipped = countSystemLogTimestamp(items, lastItem.Timestamp)
|
||||||
|
if cursor != nil && cursor.EndTime == lastItem.Timestamp {
|
||||||
|
next.Skipped += cursor.Skipped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextCursor, err := encodeSystemLogCursor(next)
|
||||||
|
if err != nil {
|
||||||
|
return dto.SystemLogRes{}, err
|
||||||
|
}
|
||||||
|
res.NextCursor = nextCursor
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeSystemLogCursor(value string) (*systemLogCursor, error) {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if value == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
decoded, err := base64.RawURLEncoding.DecodeString(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid system log cursor")
|
||||||
|
}
|
||||||
|
var cursor systemLogCursor
|
||||||
|
if err := json.Unmarshal(decoded, &cursor); err != nil || cursor.EndTime <= 0 || cursor.Skipped < 0 || cursor.Skipped > maxSystemLogCursorSkip {
|
||||||
|
return nil, fmt.Errorf("invalid system log cursor")
|
||||||
|
}
|
||||||
|
return &cursor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeSystemLogCursor(cursor systemLogCursor) (string, error) {
|
||||||
|
value, err := json.Marshal(cursor)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.RawURLEncoding.EncodeToString(value), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipSystemLogCursorItems(items []dto.SystemLogItem, cursor systemLogCursor) ([]dto.SystemLogItem, error) {
|
||||||
|
skipped := 0
|
||||||
|
for len(items) > 0 && skipped < cursor.Skipped && items[0].Timestamp == cursor.EndTime {
|
||||||
|
items = items[1:]
|
||||||
|
skipped++
|
||||||
|
}
|
||||||
|
if skipped != cursor.Skipped {
|
||||||
|
return nil, fmt.Errorf("system log cursor has expired")
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func countSystemLogTimestamp(items []dto.SystemLogItem, timestamp int64) int {
|
||||||
|
count := 0
|
||||||
|
for _, item := range items {
|
||||||
|
if item.Timestamp == timestamp {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatJournalQueryTime(value time.Time) string {
|
||||||
|
// Fractional seconds are rejected by some journalctl versions.
|
||||||
|
return value.In(time.Local).Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFileSystemLogItem(line string) (dto.SystemLogItem, bool) {
|
||||||
|
raw := strings.TrimSpace(line)
|
||||||
|
if raw == "" {
|
||||||
|
return dto.SystemLogItem{}, false
|
||||||
|
}
|
||||||
|
priority := "6"
|
||||||
|
if matches := re.GetRegex(re.SyslogPRIPattern).FindStringSubmatch(raw); len(matches) == 2 {
|
||||||
|
value, _ := strconv.Atoi(matches[1])
|
||||||
|
priority = strconv.Itoa(value % 8)
|
||||||
|
raw = strings.TrimSpace(strings.TrimPrefix(raw, matches[0]))
|
||||||
|
}
|
||||||
|
logTime, rest, ok := parseFileSystemLogTime(raw)
|
||||||
|
if !ok {
|
||||||
|
return dto.SystemLogItem{}, false
|
||||||
|
}
|
||||||
|
service, message := parseFileSystemLogService(rest)
|
||||||
|
if priority == "6" {
|
||||||
|
priority = detectFileSystemLogPriority(message)
|
||||||
|
}
|
||||||
|
return dto.SystemLogItem{
|
||||||
|
Timestamp: logTime.UnixMicro(),
|
||||||
|
Time: logTime.Format("2006-01-02 15:04:05"),
|
||||||
|
Priority: priority,
|
||||||
|
Service: service,
|
||||||
|
Message: message,
|
||||||
|
Raw: line,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFileSystemLogTime(value string) (time.Time, string, bool) {
|
||||||
|
if matches := re.GetRegex(re.SyslogRFC3339Pattern).FindStringSubmatch(value); len(matches) == 3 {
|
||||||
|
for _, layout := range []string{time.RFC3339Nano, "2006-01-02 15:04:05.999999999Z07:00"} {
|
||||||
|
if parsed, err := time.Parse(layout, matches[1]); err == nil {
|
||||||
|
return parsed.Local(), matches[2], true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, layout := range []string{"2006-01-02T15:04:05.999999999", "2006-01-02 15:04:05.999999999"} {
|
||||||
|
if parsed, err := time.ParseInLocation(layout, matches[1], time.Local); err == nil {
|
||||||
|
return parsed, matches[2], true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matches := re.GetRegex(re.SyslogRFC3164Pattern).FindStringSubmatch(value); len(matches) == 3 {
|
||||||
|
parsed, err := time.ParseInLocation("Jan _2 15:04:05", matches[1], time.Local)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, "", false
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
parsed = time.Date(now.Year(), parsed.Month(), parsed.Day(), parsed.Hour(), parsed.Minute(), parsed.Second(), 0, time.Local)
|
||||||
|
if parsed.After(now.Add(24 * time.Hour)) {
|
||||||
|
parsed = parsed.AddDate(-1, 0, 0)
|
||||||
|
}
|
||||||
|
return parsed, matches[2], true
|
||||||
|
}
|
||||||
|
return time.Time{}, "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFileSystemLogService(value string) (string, string) {
|
||||||
|
if matches := re.GetRegex(re.SyslogServicePattern).FindStringSubmatch(value); len(matches) == 3 {
|
||||||
|
return matches[1], matches[2]
|
||||||
|
}
|
||||||
|
// RFC5424 records have the form HOST APP-NAME PROCID MSGID STRUCTURED-DATA MSG.
|
||||||
|
fields := strings.Fields(value)
|
||||||
|
if len(fields) >= 6 {
|
||||||
|
return fields[1], strings.Join(fields[5:], " ")
|
||||||
|
}
|
||||||
|
return "", value
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectFileSystemLogPriority(message string) string {
|
||||||
|
lowerMessage := strings.ToLower(message)
|
||||||
|
for _, item := range syslogPriorityKeywords {
|
||||||
|
if strings.Contains(lowerMessage, item.keyword) {
|
||||||
|
return item.priority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "6"
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchSystemLogItem(item dto.SystemLogItem, req dto.SystemLogReq, startTime, endTime time.Time) bool {
|
||||||
|
itemTime := time.UnixMicro(item.Timestamp)
|
||||||
|
if itemTime.Before(startTime) || itemTime.After(endTime) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if priority := strings.TrimSpace(req.Priority); priority != "" && item.Priority != priority {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if service := strings.TrimSpace(req.Service); service != "" && !matchFileSystemLogService(item.Service, service) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if keyword := strings.TrimSpace(req.Keyword); keyword != "" && !strings.Contains(strings.ToLower(item.Raw), strings.ToLower(keyword)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchFileSystemLogService(itemService, requestedService string) bool {
|
||||||
|
itemService = strings.TrimSpace(itemService)
|
||||||
|
requestedService = strings.TrimSpace(requestedService)
|
||||||
|
if itemService == "" || requestedService == "" {
|
||||||
|
return itemService == requestedService
|
||||||
|
}
|
||||||
|
return strings.EqualFold(strings.TrimSuffix(itemService, ".service"), strings.TrimSuffix(requestedService, ".service"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *LogService) ListRunningServices() ([]string, error) {
|
||||||
|
systemctl, err := exec.LookPath("systemctl")
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
args := []string{"list-units", "--type=service", "--state=running", "--no-legend", "--no-pager", "--plain"}
|
||||||
|
output, err := exec.CommandContext(ctx, systemctl, args...).Output()
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
services := make([]string, 0)
|
||||||
|
for _, line := range strings.Split(string(output), "\n") {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) == 0 || !strings.HasSuffix(fields[0], ".service") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
services = append(services, fields[0])
|
||||||
|
}
|
||||||
|
sort.Strings(services)
|
||||||
|
return services, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseJournalLogItems(content string) []dto.SystemLogItem {
|
||||||
|
entries := strings.Split(content, "\n")
|
||||||
|
items := make([]dto.SystemLogItem, 0, len(entries))
|
||||||
|
for _, entry := range entries {
|
||||||
|
var fields map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(entry), &fields); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items = append(items, dto.SystemLogItem{
|
||||||
|
Timestamp: journalFieldMicroseconds(fields),
|
||||||
|
Cursor: journalFieldString(fields, "__CURSOR"),
|
||||||
|
Time: formatJournalTimestamp(journalFieldString(fields, "__REALTIME_TIMESTAMP")),
|
||||||
|
Priority: journalFieldString(fields, "PRIORITY"),
|
||||||
|
Service: journalFieldString(fields, "_SYSTEMD_UNIT"),
|
||||||
|
Message: journalFieldString(fields, "MESSAGE"),
|
||||||
|
Raw: entry,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func journalFieldMicroseconds(fields map[string]interface{}) int64 {
|
||||||
|
value, _ := strconv.ParseInt(journalFieldString(fields, "__REALTIME_TIMESTAMP"), 10, 64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func journalFieldString(fields map[string]interface{}, key string) string {
|
||||||
|
value, ok := fields[key]
|
||||||
|
if !ok || value == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
switch item := value.(type) {
|
||||||
|
case string:
|
||||||
|
return item
|
||||||
|
case float64:
|
||||||
|
return strconv.FormatInt(int64(item), 10)
|
||||||
|
case []interface{}:
|
||||||
|
values := make([]string, 0, len(item))
|
||||||
|
for _, value := range item {
|
||||||
|
values = append(values, fmt.Sprint(value))
|
||||||
|
}
|
||||||
|
return strings.Join(values, " ")
|
||||||
|
default:
|
||||||
|
return fmt.Sprint(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatJournalTimestamp(value string) string {
|
||||||
|
microseconds, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return time.Unix(0, microseconds*int64(time.Microsecond)).Local().Format("2006-01-02 15:04:05")
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewILogService() ILogService {
|
func NewILogService() ILogService {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/mattn/go-shellwords"
|
||||||
"github.com/subosito/gotenv"
|
"github.com/subosito/gotenv"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
@@ -127,6 +128,7 @@ func (m McpServerService) Update(req request.McpServerUpdate) error {
|
|||||||
mcpServer.Type = req.Type
|
mcpServer.Type = req.Type
|
||||||
mcpServer.GatewayImage = req.GatewayImage
|
mcpServer.GatewayImage = req.GatewayImage
|
||||||
mcpServer.ProtocolVersion = req.ProtocolVersion
|
mcpServer.ProtocolVersion = req.ProtocolVersion
|
||||||
|
mcpServer.GatewayArgs = req.GatewayArgs
|
||||||
normalizeMcpServerGateway(mcpServer)
|
normalizeMcpServerGateway(mcpServer)
|
||||||
if req.OutputTransport == mcpOutputTransportSSE {
|
if req.OutputTransport == mcpOutputTransportSSE {
|
||||||
mcpServer.SsePath = req.SsePath
|
mcpServer.SsePath = req.SsePath
|
||||||
@@ -191,6 +193,7 @@ func (m McpServerService) Create(create request.McpServerCreate) error {
|
|||||||
Type: create.Type,
|
Type: create.Type,
|
||||||
GatewayImage: create.GatewayImage,
|
GatewayImage: create.GatewayImage,
|
||||||
ProtocolVersion: create.ProtocolVersion,
|
ProtocolVersion: create.ProtocolVersion,
|
||||||
|
GatewayArgs: create.GatewayArgs,
|
||||||
}
|
}
|
||||||
normalizeMcpServerGateway(mcpServer)
|
normalizeMcpServerGateway(mcpServer)
|
||||||
if create.OutputTransport == mcpOutputTransportSSE {
|
if create.OutputTransport == mcpOutputTransportSSE {
|
||||||
@@ -738,7 +741,52 @@ func normalizeMcpServerGateway(mcpServer *model.McpServer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSupergatewayCommand(mcpServer *model.McpServer) []string {
|
var managedMcpGatewayArgs = map[string]struct{}{
|
||||||
|
"--": {},
|
||||||
|
"stdio": {},
|
||||||
|
"sse": {},
|
||||||
|
"streamablehttp": {},
|
||||||
|
"outputtransport": {},
|
||||||
|
"port": {},
|
||||||
|
"baseurl": {},
|
||||||
|
"ssepath": {},
|
||||||
|
"messagepath": {},
|
||||||
|
"streamablehttppath": {},
|
||||||
|
"protocolversion": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeMcpGatewayArgName(name string) string {
|
||||||
|
if name == "--" || !strings.HasPrefix(name, "--") {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
name = strings.TrimPrefix(name, "--")
|
||||||
|
name = strings.TrimPrefix(name, "no-")
|
||||||
|
name = strings.SplitN(name, ".", 2)[0]
|
||||||
|
return strings.ToLower(strings.ReplaceAll(name, "-", ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMcpGatewayArgs(raw string) ([]string, error) {
|
||||||
|
if strings.TrimSpace(raw) == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
parser := shellwords.NewParser()
|
||||||
|
args, err := parser.Parse(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, buserr.New("ErrMcpGatewayArgsParse")
|
||||||
|
}
|
||||||
|
if parser.Position >= 0 {
|
||||||
|
return nil, buserr.New("ErrMcpGatewayArgsParse")
|
||||||
|
}
|
||||||
|
for _, arg := range args {
|
||||||
|
name := strings.SplitN(arg, "=", 2)[0]
|
||||||
|
if _, ok := managedMcpGatewayArgs[normalizeMcpGatewayArgName(name)]; ok {
|
||||||
|
return nil, buserr.WithName("ErrMcpGatewayArgsConflict", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildSupergatewayCommand(mcpServer *model.McpServer) ([]string, error) {
|
||||||
normalizeMcpServerGateway(mcpServer)
|
normalizeMcpServerGateway(mcpServer)
|
||||||
command := []string{
|
command := []string{
|
||||||
"--stdio", mcpServer.Command,
|
"--stdio", mcpServer.Command,
|
||||||
@@ -746,14 +794,22 @@ func buildSupergatewayCommand(mcpServer *model.McpServer) []string {
|
|||||||
"--port", strconv.Itoa(mcpServer.Port),
|
"--port", strconv.Itoa(mcpServer.Port),
|
||||||
}
|
}
|
||||||
if mcpServer.OutputTransport == mcpOutputTransportSSE {
|
if mcpServer.OutputTransport == mcpOutputTransportSSE {
|
||||||
return append(command,
|
command = append(command,
|
||||||
"--baseUrl", mcpServer.BaseURL,
|
"--baseUrl", mcpServer.BaseURL,
|
||||||
"--ssePath", mcpServer.SsePath,
|
"--ssePath", mcpServer.SsePath,
|
||||||
"--messagePath", fmt.Sprintf("%s/messages", mcpServer.SsePath),
|
"--messagePath", fmt.Sprintf("%s/messages", mcpServer.SsePath),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
command = append(command,
|
||||||
|
"--streamableHttpPath", mcpServer.StreamableHttpPath,
|
||||||
|
"--protocolVersion", mcpServer.ProtocolVersion,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
command = append(command, "--streamableHttpPath", mcpServer.StreamableHttpPath)
|
gatewayArgs, err := parseMcpGatewayArgs(mcpServer.GatewayArgs)
|
||||||
return append(command, "--protocolVersion", mcpServer.ProtocolVersion)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return append(command, gatewayArgs...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildMcpEndpoint(mcpServer *model.McpServer) string {
|
func buildMcpEndpoint(mcpServer *model.McpServer) string {
|
||||||
@@ -852,7 +908,11 @@ func handleCreateParams(mcpServer *model.McpServer, environments []request.Envir
|
|||||||
delete(services, serviceName)
|
delete(services, serviceName)
|
||||||
}
|
}
|
||||||
normalizeMcpServerGateway(mcpServer)
|
normalizeMcpServerGateway(mcpServer)
|
||||||
serviceValue["command"] = buildSupergatewayCommand(mcpServer)
|
command, err := buildSupergatewayCommand(mcpServer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
serviceValue["command"] = command
|
||||||
serviceValue["image"] = mcpServer.GatewayImage
|
serviceValue["image"] = mcpServer.GatewayImage
|
||||||
serviceValue["ports"] = []string{
|
serviceValue["ports"] = []string{
|
||||||
formatComposePortMapping("HOST_IP", "PANEL_APP_PORT_HTTP", "PANEL_APP_PORT_HTTP", ""),
|
formatComposePortMapping("HOST_IP", "PANEL_APP_PORT_HTTP", "PANEL_APP_PORT_HTTP", ""),
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
@@ -34,7 +37,12 @@ type MonitorService struct {
|
|||||||
NetIO chan ([]net.IOCountersStat)
|
NetIO chan ([]net.IOCountersStat)
|
||||||
}
|
}
|
||||||
|
|
||||||
var monitorCancel context.CancelFunc
|
var (
|
||||||
|
monitorCancel context.CancelFunc
|
||||||
|
hostSysPath = loadHostSysPath()
|
||||||
|
|
||||||
|
blockDevicePartitionCache sync.Map
|
||||||
|
)
|
||||||
|
|
||||||
type IMonitorService interface {
|
type IMonitorService interface {
|
||||||
Run()
|
Run()
|
||||||
@@ -341,18 +349,10 @@ func (m *MonitorService) Run() {
|
|||||||
func (m *MonitorService) loadDiskIO() {
|
func (m *MonitorService) loadDiskIO() {
|
||||||
ioStat, _ := disk.IOCounters()
|
ioStat, _ := disk.IOCounters()
|
||||||
var diskIOList []disk.IOCountersStat
|
var diskIOList []disk.IOCountersStat
|
||||||
var ioStatAll disk.IOCountersStat
|
|
||||||
for _, io := range ioStat {
|
for _, io := range ioStat {
|
||||||
ioStatAll.Name = "all"
|
|
||||||
ioStatAll.ReadBytes += io.ReadBytes
|
|
||||||
ioStatAll.WriteBytes += io.WriteBytes
|
|
||||||
ioStatAll.ReadTime += io.ReadTime
|
|
||||||
ioStatAll.WriteTime += io.WriteTime
|
|
||||||
ioStatAll.WriteCount += io.WriteCount
|
|
||||||
ioStatAll.ReadCount += io.ReadCount
|
|
||||||
diskIOList = append(diskIOList, io)
|
diskIOList = append(diskIOList, io)
|
||||||
}
|
}
|
||||||
diskIOList = append(diskIOList, ioStatAll)
|
diskIOList = append(diskIOList, sumDiskIOCounters(ioStat))
|
||||||
m.DiskIO <- diskIOList
|
m.DiskIO <- diskIOList
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,3 +680,45 @@ func loadGPUInfoFloat(val string) float64 {
|
|||||||
data, _ := strconv.ParseFloat(val, 64)
|
data, _ := strconv.ParseFloat(val, 64)
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sumDiskIOCounters(ioStats map[string]disk.IOCountersStat) disk.IOCountersStat {
|
||||||
|
total := disk.IOCountersStat{Name: "all"}
|
||||||
|
for name, stat := range ioStats {
|
||||||
|
if isBlockDevicePartition(name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
total.ReadCount += stat.ReadCount
|
||||||
|
total.MergedReadCount += stat.MergedReadCount
|
||||||
|
total.WriteCount += stat.WriteCount
|
||||||
|
total.MergedWriteCount += stat.MergedWriteCount
|
||||||
|
total.ReadBytes += stat.ReadBytes
|
||||||
|
total.WriteBytes += stat.WriteBytes
|
||||||
|
total.ReadTime += stat.ReadTime
|
||||||
|
total.WriteTime += stat.WriteTime
|
||||||
|
total.IopsInProgress += stat.IopsInProgress
|
||||||
|
total.IoTime += stat.IoTime
|
||||||
|
total.WeightedIO += stat.WeightedIO
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBlockDevicePartition(name string) bool {
|
||||||
|
deviceName := filepath.Base(name)
|
||||||
|
if cached, ok := blockDevicePartitionCache.Load(deviceName); ok {
|
||||||
|
return cached.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := os.Stat(filepath.Join(hostSysPath, "class", "block", deviceName, "partition"))
|
||||||
|
isPartition := err == nil
|
||||||
|
actual, _ := blockDevicePartitionCache.LoadOrStore(deviceName, isPartition)
|
||||||
|
return actual.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadHostSysPath() string {
|
||||||
|
hostSys := os.Getenv("HOST_SYS")
|
||||||
|
if hostSys == "" {
|
||||||
|
return "/sys"
|
||||||
|
}
|
||||||
|
return hostSys
|
||||||
|
}
|
||||||
|
|||||||
+151
-126
@@ -1,8 +1,6 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
|
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
||||||
@@ -17,7 +15,6 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
|
||||||
"github.com/subosito/gotenv"
|
"github.com/subosito/gotenv"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
||||||
@@ -57,7 +54,7 @@ func (n NginxService) GetNginxConfig() (*response.NginxFile, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
configPath := path.Join(global.Dir.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "nginx.conf")
|
configPath := path.Join(global.Dir.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, nginxModuleConfDir, "nginx.conf")
|
||||||
byteContent, err := files.NewFileOp().GetContent(configPath)
|
byteContent, err := files.NewFileOp().GetContent(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -138,7 +135,7 @@ func (n NginxService) UpdateConfigFile(req request.NginxConfigFileUpdate) error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
filePath := path.Join(global.Dir.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "nginx.conf")
|
filePath := path.Join(global.Dir.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, nginxModuleConfDir, "nginx.conf")
|
||||||
if req.Backup {
|
if req.Backup {
|
||||||
backupPath := path.Join(path.Dir(filePath), "bak")
|
backupPath := path.Join(path.Dir(filePath), "bak")
|
||||||
if !fileOp.Stat(backupPath) {
|
if !fileOp.Stat(backupPath) {
|
||||||
@@ -181,71 +178,20 @@ func (n NginxService) Build(req request.NginxBuildReq) error {
|
|||||||
if err = task.CheckTaskIsExecuting(taskName); err != nil {
|
if err = task.CheckTaskIsExecuting(taskName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fileOp := files.NewFileOp()
|
if err = task.CheckScopeTaskIsExecuting(task.TaskScopeApp, nginxInstall.ID); err != nil {
|
||||||
buildPath := path.Join(nginxInstall.GetPath(), "build")
|
return err
|
||||||
if !fileOp.Stat(buildPath) {
|
}
|
||||||
|
buildPath := path.Join(nginxInstall.GetPath(), nginxModuleBuildDir)
|
||||||
|
if !files.NewFileOp().Stat(buildPath) {
|
||||||
return buserr.New("ErrBuildDirNotFound")
|
return buserr.New("ErrBuildDirNotFound")
|
||||||
}
|
}
|
||||||
moduleConfigPath := path.Join(buildPath, "module.json")
|
|
||||||
moduleContent, err := fileOp.GetContent(moduleConfigPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
modules []dto.NginxModule
|
|
||||||
addModuleParams []string
|
|
||||||
addPackages []string
|
|
||||||
)
|
|
||||||
if len(moduleContent) > 0 {
|
|
||||||
_ = json.Unmarshal(moduleContent, &modules)
|
|
||||||
bashFile, err := os.OpenFile(path.Join(buildPath, "tmp", "pre.sh"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.DirPerm)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer bashFile.Close()
|
|
||||||
bashFileWriter := bufio.NewWriter(bashFile)
|
|
||||||
for _, module := range modules {
|
|
||||||
if !module.Enable {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, err = bashFileWriter.WriteString(module.Script + "\n")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
addModuleParams = append(addModuleParams, module.Params)
|
|
||||||
addPackages = append(addPackages, module.Packages...)
|
|
||||||
}
|
|
||||||
err = bashFileWriter.Flush()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
envs, err := gotenv.Read(nginxInstall.GetEnvPath())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
envs["CONTAINER_PACKAGE_URL"] = req.Mirror
|
|
||||||
envs["RESTY_CONFIG_OPTIONS_MORE"] = ""
|
|
||||||
envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = ""
|
|
||||||
if len(addModuleParams) > 0 {
|
|
||||||
envs["RESTY_CONFIG_OPTIONS_MORE"] = strings.Join(addModuleParams, " ")
|
|
||||||
}
|
|
||||||
if len(addPackages) > 0 {
|
|
||||||
envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = strings.Join(addPackages, " ")
|
|
||||||
}
|
|
||||||
_ = gotenv.Write(envs, nginxInstall.GetEnvPath())
|
|
||||||
|
|
||||||
buildTask, err := task.NewTaskWithOps(nginxInstall.Name, task.TaskBuild, task.TaskScopeApp, req.TaskID, nginxInstall.ID)
|
buildTask, err := task.NewTaskWithOps(nginxInstall.Name, task.TaskBuild, task.TaskScopeApp, req.TaskID, nginxInstall.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
buildTask.AddSubTaskWithOps("", func(t *task.Task) error {
|
buildTask.AddSubTaskWithOps("", func(t *task.Task) error {
|
||||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*buildTask), cmd.WithTimeout(120*time.Minute))
|
return executeNginxModuleBuild(nginxInstall, req.Modules, req.Force, req.Mirror, t, true)
|
||||||
if err = cmdMgr.Run("docker", "compose", "-f", nginxInstall.GetComposePath(), "build"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = compose.DownAndUp(nginxInstall.GetComposePath())
|
|
||||||
return err
|
|
||||||
}, nil, 0, 120*time.Minute)
|
}, nil, 0, 120*time.Minute)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -259,27 +205,70 @@ func (n NginxService) GetModules() (*response.NginxBuildConfig, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
fileOp := files.NewFileOp()
|
modules, err := loadNginxModules(nginxInstall)
|
||||||
var modules []dto.NginxModule
|
|
||||||
moduleConfigPath := path.Join(nginxInstall.GetPath(), "build", "module.json")
|
|
||||||
if !fileOp.Stat(moduleConfigPath) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
moduleContent, err := fileOp.GetContent(moduleConfigPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(moduleContent) > 0 {
|
target, targetWarning, targetErr := resolveNginxModuleTarget(nginxInstall)
|
||||||
_ = json.Unmarshal(moduleContent, &modules)
|
if targetWarning != "" {
|
||||||
|
global.LOG.Warn(targetWarning)
|
||||||
|
}
|
||||||
|
runtimeTarget := target
|
||||||
|
runtimeTarget.BuilderDigest = ""
|
||||||
|
setNginxModuleTargetKey(&runtimeTarget)
|
||||||
|
runtimeTargetErr := targetErr
|
||||||
|
if runtimeTarget.OpenRestyVersion != "" {
|
||||||
|
runtimeTargetErr = nil
|
||||||
}
|
}
|
||||||
var resList []response.NginxModule
|
var resList []response.NginxModule
|
||||||
for _, module := range modules {
|
for _, module := range modules {
|
||||||
|
buildStatus := nginxModuleStatusPending
|
||||||
|
loadStatus := nginxModuleLoadDisabled
|
||||||
|
var artifacts []dto.NginxModuleArtifact
|
||||||
|
if module.BuildMode == nginxModuleBuildStatic && runtimeTargetErr == nil {
|
||||||
|
if build := findCurrentNginxModuleBuild(module, runtimeTarget); build != nil && build.Status == nginxModuleStatusReady {
|
||||||
|
buildStatus = nginxModuleStatusReady
|
||||||
|
if module.Enable {
|
||||||
|
loadStatus = nginxModuleLoadEnabled
|
||||||
|
}
|
||||||
|
} else if findLatestNginxModuleBuild(module, runtimeTarget) != nil {
|
||||||
|
if module.Enable {
|
||||||
|
loadStatus = nginxModuleLoadEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if targetErr == nil {
|
||||||
|
if build := findCurrentNginxModuleBuild(module, target); build != nil {
|
||||||
|
buildStatus = build.Status
|
||||||
|
artifacts = build.Artifacts
|
||||||
|
if build.Status == nginxModuleStatusReady {
|
||||||
|
if module.Enable {
|
||||||
|
loadStatus = nginxModuleLoadEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if latestBuild := findLatestNginxModuleBuild(module, target); latestBuild != nil {
|
||||||
|
artifacts = latestBuild.Artifacts
|
||||||
|
if module.Enable {
|
||||||
|
loadStatus = nginxModuleLoadEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if module.BuildMode != nginxModuleBuildStatic && module.LastError != "" {
|
||||||
|
buildStatus = nginxModuleStatusFailed
|
||||||
|
}
|
||||||
resList = append(resList, response.NginxModule{
|
resList = append(resList, response.NginxModule{
|
||||||
Name: module.Name,
|
Name: module.Name,
|
||||||
Script: module.Script,
|
Custom: module.Custom,
|
||||||
Packages: strings.Join(module.Packages, ","),
|
Script: module.Script,
|
||||||
Params: module.Params,
|
Packages: strings.Join(module.Packages, ","),
|
||||||
Enable: module.Enable,
|
Params: module.Params,
|
||||||
|
Enable: module.Enable,
|
||||||
|
BuildMode: module.BuildMode,
|
||||||
|
Provider: module.Provider,
|
||||||
|
LoadOrder: module.LoadOrder,
|
||||||
|
BuildStatus: buildStatus,
|
||||||
|
LoadStatus: loadStatus,
|
||||||
|
Artifacts: artifacts,
|
||||||
|
LastError: module.LastError,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
envs, err := gotenv.Read(nginxInstall.GetEnvPath())
|
envs, err := gotenv.Read(nginxInstall.GetEnvPath())
|
||||||
@@ -288,69 +277,105 @@ func (n NginxService) GetModules() (*response.NginxBuildConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &response.NginxBuildConfig{
|
return &response.NginxBuildConfig{
|
||||||
Mirror: envs["CONTAINER_PACKAGE_URL"],
|
Mirror: envs["CONTAINER_PACKAGE_URL"],
|
||||||
Modules: resList,
|
DynamicSupported: nginxModuleDynamicSupported(nginxInstall),
|
||||||
|
Modules: resList,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyNginxModuleUpdate(modules []dto.NginxModule, req request.NginxModuleUpdate) ([]dto.NginxModule, *dto.NginxModule, error) {
|
||||||
|
switch req.Operate {
|
||||||
|
case nginxModuleOperateCreate:
|
||||||
|
if err := validateNginxModuleBuildMode(dto.NginxModule{Name: req.Name, BuildMode: req.BuildMode}); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
for _, module := range modules {
|
||||||
|
if module.Name == req.Name {
|
||||||
|
return nil, nil, buserr.New("ErrNameIsExist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modules = append(modules, dto.NginxModule{
|
||||||
|
Name: req.Name,
|
||||||
|
Custom: true,
|
||||||
|
Script: req.Script,
|
||||||
|
Packages: strings.Split(req.Packages, ","),
|
||||||
|
Params: req.Params,
|
||||||
|
Enable: req.Enable,
|
||||||
|
BuildMode: req.BuildMode,
|
||||||
|
Provider: req.Provider,
|
||||||
|
LoadOrder: req.LoadOrder,
|
||||||
|
})
|
||||||
|
case nginxModuleOperateUpdate:
|
||||||
|
for i := range modules {
|
||||||
|
if modules[i].Name != req.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if modules[i].Custom {
|
||||||
|
if err := validateNginxModuleBuildMode(dto.NginxModule{Name: req.Name, BuildMode: req.BuildMode}); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
modules[i].Enable = req.Enable
|
||||||
|
return modules, nil, nil
|
||||||
|
}
|
||||||
|
modules[i].Enable = req.Enable
|
||||||
|
modules[i].Script = req.Script
|
||||||
|
modules[i].Packages = strings.Split(req.Packages, ",")
|
||||||
|
modules[i].Params = req.Params
|
||||||
|
modules[i].BuildMode = req.BuildMode
|
||||||
|
modules[i].Provider = req.Provider
|
||||||
|
modules[i].LoadOrder = req.LoadOrder
|
||||||
|
return modules, nil, nil
|
||||||
|
}
|
||||||
|
return nil, nil, fmt.Errorf("OpenResty module %s not found", req.Name)
|
||||||
|
case nginxModuleOperateDelete:
|
||||||
|
for i := range modules {
|
||||||
|
if modules[i].Name != req.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !modules[i].Custom {
|
||||||
|
return nil, nil, fmt.Errorf("built-in OpenResty module %s cannot be deleted", req.Name)
|
||||||
|
}
|
||||||
|
deleted := modules[i]
|
||||||
|
modules = append(modules[:i], modules[i+1:]...)
|
||||||
|
return modules, &deleted, nil
|
||||||
|
}
|
||||||
|
return nil, nil, fmt.Errorf("OpenResty module %s not found", req.Name)
|
||||||
|
default:
|
||||||
|
return nil, nil, fmt.Errorf("unsupported OpenResty module operation %q", req.Operate)
|
||||||
|
}
|
||||||
|
return modules, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (n NginxService) UpdateModule(req request.NginxModuleUpdate) error {
|
func (n NginxService) UpdateModule(req request.NginxModuleUpdate) error {
|
||||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fileOp := files.NewFileOp()
|
if err = task.CheckScopeTaskIsExecuting(task.TaskScopeApp, nginxInstall.ID); err != nil {
|
||||||
var (
|
return err
|
||||||
modules []dto.NginxModule
|
|
||||||
)
|
|
||||||
moduleConfigPath := path.Join(nginxInstall.GetPath(), "build", "module.json")
|
|
||||||
if !fileOp.Stat(moduleConfigPath) {
|
|
||||||
_ = fileOp.CreateFile(moduleConfigPath)
|
|
||||||
}
|
}
|
||||||
moduleContent, err := fileOp.GetContent(moduleConfigPath)
|
modules, err := loadNginxModules(nginxInstall)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(moduleContent) > 0 {
|
oldModules := cloneNginxModules(modules)
|
||||||
_ = json.Unmarshal(moduleContent, &modules)
|
modules, deletedModule, err := applyNginxModuleUpdate(modules, req)
|
||||||
}
|
|
||||||
|
|
||||||
switch req.Operate {
|
|
||||||
case "create":
|
|
||||||
for _, module := range modules {
|
|
||||||
if module.Name == req.Name {
|
|
||||||
return buserr.New("ErrNameIsExist")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modules = append(modules, dto.NginxModule{
|
|
||||||
Name: req.Name,
|
|
||||||
Script: req.Script,
|
|
||||||
Packages: strings.Split(req.Packages, ","),
|
|
||||||
Params: req.Params,
|
|
||||||
Enable: true,
|
|
||||||
})
|
|
||||||
case "update":
|
|
||||||
for i, module := range modules {
|
|
||||||
if module.Name == req.Name {
|
|
||||||
modules[i].Script = req.Script
|
|
||||||
modules[i].Packages = strings.Split(req.Packages, ",")
|
|
||||||
modules[i].Params = req.Params
|
|
||||||
modules[i].Enable = req.Enable
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "delete":
|
|
||||||
for i, module := range modules {
|
|
||||||
if module.Name == req.Name {
|
|
||||||
modules = append(modules[:i], modules[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
moduleByte, err := json.Marshal(modules)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return fileOp.SaveFileWithByte(moduleConfigPath, moduleByte, constant.DirPerm)
|
if err = saveNginxModules(nginxInstall, modules); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = reconcileDynamicNginxModuleConfig(nginxInstall, modules, true); err != nil {
|
||||||
|
_ = saveNginxModules(nginxInstall, oldModules)
|
||||||
|
_ = reconcileDynamicNginxModuleConfig(nginxInstall, oldModules, false)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if deletedModule != nil {
|
||||||
|
_ = removeNginxModuleArtifacts(nginxInstall, *deletedModule)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n NginxService) OperateDefaultHTTPs(req request.NginxDefaultHTTPSUpdate) error {
|
func (n NginxService) OperateDefaultHTTPs(req request.NginxDefaultHTTPSUpdate) error {
|
||||||
@@ -366,7 +391,7 @@ func (n NginxService) OperateDefaultHTTPs(req request.NginxDefaultHTTPSUpdate) e
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defaultConfigPath := path.Join(appInstall.GetPath(), "conf", "default", "00.default.conf")
|
defaultConfigPath := path.Join(appInstall.GetPath(), nginxModuleConfDir, "default", "00.default.conf")
|
||||||
content, err := os.ReadFile(defaultConfigPath)
|
content, err := os.ReadFile(defaultConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -406,7 +431,7 @@ func (n NginxService) GetDefaultHttpsStatus() (*response.NginxConfigRes, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defaultConfigPath := path.Join(appInstall.GetPath(), "conf", "default", "00.default.conf")
|
defaultConfigPath := path.Join(appInstall.GetPath(), nginxModuleConfDir, "default", "00.default.conf")
|
||||||
content, err := os.ReadFile(defaultConfigPath)
|
content, err := os.ReadFile(defaultConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,676 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"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/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNormalizeNginxModuleDoesNotInferBuildMode(t *testing.T) {
|
||||||
|
module := dto.NginxModule{
|
||||||
|
Name: "legacy",
|
||||||
|
Packages: []string{"git", "", "git", " curl "},
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizeNginxModule(&module)
|
||||||
|
|
||||||
|
if module.BuildMode != "" {
|
||||||
|
t.Fatalf("build mode must be explicit, got %s", module.BuildMode)
|
||||||
|
}
|
||||||
|
if module.Provider != nginxModuleProviderLocal {
|
||||||
|
t.Fatalf("expected local provider, got %s", module.Provider)
|
||||||
|
}
|
||||||
|
if len(module.Packages) != 2 || module.Packages[0] != "git" || module.Packages[1] != "curl" {
|
||||||
|
t.Fatalf("unexpected normalized packages: %#v", module.Packages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeDynamicModuleParams(t *testing.T) {
|
||||||
|
params, err := normalizeDynamicModuleParams("--with-http_dav_module --add-module=/tmp/nginx-dav-ext-module")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := "--with-http_dav_module --add-dynamic-module=/tmp/nginx-dav-ext-module"
|
||||||
|
if params != expected {
|
||||||
|
t.Fatalf("expected %q, got %q", expected, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = normalizeDynamicModuleParams("--add-module=/tmp/module;touch /tmp/unsafe"); err == nil {
|
||||||
|
t.Fatal("expected shell metacharacters to be rejected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindCurrentAndLatestNginxModuleBuild(t *testing.T) {
|
||||||
|
target := dto.NginxModuleTarget{Key: "target"}
|
||||||
|
module := dto.NginxModule{
|
||||||
|
Name: "example",
|
||||||
|
Params: "--add-module=/tmp/example",
|
||||||
|
BuildMode: nginxModuleBuildDynamic,
|
||||||
|
Provider: nginxModuleProviderLocal,
|
||||||
|
}
|
||||||
|
params, err := normalizeDynamicModuleParams(module.Params)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
currentHash, err := nginxModuleBuildHash(module, target, params)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
oldTime := time.Now().Add(-time.Hour)
|
||||||
|
newTime := time.Now()
|
||||||
|
module.Builds = []dto.NginxModuleBuild{
|
||||||
|
{Hash: "old", Status: nginxModuleStatusReady, Target: target, BuiltAt: oldTime},
|
||||||
|
{Hash: currentHash, Status: nginxModuleStatusReady, Target: target, BuiltAt: newTime},
|
||||||
|
}
|
||||||
|
|
||||||
|
if build := findCurrentNginxModuleBuild(module, target); build == nil || build.Hash != currentHash {
|
||||||
|
t.Fatalf("current build was not selected: %#v", build)
|
||||||
|
}
|
||||||
|
if build := findLatestNginxModuleBuild(module, target); build == nil || build.Hash != currentHash {
|
||||||
|
t.Fatalf("latest build was not selected: %#v", build)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.Params = "--add-module=/tmp/example-v2"
|
||||||
|
if build := findCurrentNginxModuleBuild(module, target); build != nil {
|
||||||
|
t.Fatalf("changed module input should be stale, got %#v", build)
|
||||||
|
}
|
||||||
|
if build := findLatestNginxModuleBuild(module, target); build == nil || build.Hash != currentHash {
|
||||||
|
t.Fatal("the previous ready build should remain available until replacement")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNginxModulePathNameAvoidsSanitizedNameCollisions(t *testing.T) {
|
||||||
|
first := nginxModulePathName("example/module")
|
||||||
|
second := nginxModulePathName("example-module")
|
||||||
|
if first == second {
|
||||||
|
t.Fatalf("module path names collided: %s", first)
|
||||||
|
}
|
||||||
|
if len(nginxModulePathName(string(make([]byte, 256)))) > 57 {
|
||||||
|
t.Fatal("module path name should remain safe for Docker resource names")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecordNginxModuleBuildFailureKeepsPreviousReadyBuild(t *testing.T) {
|
||||||
|
target := dto.NginxModuleTarget{Key: "target"}
|
||||||
|
ready := dto.NginxModuleBuild{
|
||||||
|
Hash: "ready", Status: nginxModuleStatusReady, Target: target, BuiltAt: time.Now().Add(-time.Hour),
|
||||||
|
}
|
||||||
|
original := []dto.NginxModule{{
|
||||||
|
Name: "example", BuildMode: nginxModuleBuildDynamic,
|
||||||
|
Builds: []dto.NginxModuleBuild{ready},
|
||||||
|
}}
|
||||||
|
failed := dto.NginxModuleBuild{
|
||||||
|
Hash: "candidate", Status: nginxModuleStatusFailed, Target: target, Error: "load failed", BuiltAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
result := recordNginxModuleBuildFailure(original, "example", failed, &ready)
|
||||||
|
|
||||||
|
if len(result[0].Builds) != 1 || result[0].Builds[0].Hash != "ready" {
|
||||||
|
t.Fatalf("previous ready build was replaced: %#v", result[0].Builds)
|
||||||
|
}
|
||||||
|
if result[0].LastError != failed.Error {
|
||||||
|
t.Fatalf("failure metadata was not retained: %#v", result[0])
|
||||||
|
}
|
||||||
|
result[0].Builds[0].Hash = "mutated"
|
||||||
|
if original[0].Builds[0].Hash != "ready" {
|
||||||
|
t.Fatal("module clone shares build state with the original")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasDynamicNginxModuleBuildTask(t *testing.T) {
|
||||||
|
dynamicEnabled := dto.NginxModule{Name: "brotli", Enable: true, BuildMode: nginxModuleBuildDynamic}
|
||||||
|
staticEnabled := dto.NginxModule{Name: "pagespeed", Enable: true, BuildMode: nginxModuleBuildStatic}
|
||||||
|
disabledDynamic := dto.NginxModule{Name: "waf", Enable: false, BuildMode: nginxModuleBuildDynamic}
|
||||||
|
|
||||||
|
if hasDynamicNginxModuleBuildTask(nil, nil) {
|
||||||
|
t.Fatal("empty module list should not require a dynamic build")
|
||||||
|
}
|
||||||
|
if hasDynamicNginxModuleBuildTask([]dto.NginxModule{staticEnabled}, nil) {
|
||||||
|
t.Fatal("static-only modules should not require a dynamic build")
|
||||||
|
}
|
||||||
|
if hasDynamicNginxModuleBuildTask([]dto.NginxModule{dynamicEnabled}, []string{"other"}) {
|
||||||
|
t.Fatal("enabled module outside the selection should not require a dynamic build")
|
||||||
|
}
|
||||||
|
if !hasDynamicNginxModuleBuildTask([]dto.NginxModule{disabledDynamic}, []string{"waf"}) {
|
||||||
|
t.Fatal("selected module should require a dynamic build even when disabled")
|
||||||
|
}
|
||||||
|
if !hasDynamicNginxModuleBuildTask([]dto.NginxModule{dynamicEnabled, staticEnabled}, nil) {
|
||||||
|
t.Fatal("enabled dynamic module should require a dynamic build")
|
||||||
|
}
|
||||||
|
|
||||||
|
legacy := dto.NginxModule{Name: "legacy", Enable: true}
|
||||||
|
if hasDynamicNginxModuleBuildTask([]dto.NginxModule{legacy}, nil) {
|
||||||
|
t.Fatal("legacy module without a build mode stays static and should not require a dynamic build")
|
||||||
|
}
|
||||||
|
if legacy.BuildMode != "" {
|
||||||
|
t.Fatalf("prescan must normalize a copy, got mutated BuildMode %q", legacy.BuildMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNginxModuleStaticBuildErrorHintOnlyForCustomModules(t *testing.T) {
|
||||||
|
if hint := nginxModuleStaticBuildErrorHint(dto.NginxModule{Name: "builtin"}); hint != "" {
|
||||||
|
t.Fatalf("built-in module cannot switch build mode, got hint %q", hint)
|
||||||
|
}
|
||||||
|
if hint := nginxModuleStaticBuildErrorHint(dto.NginxModule{Name: "custom", Custom: true}); hint == "" {
|
||||||
|
t.Fatal("custom module should receive the static-build alternative")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveNginxModuleTargetWithoutBuilder(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.27.1.2"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
_, _, err := resolveNginxModuleTarget(install)
|
||||||
|
if !errors.Is(err, errNginxModuleBuilderMissing) {
|
||||||
|
t.Fatalf("expected builder-missing sentinel, got %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "dynamic module builder not found") {
|
||||||
|
t.Fatalf("unexpected error message: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDynamicNginxModulesFailsWithoutBuilder(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
modules := []dto.NginxModule{{
|
||||||
|
Name: "rtmp", Enable: true, BuildMode: nginxModuleBuildDynamic,
|
||||||
|
}}
|
||||||
|
|
||||||
|
_, err := buildDynamicNginxModules(install, modules, nil, false, "", "", nil)
|
||||||
|
if !errors.Is(err, errNginxModuleBuilderMissing) {
|
||||||
|
t.Fatalf("missing target builder must fail the dynamic build, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeOpenrestyModuleVolumes(t *testing.T) {
|
||||||
|
newService := map[string]interface{}{}
|
||||||
|
oldService := map[string]interface{}{
|
||||||
|
"volumes": []interface{}{
|
||||||
|
"./conf:/etc/nginx/conf.d:ro",
|
||||||
|
"./modules:/usr/local/openresty/nginx/modules/1panel:ro",
|
||||||
|
"./conf/modules-enabled:/usr/local/openresty/nginx/conf/modules-enabled:ro",
|
||||||
|
12345,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeOpenrestyModuleVolumes(newService, oldService)
|
||||||
|
|
||||||
|
merged, ok := newService["volumes"].([]interface{})
|
||||||
|
if !ok || len(merged) != 2 {
|
||||||
|
t.Fatalf("expected two module mounts to be merged, got %#v", newService["volumes"])
|
||||||
|
}
|
||||||
|
for _, volume := range merged {
|
||||||
|
if volume == "./conf:/etc/nginx/conf.d:ro" {
|
||||||
|
t.Fatal("unrelated mount should not be merged")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeOpenrestyModuleVolumesKeepsExisting(t *testing.T) {
|
||||||
|
existing := "./modules:/usr/local/openresty/nginx/modules/1panel:ro"
|
||||||
|
newService := map[string]interface{}{
|
||||||
|
"volumes": []interface{}{existing, map[string]interface{}{"type": "bind"}},
|
||||||
|
}
|
||||||
|
oldService := map[string]interface{}{
|
||||||
|
"volumes": []interface{}{
|
||||||
|
"./modules:/usr/local/openresty/nginx/modules/1panel:ro",
|
||||||
|
"./conf/modules-enabled:/usr/local/openresty/nginx/conf/modules-enabled:ro",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeOpenrestyModuleVolumes(newService, oldService)
|
||||||
|
|
||||||
|
merged, ok := newService["volumes"].([]interface{})
|
||||||
|
if !ok || len(merged) != 3 {
|
||||||
|
t.Fatalf("expected only the missing mount to be appended, got %#v", newService["volumes"])
|
||||||
|
}
|
||||||
|
if merged[0] != existing {
|
||||||
|
t.Fatalf("existing mounts must keep their order, got %#v", merged)
|
||||||
|
}
|
||||||
|
if merged[2] != "./conf/modules-enabled:/usr/local/openresty/nginx/conf/modules-enabled:ro" {
|
||||||
|
t.Fatalf("missing module mount was not appended, got %#v", merged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveNginxModuleBuildMirror(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.27.1.2"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
if err := os.MkdirAll(install.GetPath(), constant.DirPerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := resolveNginxModuleBuildMirror(install, "https://mirror.example.com"); got != "https://mirror.example.com" {
|
||||||
|
t.Fatalf("request mirror should win, got %q", got)
|
||||||
|
}
|
||||||
|
if got := resolveNginxModuleBuildMirror(install, ""); got != "" {
|
||||||
|
t.Fatalf("missing env file should yield an empty mirror, got %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
envContent := "CONTAINER_PACKAGE_URL=https://apt.example.com\nOTHER=value\n"
|
||||||
|
if err := os.WriteFile(install.GetEnvPath(), []byte(envContent), constant.FilePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := resolveNginxModuleBuildMirror(install, ""); got != "https://apt.example.com" {
|
||||||
|
t.Fatalf("env CONTAINER_PACKAGE_URL should be the fallback, got %q", got)
|
||||||
|
}
|
||||||
|
if got := resolveNginxModuleBuildMirror(install, "https://mirror.example.com"); got != "https://mirror.example.com" {
|
||||||
|
t.Fatalf("request mirror should still win over the env value, got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeNginxModuleCatalogStateFixture(t *testing.T, install model.AppInstall, catalog []dto.NginxModule, state string) {
|
||||||
|
t.Helper()
|
||||||
|
buildDir := path.Join(install.GetPath(), nginxModuleBuildDir)
|
||||||
|
if err := os.MkdirAll(buildDir, constant.DirPerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
catalogContent, err := json.Marshal(catalog)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = os.WriteFile(path.Join(buildDir, nginxModuleCatalogFile), catalogContent, constant.FilePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if state != "" {
|
||||||
|
if err = os.WriteFile(path.Join(buildDir, nginxModuleStoreFile), []byte(state), constant.FilePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesBuiltinStateCannotOverrideCatalogDefinition(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
catalog := []dto.NginxModule{{
|
||||||
|
Name: "rtmp", Script: "catalog-script", Packages: []string{"unzip"}, Params: "--add-module=/tmp/rtmp",
|
||||||
|
BuildMode: nginxModuleBuildDynamic, Provider: nginxModuleProviderLocal, LoadOrder: 20,
|
||||||
|
}}
|
||||||
|
state := `[{"name":"rtmp","enable":true,"script":"user-script","params":"--with-user","buildMode":"static","loadOrder":99,"lastError":"failed"}]`
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, state)
|
||||||
|
|
||||||
|
modules, err := loadNginxModules(install)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(modules) != 1 {
|
||||||
|
t.Fatalf("expected one merged module, got %#v", modules)
|
||||||
|
}
|
||||||
|
module := modules[0]
|
||||||
|
if !module.Enable || module.LastError != "failed" {
|
||||||
|
t.Fatalf("builtin state was not applied: %#v", module)
|
||||||
|
}
|
||||||
|
if module.Script != "catalog-script" || module.Params != "--add-module=/tmp/rtmp" ||
|
||||||
|
module.BuildMode != nginxModuleBuildDynamic || module.LoadOrder != 20 {
|
||||||
|
t.Fatalf("builtin definition must come from catalog: %#v", module)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesWithoutStateShowsDisabledCatalogModules(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
catalog := []dto.NginxModule{
|
||||||
|
{Name: "rtmp", Enable: true, BuildMode: nginxModuleBuildDynamic},
|
||||||
|
{Name: "geoip2", BuildMode: nginxModuleBuildDynamic},
|
||||||
|
}
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, "")
|
||||||
|
|
||||||
|
modules, err := loadNginxModules(install)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(modules) != len(catalog) {
|
||||||
|
t.Fatalf("expected all catalog modules, got %#v", modules)
|
||||||
|
}
|
||||||
|
for _, module := range modules {
|
||||||
|
if module.Enable || module.Custom {
|
||||||
|
t.Fatalf("catalog modules must default to disabled built-ins: %#v", module)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveNginxModulesPersistsOnlyBuiltinState(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
catalog := []dto.NginxModule{{
|
||||||
|
Name: "rtmp", Script: "catalog-script", Params: "--add-module=/tmp/rtmp",
|
||||||
|
BuildMode: nginxModuleBuildDynamic, Provider: nginxModuleProviderLocal, LoadOrder: 20,
|
||||||
|
}}
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, "")
|
||||||
|
modules, err := loadNginxModules(install)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
modules[0].Enable = true
|
||||||
|
modules[0].LastError = "failed"
|
||||||
|
|
||||||
|
if err = saveNginxModules(install, modules); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleStoreFile))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var states []map[string]any
|
||||||
|
if err = json.Unmarshal(content, &states); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(states) != 1 || states[0]["name"] != "rtmp" || states[0]["enable"] != true || states[0]["lastError"] != "failed" {
|
||||||
|
t.Fatalf("unexpected builtin state: %s", content)
|
||||||
|
}
|
||||||
|
for _, key := range []string{"script", "packages", "params", "buildMode", "provider", "loadOrder"} {
|
||||||
|
if _, exists := states[0][key]; exists {
|
||||||
|
t.Fatalf("builtin definition field %q leaked into module state: %s", key, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveNginxModulesOmitsPristineBuiltinState(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
catalog := []dto.NginxModule{{Name: "rtmp", BuildMode: nginxModuleBuildDynamic}}
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, "")
|
||||||
|
|
||||||
|
modules, err := loadNginxModules(install)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = saveNginxModules(install, modules); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleStoreFile))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(content)) != "[]" {
|
||||||
|
t.Fatalf("pristine built-in state should not be persisted: %s", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadAndSaveNginxCustomModule(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
state := `[{"name":"custom","custom":true,"script":"prepare","packages":["git"],"params":"--add-module=/tmp/custom","enable":true,"buildMode":"static","provider":"local","loadOrder":100}]`
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, nil, state)
|
||||||
|
|
||||||
|
modules, err := loadNginxModules(install)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(modules) != 1 || !modules[0].Custom {
|
||||||
|
t.Fatalf("custom module source was not restored: %#v", modules)
|
||||||
|
}
|
||||||
|
if modules[0].Script != "prepare" || modules[0].BuildMode != nginxModuleBuildStatic || modules[0].LoadOrder != 100 {
|
||||||
|
t.Fatalf("custom module definition was not restored: %#v", modules[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = saveNginxModules(install, modules); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleStoreFile))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(content), `"custom": true`) || !strings.Contains(string(content), `"buildMode": "static"`) {
|
||||||
|
t.Fatalf("custom module definition was not persisted: %s", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActivateNginxModuleCatalogReplacesCatalogAtomically(t *testing.T) {
|
||||||
|
buildDir := t.TempDir()
|
||||||
|
activePath := path.Join(buildDir, nginxModuleCatalogFile)
|
||||||
|
pendingPath := path.Join(buildDir, nginxModuleCatalogPendingFile)
|
||||||
|
sourcePath := path.Join(buildDir, "target.catalog.json")
|
||||||
|
if err := os.WriteFile(activePath, []byte(`[{"name":"old"}]`), constant.FilePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(sourcePath, []byte(`[{"name":"new"}]`), constant.FilePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stageNginxModuleCatalog(sourcePath, pendingPath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(activePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(content) != `[{"name":"old"}]` {
|
||||||
|
t.Fatalf("staging target catalog changed the active catalog: %s", content)
|
||||||
|
}
|
||||||
|
if err := activateNginxModuleCatalog(pendingPath, activePath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
content, err = os.ReadFile(activePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(content) != `[{"name":"new"}]` {
|
||||||
|
t.Fatalf("active catalog was not replaced: %s", content)
|
||||||
|
}
|
||||||
|
if _, err = os.Stat(pendingPath); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("pending catalog should be consumed, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActivateNginxModuleCatalogRestoresCatalogWhenCommitFails(t *testing.T) {
|
||||||
|
buildDir := t.TempDir()
|
||||||
|
activePath := path.Join(buildDir, nginxModuleCatalogFile)
|
||||||
|
pendingPath := path.Join(buildDir, nginxModuleCatalogPendingFile)
|
||||||
|
if err := os.WriteFile(activePath, []byte(`[{"name":"old"}]`), constant.FilePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(pendingPath, []byte(`[{"name":"new"}]`), constant.FilePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
commitErr := errors.New("save install failed")
|
||||||
|
|
||||||
|
err := activateNginxModuleCatalogAndCommit(pendingPath, activePath, func() error {
|
||||||
|
return commitErr
|
||||||
|
})
|
||||||
|
if !errors.Is(err, commitErr) {
|
||||||
|
t.Fatalf("expected commit error, got %v", err)
|
||||||
|
}
|
||||||
|
content, readErr := os.ReadFile(activePath)
|
||||||
|
if readErr != nil {
|
||||||
|
t.Fatal(readErr)
|
||||||
|
}
|
||||||
|
if string(content) != `[{"name":"old"}]` {
|
||||||
|
t.Fatalf("failed upgrade must restore the old catalog: %s", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesRejectsDuplicateCatalogNames(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
catalog := []dto.NginxModule{
|
||||||
|
{Name: "rtmp", Params: "--add-module=/tmp/one", BuildMode: nginxModuleBuildDynamic},
|
||||||
|
{Name: "rtmp", Params: "--add-module=/tmp/two", BuildMode: nginxModuleBuildDynamic},
|
||||||
|
}
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, "")
|
||||||
|
|
||||||
|
if _, err := loadNginxModules(install); err == nil || !strings.Contains(err.Error(), "duplicate") {
|
||||||
|
t.Fatalf("expected duplicate catalog name error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesRejectsDuplicateStateNames(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
catalog := []dto.NginxModule{{Name: "rtmp", Params: "--add-module=/tmp/rtmp", BuildMode: nginxModuleBuildDynamic}}
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, `[{"name":"rtmp","enable":true},{"name":"rtmp","enable":false}]`)
|
||||||
|
|
||||||
|
if _, err := loadNginxModules(install); err == nil || !strings.Contains(err.Error(), "duplicate") {
|
||||||
|
t.Fatalf("expected duplicate state name error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesRejectsOrphanBuiltinState(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, nil, `[{"name":"removed","enable":true}]`)
|
||||||
|
|
||||||
|
if _, err := loadNginxModules(install); err == nil || !strings.Contains(err.Error(), "missing from the module catalog") {
|
||||||
|
t.Fatalf("expected orphan builtin state error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesRejectsCustomCatalogNameConflict(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
catalog := []dto.NginxModule{{Name: "rtmp", BuildMode: nginxModuleBuildDynamic}}
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, `[{"name":"rtmp","custom":true,"enable":true}]`)
|
||||||
|
|
||||||
|
if _, err := loadNginxModules(install); err == nil || !strings.Contains(err.Error(), "conflicts") {
|
||||||
|
t.Fatalf("expected custom/catalog conflict error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesRejectsInvalidBuildModes(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, []dto.NginxModule{{
|
||||||
|
Name: "rtmp", BuildMode: "auto",
|
||||||
|
}}, "")
|
||||||
|
if _, err := loadNginxModules(install); err == nil || !strings.Contains(err.Error(), "invalid build mode") {
|
||||||
|
t.Fatalf("expected invalid catalog build mode error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, nil, `[{"name":"custom","custom":true,"buildMode":"auto"}]`)
|
||||||
|
if _, err := loadNginxModules(install); err == nil || !strings.Contains(err.Error(), "invalid build mode") {
|
||||||
|
t.Fatalf("expected invalid custom build mode error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyNginxModuleUpdateKeepsBuiltinDefinitionImmutable(t *testing.T) {
|
||||||
|
modules := []dto.NginxModule{{
|
||||||
|
Name: "rtmp", Script: "catalog-script", Params: "--add-module=/tmp/rtmp",
|
||||||
|
BuildMode: nginxModuleBuildDynamic, Provider: nginxModuleProviderLocal, LoadOrder: 20,
|
||||||
|
}}
|
||||||
|
req := request.NginxModuleUpdate{
|
||||||
|
Operate: nginxModuleOperateUpdate, Name: "rtmp", Enable: true, Script: "user-script",
|
||||||
|
Params: "--with-user", BuildMode: nginxModuleBuildStatic, Provider: "prebuilt", LoadOrder: 99,
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, _, err := applyNginxModuleUpdate(modules, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !updated[0].Enable {
|
||||||
|
t.Fatal("builtin enable state was not updated")
|
||||||
|
}
|
||||||
|
if updated[0].Script != "catalog-script" || updated[0].Params != "--add-module=/tmp/rtmp" ||
|
||||||
|
updated[0].BuildMode != nginxModuleBuildDynamic || updated[0].Provider != nginxModuleProviderLocal ||
|
||||||
|
updated[0].LoadOrder != 20 {
|
||||||
|
t.Fatalf("builtin definition was modified: %#v", updated[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyNginxModuleUpdateRejectsBuiltinDelete(t *testing.T) {
|
||||||
|
modules := []dto.NginxModule{{Name: "rtmp", BuildMode: nginxModuleBuildDynamic}}
|
||||||
|
|
||||||
|
_, _, err := applyNginxModuleUpdate(modules, request.NginxModuleUpdate{
|
||||||
|
Operate: nginxModuleOperateDelete, Name: "rtmp",
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "cannot be deleted") {
|
||||||
|
t.Fatalf("expected builtin delete error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyNginxModuleUpdateCreatesAndDeletesCustomModule(t *testing.T) {
|
||||||
|
if _, _, err := applyNginxModuleUpdate(nil, request.NginxModuleUpdate{
|
||||||
|
Operate: nginxModuleOperateCreate, Name: "invalid", BuildMode: "auto",
|
||||||
|
}); err == nil || !strings.Contains(err.Error(), "invalid build mode") {
|
||||||
|
t.Fatalf("expected invalid custom build mode error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
created, _, err := applyNginxModuleUpdate(nil, request.NginxModuleUpdate{
|
||||||
|
Operate: nginxModuleOperateCreate, Name: "custom", Script: "prepare", Packages: "git,curl",
|
||||||
|
Params: "--add-module=/tmp/custom", Enable: true, BuildMode: nginxModuleBuildStatic,
|
||||||
|
Provider: nginxModuleProviderLocal, LoadOrder: 100,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(created) != 1 || !created[0].Custom || created[0].BuildMode != nginxModuleBuildStatic {
|
||||||
|
t.Fatalf("custom module was not created correctly: %#v", created)
|
||||||
|
}
|
||||||
|
updated, _, err := applyNginxModuleUpdate(created, request.NginxModuleUpdate{
|
||||||
|
Operate: nginxModuleOperateUpdate, Name: "custom", Script: "updated",
|
||||||
|
Params: "--add-module=/tmp/custom-v2", Enable: false, BuildMode: nginxModuleBuildDynamic,
|
||||||
|
Provider: nginxModuleProviderLocal, LoadOrder: 75,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if updated[0].Script != "updated" || updated[0].BuildMode != nginxModuleBuildDynamic || updated[0].LoadOrder != 75 {
|
||||||
|
t.Fatalf("custom module was not updated correctly: %#v", updated[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining, deleted, err := applyNginxModuleUpdate(updated, request.NginxModuleUpdate{
|
||||||
|
Operate: nginxModuleOperateDelete, Name: "custom",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(remaining) != 0 || deleted == nil || deleted.Name != "custom" {
|
||||||
|
t.Fatalf("custom module was not removed: remaining=%#v deleted=%#v", remaining, deleted)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,411 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
stdpprof "runtime/pprof"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
profile "github.com/google/pprof/profile"
|
||||||
|
"github.com/shirou/gopsutil/v4/process"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
diagnosticsDefaultDuration = 15
|
||||||
|
diagnosticsMaxProfileSize = 64 * 1024 * 1024
|
||||||
|
diagnosticsMaxSnapshotSize = 16 * 1024 * 1024
|
||||||
|
diagnosticsDetailedGoroutines = 10_000
|
||||||
|
diagnosticsBlockProfileRate = 1_000_000
|
||||||
|
diagnosticsMaxGroups = 200
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
runtimeDiagnosticsInstance = &RuntimeDiagnosticsService{}
|
||||||
|
goroutineHeaderPattern = regexp.MustCompile(`^goroutine \d+ \[([^]]+)\]:$`)
|
||||||
|
goroutineArgPattern = regexp.MustCompile(`0x[0-9a-fA-F]+`)
|
||||||
|
errProfileSizeLimit = errors.New("runtime profile exceeds the 64 MiB size limit")
|
||||||
|
)
|
||||||
|
|
||||||
|
type cappedWriter struct {
|
||||||
|
writer io.Writer
|
||||||
|
remaining int64
|
||||||
|
exceeded bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *cappedWriter) Write(data []byte) (int, error) {
|
||||||
|
if int64(len(data)) <= w.remaining {
|
||||||
|
n, err := w.writer.Write(data)
|
||||||
|
w.remaining -= int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
w.exceeded = true
|
||||||
|
if w.remaining <= 0 {
|
||||||
|
return 0, errProfileSizeLimit
|
||||||
|
}
|
||||||
|
allowed := int(w.remaining)
|
||||||
|
n, err := w.writer.Write(data[:allowed])
|
||||||
|
w.remaining -= int64(n)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
return n, errProfileSizeLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
type IRuntimeDiagnosticsService interface {
|
||||||
|
Summary() (dto.RuntimeDiagnosticsSummary, error)
|
||||||
|
Goroutines() (dto.RuntimeGoroutineSnapshot, error)
|
||||||
|
CreateProfile(req dto.RuntimeProfileCreate) (RuntimeProfileResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuntimeProfileResult struct {
|
||||||
|
Path string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuntimeDiagnosticsService struct {
|
||||||
|
captureMu sync.Mutex
|
||||||
|
processMu sync.Mutex
|
||||||
|
process *process.Process
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIRuntimeDiagnosticsService() IRuntimeDiagnosticsService {
|
||||||
|
return runtimeDiagnosticsInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RuntimeDiagnosticsService) Summary() (dto.RuntimeDiagnosticsSummary, error) {
|
||||||
|
rss, err := s.processRSS()
|
||||||
|
if err != nil {
|
||||||
|
return dto.RuntimeDiagnosticsSummary{}, err
|
||||||
|
}
|
||||||
|
var stats runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&stats)
|
||||||
|
return dto.RuntimeDiagnosticsSummary{
|
||||||
|
RSS: rss,
|
||||||
|
HeapAlloc: stats.HeapAlloc,
|
||||||
|
HeapObjects: stats.HeapObjects,
|
||||||
|
Goroutines: runtime.NumGoroutine(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RuntimeDiagnosticsService) Goroutines() (dto.RuntimeGoroutineSnapshot, error) {
|
||||||
|
total := runtime.NumGoroutine()
|
||||||
|
if total > diagnosticsDetailedGoroutines {
|
||||||
|
groups, truncated := compactGoroutineSnapshot(total)
|
||||||
|
return dto.RuntimeGoroutineSnapshot{
|
||||||
|
Total: total, GroupCount: len(groups), Truncated: truncated, CapturedAt: time.Now(), Goroutines: groups,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
var data bytes.Buffer
|
||||||
|
writer := &cappedWriter{writer: &data, remaining: diagnosticsMaxSnapshotSize}
|
||||||
|
if err := stdpprof.Lookup("goroutine").WriteTo(writer, 2); err != nil {
|
||||||
|
groups, truncated := compactGoroutineSnapshot(total)
|
||||||
|
return dto.RuntimeGoroutineSnapshot{
|
||||||
|
Total: total, GroupCount: len(groups), Truncated: truncated, CapturedAt: time.Now(), Goroutines: groups,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
groups, truncated := parseGoroutineDump(&data, diagnosticsMaxGroups)
|
||||||
|
result := dto.RuntimeGoroutineSnapshot{
|
||||||
|
Total: total,
|
||||||
|
GroupCount: len(groups),
|
||||||
|
Truncated: truncated,
|
||||||
|
CapturedAt: time.Now(),
|
||||||
|
}
|
||||||
|
result.Goroutines = groups
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RuntimeDiagnosticsService) CreateProfile(req dto.RuntimeProfileCreate) (RuntimeProfileResult, error) {
|
||||||
|
if !s.captureMu.TryLock() {
|
||||||
|
return RuntimeProfileResult{}, errors.New("another runtime profile is being captured")
|
||||||
|
}
|
||||||
|
defer s.captureMu.Unlock()
|
||||||
|
return captureRuntimeProfile(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func captureRuntimeProfile(req dto.RuntimeProfileCreate) (result RuntimeProfileResult, err error) {
|
||||||
|
duration := req.Duration
|
||||||
|
if duration == 0 {
|
||||||
|
duration = diagnosticsDefaultDuration
|
||||||
|
}
|
||||||
|
if duration < 5 || duration > 30 {
|
||||||
|
return result, errors.New("profile duration must be between 5 and 30 seconds")
|
||||||
|
}
|
||||||
|
if req.Type == "heap" || req.Type == "goroutine" {
|
||||||
|
duration = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
name := fmt.Sprintf("%s-%s-%ds.pb.gz", req.Type, newRuntimeEventID(), duration)
|
||||||
|
file, err := os.CreateTemp("", "1panel-runtime-profile-*.tmp")
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
writer := &cappedWriter{writer: file, remaining: diagnosticsMaxProfileSize}
|
||||||
|
removeOnError := true
|
||||||
|
defer func() {
|
||||||
|
_ = file.Close()
|
||||||
|
if removeOnError {
|
||||||
|
_ = os.Remove(file.Name())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
switch req.Type {
|
||||||
|
case "cpu":
|
||||||
|
if err = stdpprof.StartCPUProfile(writer); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(duration) * time.Second)
|
||||||
|
stdpprof.StopCPUProfile()
|
||||||
|
case "heap":
|
||||||
|
err = stdpprof.Lookup("heap").WriteTo(writer, 0)
|
||||||
|
case "goroutine":
|
||||||
|
err = stdpprof.Lookup("goroutine").WriteTo(writer, 0)
|
||||||
|
case "mutex":
|
||||||
|
err = captureWindowedRuntimeProfile("mutex", duration, writer, func() func() {
|
||||||
|
previous := runtime.SetMutexProfileFraction(5)
|
||||||
|
return func() { runtime.SetMutexProfileFraction(previous) }
|
||||||
|
})
|
||||||
|
case "block":
|
||||||
|
err = captureWindowedRuntimeProfile("block", duration, writer, func() func() {
|
||||||
|
runtime.SetBlockProfileRate(diagnosticsBlockProfileRate)
|
||||||
|
return func() { runtime.SetBlockProfileRate(0) }
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
return result, errors.New("unsupported runtime profile type")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
if writer.exceeded {
|
||||||
|
return result, errProfileSizeLimit
|
||||||
|
}
|
||||||
|
if err = file.Close(); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
removeOnError = false
|
||||||
|
return RuntimeProfileResult{Path: file.Name(), Name: name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func captureWindowedRuntimeProfile(name string, duration int, writer io.Writer, enable func() func()) error {
|
||||||
|
restore := enable()
|
||||||
|
sampling := true
|
||||||
|
defer func() {
|
||||||
|
if sampling {
|
||||||
|
restore()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
before, err := readRuntimeProfile(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
startedAt := time.Now()
|
||||||
|
time.Sleep(time.Duration(duration) * time.Second)
|
||||||
|
restore()
|
||||||
|
sampling = false
|
||||||
|
after, err := readRuntimeProfile(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delta, err := diffRuntimeProfiles(before, after, startedAt, time.Duration(duration)*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return delta.Write(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRuntimeProfile(name string) (*profile.Profile, error) {
|
||||||
|
var data bytes.Buffer
|
||||||
|
writer := &cappedWriter{writer: &data, remaining: diagnosticsMaxProfileSize}
|
||||||
|
if err := stdpprof.Lookup(name).WriteTo(writer, 0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if writer.exceeded {
|
||||||
|
return nil, errProfileSizeLimit
|
||||||
|
}
|
||||||
|
return profile.Parse(&data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func diffRuntimeProfiles(before, after *profile.Profile, startedAt time.Time, duration time.Duration) (*profile.Profile, error) {
|
||||||
|
baseline := before.Copy()
|
||||||
|
baseline.Scale(-1)
|
||||||
|
delta, err := profile.Merge([]*profile.Profile{after, baseline})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
delta.TimeNanos = startedAt.UnixNano()
|
||||||
|
delta.DurationNanos = duration.Nanoseconds()
|
||||||
|
return delta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RuntimeDiagnosticsService) processRSS() (uint64, error) {
|
||||||
|
s.processMu.Lock()
|
||||||
|
defer s.processMu.Unlock()
|
||||||
|
if s.process == nil {
|
||||||
|
proc, err := process.NewProcess(int32(os.Getpid()))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
s.process = proc
|
||||||
|
}
|
||||||
|
memoryInfo, err := s.process.MemoryInfo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if memoryInfo == nil {
|
||||||
|
return 0, errors.New("process memory information is unavailable")
|
||||||
|
}
|
||||||
|
return memoryInfo.RSS, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRuntimeEventID() string {
|
||||||
|
return time.Now().Format("20060102-150405.000000000")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGoroutineDump(reader io.Reader, maxGroups int) ([]dto.RuntimeGoroutineGroup, bool) {
|
||||||
|
type groupValue struct {
|
||||||
|
state string
|
||||||
|
top string
|
||||||
|
stack []string
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
groups := make(map[string]*groupValue)
|
||||||
|
truncated := false
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
scanner.Buffer(make([]byte, 64*1024), 4*1024*1024)
|
||||||
|
var block []string
|
||||||
|
flush := func() {
|
||||||
|
if len(block) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
matches := goroutineHeaderPattern.FindStringSubmatch(block[0])
|
||||||
|
state := "unknown"
|
||||||
|
if len(matches) == 2 {
|
||||||
|
state = matches[1]
|
||||||
|
}
|
||||||
|
stack := append([]string(nil), block[1:]...)
|
||||||
|
if len(stack) > 40 {
|
||||||
|
stack = stack[:40]
|
||||||
|
}
|
||||||
|
top := "runtime"
|
||||||
|
functionLines := make([]string, 0, len(stack)/2)
|
||||||
|
for _, line := range stack {
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
if trimmed == "" || strings.HasPrefix(trimmed, "/") || strings.HasPrefix(trimmed, "created by ") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
normalized := goroutineArgPattern.ReplaceAllString(trimmed, "0x…")
|
||||||
|
functionLines = append(functionLines, normalized)
|
||||||
|
if top == "runtime" {
|
||||||
|
top = strings.Split(normalized, "(")[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signature := state + "\n" + strings.Join(functionLines, "\n")
|
||||||
|
if existing, ok := groups[signature]; ok {
|
||||||
|
existing.count++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(groups) >= maxGroups {
|
||||||
|
truncated = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
groups[signature] = &groupValue{state: state, top: top, stack: stack, count: 1}
|
||||||
|
}
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "]:") {
|
||||||
|
flush()
|
||||||
|
block = []string{line}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(block) > 0 {
|
||||||
|
block = append(block, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scanner.Err() != nil {
|
||||||
|
truncated = true
|
||||||
|
}
|
||||||
|
flush()
|
||||||
|
|
||||||
|
result := make([]dto.RuntimeGoroutineGroup, 0, len(groups))
|
||||||
|
for _, group := range groups {
|
||||||
|
result = append(result, dto.RuntimeGoroutineGroup{State: group.state, Top: group.top, Count: group.count, Stack: group.stack})
|
||||||
|
}
|
||||||
|
sort.Slice(result, func(i, j int) bool {
|
||||||
|
if result[i].Count == result[j].Count {
|
||||||
|
return result[i].Top < result[j].Top
|
||||||
|
}
|
||||||
|
return result[i].Count > result[j].Count
|
||||||
|
})
|
||||||
|
return result, truncated
|
||||||
|
}
|
||||||
|
|
||||||
|
func compactGoroutineSnapshot(initialSize int) ([]dto.RuntimeGoroutineGroup, bool) {
|
||||||
|
records := make([]runtime.StackRecord, initialSize+32)
|
||||||
|
count, ok := runtime.GoroutineProfile(records)
|
||||||
|
if !ok {
|
||||||
|
records = make([]runtime.StackRecord, count+32)
|
||||||
|
count, ok = runtime.GoroutineProfile(records)
|
||||||
|
}
|
||||||
|
truncated := !ok
|
||||||
|
if count > len(records) {
|
||||||
|
count = len(records)
|
||||||
|
truncated = true
|
||||||
|
}
|
||||||
|
records = records[:count]
|
||||||
|
type compactGroup struct {
|
||||||
|
top string
|
||||||
|
stack []string
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
groups := make(map[string]*compactGroup)
|
||||||
|
for _, record := range records {
|
||||||
|
frames := runtime.CallersFrames(record.Stack())
|
||||||
|
stack := make([]string, 0, 16)
|
||||||
|
functions := make([]string, 0, 8)
|
||||||
|
top := "runtime"
|
||||||
|
for {
|
||||||
|
frame, more := frames.Next()
|
||||||
|
if frame.Function != "" {
|
||||||
|
if top == "runtime" {
|
||||||
|
top = frame.Function
|
||||||
|
}
|
||||||
|
functions = append(functions, frame.Function)
|
||||||
|
stack = append(stack, frame.Function, fmt.Sprintf("\t%s:%d", frame.File, frame.Line))
|
||||||
|
}
|
||||||
|
if !more || len(functions) >= 20 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signature := strings.Join(functions, "\n")
|
||||||
|
if existing, exists := groups[signature]; exists {
|
||||||
|
existing.count++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(groups) >= diagnosticsMaxGroups {
|
||||||
|
truncated = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
groups[signature] = &compactGroup{top: top, stack: stack, count: 1}
|
||||||
|
}
|
||||||
|
result := make([]dto.RuntimeGoroutineGroup, 0, len(groups))
|
||||||
|
for _, group := range groups {
|
||||||
|
result = append(result, dto.RuntimeGoroutineGroup{State: "profiled", Top: group.top, Count: group.count, Stack: group.stack})
|
||||||
|
}
|
||||||
|
sort.Slice(result, func(i, j int) bool {
|
||||||
|
if result[i].Count == result[j].Count {
|
||||||
|
return result[i].Top < result[j].Top
|
||||||
|
}
|
||||||
|
return result[i].Count > result[j].Count
|
||||||
|
})
|
||||||
|
return result, truncated
|
||||||
|
}
|
||||||
@@ -193,16 +193,20 @@ func handleSnapshot(req dto.SnapshotCreate, taskItem *task.Task, jobID, retry, t
|
|||||||
req.InterruptStep = ""
|
req.InterruptStep = ""
|
||||||
}
|
}
|
||||||
if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapUpload" {
|
if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapUpload" {
|
||||||
|
uploadTimeout := time.Duration(timeout) * time.Second
|
||||||
taskItem.AddSubTaskWithAliasAndOps(
|
taskItem.AddSubTaskWithAliasAndOps(
|
||||||
"SnapUpload",
|
"SnapUpload",
|
||||||
func(t *task.Task) error {
|
func(t *task.Task) error {
|
||||||
return snapUpload(itemHelper, req.SourceAccountIDs, req.DownloadAccountID, retry, fmt.Sprintf("%s.tar.gz", rootDir))
|
return snapUpload(itemHelper, req.SourceAccountIDs, req.DownloadAccountID, retry, fmt.Sprintf("%s.tar.gz", rootDir), uploadTimeout)
|
||||||
}, nil, int(retry), time.Duration(timeout)*time.Second,
|
}, nil, int(retry), uploadTimeout,
|
||||||
)
|
)
|
||||||
req.InterruptStep = ""
|
req.InterruptStep = ""
|
||||||
}
|
}
|
||||||
if err := taskItem.Execute(); err != nil {
|
if err := taskItem.Execute(); err != nil {
|
||||||
_ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error(), "interrupt_step": taskItem.Task.CurrentStep})
|
_ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error(), "interrupt_step": taskItem.Task.CurrentStep})
|
||||||
|
if jobID != 0 {
|
||||||
|
cleanupFailedCronjobSnapshot(req, rootDir)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusSuccess, "interrupt_step": ""})
|
_ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusSuccess, "interrupt_step": ""})
|
||||||
@@ -210,6 +214,29 @@ func handleSnapshot(req dto.SnapshotCreate, taskItem *task.Task, jobID, retry, t
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanupFailedCronjobSnapshot(req dto.SnapshotCreate, rootDir string) {
|
||||||
|
if err := os.RemoveAll(rootDir); err != nil {
|
||||||
|
global.LOG.Errorf("remove failed cronjob snapshot directory %s failed, err: %v", rootDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := path.Base(rootDir) + ".tar.gz"
|
||||||
|
filePath := path.Join("system_snapshot", fileName)
|
||||||
|
if err := os.Remove(path.Join(global.Dir.LocalBackupDir, "tmp/system", fileName)); err != nil && !os.IsNotExist(err) {
|
||||||
|
global.LOG.Errorf("remove failed cronjob snapshot file %s failed, err: %v", filePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts := NewBackupClientMap(strings.Split(req.SourceAccountIDs, ","))
|
||||||
|
for _, account := range accounts {
|
||||||
|
if !account.isOk {
|
||||||
|
global.LOG.Errorf("remove failed cronjob snapshot file %s from %s failed, err: %s", filePath, account.name, account.message)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := account.client.Delete(path.Join(account.backupPath, filePath)); err != nil {
|
||||||
|
global.LOG.Errorf("remove failed cronjob snapshot file %s from %s failed, err: %v", filePath, account.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type snapHelper struct {
|
type snapHelper struct {
|
||||||
SnapID uint
|
SnapID uint
|
||||||
SnapName string
|
SnapName string
|
||||||
@@ -561,15 +588,36 @@ func snapCompress(snap snapHelper, rootDir string, secret string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapUpload(snap snapHelper, accounts string, downloadID, retry uint, file string) error {
|
func snapUpload(snap snapHelper, accounts string, downloadID, retry uint, file string, timeout time.Duration) error {
|
||||||
snap.Task.Log("---------------------- 8 / 8 ----------------------")
|
snap.Task.Log("---------------------- 8 / 8 ----------------------")
|
||||||
snap.Task.LogStart(i18n.GetMsgByKey("SnapUpload"))
|
snap.Task.LogStart(i18n.GetMsgByKey("SnapUpload"))
|
||||||
|
|
||||||
src := path.Join(global.Dir.LocalBackupDir, "tmp/system", path.Base(file))
|
src := path.Join(global.Dir.LocalBackupDir, "tmp/system", path.Base(file))
|
||||||
dst := path.Join("system_snapshot", path.Base(file))
|
dst := path.Join("system_snapshot", path.Base(file))
|
||||||
accountMap := NewBackupClientMap(strings.Split(accounts, ","))
|
ctx := context.Background()
|
||||||
|
if timeout > 0 {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, timeout)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
downloadAccount := fmt.Sprintf("%d", downloadID)
|
||||||
|
accountMap := NewBackupClientMapWithContext(ctx, []string{downloadAccount})
|
||||||
if !accountMap[fmt.Sprintf("%d", downloadID)].isOk {
|
if !accountMap[fmt.Sprintf("%d", downloadID)].isOk {
|
||||||
return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", downloadID)].message))
|
return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", downloadID)].message))
|
||||||
}
|
}
|
||||||
return uploadWithMap(snap.Task, accountMap, src, dst, accounts, downloadID, retry)
|
remainingAccounts := make([]string, 0)
|
||||||
|
for _, account := range strings.Split(accounts, ",") {
|
||||||
|
if account == "" || account == downloadAccount {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
remainingAccounts = append(remainingAccounts, account)
|
||||||
|
}
|
||||||
|
if err := uploadWithMapWithContext(ctx, snap.Task, accountMap, src, dst, downloadAccount, downloadID, retry, false, len(remainingAccounts) == 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(remainingAccounts) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
optionalMap := NewBackupClientMapWithContext(ctx, remainingAccounts)
|
||||||
|
return uploadWithMapWithContext(ctx, snap.Task, optionalMap, src, dst, strings.Join(remainingAccounts, ","), downloadID, retry, false, true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ type ISSHService interface {
|
|||||||
|
|
||||||
LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []dto.SSHHistory, error)
|
LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []dto.SSHHistory, error)
|
||||||
ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string, error)
|
ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string, error)
|
||||||
|
CleanLog() error
|
||||||
|
|
||||||
SyncRootCert() error
|
SyncRootCert() error
|
||||||
CreateRootCert(req dto.RootCertOperate) error
|
CreateRootCert(req dto.RootCertOperate) error
|
||||||
@@ -658,6 +659,15 @@ type sshFileItem struct {
|
|||||||
Year int
|
Year int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSSHLogFileName(name string) bool {
|
||||||
|
for _, baseName := range []string{"auth.log", "secure"} {
|
||||||
|
if name == baseName || strings.HasPrefix(name, baseName+".") || strings.HasPrefix(name, baseName+"-") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []dto.SSHHistory, error) {
|
func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []dto.SSHHistory, error) {
|
||||||
var fileList []sshFileItem
|
var fileList []sshFileItem
|
||||||
var data []dto.SSHHistory
|
var data []dto.SSHHistory
|
||||||
@@ -667,10 +677,16 @@ func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []d
|
|||||||
return 0, data, err
|
return 0, data, err
|
||||||
}
|
}
|
||||||
for _, item := range fileItems {
|
for _, item := range fileItems {
|
||||||
if item.IsDir() || (!strings.HasPrefix(item.Name(), "secure") && !strings.HasPrefix(item.Name(), "auth.log")) {
|
if item.IsDir() || !isSSHLogFileName(item.Name()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
info, err := item.Info()
|
||||||
|
if err != nil {
|
||||||
|
return 0, data, err
|
||||||
|
}
|
||||||
|
if !info.Mode().IsRegular() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
info, _ := item.Info()
|
|
||||||
itemPath := path.Join(baseDir, info.Name())
|
itemPath := path.Join(baseDir, info.Name())
|
||||||
if strings.HasSuffix(itemPath, ".gz") {
|
if strings.HasSuffix(itemPath, ".gz") {
|
||||||
if _, err := os.Stat(strings.TrimSuffix(itemPath, ".gz")); err == nil {
|
if _, err := os.Stat(strings.TrimSuffix(itemPath, ".gz")); err == nil {
|
||||||
@@ -725,6 +741,41 @@ func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []d
|
|||||||
return int64(total), data, nil
|
return int64(total), data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *SSHService) CleanLog() error {
|
||||||
|
return cleanSSHLogFiles("/var/log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanSSHLogFiles(baseDir string) error {
|
||||||
|
fileItems, err := os.ReadDir(baseDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, item := range fileItems {
|
||||||
|
if item.IsDir() || !isSSHLogFileName(item.Name()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
info, err := item.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !info.Mode().IsRegular() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
itemPath := path.Join(baseDir, item.Name())
|
||||||
|
if item.Name() == "auth.log" || item.Name() == "secure" {
|
||||||
|
if err := os.Truncate(itemPath, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := os.Remove(itemPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *SSHService) ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string, error) {
|
func (u *SSHService) ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string, error) {
|
||||||
_, logs, err := u.LoadLog(ctx, req)
|
_, logs, err := u.LoadLog(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -715,11 +715,18 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
|
|||||||
if mysqlDB.ID > 0 {
|
if mysqlDB.ID > 0 {
|
||||||
deleteReq := dto.MysqlDBDelete{
|
deleteReq := dto.MysqlDBDelete{
|
||||||
ID: mysqlDB.ID,
|
ID: mysqlDB.ID,
|
||||||
|
Type: website.DbType,
|
||||||
Database: mysqlDB.MysqlName,
|
Database: mysqlDB.MysqlName,
|
||||||
ForceDelete: req.ForceDelete,
|
ForceDelete: req.ForceDelete,
|
||||||
}
|
}
|
||||||
if err = NewIMysqlService().Delete(context.TODO(), deleteReq); err != nil && !req.ForceDelete {
|
if err = deleteMysqlDatabaseForResourceOwner(
|
||||||
return err
|
context.TODO(),
|
||||||
|
deleteReq,
|
||||||
|
dto.DBResource{Type: constant.TypeWebsite, Name: website.PrimaryDomain},
|
||||||
|
); err != nil {
|
||||||
|
if isMysqlDatabaseResourceInUseError(err) || !req.ForceDelete {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case constant.AppPostgresql, constant.AppPostgres:
|
case constant.AppPostgresql, constant.AppPostgres:
|
||||||
|
|||||||
@@ -169,8 +169,8 @@ func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, e
|
|||||||
}
|
}
|
||||||
taskRepo := repo.NewITaskRepo()
|
taskRepo := repo.NewITaskRepo()
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
global.TaskCtxMap[taskID] = cancel
|
global.RegisterTaskCancel(taskID, cancel)
|
||||||
task := &Task{TaskCtx: ctx, Name: name, logFile: logFile, Logger: logger, taskRepo: taskRepo, Task: taskModel}
|
task := &Task{TaskCtx: ctx, TaskID: taskID, Name: name, logFile: logFile, Logger: logger, taskRepo: taskRepo, Task: taskModel}
|
||||||
return task, nil
|
return task, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ func ReNewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task,
|
|||||||
logger.SetOutput(logFile)
|
logger.SetOutput(logFile)
|
||||||
logger.Print("\n --------------------------------------------------- \n")
|
logger.Print("\n --------------------------------------------------- \n")
|
||||||
taskItem.Status = constant.StatusExecuting
|
taskItem.Status = constant.StatusExecuting
|
||||||
task := &Task{Name: name, logFile: logFile, Logger: logger, taskRepo: taskRepo, Task: &taskItem}
|
task := &Task{TaskID: taskID, Name: name, logFile: logFile, Logger: logger, taskRepo: taskRepo, Task: &taskItem}
|
||||||
task.updateTask(&taskItem)
|
task.updateTask(&taskItem)
|
||||||
return task, nil
|
return task, nil
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ func (t *Task) AddSubTaskWithIgnoreErr(name string, action ActionFunc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubTask) Execute() error {
|
func (s *SubTask) Execute() error {
|
||||||
defer delete(global.TaskCtxMap, s.RootTask.TaskID)
|
defer global.RemoveTaskCancel(s.RootTask.TaskID)
|
||||||
subTaskName := s.Name
|
subTaskName := s.Name
|
||||||
if s.Name == "" {
|
if s.Name == "" {
|
||||||
subTaskName = i18n.GetMsgByKey("SubTask")
|
subTaskName = i18n.GetMsgByKey("SubTask")
|
||||||
@@ -302,7 +302,11 @@ func (t *Task) Execute() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t.Task.ErrorMsg = err.Error()
|
t.Task.ErrorMsg = err.Error()
|
||||||
t.Task.Status = constant.StatusFailed
|
if errors.Is(err, context.Canceled) {
|
||||||
|
t.Task.Status = constant.StatusCanceled
|
||||||
|
} else {
|
||||||
|
t.Task.Status = constant.StatusFailed
|
||||||
|
}
|
||||||
for _, rollback := range t.Rollbacks {
|
for _, rollback := range t.Rollbacks {
|
||||||
rollback(t)
|
rollback(t)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1283,6 +1283,13 @@
|
|||||||
"formatZH": "操作 AI 网关服务 [operate]",
|
"formatZH": "操作 AI 网关服务 [operate]",
|
||||||
"formatEN": "operate AI proxy service [operate]"
|
"formatEN": "operate AI proxy service [operate]"
|
||||||
},
|
},
|
||||||
|
"/core/enterprise/ai-proxy/settings/logs/clear": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "清空 AI 网关日志",
|
||||||
|
"formatEN": "clear AI proxy logs"
|
||||||
|
},
|
||||||
"/core/enterprise/ai-proxy/settings/update": {
|
"/core/enterprise/ai-proxy/settings/update": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
@@ -1290,6 +1297,50 @@
|
|||||||
"formatZH": "更新 AI 网关设置",
|
"formatZH": "更新 AI 网关设置",
|
||||||
"formatEN": "update AI proxy settings"
|
"formatEN": "update AI proxy settings"
|
||||||
},
|
},
|
||||||
|
"/core/enterprise/ai-proxy/smart-route/policy/update": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "更新 AI 网关智能路由策略",
|
||||||
|
"formatEN": "update AI proxy smart route policy"
|
||||||
|
},
|
||||||
|
"/core/enterprise/ai-proxy/smart-route/samples/create": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"type",
|
||||||
|
"label"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建 AI 网关智能路由样本 [type] [label]",
|
||||||
|
"formatEN": "create AI proxy smart route sample [type] [label]"
|
||||||
|
},
|
||||||
|
"/core/enterprise/ai-proxy/smart-route/samples/delete": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "删除 AI 网关智能路由样本 [id]",
|
||||||
|
"formatEN": "delete AI proxy smart route sample [id]"
|
||||||
|
},
|
||||||
|
"/core/enterprise/ai-proxy/smart-route/samples/rebuild": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "重建 AI 网关智能路由样本向量",
|
||||||
|
"formatEN": "rebuild AI proxy smart route sample vectors"
|
||||||
|
},
|
||||||
|
"/core/enterprise/ai-proxy/smart-route/samples/update": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"label"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "更新 AI 网关智能路由样本 [id] [type] [label]",
|
||||||
|
"formatEN": "update AI proxy smart route sample [id] [type] [label]"
|
||||||
|
},
|
||||||
"/core/enterprise/licenses/upload": {
|
"/core/enterprise/licenses/upload": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
@@ -1596,6 +1647,13 @@
|
|||||||
"formatZH": "删除虚拟机 [name]",
|
"formatZH": "删除虚拟机 [name]",
|
||||||
"formatEN": "delete VM [name]"
|
"formatEN": "delete VM [name]"
|
||||||
},
|
},
|
||||||
|
"/core/enterprise/vms/environment/enable": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "启用虚拟机运行环境",
|
||||||
|
"formatEN": "enable VM runtime environment"
|
||||||
|
},
|
||||||
"/core/enterprise/vms/iso": {
|
"/core/enterprise/vms/iso": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"name",
|
"name",
|
||||||
@@ -1662,13 +1720,6 @@
|
|||||||
"formatZH": "删除虚拟机网络 [name]",
|
"formatZH": "删除虚拟机网络 [name]",
|
||||||
"formatEN": "delete VM network [name]"
|
"formatEN": "delete VM network [name]"
|
||||||
},
|
},
|
||||||
"/core/enterprise/vms/networks/sync": {
|
|
||||||
"bodyKeys": [],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "同步虚拟机网络",
|
|
||||||
"formatEN": "sync VM networks"
|
|
||||||
},
|
|
||||||
"/core/enterprise/vms/networks/update": {
|
"/core/enterprise/vms/networks/update": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"id"
|
"id"
|
||||||
@@ -1697,6 +1748,13 @@
|
|||||||
"formatZH": "操作虚拟机 [name] [operate]",
|
"formatZH": "操作虚拟机 [name] [operate]",
|
||||||
"formatEN": "operate VM [name] [operate]"
|
"formatEN": "operate VM [name] [operate]"
|
||||||
},
|
},
|
||||||
|
"/core/enterprise/vms/orphans/clean": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "清理虚拟机孤立数据",
|
||||||
|
"formatEN": "clean VM orphaned data"
|
||||||
|
},
|
||||||
"/core/enterprise/vms/rename": {
|
"/core/enterprise/vms/rename": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"name",
|
"name",
|
||||||
@@ -1765,13 +1823,6 @@
|
|||||||
"formatZH": "删除虚拟机存储 [name]",
|
"formatZH": "删除虚拟机存储 [name]",
|
||||||
"formatEN": "delete VM storage [name]"
|
"formatEN": "delete VM storage [name]"
|
||||||
},
|
},
|
||||||
"/core/enterprise/vms/storages/sync": {
|
|
||||||
"bodyKeys": [],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "同步虚拟机存储",
|
|
||||||
"formatEN": "sync VM storages"
|
|
||||||
},
|
|
||||||
"/core/enterprise/vms/storages/update": {
|
"/core/enterprise/vms/storages/update": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"id"
|
"id"
|
||||||
@@ -1790,6 +1841,13 @@
|
|||||||
"formatZH": "更新虚拟机存储 [name]",
|
"formatZH": "更新虚拟机存储 [name]",
|
||||||
"formatEN": "update VM storage [name]"
|
"formatEN": "update VM storage [name]"
|
||||||
},
|
},
|
||||||
|
"/core/enterprise/vms/sync": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "同步虚拟机",
|
||||||
|
"formatEN": "sync virtual machines"
|
||||||
|
},
|
||||||
"/core/enterprise/vms/templates": {
|
"/core/enterprise/vms/templates": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"name",
|
"name",
|
||||||
@@ -2253,6 +2311,16 @@
|
|||||||
"formatZH": "修复节点 [name]",
|
"formatZH": "修复节点 [name]",
|
||||||
"formatEN": "fix node [name]"
|
"formatEN": "fix node [name]"
|
||||||
},
|
},
|
||||||
|
"/core/xpack/nodes/health/setting": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"interval",
|
||||||
|
"loadFailedResources"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "更新节点健康检查设置",
|
||||||
|
"formatEN": "update node health check setting"
|
||||||
|
},
|
||||||
"/core/xpack/nodes/reinstall": {
|
"/core/xpack/nodes/reinstall": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"id"
|
"id"
|
||||||
@@ -2692,51 +2760,23 @@
|
|||||||
"formatZH": "创建 mysql 数据库 [name]",
|
"formatZH": "创建 mysql 数据库 [name]",
|
||||||
"formatEN": "create mysql database [name]"
|
"formatEN": "create mysql database [name]"
|
||||||
},
|
},
|
||||||
"/databases/bind": {
|
"/databases/change/access": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"database",
|
"database"
|
||||||
"username"
|
|
||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "绑定 mysql 数据库名 [database] [username]",
|
"formatZH": "更新数据库 [database] root 访问权限",
|
||||||
"formatEN": "bind mysql database [database] [username]"
|
"formatEN": "Update database [database] root access"
|
||||||
},
|
|
||||||
"/databases/change/access": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "database_mysqls",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "更新数据库 [name] 访问权限",
|
|
||||||
"formatEN": "Update database [name] access"
|
|
||||||
},
|
},
|
||||||
"/databases/change/password": {
|
"/databases/change/password": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"id"
|
"database"
|
||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [
|
"beforeFunctions": [],
|
||||||
{
|
"formatZH": "更新数据库 [database] root 密码",
|
||||||
"input_column": "id",
|
"formatEN": "Update database [database] root password"
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "database_mysqls",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "更新数据库 [name] 密码",
|
|
||||||
"formatEN": "Update database [name] password"
|
|
||||||
},
|
},
|
||||||
"/databases/common/update/conf": {
|
"/databases/common/update/conf": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -2832,6 +2872,30 @@
|
|||||||
"formatZH": "mysql 数据库 [name] 描述信息修改 [description]",
|
"formatZH": "mysql 数据库 [name] 描述信息修改 [description]",
|
||||||
"formatEN": "The description of the mysql database [name] is modified =\u003e [description]"
|
"formatEN": "The description of the mysql database [name] is modified =\u003e [description]"
|
||||||
},
|
},
|
||||||
|
"/databases/grants": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"database",
|
||||||
|
"db",
|
||||||
|
"username",
|
||||||
|
"host"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "授权 mysql 数据库 [database] 用户 [username]@[host] 访问 [db]",
|
||||||
|
"formatEN": "grant mysql database [database] user [username]@[host] access to [db]"
|
||||||
|
},
|
||||||
|
"/databases/grants/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"database",
|
||||||
|
"db",
|
||||||
|
"username",
|
||||||
|
"host"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "取消 mysql 数据库 [database] 用户 [username]@[host] 对 [db] 的授权",
|
||||||
|
"formatEN": "revoke mysql database [database] user [username]@[host] access to [db]"
|
||||||
|
},
|
||||||
"/databases/mongodb": {
|
"/databases/mongodb": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"name"
|
"name"
|
||||||
@@ -3023,6 +3087,63 @@
|
|||||||
"formatZH": "redis 数据库持久化配置更新",
|
"formatZH": "redis 数据库持久化配置更新",
|
||||||
"formatEN": "redis database persistence configuration update"
|
"formatEN": "redis database persistence configuration update"
|
||||||
},
|
},
|
||||||
|
"/databases/users": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"database",
|
||||||
|
"username",
|
||||||
|
"host"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建 mysql 数据库 [database] 用户 [username]@[host]",
|
||||||
|
"formatEN": "create mysql database [database] user [username]@[host]"
|
||||||
|
},
|
||||||
|
"/databases/users/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"database",
|
||||||
|
"username",
|
||||||
|
"host"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "删除 mysql 数据库 [database] 用户 [username]@[host]",
|
||||||
|
"formatEN": "delete mysql database [database] user [username]@[host]"
|
||||||
|
},
|
||||||
|
"/databases/users/password": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"database",
|
||||||
|
"username",
|
||||||
|
"host"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "更新 mysql 数据库 [database] 用户 [username]@[host] 密码",
|
||||||
|
"formatEN": "update mysql database [database] user [username]@[host] password"
|
||||||
|
},
|
||||||
|
"/databases/users/password/save": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"database",
|
||||||
|
"username",
|
||||||
|
"host"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "补充 mysql 数据库 [database] 用户 [username]@[host] 密码",
|
||||||
|
"formatEN": "save mysql database [database] user [username]@[host] password locally"
|
||||||
|
},
|
||||||
|
"/databases/users/update": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"database",
|
||||||
|
"username",
|
||||||
|
"host",
|
||||||
|
"newHost",
|
||||||
|
"description"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "更新 mysql 数据库 [database] 用户 [username] 访问权限 [host] =\u003e [newHost] 描述 [description]",
|
||||||
|
"formatEN": "update mysql database [database] user [username] access [host] =\u003e [newHost] description [description]"
|
||||||
|
},
|
||||||
"/databases/variables/update": {
|
"/databases/variables/update": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
@@ -3488,6 +3609,13 @@
|
|||||||
"formatZH": "修改 SSH 配置文件 [key]",
|
"formatZH": "修改 SSH 配置文件 [key]",
|
||||||
"formatEN": "update SSH conf [key]"
|
"formatEN": "update SSH conf [key]"
|
||||||
},
|
},
|
||||||
|
"/hosts/ssh/log/clean": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "清空 SSH 登录日志",
|
||||||
|
"formatEN": "clean SSH login logs"
|
||||||
|
},
|
||||||
"/hosts/ssh/operate": {
|
"/hosts/ssh/operate": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"operation"
|
"operation"
|
||||||
@@ -5071,6 +5199,15 @@
|
|||||||
"formatZH": "更新 CDN 配置",
|
"formatZH": "更新 CDN 配置",
|
||||||
"formatEN": "update CDN config"
|
"formatEN": "update CDN config"
|
||||||
},
|
},
|
||||||
|
"/xpack/waf/config/global/spider": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"rules"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "更新全局蜘蛛允许列表",
|
||||||
|
"formatEN": "update global spider allowlist"
|
||||||
|
},
|
||||||
"/xpack/waf/config/global/state": {
|
"/xpack/waf/config/global/state": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"scope",
|
"scope",
|
||||||
@@ -5084,7 +5221,8 @@
|
|||||||
"/xpack/waf/config/website/state": {
|
"/xpack/waf/config/website/state": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"scope",
|
"scope",
|
||||||
"state"
|
"state",
|
||||||
|
"mode"
|
||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package global
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
badger_db "github.com/1Panel-dev/1Panel/agent/init/cache/db"
|
badger_db "github.com/1Panel-dev/1Panel/agent/init/cache/db"
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
@@ -39,8 +40,28 @@ var (
|
|||||||
AlertResourceJobID cron.EntryID
|
AlertResourceJobID cron.EntryID
|
||||||
|
|
||||||
TaskCtxMap = make(map[string]context.CancelFunc)
|
TaskCtxMap = make(map[string]context.CancelFunc)
|
||||||
|
taskCtxMu sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func RegisterTaskCancel(taskID string, cancel context.CancelFunc) {
|
||||||
|
taskCtxMu.Lock()
|
||||||
|
defer taskCtxMu.Unlock()
|
||||||
|
TaskCtxMap[taskID] = cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadTaskCancel(taskID string) (context.CancelFunc, bool) {
|
||||||
|
taskCtxMu.RLock()
|
||||||
|
defer taskCtxMu.RUnlock()
|
||||||
|
cancel, ok := TaskCtxMap[taskID]
|
||||||
|
return cancel, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveTaskCancel(taskID string) {
|
||||||
|
taskCtxMu.Lock()
|
||||||
|
defer taskCtxMu.Unlock()
|
||||||
|
delete(TaskCtxMap, taskID)
|
||||||
|
}
|
||||||
|
|
||||||
func RepoURL() string {
|
func RepoURL() string {
|
||||||
if CONF.Base.IsEnterprise {
|
if CONF.Base.IsEnterprise {
|
||||||
return "https://resource.fit2cloud.com/1panel/package/enterprise"
|
return "https://resource.fit2cloud.com/1panel/package/enterprise"
|
||||||
|
|||||||
+49
-48
@@ -4,30 +4,32 @@ go 1.25.10
|
|||||||
|
|
||||||
replace github.com/moby/go-archive => github.com/moby/go-archive v0.1.0
|
replace github.com/moby/go-archive => github.com/moby/go-archive v0.1.0
|
||||||
|
|
||||||
|
replace github.com/go-acme/lego/v5 => github.com/1Panel-dev/lego/v5 v5.3.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||||
github.com/compose-spec/compose-go/v2 v2.12.1
|
github.com/compose-spec/compose-go/v2 v2.13.0
|
||||||
github.com/creack/pty v1.1.24
|
github.com/creack/pty v1.1.24
|
||||||
github.com/docker/cli v29.6.1+incompatible
|
github.com/docker/cli v29.6.2+incompatible
|
||||||
github.com/docker/docker v28.5.2+incompatible
|
github.com/docker/docker v28.5.2+incompatible
|
||||||
github.com/docker/go-connections v0.7.0
|
github.com/docker/go-connections v0.7.0
|
||||||
github.com/fsnotify/fsnotify v1.10.1
|
github.com/fsnotify/fsnotify v1.10.1
|
||||||
github.com/gin-gonic/gin v1.12.0
|
github.com/gin-gonic/gin v1.12.0
|
||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
github.com/go-acme/lego/v5 v5.2.2
|
github.com/go-acme/lego/v5 v5.3.1
|
||||||
github.com/go-gormigrate/gormigrate/v2 v2.1.6
|
github.com/go-gormigrate/gormigrate/v2 v2.1.6
|
||||||
github.com/go-playground/validator/v10 v10.30.3
|
github.com/go-playground/validator/v10 v10.30.3
|
||||||
github.com/go-redis/redis v6.15.9+incompatible
|
github.com/go-redis/redis v6.15.9+incompatible
|
||||||
github.com/go-resty/resty/v2 v2.17.2
|
github.com/go-resty/resty/v2 v2.17.2
|
||||||
github.com/go-sql-driver/mysql v1.10.0
|
github.com/go-sql-driver/mysql v1.10.0
|
||||||
github.com/goh-chunlin/go-onedrive v1.1.1
|
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
||||||
github.com/jackc/pgx/v5 v5.10.0
|
github.com/jackc/pgx/v5 v5.10.0
|
||||||
github.com/jinzhu/copier v0.4.0
|
github.com/jinzhu/copier v0.4.0
|
||||||
github.com/jinzhu/gorm v1.9.16
|
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/klauspost/compress v1.18.6
|
github.com/klauspost/compress v1.19.1
|
||||||
|
github.com/mattn/go-shellwords v1.0.14
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
||||||
github.com/miekg/dns v1.1.72
|
github.com/miekg/dns v1.1.72
|
||||||
github.com/minio/minio-go/v7 v7.2.1
|
github.com/minio/minio-go/v7 v7.2.1
|
||||||
@@ -36,10 +38,10 @@ require (
|
|||||||
github.com/oschwald/maxminddb-golang v1.13.1
|
github.com/oschwald/maxminddb-golang v1.13.1
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pkg/sftp v1.13.10
|
github.com/pkg/sftp v1.13.11
|
||||||
github.com/qiniu/go-sdk/v7 v7.26.13
|
github.com/qiniu/go-sdk/v7 v7.26.17
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v4 v4.26.5
|
github.com/shirou/gopsutil/v4 v4.26.6
|
||||||
github.com/sirupsen/logrus v1.9.4
|
github.com/sirupsen/logrus v1.9.4
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/spf13/afero v1.15.0
|
github.com/spf13/afero v1.15.0
|
||||||
@@ -49,13 +51,12 @@ require (
|
|||||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.74
|
github.com/tencentyun/cos-go-sdk-v5 v0.7.74
|
||||||
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19
|
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19
|
||||||
github.com/upyun/go-sdk v2.1.0+incompatible
|
github.com/upyun/go-sdk v2.1.0+incompatible
|
||||||
go.mongodb.org/mongo-driver/v2 v2.7.0
|
go.mongodb.org/mongo-driver/v2 v2.8.0
|
||||||
golang.org/x/crypto v0.53.0
|
golang.org/x/crypto v0.54.0
|
||||||
golang.org/x/net v0.56.0
|
golang.org/x/net v0.57.0
|
||||||
golang.org/x/oauth2 v0.36.0
|
golang.org/x/sync v0.22.0
|
||||||
golang.org/x/sync v0.21.0
|
golang.org/x/sys v0.47.0
|
||||||
golang.org/x/sys v0.46.0
|
golang.org/x/text v0.40.0
|
||||||
golang.org/x/text v0.38.0
|
|
||||||
golang.org/x/time v0.15.0
|
golang.org/x/time v0.15.0
|
||||||
google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478
|
google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478
|
||||||
gopkg.in/ini.v1 v1.67.3
|
gopkg.in/ini.v1 v1.67.3
|
||||||
@@ -69,28 +70,28 @@ require (
|
|||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
|
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
|
||||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.1 // indirect
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.3 // indirect
|
||||||
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||||
github.com/alibabacloud-go/tea v1.5.0 // indirect
|
github.com/alibabacloud-go/tea v1.5.2 // indirect
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.9 // indirect
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.9 // indirect
|
||||||
github.com/aliyun/credentials-go v1.4.12 // indirect
|
github.com/aliyun/credentials-go v1.4.12 // indirect
|
||||||
github.com/andybalholm/brotli v1.2.1 // indirect
|
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.8 // indirect
|
github.com/aws/aws-sdk-go-v2 v1.42.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.19 // indirect
|
github.com/aws/aws-sdk-go-v2/config v1.32.30 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.18 // indirect
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.29 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.24 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.30 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.24 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.24 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.25 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.31 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.24 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.62.8 // indirect
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.64.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.1.0 // indirect
|
github.com/aws/aws-sdk-go-v2/service/signin v1.4.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.18 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.32.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.1 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.42.2 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.44.1 // indirect
|
||||||
github.com/aws/smithy-go v1.25.1 // indirect
|
github.com/aws/smithy-go v1.27.3 // indirect
|
||||||
github.com/baidubce/bce-sdk-go v0.9.267 // indirect
|
github.com/baidubce/bce-sdk-go v0.9.270 // indirect
|
||||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||||
github.com/bodgit/sevenzip v1.6.1 // indirect
|
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||||
github.com/bodgit/windows v1.0.1 // indirect
|
github.com/bodgit/windows v1.0.1 // indirect
|
||||||
@@ -106,7 +107,6 @@ require (
|
|||||||
github.com/containerd/errdefs v1.0.0 // indirect
|
github.com/containerd/errdefs v1.0.0 // indirect
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 // indirect
|
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
@@ -115,12 +115,10 @@ require (
|
|||||||
github.com/ebitengine/purego v0.10.0 // indirect
|
github.com/ebitengine/purego v0.10.0 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||||
github.com/gammazero/toposort v0.1.1 // indirect
|
|
||||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||||
github.com/go-acme/alidns-20150109/v5 v5.4.1 // indirect
|
github.com/go-acme/alidns-20150109/v5 v5.5.0 // indirect
|
||||||
github.com/go-acme/esa-20240910/v3 v3.2.2 // indirect
|
github.com/go-acme/esa-20240910/v3 v3.4.0 // indirect
|
||||||
github.com/go-acme/tencentclouddnspod v1.3.24 // indirect
|
|
||||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
@@ -133,15 +131,14 @@ require (
|
|||||||
github.com/gofrs/flock v0.13.0 // indirect
|
github.com/gofrs/flock v0.13.0 // indirect
|
||||||
github.com/golang/snappy v1.0.0 // indirect
|
github.com/golang/snappy v1.0.0 // indirect
|
||||||
github.com/google/go-querystring v1.2.0 // indirect
|
github.com/google/go-querystring v1.2.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
|
||||||
github.com/h2non/filetype v1.1.3 // indirect
|
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.198 // indirect
|
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.205 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/jarcoal/httpmock v1.4.1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
||||||
@@ -152,7 +149,7 @@ require (
|
|||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect
|
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect
|
||||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||||
github.com/mattn/go-shellwords v1.0.13 // indirect
|
github.com/maxatome/go-testdeep v1.14.0 // indirect
|
||||||
github.com/minio/crc64nvme v1.1.1 // indirect
|
github.com/minio/crc64nvme v1.1.1 // indirect
|
||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
@@ -175,6 +172,8 @@ require (
|
|||||||
github.com/nrdcg/namesilo v0.5.0 // indirect
|
github.com/nrdcg/namesilo v0.5.0 // indirect
|
||||||
github.com/nrdcg/porkbun v0.4.0 // indirect
|
github.com/nrdcg/porkbun v0.4.0 // indirect
|
||||||
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
|
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
|
||||||
|
github.com/onsi/ginkgo v1.16.4 // indirect
|
||||||
|
github.com/onsi/gomega v1.37.0 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/ovh/go-ovh v1.9.0 // indirect
|
github.com/ovh/go-ovh v1.9.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.3.1 // indirect
|
github.com/pelletier/go-toml/v2 v2.3.1 // indirect
|
||||||
@@ -189,7 +188,8 @@ require (
|
|||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
||||||
github.com/spf13/cast v1.10.0 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.106 // indirect
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.133 // indirect
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.3.131 // indirect
|
||||||
github.com/therootcompany/xz v1.0.1 // indirect
|
github.com/therootcompany/xz v1.0.1 // indirect
|
||||||
github.com/tinylib/msgp v1.6.4 // indirect
|
github.com/tinylib/msgp v1.6.4 // indirect
|
||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
@@ -198,7 +198,7 @@ require (
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||||
github.com/volcengine/volc-sdk-golang v1.0.249 // indirect
|
github.com/volcengine/volc-sdk-golang v1.0.251 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
github.com/xdg-go/scram v1.2.0 // indirect
|
github.com/xdg-go/scram v1.2.0 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
@@ -217,13 +217,14 @@ require (
|
|||||||
go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect
|
go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect
|
||||||
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
||||||
golang.org/x/arch v0.26.0 // indirect
|
golang.org/x/arch v0.26.0 // indirect
|
||||||
golang.org/x/mod v0.36.0 // indirect
|
golang.org/x/mod v0.37.0 // indirect
|
||||||
golang.org/x/tools v0.45.0 // indirect
|
golang.org/x/oauth2 v0.36.0 // indirect
|
||||||
|
golang.org/x/tools v0.47.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
modernc.org/fileutil v1.4.0 // indirect
|
modernc.org/fileutil v1.4.0 // indirect
|
||||||
modernc.org/libc v1.72.0 // indirect
|
modernc.org/libc v1.72.0 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
modernc.org/sqlite v1.48.2 // indirect
|
modernc.org/sqlite v1.48.2 // indirect
|
||||||
software.sslmate.com/src/go-pkcs12 v0.7.1 // indirect
|
software.sslmate.com/src/go-pkcs12 v0.7.3 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
+99
-124
@@ -33,6 +33,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
|||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||||
|
github.com/1Panel-dev/lego/v5 v5.3.1 h1:Fp8cUTooZIFG84dJlj6TDfwIyOy0HWqY/1pnqA8v8vs=
|
||||||
|
github.com/1Panel-dev/lego/v5 v5.3.1/go.mod h1:yNl7Zqt0FK1zjhE1yuGz/rjcECyBeDk2IxkKUzkfEN8=
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||||
@@ -48,7 +50,6 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go
|
|||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
|
||||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||||
github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw=
|
github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw=
|
||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
@@ -74,9 +75,10 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC
|
|||||||
github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
|
github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
|
||||||
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
|
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
|
||||||
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
|
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.14/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE=
|
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.1 h1:R8b55YFS4K9x5P5IdgA+QWinVfVmulzqaJG/tr6HhxM=
|
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.1/go.mod h1:OCFim1kMbp2m+V8WS5IBnnVrk6nXaJiDwZpg3uqw8Po=
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.1/go.mod h1:OCFim1kMbp2m+V8WS5IBnnVrk6nXaJiDwZpg3uqw8Po=
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.2/go.mod h1:CsqHkHPpAKL64jl/fChR249EL0abYEIDWmlwFYgo7/E=
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.3 h1:rec08F9BzxIiJ03h2u6BT2QqLVa8ktnpvJ27j5PsjLM=
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.3/go.mod h1:eHjVxrT9g8uVYN/nyAwOFQEfkVA154ChiqRc2XnNKYU=
|
||||||
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
|
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
|
||||||
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
|
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
|
||||||
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
|
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
|
||||||
@@ -97,12 +99,12 @@ github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/Ke
|
|||||||
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||||
github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||||
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
|
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
|
||||||
github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
|
|
||||||
github.com/alibabacloud-go/tea v1.5.0 h1:8pUo8WzMChtJT+jpLIcN1vzMi6SW3rihzzBoihPGUvs=
|
|
||||||
github.com/alibabacloud-go/tea v1.5.0/go.mod h1:hgSs82CkOiehSQMoiFN79dL6zsGX7pVGvnn9SIEs8/0=
|
github.com/alibabacloud-go/tea v1.5.0/go.mod h1:hgSs82CkOiehSQMoiFN79dL6zsGX7pVGvnn9SIEs8/0=
|
||||||
|
github.com/alibabacloud-go/tea v1.5.1/go.mod h1:hgSs82CkOiehSQMoiFN79dL6zsGX7pVGvnn9SIEs8/0=
|
||||||
|
github.com/alibabacloud-go/tea v1.5.2 h1:DUzV/cRTbJC++rmADfqVu//GuWbaD64ZJ3yW3v2ukNU=
|
||||||
|
github.com/alibabacloud-go/tea v1.5.2/go.mod h1:hgSs82CkOiehSQMoiFN79dL6zsGX7pVGvnn9SIEs8/0=
|
||||||
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
|
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.9 h1:y6pUIlhjxbZl9ObDAcmA1H3c21eaAxADHTDQmBnAIgA=
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.9 h1:y6pUIlhjxbZl9ObDAcmA1H3c21eaAxADHTDQmBnAIgA=
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.9/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.9/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
||||||
@@ -115,7 +117,6 @@ github.com/aliyun/credentials-go v1.4.12 h1:7D8eXGotNwthZuUEgAMgBoqxmIHwfaPVwW+/
|
|||||||
github.com/aliyun/credentials-go v1.4.12/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
|
github.com/aliyun/credentials-go v1.4.12/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
|
||||||
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
||||||
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
@@ -125,40 +126,40 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
|
|||||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||||
github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.8 h1:sRs7nG6/RiEBZ/K5UO2sNw0w40U02Nmz1VtARloTZXk=
|
github.com/aws/aws-sdk-go-v2 v1.42.1 h1:9eOTgu1z/dVtYpNZ3/8/XbbaX0x/BqE3HUzAzs6K0ek=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.8/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
|
github.com/aws/aws-sdk-go-v2 v1.42.1/go.mod h1:5pKeft2eJj+gElQ38Jqg4ibCqh+/AK33/0X3hip7IjM=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.19 h1:qRhIJMbevHUvIE7X4TK8N8zye5+5AhapcslPrvB+qKE=
|
github.com/aws/aws-sdk-go-v2/config v1.32.30 h1:XwsEzpTJfQYJbFicz/QMLwAZdyeNVVoOEkbF7R3gPJk=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.19/go.mod h1:RbJ24nfoya63+Mf5VI+CGCGk9vEdv28xPeii+gojRYs=
|
github.com/aws/aws-sdk-go-v2/config v1.32.30/go.mod h1:Ud32SuMc+/9BGxfpSVld7HrE2o05JwKmXY4M3jOQNZU=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.18 h1:GcXQz2M/0ZvMo0v5DakUqbDBeBM1ZNaivkolEF4Esgw=
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.29 h1:WHZGssHH887cO0ox07SIQZsFx3MKD4ps6w0xUEmnKYQ=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.18/go.mod h1:sHJ06tMGcD3ZpmMyJqV+VBsGilhSIZPIN+ZFy5Dg0C4=
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.29/go.mod h1:Mhl0xR6zjguiuj00XRx2wMx22sAltk7oya39sT7fdg8=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.24 h1:FQm5ApnyzkuJdXLGskPce83CK1CQKC4RUnIHKVe4BU4=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.30 h1:/hi1JADLEW9YYryEz1w4GQu0EtP23pP553Cf9KgsDV4=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.24/go.mod h1:JsC7dqQc55MlZ5mvNsDMMge71u8pVcSzU3RNz2h/5yQ=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.30/go.mod h1:/3AOgy4K17Dm4ucMZVC/MJkzy5kmfKUcINRHZyo0koQ=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.24 h1:u6kJU2i0va1AgtJsH3RdWKWqHULlTh7zHwb35Womf74=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 h1:xM/Is9cKMHa8Jj8zkvWhvrFkZsXJV9E+BB4g0HW0duQ=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.24/go.mod h1:7GY+xLcXOFUpCkNwDReft9qOAVg54A4/AnjHIU7sSAY=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30/go.mod h1:WueJeNDZvK1fMYEWJIkcivBfEzUkTpBhzlrUKKY8EuA=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.24 h1:Xhbcf3KugX6vX7SDyUK205Oicyfg7EGuvoVNyP5L6DM=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 h1:jn46zC9LdsVR/ZpMIJqMqb8hHv31BlLx3ulVqNspUOk=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.24/go.mod h1:rwDgb2HNOGZsnTHylOUedM7Vnl+bCfnXDqUNPsFWYfk=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30/go.mod h1:1hTMsAgbdS/AtUi4bw8+gUuh1pceo+eXRLfpSuSQj3M=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.25 h1:54CTMmlJ71Rk2dYvM9qZOob+39wjlVja2zDLxCu69Ew=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.31 h1:3GUprIsfmGcC5SACIyB0e7E0BM1O1b3Erl5CePYIAeQ=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.25/go.mod h1:BZaHqxsS9vN1fvV5EfEl0OBLOk5+AajWsMu6MjqnZB4=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.31/go.mod h1:7PuV1yl5e2xnUbm+RqvVg5i2iBM8EyijZNoI9wsOoOc=
|
||||||
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
|
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13 h1:mbRIur/BiHK6SKPjoBIXSE/hJ6g6JGRLuxQy1jGjlN4=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13/go.mod h1:ITg9em2KbJx1s0y4aqRX5OYWG6HBZ5TVR//OdpEZ2CQ=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.24 h1:CQW2FTrflfoslYWLf3fv7vG28Q219+v8YJS5QTQb2+Y=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30 h1:/Z5jmNrKsSD7EmDjzAPsm/3L9IuOkzaynklJZ1qX7S4=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.24/go.mod h1:Xfx13T+u3nH6EEzgl9fBSO6nDRmze1FvnZNYkctQ2zw=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30/go.mod h1:lEzEZnOosE7zi8Z6royW1cFJTD9fpab4Ul1SBrllewk=
|
||||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.62.8 h1:1QC1xoZg5XSes1CXQ20Y0qTaaeDj12e7bB0/Yw9sQwI=
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.64.1 h1:qL5ELChQ1o1ZbzHvsl8gzQfAZdbQtrDbUyzJkjkMKcg=
|
||||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.62.8/go.mod h1:+GpgmfX7rnzQ8WMbMCMnI+VOjPZCf9yaUlBmRTvQgFE=
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.64.1/go.mod h1:0hIRXFez1bZsDFMGkLZvNJbByTSVZ4sFZWpxZ39NPuM=
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.1.0 h1:yQo3eZ5qFaL1sJWqs1nL6j3yPHA2/R7c6tQ4T+0IO10=
|
github.com/aws/aws-sdk-go-v2/service/signin v1.4.1 h1:V7ZZ300WPXGjvkyore5DGe0ljVPOxCXie/thWdtSBXE=
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.1.0/go.mod h1:3Zzou41Qt/ueXfIzHvTEjDNuR5IjCUBVF01SNhrt1e8=
|
github.com/aws/aws-sdk-go-v2/service/signin v1.4.1/go.mod h1:mxC0nT/C8wMMS97DemZPzvUZxvIt+2Iq+eS3JdFZGgg=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.18 h1:ApLTFdAZfDhZSiY5uskwECKHkSNNF83y2Ru2r7SezWA=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.32.1 h1:gYFYh4iLLcAOJRLNPY2aD2g9DIhKn4eof8UkIrr1rTk=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.18/go.mod h1:A9K9qx2l6nK89hp+a350FdGfRkrkH5HdiEjHbiy/Q/c=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.32.1/go.mod h1:u8af9Nqkmqnr96f7v9nHqzZT9XBwbXEkTiqT4ROuJSE=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.1 h1:4VD7TIZOGzehrgQ8vDE+1c6BQW4ErZPGY8ohZT5LXEE=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.1 h1:arjT9Cm3/WYbGmD5TUZHk4UQn4Lle1fUNZs5FC6CtF0=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.1/go.mod h1:er0SFJfdV89Rit5hIJu/EXtv+qC2XMnxoksLmcUFkqM=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.1/go.mod h1:DMPWJBjYs6+3+f/qhBFEFPPlQ6NlhWjai3dJNvipJ84=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.42.2 h1:XKnxlM4KZH1gktcsh3zSWc7GW4KivEv/OkifmHOhCUY=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.44.1 h1:RvfHDg+xvAeZ+5741vUEjpOVtYSIm93W2zhx10Xtydw=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.42.2/go.mod h1:KJYmkQaFB3SUW2j3aBkPsxNmAb4ZsSOvbvCpuxzHJA0=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.44.1/go.mod h1:9gdl4RrflIdpDb2TlXshWgR1F9TeCkvqDx77Vpr4Z/Q=
|
||||||
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||||
github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI=
|
github.com/aws/smithy-go v1.27.3 h1:F3Zb497UhhskkfpJmfkXswyo+t0sh9OTBnIHjogWbVY=
|
||||||
github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
github.com/aws/smithy-go v1.27.3/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||||
github.com/baidubce/bce-sdk-go v0.9.267 h1:0l3UipFvWVsEv+FcontxWRs6ny8xvBeRISLbdNtO/bw=
|
github.com/baidubce/bce-sdk-go v0.9.270 h1:WAYDTBdrE2FU+XQVdKReuDuK9QVCjRdekK3lr3ByDvg=
|
||||||
github.com/baidubce/bce-sdk-go v0.9.267/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
github.com/baidubce/bce-sdk-go v0.9.270/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
@@ -204,8 +205,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-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/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/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/compose-spec/compose-go/v2 v2.12.1 h1:+xBZNxcgSus4atQJwXPEdhHRgCEyZmj/BuqN5m33Ou0=
|
github.com/compose-spec/compose-go/v2 v2.13.0 h1:2+2oS3v4SrtAOBdZRAZYBsBy47D571p5EXMSCppmTtE=
|
||||||
github.com/compose-spec/compose-go/v2 v2.12.1/go.mod h1:ZU6zlcweCZKyiB7BVfCizQT9XmkEIMFE+PRZydVcsZg=
|
github.com/compose-spec/compose-go/v2 v2.13.0/go.mod h1:ZU6zlcweCZKyiB7BVfCizQT9XmkEIMFE+PRZydVcsZg=
|
||||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
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 v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||||
@@ -223,15 +224,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 h1:OGNva6WhsKst5OZf7eZOklDztV3hwtTHovdrLHV+MsA=
|
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
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 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
|
||||||
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/docker/cli v29.6.1+incompatible h1:oO7F4nn3Ovr/5TlfTUWFbMwBSS/B7Xs6Epv26gBrUP8=
|
github.com/docker/cli v29.6.2+incompatible h1:/bjePvcbbFTnRrMfWJBY7AjfICdsiLVgHn6LwTVOcqw=
|
||||||
github.com/docker/cli v29.6.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v29.6.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
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 v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
|
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
|
||||||
@@ -260,8 +258,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
|
|||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
|
||||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
@@ -281,8 +277,6 @@ github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx5
|
|||||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
github.com/gammazero/toposort v0.1.1 h1:OivGxsWxF3U3+U80VoLJ+f50HcPU1MIqE1JlKzoJ2Eg=
|
|
||||||
github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
|
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
|
||||||
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
|
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
|
||||||
@@ -292,14 +286,10 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec
|
|||||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||||
github.com/go-acme/alidns-20150109/v5 v5.4.1 h1:Bp+EDtIGxBVKnIq1hPECBFf6eueTiESYqsHkY+T+CIQ=
|
github.com/go-acme/alidns-20150109/v5 v5.5.0 h1:/LUHZSQoaDqagi8wCq8f9Rx4AoVr/Jd4rKEV2IQYvxs=
|
||||||
github.com/go-acme/alidns-20150109/v5 v5.4.1/go.mod h1:L+SrZRmoQFx2UzOqng3z3+LA/gNgFCaLXrwr/ndyW6g=
|
github.com/go-acme/alidns-20150109/v5 v5.5.0/go.mod h1:J2B9WBwSFfZalzlCqSkeyBkqBcWPEZMDkxwhOjjF0MA=
|
||||||
github.com/go-acme/esa-20240910/v3 v3.2.2 h1:dZ+HKddDMUcPr46gkFYdn5FbtJwIic59+MEaCaH/cxM=
|
github.com/go-acme/esa-20240910/v3 v3.4.0 h1:t2ih22iRV+BzLkhvRG/i5hkyfkfi4FSPTZ3ves8oQ/0=
|
||||||
github.com/go-acme/esa-20240910/v3 v3.2.2/go.mod h1:qn4WK9pNgXoS5ypmiZfCWSYdOtWcte9GDuhDjKP752A=
|
github.com/go-acme/esa-20240910/v3 v3.4.0/go.mod h1:QGkL4IcH2mT4K+VoTMMJnq9tOBPNa0E6QdSmKmn0D2s=
|
||||||
github.com/go-acme/lego/v5 v5.2.2 h1:KqFas/Ak2QDdU+Qm7MO9OEnS35zC000kPmM+5tLdZKk=
|
|
||||||
github.com/go-acme/lego/v5 v5.2.2/go.mod h1:H/hb7OJKmpVGa8zypgZh0ys5P4VUu++ZNgAEcOwq+OI=
|
|
||||||
github.com/go-acme/tencentclouddnspod v1.3.24 h1:uCSiOW1EJttcnOON+MVVyVDJguFL/Q4NIGkq1CrT9p8=
|
|
||||||
github.com/go-acme/tencentclouddnspod v1.3.24/go.mod h1:RKcB2wSoZncjBA0OEFj59s1ko1XDy+ZsAtk+9uMxUF0=
|
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
@@ -340,7 +330,6 @@ github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGK
|
|||||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
|
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
|
||||||
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
|
||||||
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
|
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
|
||||||
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
|
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
@@ -358,12 +347,8 @@ github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
|
|||||||
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
|
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/goh-chunlin/go-onedrive v1.1.1 h1:HGtHk5iG0MZ92zYUtaY04czfZPBIJUr12UuFc+PW8m4=
|
|
||||||
github.com/goh-chunlin/go-onedrive v1.1.1/go.mod h1:N8qIGHD7tryO734epiBKk5oXcpGwxKET/u3LuBHciTs=
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
|
||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
@@ -456,9 +441,6 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
|
|||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||||
github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
|
||||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
|
||||||
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
|
||||||
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@@ -489,8 +471,8 @@ github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg
|
|||||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.198 h1:/Qcq965fQ5AjM0lg8ndczox8jqAqp+5J8r4KFrfpb3Y=
|
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.205 h1:YnI2+56S5oxByz8BYChRrsmqTUmCp1Ww2KIzZtF4XOU=
|
||||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.198/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI=
|
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.205/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI=
|
||||||
github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
|
github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
@@ -504,8 +486,8 @@ github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
|
|||||||
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
|
github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=
|
||||||
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||||
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
|
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
|
||||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||||
@@ -514,11 +496,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aW
|
|||||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||||
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
|
|
||||||
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
@@ -544,8 +523,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
||||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
@@ -571,8 +550,6 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
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/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
|
||||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak=
|
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak=
|
||||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
@@ -587,14 +564,13 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
|||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||||
github.com/mattn/go-shellwords v1.0.13 h1:DC0OMEpGjm6LfNFU4ckYcvbQKyp2vE8atyFGXNtDcf4=
|
github.com/mattn/go-shellwords v1.0.14 h1:yUKzIgsCnosndOASY6/enly1EAuaXeFSQ7cdyA3OuYg=
|
||||||
github.com/mattn/go-shellwords v1.0.13/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.14/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
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/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
|
github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI=
|
||||||
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
|
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
|
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
@@ -681,13 +657,15 @@ github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+
|
|||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
github.com/onsi/ginkgo v1.16.2 h1:HFB2fbVIlhIfCfOW81bZFbiC/RvnpXSdhbF2/DJr134=
|
|
||||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||||
|
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||||
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
|
||||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||||
|
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
|
||||||
|
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
@@ -717,8 +695,8 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||||
github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
|
github.com/pkg/sftp v1.13.11 h1:0N92SLTB8JqASJB14ZLHHzFnBV8mG9zw4K7jghEFWuE=
|
||||||
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
|
github.com/pkg/sftp v1.13.11/go.mod h1:uNkH9roSXglNJqM+glJJi+TQXQUm0fXFWqCFmT8hsN0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
@@ -746,8 +724,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.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.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/prometheus/procfs v0.7.3/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.13 h1:U4usDKoLldAqJntLN4wv4pkAjwvM9dueOUY/wbbO/yM=
|
github.com/qiniu/go-sdk/v7 v7.26.17 h1:SMjjdKak2j1eedAHreMiS6sObDOLuUrvRqPtlV4KPQo=
|
||||||
github.com/qiniu/go-sdk/v7 v7.26.13/go.mod h1:ri7fGwbio0pRDFr8EK5TUpx0DbnpIMJ2bMSDxGWfCbk=
|
github.com/qiniu/go-sdk/v7 v7.26.17/go.mod h1:pTwVR1B+8SXcPLhDzBUasiKFTD9F7jRglRDR553BW3k=
|
||||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
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/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||||
@@ -773,8 +751,8 @@ github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9t
|
|||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/shirou/gopsutil/v4 v4.26.5 h1:RPcBXkpz7kOj9PqGFQOlBPZHsyaPvPVQc098y9RmCNM=
|
github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs=
|
||||||
github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
@@ -829,9 +807,11 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD
|
|||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.24/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.131/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
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.133 h1:EKJsaGHbifEKqWCdJ4tJNLxbJ5h/Pm3CaLKzcCS/pkw=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.106/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.133/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.3.131 h1:YfzhVkH30GipMEOGIx6nkaJv2E/mWN+G6Vvg+M7SqGg=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.3.131/go.mod h1:Eejvn572HmciFhWFMg6a+eZvkegnEzJrPSnJYKYZX2s=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
|
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.74 h1:YCsGhIK+Q5vMcmAiQqPuMyhSQ3zN1p7U+dhlHQD++6c=
|
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/cos-go-sdk-v5 v0.7.74/go.mod h1:STbTNaNKq03u+gscPEGOahKzLcGSYOj6Dzc5zNay7Pg=
|
||||||
@@ -860,8 +840,8 @@ github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o
|
|||||||
github.com/upyun/go-sdk v2.1.0+incompatible h1:OdjXghQ/TVetWV16Pz3C1/SUpjhGBVPr+cLiqZLLyq0=
|
github.com/upyun/go-sdk v2.1.0+incompatible h1:OdjXghQ/TVetWV16Pz3C1/SUpjhGBVPr+cLiqZLLyq0=
|
||||||
github.com/upyun/go-sdk v2.1.0+incompatible/go.mod h1:eu3F5Uz4b9ZE5bE5QsCL6mgSNWRwfj0zpJ9J626HEqs=
|
github.com/upyun/go-sdk v2.1.0+incompatible/go.mod h1:eu3F5Uz4b9ZE5bE5QsCL6mgSNWRwfj0zpJ9J626HEqs=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/volcengine/volc-sdk-golang v1.0.249 h1:BNFGca7CI2t9DktB+FlXBg7GZFezS3NnKAPLmRirLlw=
|
github.com/volcengine/volc-sdk-golang v1.0.251 h1:N/3jkDfY+w/U/MC8T91Pvhpv8nBK4ESsxE7ykLWntlg=
|
||||||
github.com/volcengine/volc-sdk-golang v1.0.249/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM=
|
github.com/volcengine/volc-sdk-golang v1.0.251/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||||
@@ -898,8 +878,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.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 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
|
||||||
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
|
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.7.0 h1:RO+zqavD2/GCL3cxOMyZhx6R9Irzr8/6gsoqx5tcY/c=
|
go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.7.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
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.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
@@ -947,12 +927,10 @@ golang.org/x/arch v0.26.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
|||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
@@ -973,8 +951,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
|||||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@@ -1016,9 +994,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -1073,14 +1050,13 @@ 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.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
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.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||||
@@ -1100,8 +1076,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -1177,8 +1153,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@@ -1192,8 +1168,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
|||||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -1210,8 +1186,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@@ -1273,8 +1249,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
||||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -1341,8 +1317,8 @@ google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478 h1:aLsVTW0lZ8+IY5u
|
|||||||
google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478/go.mod h1:YJAzKjfHIUHb9T+bfu8L7mthAp7VVXQBUs1PLdBWS7M=
|
google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478/go.mod h1:YJAzKjfHIUHb9T+bfu8L7mthAp7VVXQBUs1PLdBWS7M=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7 h1:eM/YSd5bBFagF51o1E745Ta7RwzpW0h+z+QDNZOgmQ8=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
@@ -1360,8 +1336,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv
|
|||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
google.golang.org/grpc v1.82.0 h1:vguDnZUPjE26w09A63VoxZPnvPjB5Riyc0mkXPFmAIU=
|
||||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
google.golang.org/grpc v1.82.0/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@@ -1403,7 +1379,6 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -1455,5 +1430,5 @@ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||||
software.sslmate.com/src/go-pkcs12 v0.7.1 h1:bxkUPRsvTPNRBZa4M/aSX4PyMOEbq3V8I6hbkG4F4Q8=
|
software.sslmate.com/src/go-pkcs12 v0.7.3 h1:JBQD3FDqYjTeyDAeZQklj2ar88ykBLtALloPJHyAauU=
|
||||||
software.sslmate.com/src/go-pkcs12 v0.7.1/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
software.sslmate.com/src/go-pkcs12 v0.7.3/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ var langFiles = map[string]string{
|
|||||||
"ru": "lang/ru.yaml",
|
"ru": "lang/ru.yaml",
|
||||||
"ms": "lang/ms.yaml",
|
"ms": "lang/ms.yaml",
|
||||||
"ko": "lang/ko.yaml",
|
"ko": "lang/ko.yaml",
|
||||||
|
"lo": "lang/lo.yaml",
|
||||||
"tr": "lang/tr.yaml",
|
"tr": "lang/tr.yaml",
|
||||||
"es-ES": "lang/es-ES.yaml",
|
"es-ES": "lang/es-ES.yaml",
|
||||||
"fa": "lang/fa.yaml",
|
"fa": "lang/fa.yaml",
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
# base errors
|
# base errors
|
||||||
ErrInvalidParams: 'Bad request: {{ .detail }}'
|
ErrInvalidParams: 'Bad request: {{ .detail }}'
|
||||||
|
ErrMcpGatewayArgsParse: 'Invalid MCP custom argument format'
|
||||||
|
ErrMcpGatewayArgsConflict: 'Argument {{ .name }} is managed by 1Panel and cannot be set in custom arguments'
|
||||||
ErrTokenParse: 'Token error: {{ .detail }}'
|
ErrTokenParse: 'Token error: {{ .detail }}'
|
||||||
ErrInitialPassword: 'Original password is incorrect'
|
ErrInitialPassword: 'Original password is incorrect'
|
||||||
ErrInternalServer: 'Internal server error: {{ .detail }}'
|
ErrInternalServer: 'Internal server error: {{ .detail }}'
|
||||||
ErrRecordExist: 'Record already exists'
|
ErrRecordExist: 'Record already exists'
|
||||||
ErrRecordNotFound: 'Record not found'
|
ErrRecordNotFound: 'Record not found'
|
||||||
|
WafLogExport: 'WAF log export'
|
||||||
|
MonitorLogExport: 'Website monitoring log export'
|
||||||
|
LogExportNoData: 'No logs match the selected filters'
|
||||||
|
LogExportDiskSpace: 'Less than 1 GiB of disk space is available; the export was stopped'
|
||||||
|
LogExportRetentionInvalid: 'Export record retention must be between 1 and 30 days'
|
||||||
|
ErrSystemLogKeywordFilterUnsupported: 'The current system version does not support system log keyword filtering (requires systemd 237 or later)'
|
||||||
ErrStructTransform: 'Type conversion failed: {{ .err }}'
|
ErrStructTransform: 'Type conversion failed: {{ .err }}'
|
||||||
ErrNotSupportType: 'Unsupported type: {{ .name }}'
|
ErrNotSupportType: 'Unsupported type: {{ .name }}'
|
||||||
|
|
||||||
@@ -63,6 +71,7 @@ Decrypt: "Decrypt"
|
|||||||
# agent
|
# agent
|
||||||
ErrAgentAccountBound: 'Account is bound to an agent and cannot be deleted'
|
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.'
|
ErrTerminalAIAccountInUse: 'This model account is currently used by Terminal AI. Switch the account in Terminal AI settings or disable Terminal AI and try again.'
|
||||||
|
ErrFileAIAccountInUse: 'This model account is currently used by File Management AI. Switch the account in File Management AI settings or disable File Management AI and try again.'
|
||||||
ErrAgentAccountUnavailable: 'Account connection unavailable: {{ .err }}'
|
ErrAgentAccountUnavailable: 'Account connection unavailable: {{ .err }}'
|
||||||
ErrAgentProviderNotSupported: 'Unsupported agent provider'
|
ErrAgentProviderNotSupported: 'Unsupported agent provider'
|
||||||
ErrAgentAccountRequired: 'Select an agent account first'
|
ErrAgentAccountRequired: 'Select an agent account first'
|
||||||
@@ -70,6 +79,7 @@ ErrAgentAccountNotVerified: 'Agent account not verified'
|
|||||||
ErrAgentAccountModelsRequired: 'Please configure at least one model'
|
ErrAgentAccountModelsRequired: 'Please configure at least one model'
|
||||||
ErrAgentAccountSingleInitialModel: 'Only one initial model can be configured during account creation'
|
ErrAgentAccountSingleInitialModel: 'Only one initial model can be configured during account creation'
|
||||||
ErrAgentModelNotInAccount: 'Model is not configured in the selected account'
|
ErrAgentModelNotInAccount: 'Model is not configured in the selected account'
|
||||||
|
ErrAgentVerifyModelInUse: 'This model is used to verify the account. Select another verification model before deleting it'
|
||||||
ErrAgentFallbackModelDuplicate: 'Fallback models cannot be duplicated'
|
ErrAgentFallbackModelDuplicate: 'Fallback models cannot be duplicated'
|
||||||
ErrAgentFallbackModelPrimary: 'Fallback model cannot match the primary model'
|
ErrAgentFallbackModelPrimary: 'Fallback model cannot match the primary model'
|
||||||
ErrAgentModelInUse: 'A bound agent is still using this model: {{ .name }}'
|
ErrAgentModelInUse: 'A bound agent is still using this model: {{ .name }}'
|
||||||
@@ -101,6 +111,7 @@ ErrAppLimit: 'App install limit reached'
|
|||||||
ErrNotInstall: 'Application not installed'
|
ErrNotInstall: 'Application not installed'
|
||||||
ErrPortInOtherApp: '{{ .port }} port is already occupied by application {{ .apps }}!'
|
ErrPortInOtherApp: '{{ .port }} port is already occupied by application {{ .apps }}!'
|
||||||
ErrDbUserNotValid: 'Database username/password mismatch'
|
ErrDbUserNotValid: 'Database username/password mismatch'
|
||||||
|
ErrMysqlUserUsedByApps: 'This database user is used by [{{ .detail }}]; its host cannot be changed, and it cannot be deleted or have its authorization revoked'
|
||||||
ErrUpdateBuWebsite: 'App updated, but website config update failed'
|
ErrUpdateBuWebsite: 'App updated, but website config update failed'
|
||||||
Err1PanelNetworkFailed: 'Default container network creation failed! {{ .detail }}'
|
Err1PanelNetworkFailed: 'Default container network creation failed! {{ .detail }}'
|
||||||
ErrFileParse: 'Failed to parse app docker-compose file'
|
ErrFileParse: 'Failed to parse app docker-compose file'
|
||||||
@@ -121,6 +132,7 @@ ErrAppNameExist: 'The application name already exists'
|
|||||||
ErrAppInstallNameExist: 'App install name {{ .name }} already exists'
|
ErrAppInstallNameExist: 'App install name {{ .name }} already exists'
|
||||||
AppStoreIsSyncing: 'App Store is syncing; try again later'
|
AppStoreIsSyncing: 'App Store is syncing; try again later'
|
||||||
ErrGetCompose: 'Failed to read docker-compose.yml: {{ .detail }}'
|
ErrGetCompose: 'Failed to read docker-compose.yml: {{ .detail }}'
|
||||||
|
ErrAppVersionUnavailable: 'This application version has been removed from the remote service. Please select another version and try again.'
|
||||||
ErrAppWarn: 'App status abnormal; check logs'
|
ErrAppWarn: 'App status abnormal; check logs'
|
||||||
ErrAppParamKey: 'Invalid app parameter: {{ .name }}'
|
ErrAppParamKey: 'Invalid app parameter: {{ .name }}'
|
||||||
ErrAppUpgrade: 'App upgrade failed: {{ .name }} {{ .err }}'
|
ErrAppUpgrade: 'App upgrade failed: {{ .name }} {{ .err }}'
|
||||||
@@ -372,6 +384,9 @@ ErrParseIP: 'Invalid IP format'
|
|||||||
ErrDefaultIP: 'default is reserved; use another name'
|
ErrDefaultIP: 'default is reserved; use another name'
|
||||||
ErrGroupInUse: 'IP group is used by blacklist/whitelist rules'
|
ErrGroupInUse: 'IP group is used by blacklist/whitelist rules'
|
||||||
ErrIPGroupAclUse: 'IP group is used by website {{ .name }} custom rules'
|
ErrIPGroupAclUse: 'IP group is used by website {{ .name }} custom rules'
|
||||||
|
ErrIPGroupOriginProtectionUse: 'IP group is used by CDN origin protection for website {{ .name }}'
|
||||||
|
ErrOriginProtectionIPGroupRequired: 'Select at least one non-empty IP group before enabling origin protection'
|
||||||
|
ErrOriginProtectionIPGroupInvalid: 'IP group {{ .name }} does not exist or is empty'
|
||||||
ErrGroupExist: 'IP group name already exists'
|
ErrGroupExist: 'IP group name already exists'
|
||||||
ErrIPRange: 'Invalid IP range'
|
ErrIPRange: 'Invalid IP range'
|
||||||
ErrIPExist: 'IP already exists'
|
ErrIPExist: 'IP already exists'
|
||||||
@@ -550,6 +565,20 @@ ContainerRecreate: 'Container update failed; restoring original container'
|
|||||||
ContainerCreate: 'Create a new container {{ .name }}'
|
ContainerCreate: 'Create a new container {{ .name }}'
|
||||||
ContainerCreateFailed: 'Container create failed; deleting failed container'
|
ContainerCreateFailed: 'Container create failed; deleting failed container'
|
||||||
ContainerStartCheck: 'Check container started'
|
ContainerStartCheck: 'Check container started'
|
||||||
|
ContainerAcquireLock: 'Wait for container {{ .name }} operation lock'
|
||||||
|
ContainerUpgradeItem: 'Upgrade container {{ .name }}'
|
||||||
|
ContainerUpgradeSummary: 'Summarize container upgrade results'
|
||||||
|
ContainerCleanupWarning: 'The update succeeded, but backup container {{ .name }} could not be removed. After confirming the new container is working, remove the backup container manually. Error: {{ .err }}'
|
||||||
|
ContainerStopOld: 'Stop original container {{ .name }}'
|
||||||
|
ContainerRenameOld: 'Rename original container {{ .name }} for rollback'
|
||||||
|
ContainerDisconnectOld: 'Release original container {{ .name }} network addresses'
|
||||||
|
ContainerCreateReplacement: 'Create replacement container {{ .name }}'
|
||||||
|
ContainerStartReplacement: 'Start replacement container {{ .name }}'
|
||||||
|
ContainerWaitReplacement: 'Check replacement container {{ .name }} readiness'
|
||||||
|
ContainerRollbackRestartOld: 'Restart original container {{ .name }}'
|
||||||
|
ContainerRollbackRemoveReplacement: 'Remove failed replacement container {{ .name }}'
|
||||||
|
ContainerRollbackRenameOld: 'Restore original container {{ .name }} name'
|
||||||
|
ContainerRollbackReconnectOld: 'Restore original container {{ .name }} networks'
|
||||||
|
|
||||||
# task - image
|
# task - image
|
||||||
ImageBuild: 'Build image'
|
ImageBuild: 'Build image'
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
# errores básicos
|
# errores básicos
|
||||||
ErrInvalidParams: 'Solicitud inválida: {{ .detail }}'
|
ErrInvalidParams: 'Solicitud inválida: {{ .detail }}'
|
||||||
|
ErrMcpGatewayArgsParse: 'Formato de argumentos personalizados de MCP no válido'
|
||||||
|
ErrMcpGatewayArgsConflict: 'El argumento {{ .name }} está gestionado por 1Panel y no puede definirse en los argumentos personalizados'
|
||||||
ErrTokenParse: 'Token inválido: {{ .detail }}'
|
ErrTokenParse: 'Token inválido: {{ .detail }}'
|
||||||
ErrInitialPassword: 'Contraseña original incorrecta'
|
ErrInitialPassword: 'Contraseña original incorrecta'
|
||||||
ErrInternalServer: 'Error interno: {{ .detail }}'
|
ErrInternalServer: 'Error interno: {{ .detail }}'
|
||||||
ErrRecordExist: 'Registro ya existe'
|
ErrRecordExist: 'Registro ya existe'
|
||||||
ErrRecordNotFound: 'Registro no encontrado'
|
ErrRecordNotFound: 'Registro no encontrado'
|
||||||
|
WafLogExport: 'Exportación de registros WAF'
|
||||||
|
MonitorLogExport: 'Exportación de registros de monitorización web'
|
||||||
|
LogExportNoData: 'Ningún registro coincide con los filtros seleccionados'
|
||||||
|
LogExportDiskSpace: 'Hay menos de 1 GiB disponible; la exportación se detuvo'
|
||||||
|
LogExportRetentionInvalid: 'La retención del historial de exportación debe ser de 1 a 30 días'
|
||||||
|
ErrSystemLogKeywordFilterUnsupported: 'La versión actual del sistema no admite el filtrado por palabras clave de los registros del sistema (requiere systemd 237 o posterior)'
|
||||||
ErrStructTransform: 'Error de conversión: {{ .err }}'
|
ErrStructTransform: 'Error de conversión: {{ .err }}'
|
||||||
ErrNotSupportType: 'Tipo no soportado: {{ .name }}'
|
ErrNotSupportType: 'Tipo no soportado: {{ .name }}'
|
||||||
|
|
||||||
@@ -63,6 +71,7 @@ Decrypt: 'Descifrar'
|
|||||||
# agente
|
# agente
|
||||||
ErrAgentAccountBound: 'Cuenta vinculada a 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.'
|
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.'
|
||||||
|
ErrFileAIAccountInUse: 'Esta cuenta de modelo está siendo utilizada por la IA del gestor de archivos. Cambia la cuenta en su configuración o desactívala y vuelve a intentarlo.'
|
||||||
ErrAgentAccountUnavailable: 'Conexión de cuenta no disponible: {{ .err }}'
|
ErrAgentAccountUnavailable: 'Conexión de cuenta no disponible: {{ .err }}'
|
||||||
ErrAgentProviderNotSupported: 'Proveedor de agente no soportado'
|
ErrAgentProviderNotSupported: 'Proveedor de agente no soportado'
|
||||||
ErrAgentAccountRequired: 'Elige una cuenta de agente primero'
|
ErrAgentAccountRequired: 'Elige una cuenta de agente primero'
|
||||||
@@ -70,6 +79,7 @@ ErrAgentAccountNotVerified: 'Cuenta de agente no verificada'
|
|||||||
ErrAgentAccountModelsRequired: 'Configure al menos un modelo'
|
ErrAgentAccountModelsRequired: 'Configure al menos un modelo'
|
||||||
ErrAgentAccountSingleInitialModel: 'Solo se puede configurar un modelo inicial al crear la cuenta'
|
ErrAgentAccountSingleInitialModel: 'Solo se puede configurar un modelo inicial al crear la cuenta'
|
||||||
ErrAgentModelNotInAccount: 'El modelo no está configurado en la cuenta seleccionada'
|
ErrAgentModelNotInAccount: 'El modelo no está configurado en la cuenta seleccionada'
|
||||||
|
ErrAgentVerifyModelInUse: 'Este modelo se usa para verificar la cuenta. Seleccione otro modelo de verificación antes de eliminarlo'
|
||||||
ErrAgentFallbackModelDuplicate: 'Los modelos de respaldo no pueden duplicarse'
|
ErrAgentFallbackModelDuplicate: 'Los modelos de respaldo no pueden duplicarse'
|
||||||
ErrAgentFallbackModelPrimary: 'El modelo de respaldo no puede ser igual al modelo principal'
|
ErrAgentFallbackModelPrimary: 'El modelo de respaldo no puede ser igual al modelo principal'
|
||||||
ErrAgentModelInUse: 'Un agente vinculado todavía usa este modelo: {{ .name }}'
|
ErrAgentModelInUse: 'Un agente vinculado todavía usa este modelo: {{ .name }}'
|
||||||
@@ -101,6 +111,7 @@ ErrAppLimit: 'El número de aplicaciones instaladas ha superado el límite'
|
|||||||
ErrNotInstall: 'Aplicación no instalada'
|
ErrNotInstall: 'Aplicación no instalada'
|
||||||
ErrPortInOtherApp: 'El puerto {{ .port }} ya está ocupado por la aplicación {{ .apps }}'
|
ErrPortInOtherApp: 'El puerto {{ .port }} ya está ocupado por la aplicación {{ .apps }}'
|
||||||
ErrDbUserNotValid: 'Base de datos existente, usuario y contraseña no coinciden'
|
ErrDbUserNotValid: 'Base de datos existente, usuario y contraseña no coinciden'
|
||||||
|
ErrMysqlUserUsedByApps: 'Este usuario de base de datos está siendo utilizado por [{{ .detail }}]; no se puede cambiar su host, eliminarlo ni revocar su autorización'
|
||||||
ErrUpdateBuWebsite: 'La aplicación se actualizó correctamente, pero falló la modificación del archivo de configuración del sitio web. revise la configuración'
|
ErrUpdateBuWebsite: 'La aplicación se actualizó correctamente, pero falló la modificación del archivo de configuración del sitio web. revise la configuración'
|
||||||
Err1PanelNetworkFailed: 'Fallo la creación de la red por defecto del contenedor {{ .detail }}'
|
Err1PanelNetworkFailed: 'Fallo la creación de la red por defecto del contenedor {{ .detail }}'
|
||||||
ErrFileParse: 'Fallo al analizar el archivo docker-compose de la aplicación'
|
ErrFileParse: 'Fallo al analizar el archivo docker-compose de la aplicación'
|
||||||
@@ -121,6 +132,7 @@ ErrAppNameExist: 'El nombre de la aplicación ya existe'
|
|||||||
ErrAppInstallNameExist: 'El nombre de instalación de la aplicación {{ .name }} 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'
|
AppStoreIsSyncing: 'La tienda de aplicaciones está sincronizando, inténtelo más tarde'
|
||||||
ErrGetCompose: 'Fallo al obtener el archivo docker-compose.yml {{ .detail }}'
|
ErrGetCompose: 'Fallo al obtener el archivo docker-compose.yml {{ .detail }}'
|
||||||
|
ErrAppVersionUnavailable: 'Esta versión de la aplicación se ha eliminado del servicio remoto. Seleccione otra versión e inténtelo de nuevo.'
|
||||||
ErrAppWarn: 'Estado anómalo, revise el log'
|
ErrAppWarn: 'Estado anómalo, revise el log'
|
||||||
ErrAppParamKey: 'El campo de parámetro {{ .name }} es anómalo'
|
ErrAppParamKey: 'El campo de parámetro {{ .name }} es anómalo'
|
||||||
ErrAppUpgrade: 'La actualización de la aplicación {{ .name }} falló {{ .err }}'
|
ErrAppUpgrade: 'La actualización de la aplicación {{ .name }} falló {{ .err }}'
|
||||||
@@ -372,6 +384,9 @@ ErrParseIP: 'Formato de IP incorrecto'
|
|||||||
ErrDefaultIP: 'default es un nombre reservado, cámbielo'
|
ErrDefaultIP: 'default es un nombre reservado, cámbielo'
|
||||||
ErrGroupInUse: 'El grupo IP está en uso por listas negras/blancas y no se puede eliminar'
|
ErrGroupInUse: 'El grupo IP está en uso por listas negras/blancas y no se puede eliminar'
|
||||||
ErrIPGroupAclUse: 'El grupo IP está en uso por reglas personalizadas del sitio web {{ .name }}, no puede eliminarse'
|
ErrIPGroupAclUse: 'El grupo IP está en uso por reglas personalizadas del sitio web {{ .name }}, no puede eliminarse'
|
||||||
|
ErrIPGroupOriginProtectionUse: 'El grupo IP está en uso por la protección de origen CDN del sitio web {{ .name }}'
|
||||||
|
ErrOriginProtectionIPGroupRequired: 'Seleccione al menos un grupo IP no vacío antes de habilitar la protección de origen'
|
||||||
|
ErrOriginProtectionIPGroupInvalid: 'El grupo IP {{ .name }} no existe o está vacío'
|
||||||
ErrGroupExist: 'El nombre del grupo IP ya existe'
|
ErrGroupExist: 'El nombre del grupo IP ya existe'
|
||||||
ErrIPRange: 'Rango de IP incorrecto'
|
ErrIPRange: 'Rango de IP incorrecto'
|
||||||
ErrIPExist: 'La IP ya existe'
|
ErrIPExist: 'La IP ya existe'
|
||||||
@@ -550,6 +565,20 @@ ContainerRecreate: 'Actualización del contenedor fallida, restaurando el conten
|
|||||||
ContainerCreate: 'Crear nuevo contenedor {{ .name }}'
|
ContainerCreate: 'Crear nuevo contenedor {{ .name }}'
|
||||||
ContainerCreateFailed: 'Falló la creación del contenedor, eliminando el contenedor fallido'
|
ContainerCreateFailed: 'Falló la creación del contenedor, eliminando el contenedor fallido'
|
||||||
ContainerStartCheck: 'Verificar si el contenedor ha iniciado'
|
ContainerStartCheck: 'Verificar si el contenedor ha iniciado'
|
||||||
|
ContainerAcquireLock: 'Esperar el bloqueo de operación del contenedor {{ .name }}'
|
||||||
|
ContainerUpgradeItem: 'Actualizar el contenedor {{ .name }}'
|
||||||
|
ContainerUpgradeSummary: 'Resumir los resultados de actualización de contenedores'
|
||||||
|
ContainerCleanupWarning: 'La actualización se completó, pero no se pudo eliminar el contenedor de respaldo {{ .name }}. Después de confirmar que el nuevo contenedor funciona, elimine manualmente el contenedor de respaldo. Error: {{ .err }}'
|
||||||
|
ContainerStopOld: 'Detener el contenedor original {{ .name }}'
|
||||||
|
ContainerRenameOld: 'Renombrar el contenedor original {{ .name }} para la reversión'
|
||||||
|
ContainerDisconnectOld: 'Liberar las direcciones de red del contenedor original {{ .name }}'
|
||||||
|
ContainerCreateReplacement: 'Crear el contenedor de reemplazo {{ .name }}'
|
||||||
|
ContainerStartReplacement: 'Iniciar el contenedor de reemplazo {{ .name }}'
|
||||||
|
ContainerWaitReplacement: 'Verificar que el contenedor de reemplazo {{ .name }} esté listo'
|
||||||
|
ContainerRollbackRestartOld: 'Reiniciar el contenedor original {{ .name }}'
|
||||||
|
ContainerRollbackRemoveReplacement: 'Eliminar el contenedor de reemplazo fallido {{ .name }}'
|
||||||
|
ContainerRollbackRenameOld: 'Restaurar el nombre del contenedor original {{ .name }}'
|
||||||
|
ContainerRollbackReconnectOld: 'Restaurar las redes del contenedor original {{ .name }}'
|
||||||
|
|
||||||
# tarea - imagen
|
# tarea - imagen
|
||||||
ImageBuild: 'Construcción de imagen'
|
ImageBuild: 'Construcción de imagen'
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
# خطاهای پایه
|
# خطاهای پایه
|
||||||
ErrInvalidParams: 'درخواست نامعتبر: {{ .detail }}'
|
ErrInvalidParams: 'درخواست نامعتبر: {{ .detail }}'
|
||||||
|
ErrMcpGatewayArgsParse: 'قالب آرگومانهای سفارشی MCP نامعتبر است'
|
||||||
|
ErrMcpGatewayArgsConflict: 'آرگومان {{ .name }} توسط 1Panel مدیریت میشود و نمیتوان آن را در آرگومانهای سفارشی تنظیم کرد'
|
||||||
ErrTokenParse: 'خطا در توکن: {{ .detail }}'
|
ErrTokenParse: 'خطا در توکن: {{ .detail }}'
|
||||||
ErrInitialPassword: 'رمز عبور اصلی اشتباه است'
|
ErrInitialPassword: 'رمز عبور اصلی اشتباه است'
|
||||||
ErrInternalServer: 'خطای داخلی سرور: {{ .detail }}'
|
ErrInternalServer: 'خطای داخلی سرور: {{ .detail }}'
|
||||||
ErrRecordExist: 'رکورد از قبل وجود دارد'
|
ErrRecordExist: 'رکورد از قبل وجود دارد'
|
||||||
ErrRecordNotFound: 'رکورد یافت نشد'
|
ErrRecordNotFound: 'رکورد یافت نشد'
|
||||||
|
WafLogExport: 'خروجی لاگ WAF'
|
||||||
|
MonitorLogExport: 'خروجی لاگ پایش وبسایت'
|
||||||
|
LogExportNoData: 'هیچ لاگی با فیلترهای انتخابشده مطابقت ندارد'
|
||||||
|
LogExportDiskSpace: 'فضای آزاد کمتر از ۱ گیگابایت است؛ خروجی متوقف شد'
|
||||||
|
LogExportRetentionInvalid: 'مدت نگهداری سوابق خروجی باید بین ۱ تا ۳۰ روز باشد'
|
||||||
|
ErrSystemLogKeywordFilterUnsupported: 'نسخه فعلی سیستم از فیلتر کلیدواژه در گزارشهای سیستم پشتیبانی نمیکند (به systemd 237 یا جدیدتر نیاز است)'
|
||||||
ErrStructTransform: 'تبدیل نوع ناموفق بود: {{ .err }}'
|
ErrStructTransform: 'تبدیل نوع ناموفق بود: {{ .err }}'
|
||||||
ErrNotSupportType: 'نوع پشتیبانی نمیشود: {{ .name }}'
|
ErrNotSupportType: 'نوع پشتیبانی نمیشود: {{ .name }}'
|
||||||
|
|
||||||
@@ -63,6 +71,7 @@ Decrypt: "رمزگشایی"
|
|||||||
# عامل
|
# عامل
|
||||||
ErrAgentAccountBound: 'حساب به یک عامل متصل است و قابل حذف نیست'
|
ErrAgentAccountBound: 'حساب به یک عامل متصل است و قابل حذف نیست'
|
||||||
ErrTerminalAIAccountInUse: 'این حساب مدل در حال حاضر توسط ترمینال هوش مصنوعی استفاده میشود. حساب را در تنظیمات ترمینال هوش مصنوعی تغییر دهید یا ترمینال هوش مصنوعی را غیرفعال کرده و دوباره تلاش کنید.'
|
ErrTerminalAIAccountInUse: 'این حساب مدل در حال حاضر توسط ترمینال هوش مصنوعی استفاده میشود. حساب را در تنظیمات ترمینال هوش مصنوعی تغییر دهید یا ترمینال هوش مصنوعی را غیرفعال کرده و دوباره تلاش کنید.'
|
||||||
|
ErrFileAIAccountInUse: 'این حساب مدل توسط هوش مصنوعی مدیریت فایل استفاده میشود. حساب را در تنظیمات آن تغییر دهید یا هوش مصنوعی مدیریت فایل را غیرفعال کرده و دوباره تلاش کنید.'
|
||||||
ErrAgentAccountUnavailable: 'اتصال حساب در دسترس نیست: {{ .err }}'
|
ErrAgentAccountUnavailable: 'اتصال حساب در دسترس نیست: {{ .err }}'
|
||||||
ErrAgentProviderNotSupported: 'ارائهدهنده عامل پشتیبانی نمیشود'
|
ErrAgentProviderNotSupported: 'ارائهدهنده عامل پشتیبانی نمیشود'
|
||||||
ErrAgentAccountRequired: 'ابتدا یک حساب عامل انتخاب کنید'
|
ErrAgentAccountRequired: 'ابتدا یک حساب عامل انتخاب کنید'
|
||||||
@@ -70,6 +79,7 @@ ErrAgentAccountNotVerified: 'حساب عامل تأیید نشده است'
|
|||||||
ErrAgentAccountModelsRequired: 'لطفاً حداقل یک مدل پیکربندی کنید'
|
ErrAgentAccountModelsRequired: 'لطفاً حداقل یک مدل پیکربندی کنید'
|
||||||
ErrAgentAccountSingleInitialModel: 'فقط یک مدل اولیه میتواند در هنگام ایجاد حساب پیکربندی شود'
|
ErrAgentAccountSingleInitialModel: 'فقط یک مدل اولیه میتواند در هنگام ایجاد حساب پیکربندی شود'
|
||||||
ErrAgentModelNotInAccount: 'مدل در حساب انتخاب شده پیکربندی نشده است'
|
ErrAgentModelNotInAccount: 'مدل در حساب انتخاب شده پیکربندی نشده است'
|
||||||
|
ErrAgentVerifyModelInUse: 'این مدل برای بررسی حساب استفاده میشود. پیش از حذف، مدل بررسی دیگری انتخاب کنید'
|
||||||
ErrAgentFallbackModelDuplicate: 'مدلهای جایگزین نمیتوانند تکراری باشند'
|
ErrAgentFallbackModelDuplicate: 'مدلهای جایگزین نمیتوانند تکراری باشند'
|
||||||
ErrAgentFallbackModelPrimary: 'مدل جایگزین نمیتواند با مدل اصلی یکی باشد'
|
ErrAgentFallbackModelPrimary: 'مدل جایگزین نمیتواند با مدل اصلی یکی باشد'
|
||||||
ErrAgentModelInUse: 'یک عامل متصل هنوز از این مدل استفاده میکند: {{ .name }}'
|
ErrAgentModelInUse: 'یک عامل متصل هنوز از این مدل استفاده میکند: {{ .name }}'
|
||||||
@@ -101,6 +111,7 @@ ErrAppLimit: 'محدودیت نصب برنامه رسیده است'
|
|||||||
ErrNotInstall: 'برنامه نصب نشده است'
|
ErrNotInstall: 'برنامه نصب نشده است'
|
||||||
ErrPortInOtherApp: 'پورت {{ .port }} قبلاً توسط برنامه {{ .apps }} اشغال شده است!'
|
ErrPortInOtherApp: 'پورت {{ .port }} قبلاً توسط برنامه {{ .apps }} اشغال شده است!'
|
||||||
ErrDbUserNotValid: 'نام کاربری/رمز عبور پایگاه داده مطابقت ندارد'
|
ErrDbUserNotValid: 'نام کاربری/رمز عبور پایگاه داده مطابقت ندارد'
|
||||||
|
ErrMysqlUserUsedByApps: 'این کاربر پایگاه داده توسط [{{ .detail }}] استفاده میشود و میزبان آن قابل تغییر نیست؛ همچنین نمیتوان آن را حذف یا مجوزش را لغو کرد'
|
||||||
ErrUpdateBuWebsite: 'برنامه بهروزرسانی شد، اما بهروزرسانی پیکربندی وبسایت ناموفق بود'
|
ErrUpdateBuWebsite: 'برنامه بهروزرسانی شد، اما بهروزرسانی پیکربندی وبسایت ناموفق بود'
|
||||||
Err1PanelNetworkFailed: 'ایجاد شبکه پیشفرض کانتینر ناموفق بود! {{ .detail }}'
|
Err1PanelNetworkFailed: 'ایجاد شبکه پیشفرض کانتینر ناموفق بود! {{ .detail }}'
|
||||||
ErrFileParse: 'تجزیه فایل docker-compose برنامه ناموفق بود'
|
ErrFileParse: 'تجزیه فایل docker-compose برنامه ناموفق بود'
|
||||||
@@ -121,6 +132,7 @@ ErrAppNameExist: 'نام برنامه از قبل وجود دارد'
|
|||||||
ErrAppInstallNameExist: 'نام نصب برنامه {{ .name }} از قبل وجود دارد'
|
ErrAppInstallNameExist: 'نام نصب برنامه {{ .name }} از قبل وجود دارد'
|
||||||
AppStoreIsSyncing: 'فروشگاه برنامه در حال همگامسازی است؛ بعداً دوباره تلاش کنید'
|
AppStoreIsSyncing: 'فروشگاه برنامه در حال همگامسازی است؛ بعداً دوباره تلاش کنید'
|
||||||
ErrGetCompose: 'خواندن docker-compose.yml ناموفق بود: {{ .detail }}'
|
ErrGetCompose: 'خواندن docker-compose.yml ناموفق بود: {{ .detail }}'
|
||||||
|
ErrAppVersionUnavailable: 'این نسخه برنامه از سرویس راه دور حذف شده است. لطفاً نسخه دیگری را انتخاب کرده و دوباره تلاش کنید.'
|
||||||
ErrAppWarn: 'وضعیت برنامه غیرعادی است؛ لاگها را بررسی کنید'
|
ErrAppWarn: 'وضعیت برنامه غیرعادی است؛ لاگها را بررسی کنید'
|
||||||
ErrAppParamKey: 'پارامتر برنامه نامعتبر است: {{ .name }}'
|
ErrAppParamKey: 'پارامتر برنامه نامعتبر است: {{ .name }}'
|
||||||
ErrAppUpgrade: 'ارتقاء برنامه ناموفق بود: {{ .name }} {{ .err }}'
|
ErrAppUpgrade: 'ارتقاء برنامه ناموفق بود: {{ .name }} {{ .err }}'
|
||||||
@@ -372,6 +384,9 @@ ErrParseIP: 'فرمت IP نامعتبر است'
|
|||||||
ErrDefaultIP: 'default رزرو شده است؛ از نام دیگری استفاده کنید'
|
ErrDefaultIP: 'default رزرو شده است؛ از نام دیگری استفاده کنید'
|
||||||
ErrGroupInUse: 'گروه IP توسط قوانین لیست سیاه/سفید استفاده میشود'
|
ErrGroupInUse: 'گروه IP توسط قوانین لیست سیاه/سفید استفاده میشود'
|
||||||
ErrIPGroupAclUse: 'گروه IP توسط قوانین سفارشی وبسایت {{ .name }} استفاده میشود'
|
ErrIPGroupAclUse: 'گروه IP توسط قوانین سفارشی وبسایت {{ .name }} استفاده میشود'
|
||||||
|
ErrIPGroupOriginProtectionUse: 'گروه IP توسط محافظت مبدأ CDN وبسایت {{ .name }} استفاده میشود'
|
||||||
|
ErrOriginProtectionIPGroupRequired: 'پیش از فعالسازی محافظت مبدأ، حداقل یک گروه IP غیرخالی انتخاب کنید'
|
||||||
|
ErrOriginProtectionIPGroupInvalid: 'گروه IP {{ .name }} وجود ندارد یا خالی است'
|
||||||
ErrGroupExist: 'نام گروه IP از قبل وجود دارد'
|
ErrGroupExist: 'نام گروه IP از قبل وجود دارد'
|
||||||
ErrIPRange: 'محدوده IP نامعتبر است'
|
ErrIPRange: 'محدوده IP نامعتبر است'
|
||||||
ErrIPExist: 'IP از قبل وجود دارد'
|
ErrIPExist: 'IP از قبل وجود دارد'
|
||||||
@@ -550,6 +565,20 @@ ContainerRecreate: 'بهروزرسانی کانتینر ناموفق بود؛
|
|||||||
ContainerCreate: 'ایجاد کانتینر جدید {{ .name }}'
|
ContainerCreate: 'ایجاد کانتینر جدید {{ .name }}'
|
||||||
ContainerCreateFailed: 'ایجاد کانتینر ناموفق بود؛ حذف کانتینر ناموفق'
|
ContainerCreateFailed: 'ایجاد کانتینر ناموفق بود؛ حذف کانتینر ناموفق'
|
||||||
ContainerStartCheck: 'بررسی شروع کانتینر'
|
ContainerStartCheck: 'بررسی شروع کانتینر'
|
||||||
|
ContainerAcquireLock: 'انتظار برای قفل عملیات کانتینر {{ .name }}'
|
||||||
|
ContainerUpgradeItem: 'ارتقای کانتینر {{ .name }}'
|
||||||
|
ContainerUpgradeSummary: 'خلاصه نتایج ارتقای کانتینرها'
|
||||||
|
ContainerCleanupWarning: 'بهروزرسانی موفق بود، اما کانتینر پشتیبان {{ .name }} حذف نشد. پس از اطمینان از عملکرد صحیح کانتینر جدید، کانتینر پشتیبان را بهصورت دستی حذف کنید. خطا: {{ .err }}'
|
||||||
|
ContainerStopOld: 'توقف کانتینر اصلی {{ .name }}'
|
||||||
|
ContainerRenameOld: 'تغییر نام کانتینر اصلی {{ .name }} برای بازگردانی'
|
||||||
|
ContainerDisconnectOld: 'آزادسازی آدرسهای شبکه کانتینر اصلی {{ .name }}'
|
||||||
|
ContainerCreateReplacement: 'ایجاد کانتینر جایگزین {{ .name }}'
|
||||||
|
ContainerStartReplacement: 'راهاندازی کانتینر جایگزین {{ .name }}'
|
||||||
|
ContainerWaitReplacement: 'بررسی آماده بودن کانتینر جایگزین {{ .name }}'
|
||||||
|
ContainerRollbackRestartOld: 'راهاندازی دوباره کانتینر اصلی {{ .name }}'
|
||||||
|
ContainerRollbackRemoveReplacement: 'حذف کانتینر جایگزین ناموفق {{ .name }}'
|
||||||
|
ContainerRollbackRenameOld: 'بازگردانی نام کانتینر اصلی {{ .name }}'
|
||||||
|
ContainerRollbackReconnectOld: 'بازگردانی شبکههای کانتینر اصلی {{ .name }}'
|
||||||
|
|
||||||
# وظیفه - تصویر
|
# وظیفه - تصویر
|
||||||
ImageBuild: 'ساخت تصویر'
|
ImageBuild: 'ساخت تصویر'
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
# 基本エラー
|
# 基本エラー
|
||||||
ErrInvalidParams: '不正なリクエスト: {{ .detail }}'
|
ErrInvalidParams: '不正なリクエスト: {{ .detail }}'
|
||||||
|
ErrMcpGatewayArgsParse: 'MCP カスタム引数の形式が正しくありません'
|
||||||
|
ErrMcpGatewayArgsConflict: '引数 {{ .name }} は 1Panel によって管理されているため、カスタム引数には設定できません'
|
||||||
ErrTokenParse: 'トークンエラー: {{ .detail }}'
|
ErrTokenParse: 'トークンエラー: {{ .detail }}'
|
||||||
ErrInitialPassword: '元のパスワードが違います'
|
ErrInitialPassword: '元のパスワードが違います'
|
||||||
ErrInternalServer: '内部エラー: {{ .detail }}'
|
ErrInternalServer: '内部エラー: {{ .detail }}'
|
||||||
ErrRecordExist: 'レコードが既に存在します'
|
ErrRecordExist: 'レコードが既に存在します'
|
||||||
ErrRecordNotFound: 'レコードが見つかりません'
|
ErrRecordNotFound: 'レコードが見つかりません'
|
||||||
|
WafLogExport: 'WAF ログのエクスポート'
|
||||||
|
MonitorLogExport: 'Web サイト監視ログのエクスポート'
|
||||||
|
LogExportNoData: '条件に一致するログがありません'
|
||||||
|
LogExportDiskSpace: '空き容量が 1 GiB 未満のため、エクスポートを停止しました'
|
||||||
|
LogExportRetentionInvalid: 'エクスポート履歴の保持期間は 1~30 日にしてください'
|
||||||
|
ErrSystemLogKeywordFilterUnsupported: '現在のシステムバージョンはシステムログのキーワードフィルタリングに対応していません(systemd 237 以降が必要です)'
|
||||||
ErrStructTransform: '型変換エラー: {{ .err }}'
|
ErrStructTransform: '型変換エラー: {{ .err }}'
|
||||||
ErrNotSupportType: '未対応のタイプ: {{ .name }}'
|
ErrNotSupportType: '未対応のタイプ: {{ .name }}'
|
||||||
|
|
||||||
@@ -63,6 +71,7 @@ Decrypt: '復号化'
|
|||||||
# エージェント
|
# エージェント
|
||||||
ErrAgentAccountBound: 'アカウントはエージェントに紐づいています'
|
ErrAgentAccountBound: 'アカウントはエージェントに紐づいています'
|
||||||
ErrTerminalAIAccountInUse: 'このモデルアカウントは Terminal AI で使用中です。Terminal AI の設定で別のアカウントへ切り替えるか、Terminal AI を無効にしてから再試行してください。'
|
ErrTerminalAIAccountInUse: 'このモデルアカウントは Terminal AI で使用中です。Terminal AI の設定で別のアカウントへ切り替えるか、Terminal AI を無効にしてから再試行してください。'
|
||||||
|
ErrFileAIAccountInUse: 'このモデルアカウントはファイル管理 AI で使用中です。ファイル管理 AI の設定で別のアカウントへ切り替えるか、ファイル管理 AI を無効にしてから再試行してください。'
|
||||||
ErrAgentAccountUnavailable: 'アカウント接続不可: {{ .err }}'
|
ErrAgentAccountUnavailable: 'アカウント接続不可: {{ .err }}'
|
||||||
ErrAgentProviderNotSupported: 'エージェントプロバイダ非対応'
|
ErrAgentProviderNotSupported: 'エージェントプロバイダ非対応'
|
||||||
ErrAgentAccountRequired: 'まずエージェントアカウントを選択'
|
ErrAgentAccountRequired: 'まずエージェントアカウントを選択'
|
||||||
@@ -70,6 +79,7 @@ ErrAgentAccountNotVerified: 'アカウント未検証です'
|
|||||||
ErrAgentAccountModelsRequired: '少なくとも 1 つのモデルを設定してください'
|
ErrAgentAccountModelsRequired: '少なくとも 1 つのモデルを設定してください'
|
||||||
ErrAgentAccountSingleInitialModel: 'アカウント作成時に設定できる初期モデルは 1 つだけです'
|
ErrAgentAccountSingleInitialModel: 'アカウント作成時に設定できる初期モデルは 1 つだけです'
|
||||||
ErrAgentModelNotInAccount: '選択したモデルはこのアカウントに設定されていません'
|
ErrAgentModelNotInAccount: '選択したモデルはこのアカウントに設定されていません'
|
||||||
|
ErrAgentVerifyModelInUse: 'このモデルはアカウント検証に使用されています。削除する前に別の検証モデルを選択してください'
|
||||||
ErrAgentFallbackModelDuplicate: 'フォールバックモデルは重複できません'
|
ErrAgentFallbackModelDuplicate: 'フォールバックモデルは重複できません'
|
||||||
ErrAgentFallbackModelPrimary: 'フォールバックモデルは主モデルと同じにできません'
|
ErrAgentFallbackModelPrimary: 'フォールバックモデルは主モデルと同じにできません'
|
||||||
ErrAgentModelInUse: 'このモデルはまだエージェントで使用中です: {{ .name }}'
|
ErrAgentModelInUse: 'このモデルはまだエージェントで使用中です: {{ .name }}'
|
||||||
@@ -101,6 +111,7 @@ ErrAppLimit: 'インストールされているアプリケーションの数が
|
|||||||
ErrNotInstall: 'アプリケーションがインストールされていません'
|
ErrNotInstall: 'アプリケーションがインストールされていません'
|
||||||
ErrPortInOtherApp: '{{ .port }} ポートは既にアプリケーション {{ .apps }} によって使用されています'
|
ErrPortInOtherApp: '{{ .port }} ポートは既にアプリケーション {{ .apps }} によって使用されています'
|
||||||
ErrDbUserNotValid: '既存のデータベース、ユーザー名、およびパスワードが一致しません'
|
ErrDbUserNotValid: '既存のデータベース、ユーザー名、およびパスワードが一致しません'
|
||||||
|
ErrMysqlUserUsedByApps: 'このデータベースユーザーは【{{ .detail }}】で使用されているため、ホストの変更、削除、または権限の取り消しはできません'
|
||||||
ErrUpdateBuWebsite: 'アプリケーションは正常に更新されましたが、Web サイト構成ファイルの変更に失敗しました。設定を確認してください'
|
ErrUpdateBuWebsite: 'アプリケーションは正常に更新されましたが、Web サイト構成ファイルの変更に失敗しました。設定を確認してください'
|
||||||
Err1PanelNetworkFailed: 'デフォルトのコンテナ ネットワークの作成に失敗しました。 {{ .detail }}'
|
Err1PanelNetworkFailed: 'デフォルトのコンテナ ネットワークの作成に失敗しました。 {{ .detail }}'
|
||||||
ErrFileParse: 'アプリケーションの docker-compose ファイルの解析に失敗しました'
|
ErrFileParse: 'アプリケーションの docker-compose ファイルの解析に失敗しました'
|
||||||
@@ -121,6 +132,7 @@ ErrAppNameExist: 'アプリケーション名がすでに存在します'
|
|||||||
ErrAppInstallNameExist: 'アプリケーションインストール名 {{ .name }} はすでに存在します'
|
ErrAppInstallNameExist: 'アプリケーションインストール名 {{ .name }} はすでに存在します'
|
||||||
AppStoreIsSyncing: 'App Store が同期中です。しばらくしてからもう一度お試しください'
|
AppStoreIsSyncing: 'App Store が同期中です。しばらくしてからもう一度お試しください'
|
||||||
ErrGetCompose: 'docker-compose.yml ファイルの取得に失敗しました {{ .detail }}'
|
ErrGetCompose: 'docker-compose.yml ファイルの取得に失敗しました {{ .detail }}'
|
||||||
|
ErrAppVersionUnavailable: '現在のアプリケーションバージョンはリモートサービスから削除されています。別のバージョンを選択して再試行してください。'
|
||||||
ErrAppWarn: '異常な状態です。ログを確認してください'
|
ErrAppWarn: '異常な状態です。ログを確認してください'
|
||||||
ErrAppParamKey: 'パラメータ {{ .name }} フィールドが異常です'
|
ErrAppParamKey: 'パラメータ {{ .name }} フィールドが異常です'
|
||||||
ErrAppUpgrade: 'アプリケーション {{ .name }} のアップグレードに失敗しました {{ .err }}'
|
ErrAppUpgrade: 'アプリケーション {{ .name }} のアップグレードに失敗しました {{ .err }}'
|
||||||
@@ -372,6 +384,9 @@ ErrParseIP: 'IP 形式が間違っています'
|
|||||||
ErrDefaultIP: 'デフォルトは予約名です。別の名前に変更してください'
|
ErrDefaultIP: 'デフォルトは予約名です。別の名前に変更してください'
|
||||||
ErrGroupInUse: 'IP グループはブラックリスト/ホワイトリストで使用されており、削除できません'
|
ErrGroupInUse: 'IP グループはブラックリスト/ホワイトリストで使用されており、削除できません'
|
||||||
ErrIPGroupAclUse: 'IPグループはウェブサイト {{ .name }} のカスタムルールで使用されているため、削除できません'
|
ErrIPGroupAclUse: 'IPグループはウェブサイト {{ .name }} のカスタムルールで使用されているため、削除できません'
|
||||||
|
ErrIPGroupOriginProtectionUse: 'IP グループはウェブサイト {{ .name }} の CDN オリジン保護で使用されています'
|
||||||
|
ErrOriginProtectionIPGroupRequired: 'オリジン保護を有効にする前に空でない IP グループを少なくとも 1 つ選択してください'
|
||||||
|
ErrOriginProtectionIPGroupInvalid: 'IP グループ {{ .name }} は存在しないか空です'
|
||||||
ErrGroupExist: 'IP グループ名がすでに存在します'
|
ErrGroupExist: 'IP グループ名がすでに存在します'
|
||||||
ErrIPRange: 'IP 範囲が間違っています'
|
ErrIPRange: 'IP 範囲が間違っています'
|
||||||
ErrIPExist: 'IP がすでに存在します'
|
ErrIPExist: 'IP がすでに存在します'
|
||||||
@@ -550,6 +565,20 @@ ContainerRecreate: 'コンテナの更新に失敗しました。元のコンテ
|
|||||||
ContainerCreate: '新しいコンテナ {{ .name }} を作成します'
|
ContainerCreate: '新しいコンテナ {{ .name }} を作成します'
|
||||||
ContainerCreateFailed: 'コンテナの作成に失敗しました。失敗したコンテナを削除してください'
|
ContainerCreateFailed: 'コンテナの作成に失敗しました。失敗したコンテナを削除してください'
|
||||||
ContainerStartCheck: 'コンテナが起動されているかどうかを確認します'
|
ContainerStartCheck: 'コンテナが起動されているかどうかを確認します'
|
||||||
|
ContainerAcquireLock: 'コンテナ {{ .name }} の操作ロックを待機'
|
||||||
|
ContainerUpgradeItem: 'コンテナ {{ .name }} をアップグレード'
|
||||||
|
ContainerUpgradeSummary: 'コンテナのアップグレード結果を集計'
|
||||||
|
ContainerCleanupWarning: '更新は成功しましたが、バックアップコンテナ {{ .name }} を削除できませんでした。新しいコンテナが正常に動作していることを確認してから、バックアップコンテナを手動で削除してください。エラー: {{ .err }}'
|
||||||
|
ContainerStopOld: '元のコンテナ {{ .name }} を停止'
|
||||||
|
ContainerRenameOld: 'ロールバック用に元のコンテナ {{ .name }} の名前を変更'
|
||||||
|
ContainerDisconnectOld: '元のコンテナ {{ .name }} のネットワークアドレスを解放'
|
||||||
|
ContainerCreateReplacement: '置換コンテナ {{ .name }} を作成'
|
||||||
|
ContainerStartReplacement: '置換コンテナ {{ .name }} を起動'
|
||||||
|
ContainerWaitReplacement: '置換コンテナ {{ .name }} の準備状態を確認'
|
||||||
|
ContainerRollbackRestartOld: '元のコンテナ {{ .name }} を再起動'
|
||||||
|
ContainerRollbackRemoveReplacement: '起動に失敗した置換コンテナ {{ .name }} を削除'
|
||||||
|
ContainerRollbackRenameOld: '元のコンテナ {{ .name }} の名前を復元'
|
||||||
|
ContainerRollbackReconnectOld: '元のコンテナ {{ .name }} のネットワークを復元'
|
||||||
|
|
||||||
# タスク - イメージ
|
# タスク - イメージ
|
||||||
ImageBuild: 'イメージビルド'
|
ImageBuild: 'イメージビルド'
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
# 기본 오류
|
# 기본 오류
|
||||||
ErrInvalidParams: '잘못된 요청: {{ .detail }}'
|
ErrInvalidParams: '잘못된 요청: {{ .detail }}'
|
||||||
|
ErrMcpGatewayArgsParse: 'MCP 사용자 지정 인수 형식이 잘못되었습니다'
|
||||||
|
ErrMcpGatewayArgsConflict: '{{ .name }} 인수는 1Panel에서 관리하므로 사용자 지정 인수에 설정할 수 없습니다'
|
||||||
ErrTokenParse: '토큰 오류: {{ .detail }}'
|
ErrTokenParse: '토큰 오류: {{ .detail }}'
|
||||||
ErrInitialPassword: '원래 비밀번호가 틀립니다'
|
ErrInitialPassword: '원래 비밀번호가 틀립니다'
|
||||||
ErrInternalServer: '서버 오류: {{ .detail }}'
|
ErrInternalServer: '서버 오류: {{ .detail }}'
|
||||||
ErrRecordExist: '레코드가 이미 있습니다'
|
ErrRecordExist: '레코드가 이미 있습니다'
|
||||||
ErrRecordNotFound: '레코드를 찾을 수 없습니다'
|
ErrRecordNotFound: '레코드를 찾을 수 없습니다'
|
||||||
|
WafLogExport: 'WAF 로그 내보내기'
|
||||||
|
MonitorLogExport: '웹사이트 모니터링 로그 내보내기'
|
||||||
|
LogExportNoData: '선택한 조건과 일치하는 로그가 없습니다'
|
||||||
|
LogExportDiskSpace: '사용 가능한 공간이 1 GiB 미만이어서 내보내기를 중지했습니다'
|
||||||
|
LogExportRetentionInvalid: '내보내기 기록 보관 기간은 1일에서 30일 사이여야 합니다'
|
||||||
|
ErrSystemLogKeywordFilterUnsupported: '현재 시스템 버전은 시스템 로그 키워드 필터링을 지원하지 않습니다(systemd 237 이상 필요)'
|
||||||
ErrStructTransform: '형 변환 실패: {{ .err }}'
|
ErrStructTransform: '형 변환 실패: {{ .err }}'
|
||||||
ErrNotSupportType: '지원하지 않는 타입: {{ .name }}'
|
ErrNotSupportType: '지원하지 않는 타입: {{ .name }}'
|
||||||
|
|
||||||
@@ -63,6 +71,7 @@ Decrypt: '복호화'
|
|||||||
# 에이전트
|
# 에이전트
|
||||||
ErrAgentAccountBound: '계정이 에이전트에 묶여 있습니다'
|
ErrAgentAccountBound: '계정이 에이전트에 묶여 있습니다'
|
||||||
ErrTerminalAIAccountInUse: '이 모델 계정은 현재 터미널 AI에서 사용 중입니다. 터미널 AI 설정에서 다른 계정으로 변경하거나 터미널 AI를 비활성화한 뒤 다시 시도하세요.'
|
ErrTerminalAIAccountInUse: '이 모델 계정은 현재 터미널 AI에서 사용 중입니다. 터미널 AI 설정에서 다른 계정으로 변경하거나 터미널 AI를 비활성화한 뒤 다시 시도하세요.'
|
||||||
|
ErrFileAIAccountInUse: '이 모델 계정은 현재 파일 관리 AI에서 사용 중입니다. 파일 관리 AI 설정에서 다른 계정으로 변경하거나 파일 관리 AI를 비활성화한 뒤 다시 시도하세요.'
|
||||||
ErrAgentAccountUnavailable: '계정 연결 불가: {{ .err }}'
|
ErrAgentAccountUnavailable: '계정 연결 불가: {{ .err }}'
|
||||||
ErrAgentProviderNotSupported: '지원되지 않는 에이전트 공급자'
|
ErrAgentProviderNotSupported: '지원되지 않는 에이전트 공급자'
|
||||||
ErrAgentAccountRequired: '먼저 에이전트 계정을 선택하세요'
|
ErrAgentAccountRequired: '먼저 에이전트 계정을 선택하세요'
|
||||||
@@ -70,6 +79,7 @@ ErrAgentAccountNotVerified: '에이전트 계정 미확인'
|
|||||||
ErrAgentAccountModelsRequired: '최소 한 개의 모델을 구성해 주세요'
|
ErrAgentAccountModelsRequired: '최소 한 개의 모델을 구성해 주세요'
|
||||||
ErrAgentAccountSingleInitialModel: '계정 생성 시 초기 모델은 하나만 구성할 수 있습니다'
|
ErrAgentAccountSingleInitialModel: '계정 생성 시 초기 모델은 하나만 구성할 수 있습니다'
|
||||||
ErrAgentModelNotInAccount: '선택한 모델이 현재 계정에 구성되어 있지 않습니다'
|
ErrAgentModelNotInAccount: '선택한 모델이 현재 계정에 구성되어 있지 않습니다'
|
||||||
|
ErrAgentVerifyModelInUse: '이 모델은 계정 검증에 사용 중입니다. 삭제하기 전에 다른 검증 모델을 선택하세요'
|
||||||
ErrAgentFallbackModelDuplicate: '폴백 모델은 중복될 수 없습니다'
|
ErrAgentFallbackModelDuplicate: '폴백 모델은 중복될 수 없습니다'
|
||||||
ErrAgentFallbackModelPrimary: '폴백 모델은 기본 모델과 같을 수 없습니다'
|
ErrAgentFallbackModelPrimary: '폴백 모델은 기본 모델과 같을 수 없습니다'
|
||||||
ErrAgentModelInUse: '이 모델을 아직 사용 중인 에이전트가 있습니다: {{ .name }}'
|
ErrAgentModelInUse: '이 모델을 아직 사용 중인 에이전트가 있습니다: {{ .name }}'
|
||||||
@@ -101,6 +111,7 @@ ErrAppLimit: '설치된 애플리케이션 수가 한도를 초과했습니다'
|
|||||||
ErrNotInstall: '응용 프로그램이 설치되지 않았습니다'
|
ErrNotInstall: '응용 프로그램이 설치되지 않았습니다'
|
||||||
ErrPortInOtherApp: '{{ .port }} 포트는 이미 {{ .apps }} 애플리케이션에 의해 사용되고 있습니다'
|
ErrPortInOtherApp: '{{ .port }} 포트는 이미 {{ .apps }} 애플리케이션에 의해 사용되고 있습니다'
|
||||||
ErrDbUserNotValid: '기존 데이터베이스, 사용자 이름 및 비밀번호가 일치하지 않습니다'
|
ErrDbUserNotValid: '기존 데이터베이스, 사용자 이름 및 비밀번호가 일치하지 않습니다'
|
||||||
|
ErrMysqlUserUsedByApps: '이 데이터베이스 사용자는 [{{ .detail }}]에서 사용 중이므로 호스트 변경, 삭제 또는 권한 해제를 할 수 없습니다'
|
||||||
ErrUpdateBuWebsite: '응용 프로그램이 성공적으로 업데이트되었지만, 웹사이트 구성 파일 수정에 실패했습니다. 구성을 확인하세요'
|
ErrUpdateBuWebsite: '응용 프로그램이 성공적으로 업데이트되었지만, 웹사이트 구성 파일 수정에 실패했습니다. 구성을 확인하세요'
|
||||||
Err1PanelNetworkFailed: '기본 컨테이너 네트워크 생성에 실패했습니다 {{ .세부 사항 }}'
|
Err1PanelNetworkFailed: '기본 컨테이너 네트워크 생성에 실패했습니다 {{ .세부 사항 }}'
|
||||||
ErrFileParse: '응용 프로그램 docker-compose 파일 구문 분석에 실패했습니다'
|
ErrFileParse: '응용 프로그램 docker-compose 파일 구문 분석에 실패했습니다'
|
||||||
@@ -121,6 +132,7 @@ ErrAppNameExist: '응용 프로그램 이름이 이미 존재합니다'
|
|||||||
ErrAppInstallNameExist: '앱 설치 이름 {{ .name }} 이(가) 이미 존재합니다'
|
ErrAppInstallNameExist: '앱 설치 이름 {{ .name }} 이(가) 이미 존재합니다'
|
||||||
AppStoreIsSyncing: 'App Store가 동기화 중입니다. 나중에 다시 시도하세요.'
|
AppStoreIsSyncing: 'App Store가 동기화 중입니다. 나중에 다시 시도하세요.'
|
||||||
ErrGetCompose: 'docker-compose.yml 파일을 가져오지 못했습니다 {{ .detail }}'
|
ErrGetCompose: 'docker-compose.yml 파일을 가져오지 못했습니다 {{ .detail }}'
|
||||||
|
ErrAppVersionUnavailable: '현재 애플리케이션 버전이 원격 서비스에서 제거되었습니다. 다른 버전을 선택한 후 다시 시도해 주세요.'
|
||||||
ErrAppWarn: '비정상적인 상태입니다. 로그를 확인해 주세요.'
|
ErrAppWarn: '비정상적인 상태입니다. 로그를 확인해 주세요.'
|
||||||
ErrAppParamKey: '매개변수 {{ .name }} 필드가 비정상입니다.'
|
ErrAppParamKey: '매개변수 {{ .name }} 필드가 비정상입니다.'
|
||||||
ErrAppUpgrade: '애플리케이션 {{ .name }} 업그레이드에 실패했습니다 {{ .err }}'
|
ErrAppUpgrade: '애플리케이션 {{ .name }} 업그레이드에 실패했습니다 {{ .err }}'
|
||||||
@@ -372,6 +384,9 @@ ErrParseIP: '잘못된 IP 형식'
|
|||||||
ErrDefaultIP: '기본값은 예약된 이름입니다. 다른 이름으로 변경해 주세요'
|
ErrDefaultIP: '기본값은 예약된 이름입니다. 다른 이름으로 변경해 주세요'
|
||||||
ErrGroupInUse: 'IP 그룹이 블랙리스트/화이트리스트에 사용 중이므로 삭제할 수 없습니다.'
|
ErrGroupInUse: 'IP 그룹이 블랙리스트/화이트리스트에 사용 중이므로 삭제할 수 없습니다.'
|
||||||
ErrIPGroupAclUse: 'IP 그룹은 웹사이트 {{ .name }} 의 사용자 정의 규칙에 사용되므로 삭제할 수 없습니다'
|
ErrIPGroupAclUse: 'IP 그룹은 웹사이트 {{ .name }} 의 사용자 정의 규칙에 사용되므로 삭제할 수 없습니다'
|
||||||
|
ErrIPGroupOriginProtectionUse: 'IP 그룹이 웹사이트 {{ .name }} 의 CDN 원본 서버 보호에 사용 중입니다'
|
||||||
|
ErrOriginProtectionIPGroupRequired: '원본 서버 보호를 활성화하기 전에 비어 있지 않은 IP 그룹을 하나 이상 선택하세요'
|
||||||
|
ErrOriginProtectionIPGroupInvalid: 'IP 그룹 {{ .name }} 이 존재하지 않거나 비어 있습니다'
|
||||||
ErrGroupExist: 'IP 그룹 이름이 이미 존재합니다'
|
ErrGroupExist: 'IP 그룹 이름이 이미 존재합니다'
|
||||||
ErrIPRange: '잘못된 IP 범위'
|
ErrIPRange: '잘못된 IP 범위'
|
||||||
ErrIPExist: 'IP가 이미 존재합니다'
|
ErrIPExist: 'IP가 이미 존재합니다'
|
||||||
@@ -550,6 +565,20 @@ ContainerRecreate: '컨테이너 업데이트에 실패했습니다. 이제 원
|
|||||||
ContainerCreate: '새로운 컨테이너 {{ .name }}를 만듭니다'
|
ContainerCreate: '새로운 컨테이너 {{ .name }}를 만듭니다'
|
||||||
ContainerCreateFailed: '컨테이너 생성에 실패했습니다. 실패한 컨테이너를 삭제하세요'
|
ContainerCreateFailed: '컨테이너 생성에 실패했습니다. 실패한 컨테이너를 삭제하세요'
|
||||||
ContainerStartCheck: '컨테이너가 시작되었는지 확인'
|
ContainerStartCheck: '컨테이너가 시작되었는지 확인'
|
||||||
|
ContainerAcquireLock: '컨테이너 {{ .name }} 작업 잠금 대기'
|
||||||
|
ContainerUpgradeItem: '컨테이너 {{ .name }} 업그레이드'
|
||||||
|
ContainerUpgradeSummary: '컨테이너 업그레이드 결과 요약'
|
||||||
|
ContainerCleanupWarning: '업데이트는 성공했지만 백업 컨테이너 {{ .name }} 삭제에 실패했습니다. 새 컨테이너가 정상적으로 작동하는지 확인한 후 백업 컨테이너를 수동으로 삭제하십시오. 오류: {{ .err }}'
|
||||||
|
ContainerStopOld: '기존 컨테이너 {{ .name }} 중지'
|
||||||
|
ContainerRenameOld: '롤백을 위해 기존 컨테이너 {{ .name }} 이름 변경'
|
||||||
|
ContainerDisconnectOld: '기존 컨테이너 {{ .name }} 네트워크 주소 해제'
|
||||||
|
ContainerCreateReplacement: '대체 컨테이너 {{ .name }} 생성'
|
||||||
|
ContainerStartReplacement: '대체 컨테이너 {{ .name }} 시작'
|
||||||
|
ContainerWaitReplacement: '대체 컨테이너 {{ .name }} 준비 상태 확인'
|
||||||
|
ContainerRollbackRestartOld: '기존 컨테이너 {{ .name }} 다시 시작'
|
||||||
|
ContainerRollbackRemoveReplacement: '시작에 실패한 대체 컨테이너 {{ .name }} 제거'
|
||||||
|
ContainerRollbackRenameOld: '기존 컨테이너 {{ .name }} 이름 복원'
|
||||||
|
ContainerRollbackReconnectOld: '기존 컨테이너 {{ .name }} 네트워크 복원'
|
||||||
|
|
||||||
# 작업 - 이미지
|
# 작업 - 이미지
|
||||||
ImageBuild: '이미지 빌드'
|
ImageBuild: '이미지 빌드'
|
||||||
|
|||||||
@@ -0,0 +1,642 @@
|
|||||||
|
ErrInvalidParams: 'ຄຳຮ້ອງຂໍບໍ່ຖືກຕ້ອງ: {{ .detail }}'
|
||||||
|
ErrMcpGatewayArgsParse: 'ຮູບແບບອາກິວເມັນ MCP ກຳນົດເອງບໍ່ຖືກຕ້ອງ'
|
||||||
|
ErrMcpGatewayArgsConflict: 'ອາກິວເມັນ {{ .name }} ຖືກຈັດການໂດຍ 1Panel ແລະ ບໍ່ສາມາດຕັ້ງຄ່າໃນອາກິວເມັນກຳນົດເອງໄດ້'
|
||||||
|
ErrTokenParse: 'ໂທເຄັນຜິດພາດ: {{ .detail }}'
|
||||||
|
ErrInitialPassword: 'ລະຫັດຜ່ານເດີມບໍ່ຖືກຕ້ອງ'
|
||||||
|
ErrInternalServer: 'ລະບົບພາຍໃນຜິດພາດ: {{ .detail }}'
|
||||||
|
ErrFileAINotEnabled: 'ການສະຫຼຸບໄຟລ໌ດ້ວຍ AI ຕ້ອງມີການເປີດໃຊ້ບັນຊີ AI ໃນການຈັດການໄຟລ໌. ການຄົ້ນຫາເນື້ອໃນຍັງສາມາດໃຊ້ງານໄດ້ປົກກະຕິເຖິງແມ່ນວ່າ AI ຈະປິດຢູ່.'
|
||||||
|
ErrFileAISearchBadPattern: 'ຮູບແບບການຄົ້ນຫາໄຟລ໌ບໍ່ຖືກຕ້ອງ (regex ຫຼື ຄຳສັບ): {{ .detail }}'
|
||||||
|
ErrWgetRemoteFailed: 'ດາວໂຫຼດທາງໄກລົ້ມເຫຼວ ຫຼື URL ເຂົ້າເຖິງບໍ່ໄດ້: {{ .detail }}'
|
||||||
|
ErrWgetInvalidContentType: 'URL ທາງໄກບໍ່ໄດ້ສົ່ງໄຟລ໌ກັບຄືນມາ (ອາດຈະເປັນໜ້າຂໍ້ຜິດພາດ), {{ .detail }}'
|
||||||
|
ErrWgetProxyNotConfigured: 'ກະລຸນາກຳນົດຄ່າເຊີເວີພຣັອກຊີໃນການຕັ້ງຄ່າແຜງຄວບຄຸມກ່ອນ'
|
||||||
|
ErrWgetProxyInvalid: 'ທີ່ຢູ່ເຊີເວີພຣັອກຊີບໍ່ຖືກຕ້ອງ: {{ .detail }}'
|
||||||
|
ErrRecordExist: 'ຂໍ້ມູນນີ້ມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
ErrRecordNotFound: 'ບໍ່ພົບຂໍ້ມູນ'
|
||||||
|
WafLogExport: 'ສົ່ງອອກລັອກ WAF'
|
||||||
|
MonitorLogExport: 'ສົ່ງອອກລັອກການຕິດຕາມເວັບໄຊ'
|
||||||
|
LogExportNoData: 'ບໍ່ມີລັອກທີ່ກົງກັບເງື່ອນໄຂການກັ່ນຕອງ'
|
||||||
|
LogExportDiskSpace: 'ພື້ນທີ່ດິສກ໌ທີ່ໃຊ້ໄດ້ໜ້ອຍກວ່າ 1 GiB; ການສົ່ງອອກຖືກຢຸດແລ້ວ'
|
||||||
|
LogExportRetentionInvalid: 'ໄລຍະເກັບບັນທຶກການສົ່ງອອກຕ້ອງຢູ່ລະຫວ່າງ 1 ຫາ 30 ມື້'
|
||||||
|
ErrSystemLogKeywordFilterUnsupported: 'ເວີຊັນລະບົບປັດຈຸບັນບໍ່ຮອງຮັບການກັ່ນຕອງຄຳສຳຄັນຂອງບັນທຶກລະບົບ (ຕ້ອງການ systemd 237 ຫຼືໃໝ່ກວ່າ)'
|
||||||
|
ErrStructTransform: 'ການແປງປະເພດຂໍ້ມູນລົ້ມເຫຼວ: {{ .err }}'
|
||||||
|
ErrNotLogin: 'ຍັງບໍ່ໄດ້ເຂົ້າສູ່ລະບົບ: {{ .detail }}'
|
||||||
|
ErrPasswordExpired: 'ລະຫັດຜ່ານໝົດອາຍຸ: {{ .detail }}'
|
||||||
|
ErrNotSupportType: 'ບໍ່ຮອງຮັບປະເພດນີ້: {{ .name }}'
|
||||||
|
ErrAlertMethodNotSupported: 'ເວີຊັນນີ້ບໍ່ຮອງຮັບວິທີການແຈ້ງເຕືອນນີ້'
|
||||||
|
ErrProxy: 'ຄຳຮ້ອງຂໍລົ້ມເຫຼວ: {{ .detail }}'
|
||||||
|
ErrApiConfigStatusInvalid: 'ການເຂົ້າເຖິງ API ຖືກປິດໃຊ້ງານ: {{ .detail }}'
|
||||||
|
ErrApiConfigKeyInvalid: 'API key ບໍ່ຖືກຕ້ອງ: {{ .detail }}'
|
||||||
|
ErrApiConfigIPInvalid: 'IP ບໍ່ຢູ່ໃນລາຍຊື່ທີ່ອະນຸຍາດຂອງ API: {{ .detail }}'
|
||||||
|
ErrApiConfigDisable: 'ຈຸດເຊື່ອມຕໍ່ (Endpoint) ບໍ່ໄດ້ຮັບອະນຸຍາດຜ່ານ API: {{ .detail }}'
|
||||||
|
ErrApiConfigKeyTimeInvalid: 'ເວລາ (Timestamp) ຂອງ API ບໍ່ຖືກຕ້ອງ: {{ .detail }}'
|
||||||
|
ErrShutDown: 'ຄຳສັ່ງຖືກຢຸດດ້ວຍຕົນເອງ'
|
||||||
|
|
||||||
|
ErrMinQuickJump: 'ກະລຸນາຕັ້ງຄ່າລາຍການທາງລັດຢ່າງໜ້ອຍໜຶ່ງລາຍການ'
|
||||||
|
ErrMaxQuickJump: 'ສາມາດຕັ້ງຄ່າລາຍການທາງລັດໄດ້ສູງສຸດ 4 ລາຍການ'
|
||||||
|
|
||||||
|
#common
|
||||||
|
ErrUsernameIsExist: 'ຊື່ຜູ້ໃຊ້ມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
ErrNameIsExist: 'ຊື່ມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
ErrDemoEnvironment: 'ຖືກປິດໃຊ້ງານໃນສະພາບແວດລ້ອມຕົວຢ່າງ (Demo)'
|
||||||
|
ErrCmdTimeout: 'ຄຳສັ່ງເກີນກຳນົດເວລາ'
|
||||||
|
ErrCmdIllegal: 'ຄຳສັ່ງມີຕົວອັກສອນທີ່ບໍ່ຖືກຕ້ອງ'
|
||||||
|
ErrPortExist: 'ພອດ {{ .port }} ຖືກໃຊ້ງານແລ້ວໂດຍ {{ .type }} [{{ .name }}]'
|
||||||
|
TYPE_APP: 'ແອັບພລິເຄຊັນ'
|
||||||
|
TYPE_RUNTIME: 'ຣັນທາມ (Runtime)'
|
||||||
|
TYPE_DOMAIN: 'ໂດເມນ'
|
||||||
|
ErrTypePort: 'ຮູບແບບພອດບໍ່ຖືກຕ້ອງ: {{ .name }}'
|
||||||
|
ErrTypePortRange: 'ຊ່ວງຂອງພອດຕ້ອງແມ່ນ 1-65535'
|
||||||
|
Success: 'ສຳເລັດ'
|
||||||
|
Failed: 'ລົ້ມເຫຼວ'
|
||||||
|
SystemRestart: 'ວຽກຖືກຂັດຈັງຫວະເນື່ອງຈາກການເລີ່ມຕົ້ນລະບົບໃໝ່'
|
||||||
|
ErrGroupIsDefault: 'ກຸ່ມເລີ່ມຕົ້ນ ບໍ່ສາມາດລຶບໄດ້'
|
||||||
|
ErrGroupIsInWebsiteUse: 'ກຸ່ມກຳລັງຖືກໃຊ້ງານໂດຍເວັບໄຊ'
|
||||||
|
Decrypt: "ຖອດລະຫັດ"
|
||||||
|
|
||||||
|
#agent
|
||||||
|
ErrAgentAccountBound: 'ບັນຊີຖືກຜູກມັດກັບຕົວແທນ (Agent) ແລະບໍ່ສາມາດລຶບໄດ້'
|
||||||
|
ErrTerminalAIAccountInUse: 'ບັນຊີໂມເດລນີ້ກຳລັງຖືກໃຊ້ໂດຍ Terminal AI. ກະລຸນາປ່ຽນບັນຊີໃນການຕັ້ງຄ່າ Terminal AI ຫຼື ປິດໃຊ້ງານ Terminal AI ແລ້ວລອງໃໝ່.'
|
||||||
|
ErrFileAIAccountInUse: 'ບັນຊີໂມເດລນີ້ກຳລັງຖືກໃຊ້ໂດຍ AI ຈັດການໄຟລ໌. ກະລຸນາປ່ຽນບັນຊີໃນການຕັ້ງຄ່າ AI ຈັດການໄຟລ໌ ຫຼື ປິດໃຊ້ງານແລ້ວລອງໃໝ່.'
|
||||||
|
ErrAgentAccountUnavailable: 'ການເຊື່ອມຕໍ່ບັນຊີບໍ່ສາມາດໃຊ້ງານໄດ້: {{ .err }}'
|
||||||
|
ErrAgentProviderNotSupported: 'ບໍ່ຮອງຮັບຜູ້ໃຫ້ບໍລິການຕົວແທນນີ້'
|
||||||
|
ErrAgentAccountRequired: 'ກະລຸນາເລືອກບັນຊີຕົວແທນກ່ອນ'
|
||||||
|
ErrAgentAccountNotVerified: 'ບັນຊີຕົວແທນຍັງບໍ່ໄດ້ຮັບການຢືນຢັນ'
|
||||||
|
ErrAgentAccountModelsRequired: 'ກະລຸນາກຳນົດຄ່າໂມເດລຢ່າງໜ້ອຍໜຶ່ງຕົວ'
|
||||||
|
ErrAgentAccountSingleInitialModel: 'ສາມາດກຳນົດຄ່າໂມເດລເລີ່ມຕົ້ນໄດ້ພຽງຕົວດຽວໃນລະຫວ່າງການສ້າງບັນຊີ'
|
||||||
|
ErrAgentModelNotInAccount: 'ໂມເດລບໍ່ໄດ້ຖືກກຳນົດຄ່າໃນບັນຊີທີ່ເລືອກ'
|
||||||
|
ErrAgentVerifyModelInUse: 'ໂມເດລນີ້ຖືກໃຊ້ສຳລັບກວດສອບບັນຊີ. ກະລຸນາເລືອກໂມເດລກວດສອບອື່ນກ່ອນລຶບ.'
|
||||||
|
ErrAgentFallbackModelDuplicate: 'ໂມເດລສຳຮອງ (Fallback) ບໍ່ສາມາດຊ້ຳກັນໄດ້'
|
||||||
|
ErrAgentFallbackModelPrimary: 'ໂມເດລສຳຮອງບໍ່ສາມາດຄືກັນກັບໂມເດລຫຼັກ'
|
||||||
|
ErrAgentModelInUse: 'ຕົວແທນທີ່ຜູກມັດກຳລັງໃຊ້ໂມເດລນີ້ຢູ່: {{ .name }}'
|
||||||
|
ErrAgentBaseURLRequired: 'ຈຳເປັນຕ້ອງມີ Base URL'
|
||||||
|
ErrAgentApiKeyRequired: 'ຈຳເປັນຕ້ອງມີ API key'
|
||||||
|
ErrAgentComposeRequired: 'ຈຳເປັນຕ້ອງມີເນື້ອໃນ Compose'
|
||||||
|
ErrAgentIDRequired: 'ຈຳເປັນຕ້ອງມີ Agent ID'
|
||||||
|
ErrAgentAccountIDRequired: 'ຈຳເປັນຕ້ອງມີ Account ID'
|
||||||
|
ErrAgentLimitReached: 'ເວີຊັນ Community ຮອງຮັບຕົວແທນ AI ສູງສຸດ {{ .max }} ຕົວ. ກະລຸນາອັບເກຣດເປັນເວີຊັນ Business ເພື່ອໃຊ້ໄດ້ແບບບໍ່ຈຳກັດ'
|
||||||
|
ErrAgentWebsiteBound: 'ຕົວແທນນີ້ຖືກຜູກມັດກັບເວັບໄຊແລ້ວ'
|
||||||
|
ErrAgentWebsiteTypeUnsupported: 'ສາມາດຜູກມັດໄດ້ສະເພາະເວັບໄຊປະເພດ Proxy ຫຼື Static ເທົ່ານັ້ນ'
|
||||||
|
ErrAgentWebsiteInUse: 'ເວັບໄຊນີ້ຖືກຜູກມັດກັບຕົວແທນອື່ນແລ້ວ'
|
||||||
|
ErrAgentWebsiteUnbindUnsupported: 'ເວັບໄຊທີ່ຕິດຕັ້ງແລ້ວບໍ່ສາມາດຍົກເລີກການຜູກມັດດ້ວຍຕົນເອງໄດ້'
|
||||||
|
ErrHermesPairingCodeUnavailable: 'ລະຫັດການຈັບຄູ່ໃນ Hermes ບໍ່ສາມາດໃຊ້ງານໄດ້ຊົ່ວຄາວ, ອາດຈະເກີດຈາກບັນຫາເຄືອຂ່າຍ. ກະລຸນາລອງໃໝ່ພາຍຫຼັງ.'
|
||||||
|
ErrHermesFeishuGroupAllowlistRequiresAllowlist: 'ເມື່ອນະໂຍບາຍກຸ່ມ Feishu ແມ່ນ Allowlist, ນະໂຍບາຍ DM ບໍ່ສາມາດເປັນ Pairing Code ໄດ້.'
|
||||||
|
|
||||||
|
#backup
|
||||||
|
Localhost: 'ພາຍໃນເຄື່ອງ'
|
||||||
|
ErrBackupInUsed: 'ບັນຊີສຳຮອງຂໍ້ມູນກຳລັງຖືກໃຊ້ງານໂດຍວຽກທີ່ຕັ້ງເວລາໄວ້'
|
||||||
|
ErrBackupCheck: 'ການທົດສອບການເຊື່ອມຕໍ່ບັນຊີສຳຮອງລົ້ມເຫຼວ: {{ .err }}'
|
||||||
|
ErrBackupLocalDelete: 'ບໍ່ສາມາດລຶບບັນຊີສຳຮອງພາຍໃນເຄື່ອງໄດ້'
|
||||||
|
ErrBackupLocalCreate: 'ບໍ່ສາມາດສ້າງບັນຊີສຳຮອງພາຍໃນເຄື່ອງໄດ້'
|
||||||
|
ErrAlertConfigInUse: 'ການຕັ້ງຄ່າການແຈ້ງເຕືອນກຳລັງຖືກໃຊ້ງານ ແລະ ບໍ່ສາມາດລຶບໄດ້'
|
||||||
|
ErrAlertConfigPhoneExist: 'ເບີໂທລະສັບມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
|
||||||
|
#app
|
||||||
|
ErrPortInUsed: 'ພອດ {{ .detail }} ຖືກໃຊ້ງານແລ້ວ!'
|
||||||
|
ErrAppLimit: 'ຮອດຂີດຈຳກັດການຕິດຕັ້ງແອັບແລ້ວ'
|
||||||
|
ErrNotInstall: 'ແອັບພລິເຄຊັນຍັງບໍ່ໄດ້ຕິດຕັ້ງ'
|
||||||
|
ErrPortInOtherApp: 'ພອດ {{ .port }} ຖືກໃຊ້ງານແລ້ວໂດຍແອັບພລິເຄຊັນ {{ .apps }}!'
|
||||||
|
ErrDbUserNotValid: 'ຊື່ຜູ້ໃຊ້ ຫຼື ລະຫັດຜ່ານຖານຂໍ້ມູນບໍ່ຖືກຕ້ອງ'
|
||||||
|
ErrMysqlUserUsedByApps: 'ຜູ້ໃຊ້ຖານຂໍ້ມູນນີ້ກຳລັງຖືກໃຊ້ໂດຍ [{{ .detail }}] ແລະບໍ່ສາມາດປ່ຽນ Host, ລຶບ ຫຼືຖອນການອະນຸຍາດໄດ້'
|
||||||
|
ErrUpdateBuWebsite: 'ອັບເດດແອັບແລ້ວ, ແຕ່ການອັບເດດການຕັ້ງຄ່າເວັບໄຊລົ້ມເຫຼວ'
|
||||||
|
Err1PanelNetworkFailed: 'ການສ້າງເຄືອຂ່າຍຄອນເທນເນີເລີ່ມຕົ້ນລົ້ມເຫຼວ! {{ .detail }}'
|
||||||
|
ErrFileParse: 'ການວິເຄາະໄຟລ໌ docker-compose ຂອງແອັບລົ້ມເຫຼວ'
|
||||||
|
ErrInstallDirNotFound: 'ບໍ່ພົບໂຟນເດີທີ່ຕິດຕັ້ງ; ກະລຸນາໃຊ້ການຖອນການຕິດຕັ້ງແບບບັງຄັບ (Force Uninstall)'
|
||||||
|
AppStoreIsUpToDate: 'ຮ້ານຄ້າແອັບເປັນເວີຊັນຫຼ້າສຸດແລ້ວ'
|
||||||
|
LocalAppVersionNull: 'ແອັບ {{ .name }} ບໍ່ມີເວີຊັນທີ່ຊິງຄ໌ (Sync)'
|
||||||
|
LocalAppVersionErr: 'ຊິງຄ໌ເວີຊັນ {{ .version }} ຂອງ {{ .name }} ລົ້ມເຫຼວ: {{ .err }}'
|
||||||
|
ErrFileNotFound: 'ບໍ່ມີໄຟລ໌ {{ .name }} ຢູ່'
|
||||||
|
ErrFileParseApp: 'ວິເຄາະ {{ .name }} ລົ້ມເຫຼວ: {{ .err }}'
|
||||||
|
ErrAppDirNull: 'ບໍ່ມີໂຟນເດີເວີຊັນຢູ່'
|
||||||
|
LocalAppErr: 'ຊິງຄ໌ແອັບ {{ .name }} ລົ້ມເຫຼວ: {{ .err }}'
|
||||||
|
ErrContainerName: 'ຊື່ຄອນເທນເນີມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
ErrCreateHttpClient: 'ສ້າງຄຳຮ້ອງຂໍ HTTP ລົ້ມເຫຼວ: {{ .err }}'
|
||||||
|
ErrHttpReqTimeOut: 'ຄຳຮ້ອງຂໍ HTTP ເກີນກຳນົດເວລາ: {{ .err }}'
|
||||||
|
ErrHttpReqFailed: 'ຄຳຮ້ອງຂໍ HTTP ລົ້ມເຫຼວ: {{ .err }}'
|
||||||
|
ErrNoSuchHost: 'ການແກ້ໄຂຊື່ໂຮສລົ້ມເຫຼວ: {{ .err }}'
|
||||||
|
ErrHttpReqNotFound: 'ບໍ່ພົບຊັບພະຍາກອນທີ່ຮ້ອງຂໍ: {{ .err }}'
|
||||||
|
ErrContainerNotFound: 'ບໍ່ມີຄອນເທນເນີ {{ .name }} ຢູ່'
|
||||||
|
ErrContainerMsg: 'ຄອນເທນເນີ {{ .name }} ຜິດປົກກະຕິ; ກະລຸນາກວດສອບບັນທຶກ (Logs)'
|
||||||
|
ErrAppBackup: 'ການສຳຮອງຂໍ້ມູນແອັບລົ້ມເຫຼວ: {{ .name }} {{ .err }}'
|
||||||
|
ErrVersionTooLow: 'ເວີຊັນ 1Panel ຕ່ຳເກີນໄປ ບໍ່ສາມາດອັບເດດ App Store ໄດ້'
|
||||||
|
ErrAppNameExist: 'ຊື່ແອັບພລິເຄຊັນມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
ErrAppInstallNameExist: 'ຊື່ການຕິດຕັ້ງແອັບ {{ .name }} ມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
AppStoreIsSyncing: 'App Store ກຳລັງຊິງຄ໌ຂໍ້ມູນ; ກະລຸນາລອງໃໝ່ພາຍຫຼັງ'
|
||||||
|
ErrGetCompose: 'ອ່ານໄຟລ໌ docker-compose.yml ລົ້ມເຫຼວ: {{ .detail }}'
|
||||||
|
ErrAppVersionUnavailable: 'ເວີຊັນແອັບນີ້ຖືກລຶບອອກຈາກບໍລິການທາງໄກແລ້ວ ກະລຸນາເລືອກເວີຊັນອື່ນແລ້ວລອງໃໝ່'
|
||||||
|
ErrAppWarn: 'ສະຖານະແອັບຜິດປົກກະຕິ; ກະລຸນາກວດສອບບັນທຶກ'
|
||||||
|
ErrAppParamKey: 'ພາຣາມິເຕີຂອງແອັບບໍ່ຖືກຕ້ອງ: {{ .name }}'
|
||||||
|
ErrAppUpgrade: 'ອັບເກຣດແອັບລົ້ມເຫຼວ: {{ .name }} {{ .err }}'
|
||||||
|
AppRecover: 'ຍ້ອນກັບການເຮັດວຽກຂອງແອັບ {{ .name }}'
|
||||||
|
PullImageStart: 'ກຳລັງດຶງຮູບພາບ (Image) [{{ .name }}]'
|
||||||
|
PullImageSuccess: 'ດຶງຮູບພາບສຳເລັດແລ້ວ'
|
||||||
|
AppStoreSyncSuccess: 'ຊິງຄ໌ App Store ສຳເລັດແລ້ວ'
|
||||||
|
SyncAppDetail: 'ຊິງຄ໌ການຕັ້ງຄ່າແອັບ'
|
||||||
|
AppVersionNotMatch: 'ຂ້າມ {{ .name }}: ຕ້ອງການ 1Panel ເວີຊັນທີ່ໃໝ່ກວ່າ'
|
||||||
|
MoveSiteDir: 'ໂຟນເດີເວັບໄຊປ່ຽນແປງ; ກຳລັງຍ້າຍໂຟນເດີ OpenResty ໄປທີ່ {{ .name }}'
|
||||||
|
MoveSiteDirSuccess: 'ຍ້າຍໂຟນເດີເວັບໄຊສຳເລັດແລ້ວ'
|
||||||
|
DeleteRuntimePHP: 'ລຶບ PHP runtime'
|
||||||
|
CustomAppStoreFileValid: 'ແພັກເກັດ App Store ຕ້ອງເປັນ .tar.gz'
|
||||||
|
PullImageTimeout: 'ດຶງຮູບພາບເກີນກຳນົດເວລາ; ກະລຸນາລອງໃຊ້ Mirror ອື່ນ'
|
||||||
|
ErrAppIsDown: 'ແອັບ {{ .name }} ຜິດປົກກະຕິ'
|
||||||
|
ErrCustomApps: 'ກວດພົບແອັບທີ່ຕິດຕັ້ງແລ້ວ; ກະລຸນາຖອນການຕິດຕັ້ງກ່ອນ'
|
||||||
|
ErrCustomRuntimes: 'ກວດພົບ runtime ທີ່ຕິດຕັ້ງແລ້ວ; ກະລຸນາລຶບອອກກ່ອນ'
|
||||||
|
ErrAppVersionDeprecated: "ແອັບພລິເຄຊັນ {{ .name }} ບໍ່ຮອງຮັບກັບເວີຊັນ 1Panel ປັດຈຸບັນ, ຈຶ່ງຖືກຂ້າມໄປ"
|
||||||
|
ErrDockerFailed: 'Docker ຜິດປົກກະຕິ; ກະລຸນາກວດສອບສະຖານະການບໍລິການ'
|
||||||
|
ErrDockerComposeCmdNotFound: 'ບໍ່ພົບຄຳສັ່ງ Docker Compose ໃນເຄື່ອງ'
|
||||||
|
UseExistImage: 'ມີຮູບພາບຢູ່ແລ້ວ; ກຳລັງໃຊ້ຮູບພາບທີ່ມີຢູ່'
|
||||||
|
ErrDatabaseNotFound: 'ບໍ່ພົບຖານຂໍ້ມູນ: {{ .name }}'
|
||||||
|
|
||||||
|
#ssh
|
||||||
|
ExportIP: "IP ເຂົ້າສູ່ລະບົບ"
|
||||||
|
ExportArea: "ສະຖານທີ່"
|
||||||
|
ExportPort: "ພອດ"
|
||||||
|
ExportAuthMode: "ວິທີການເຂົ້າສູ່ລະບົບ"
|
||||||
|
ExportUser: "ຜູ້ໃຊ້"
|
||||||
|
ExportStatus: "ສະຖານະການເຂົ້າສູ່ລະບົບ"
|
||||||
|
ExportDate: "ເວລາ"
|
||||||
|
|
||||||
|
#file
|
||||||
|
ErrFileCanNotRead: 'ບໍ່ຮອງຮັບການເບິ່ງຕົວຢ່າງໄຟລ໌'
|
||||||
|
ErrSensitiveFileRead: 'ບໍ່ອະນຸຍາດໃຫ້ອ່ານໄຟລ໌ທີ່ມີຄວາມອ່ອນໄຫວ'
|
||||||
|
ErrFileToLarge: 'ໄຟລ໌ມີຂະໜາດໃຫຍ່ກວ່າ 10 MB'
|
||||||
|
ErrPathNotFound: 'ບໍ່ມີໂຟນເດີນີ້ຢູ່'
|
||||||
|
ErrMovePathFailed: 'ເສັ້ນທາງປາຍທາງບໍ່ສາມາດລວມເອົາເສັ້ນທາງຕົ້ນທາງໄດ້'
|
||||||
|
ErrLinkPathNotFound: 'ເສັ້ນທາງປາຍທາງບໍ່ມີຢູ່!'
|
||||||
|
ErrFileIsExist: 'ໄຟລ໌ ຫຼື ໂຟນເດີມີຢູ່ໃນລະບົບແລ້ວ!'
|
||||||
|
ErrFileUpload: 'ອັບໂຫຼດລົ້ມເຫຼວ: {{ .name }} {{ .detail }}'
|
||||||
|
ErrFileDownloadDir: 'ບໍ່ຮອງຮັບການດາວໂຫຼດແບບທັງໂຟນເດີ'
|
||||||
|
ErrFileSharePath: 'ເສັ້ນທາງໄຟລ໌ບໍ່ຖືກຕ້ອງ ຫຼື ປາຍທາງບໍ່ແມ່ນໄຟລ໌'
|
||||||
|
ErrFileShareInvalid: 'ລິ້ງການແບ່ງປັນບໍ່ຖືກຕ້ອງ ຫຼື ໝົດອາຍຸແລ້ວ'
|
||||||
|
ErrFileShareExpired: 'ລິ້ງການແບ່ງປັນໝົດອາຍຸແລ້ວ'
|
||||||
|
ErrFileSharePassword: 'ລະຫັດຜ່ານການແບ່ງປັນບໍ່ຖືກຕ້ອງ'
|
||||||
|
ErrFileShareExhausted: 'ຮອດຂີດຈຳກັດການດາວໂຫຼດສຳລັບການແບ່ງປັນນີ້ແລ້ວ'
|
||||||
|
ErrFileSharePasswordPolicy: 'ລະຫັດຜ່ານຕ້ອງມີຢ່າງໜ້ອຍ 4 ຕົວອັກສອນ'
|
||||||
|
ErrFileShareRateLimit: 'ຄຳຮ້ອງຂໍຫຼາຍເກີນໄປ, ກະລຸນາລອງໃໝ່ພາຍຫຼັງ'
|
||||||
|
ErrCmdNotFound: 'ບໍ່ພົບຄຳສັ່ງ: {{ .name }}'
|
||||||
|
ErrSourcePathNotFound: 'ບໍ່ມີໂຟນເດີຕົ້ນທາງຢູ່'
|
||||||
|
ErrFavoriteExist: 'ເສັ້ນທາງນີ້ຢູ່ໃນລາຍການທີ່ມັກແລ້ວ'
|
||||||
|
ErrInvalidChar: 'ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ຕົວອັກສອນທີ່ບໍ່ຖືກຕ້ອງ'
|
||||||
|
ErrPathNotDelete: 'ໂຟນເດີທີ່ເລືອກບໍ່ສາມາດລຶບໄດ້'
|
||||||
|
ErrLogFileToLarge: 'ໄຟລ໌ບັນທຶກ (Log) ມີຂະໜາດເກີນ 500 MB'
|
||||||
|
FileDropFailed: 'ການລຶບໄຟລ໌ລົ້ມເຫຼວ: {{ .name }} {{ .err }}'
|
||||||
|
FileDropSuccess: 'ລຶບ {{ .name }} ສຳເລັດ; ໄຟລ໌: {{ .count }}, ຄືນພື້ນທີ່: {{ .size }}'
|
||||||
|
FileDropSum: 'ການລຶບສຳເລັດແລ້ວ; ໄຟລ໌: {{ .count }}, ຄືນພື້ນທີ່: {{ .size }}'
|
||||||
|
ErrBadDecrypt: 'ລະຫັດຜ່ານໄຟລ໌ບີບອັດບໍ່ຖືກຕ້ອງ; ການແຕກໄຟລ໌ລົ້ມເຫຼວ'
|
||||||
|
|
||||||
|
#website
|
||||||
|
ErrAliasIsExist: 'ຊື່ຫຼິ້ນ (Alias) ມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
ErrBackupMatch: 'ຂໍ້ມູນສຳຮອງບໍ່ກົງກັນ: {{ .detail }}'
|
||||||
|
ErrBackupExist: 'ຂໍ້ມູນສຳຮອງຕົ້ນທາງຫາຍສາບສູນ: {{ .detail }}'
|
||||||
|
ErrPHPResource: 'ບໍ່ຮອງຮັບການປ່ຽນ runtime ພາຍໃນເຄື່ອງ'
|
||||||
|
ErrPathPermission: 'ໂຟນເດີທີ່ບໍ່ແມ່ນ 1000:1000 ໃນໂຟນເດີດັດສະນີ ອາດເຮັດໃຫ້ການເຂົ້າເຖິງຖືກປະຕິເສດ'
|
||||||
|
ErrDomainIsUsed: 'ຊື່ໂດເມນຖືກໃຊ້ງານແລ້ວໂດຍເວັບໄຊ [{{ .name }}]'
|
||||||
|
ErrDomainFormat: 'ຮູບແບບໂດເມນບໍ່ຖືກຕ້ອງ: {{ .name }}'
|
||||||
|
ErrDefaultAlias: 'ຊື່ default ຖືກສະຫງວນໄວ້; ກະລຸນາໃຊ້ຊື່ອື່ນ'
|
||||||
|
ErrParentWebsite: 'ກະລຸນາລຶບເວັບໄຊຍ່ອຍ {{ .name }} ກ່ອນ'
|
||||||
|
ErrBuildDirNotFound: 'ບໍ່ມີໂຟນເດີ Build ຢູ່'
|
||||||
|
ErrImageNotExist: 'ບໍ່ພົບຮູບພາບ runtime: {{ .name }}'
|
||||||
|
ErrProxyIsUsed: 'ຕົວຈັດການການໂຫຼດ (Load balancer) ຖືກໃຊ້ງານໂດຍ reverse proxy'
|
||||||
|
ErrSSLValid: 'ໄຟລ໌ໃບຮັບຮອງບໍ່ຖືກຕ້ອງ'
|
||||||
|
ErrWebsiteDir: 'ກະລຸນາເລືອກໂຟນເດີຍ່ອຍພາຍໃຕ້ໂຟນເດີເວັບໄຊ'
|
||||||
|
ErrComposerFileNotFound: "ບໍ່ມີໄຟລ໌ composer.json ຢູ່"
|
||||||
|
ErrRuntimeNoPort: 'ຍັງບໍ່ໄດ້ຕັ້ງພອດ runtime'
|
||||||
|
ErrRuntimeProjectDirContainsCodeDir: 'ໂຟນເດີ runtime ມີໂຟນເດີໂຄງການທີ່ເລືອກໄວ້. ກະລຸນາເລືອກໂຟນເດີໂຄງການອື່ນ ແລະ ສ້າງໃໝ່.'
|
||||||
|
Status: 'ສະຖານະ'
|
||||||
|
start: 'ເລີ່ມ'
|
||||||
|
stop: 'ຢຸດ'
|
||||||
|
restart: 'ເລີ່ມໃໝ່'
|
||||||
|
kill: 'ບັງຄັບຢຸດ'
|
||||||
|
pause: 'ພັກຊົ່ວຄາວ'
|
||||||
|
unpause: 'ກັບມາເຮັດວຽກ'
|
||||||
|
remove: 'ລຶບ'
|
||||||
|
delete: 'ລຶບ'
|
||||||
|
ErrDefaultWebsite: 'ມີການຕັ້ງເວັບໄຊເລີ່ມຕົ້ນໄວ້ແລ້ວ; ກະລຸນາຍົກເລີກກ່ອນ'
|
||||||
|
SSL: 'ໃບຮັບຮອງ (SSL)'
|
||||||
|
Set: 'ຕັ້ງຄ່າ'
|
||||||
|
|
||||||
|
#ssl
|
||||||
|
ErrSSLCannotDelete: 'ໃບຮັບຮອງ {{ .name }} ກຳລັງຖືກໃຊ້ງານໂດຍເວັບໄຊ'
|
||||||
|
ErrAccountCannotDelete: 'ບັນຊີຖືກເຊື່ອມໂຍງກັບໃບຮັບຮອງ ແລະ ບໍ່ສາມາດລຶບໄດ້'
|
||||||
|
ErrSSLApply: 'ຕໍ່ອາຍຸໃບຮັບຮອງແລ້ວ; ແຕ່ການ reload OpenResty ລົ້ມເຫຼວ'
|
||||||
|
ErrEmailIsExist: 'ອີເມວມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
ErrSSLKeyNotFound: 'ບໍ່ມີໄຟລ໌ກຸນແຈສ່ວນຕົວ (Private key) ຢູ່'
|
||||||
|
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: 'ບໍ່ຮອງຮັບໃບຮັບຮອງແບບ Wildcard ໃນໂໝດ HTTP'
|
||||||
|
ErrApplySSLCanNotDelete: 'ໃບຮັບຮອງ {{ .name }} ກຳລັງຢູ່ໃນຂັ້ນຕອນການອອກ ແລະ ຍັງບໍ່ສາມາດລຶບໄດ້'
|
||||||
|
StartPushSSLToNode: 'ສົ່ງໃບຮັບຮອງໄປທີ່ໂນດ (Node)'
|
||||||
|
PushSSLToNodeFailed: "ສົ່ງໃບຮັບຮອງໄປທີ່ໂນດລົ້ມເຫຼວ: {{ .err }}"
|
||||||
|
PushSSLToNodeSuccess: 'ສົ່ງໃບຮັບຮອງໄປທີ່ໂນດສຳເລັດແລ້ວ'
|
||||||
|
|
||||||
|
#mysql
|
||||||
|
ErrUserIsExist: 'ຜູ້ໃຊ້ມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
ErrDatabaseIsExist: 'ຖານຂໍ້ມູນມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
ErrExecTimeOut: 'ການປະຕິບັດ SQL ເກີນກຳນົດເວລາ'
|
||||||
|
ErrRemoteExist: 'ຊື່ຖານຂໍ້ມູນທາງໄກມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
ErrLocalExist: 'ຊື່ຖານຂໍ້ມູນພາຍໃນເຄື່ອງມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
RemoteBackup: 'ກະລຸນາເລີ່ມຄອນເທນເນີຖານຂໍ້ມູນພາຍໃນເຄື່ອງດ້ວຍຮູບພາບ {{ .name }} ກ່ອນທີ່ຈະສຳຮອງຂໍ້ມູນຖານຂໍ້ມູນທາງໄກ'
|
||||||
|
RemoteRecover: 'ກະລຸນາເລີ່ມຄອນເທນເນີຖານຂໍ້ມູນພາຍໃນເຄື່ອງດ້ວຍຮູບພາບ {{ .name }} ກ່ອນທີ່ຈະກູ້ຄືນຖານຂໍ້ມູນທາງໄກ'
|
||||||
|
Arg: "ອາກິວເມນ"
|
||||||
|
|
||||||
|
#redis
|
||||||
|
ErrTypeOfRedis: 'ປະເພດໄຟລ໌ສຳຮອງຂໍ້ມູນບໍ່ກົງກັບໂໝດ persistence ປັດຈຸບັນ'
|
||||||
|
|
||||||
|
#container
|
||||||
|
ErrInUsed: "{{ .detail }} ກຳລັງຖືກໃຊ້ງານ ແລະ ບໍ່ສາມາດລຶບໄດ້!"
|
||||||
|
ErrObjectInUsed: "ວັດຖຸນີ້ກຳລັງຖືກໃຊ້ງານ ແລະ ບໍ່ສາມາດລຶບໄດ້!"
|
||||||
|
ErrObjectBeDependent: "ຮູບພາບນີ້ຖືກອ້າງອີງໂດຍຮູບພາບອື່ນ ແລະ ບໍ່ສາມາດລຶບໄດ້!"
|
||||||
|
ErrPortRules: 'ຈຳນວນພອດບໍ່ກົງກັນ'
|
||||||
|
ErrPgImagePull: 'ດຶງຮູບພາບເກີນກຳນົດເວລາ; ກະລຸນາກຳນົດຄ່າການເລັ່ງ (Acceleration) ຫຼື ດຶງ {{ .name }} ດ້ວຍຕົນເອງ'
|
||||||
|
PruneHelper: "ທຳຄວາມສະອາດ {{ .name }} ຄືນພື້ນທີ່ດິສກ໌ {{ .size }}"
|
||||||
|
ImageRemoveHelper: "ລຶບຮູບພາບ {{ .name }} ແລ້ວ, ຄືນພື້ນທີ່ດິສກ໌ {{ .size }}"
|
||||||
|
BuildCache: 'Build Cache'
|
||||||
|
Volume: 'ວໍລຸມ (Volume)'
|
||||||
|
Network: "ເຄືອຂ່າຍ"
|
||||||
|
PruneStart: 'ກຳລັງດຳເນີນການທຳຄວາມສະອາດ'
|
||||||
|
ContainerBackupPrepare: 'ກຽມພື້ນທີ່ເຮັດວຽກສຳລັບສຳຮອງຂໍ້ມູນຄອນເທນເນີ'
|
||||||
|
ContainerBackupStop: 'ຢຸດຄອນເທນເນີກ່ອນການສຳຮອງຂໍ້ມູນ'
|
||||||
|
ContainerBackupInspect: 'ກວດສອບຄອນເທນເນີ ແລະ ເຄືອຂ່າຍສຳລັບການສຳຮອງຂໍ້ມູນ'
|
||||||
|
ContainerBackupMounts: 'ສຳຮອງຂໍ້ມູນການ Mount ຂອງຄອນເທນເນີ'
|
||||||
|
ContainerBackupMeta: 'ຂຽນຂໍ້ມູນ Metadata ສຳລັບການສຳຮອງຂໍ້ມູນຄອນເທນເນີ'
|
||||||
|
ContainerBackupStart: 'ເລີ່ມຄອນເທນເນີຫຼັງຈາກສຳຮອງຂໍ້ມູນ'
|
||||||
|
ContainerRecoverPrepare: 'ກຽມພື້ນທີ່ເຮັດວຽກສຳລັບການກູ້ຄືນຄອນເທນເນີ'
|
||||||
|
ContainerRecoverExtract: 'ແຕກໄຟລ໌ສຳຮອງຂໍ້ມູນຄອນເທນເນີ'
|
||||||
|
ContainerRecoverParse: 'ໂຫຼດຂໍ້ມູນ Metadata ຂອງການສຳຮອງຂໍ້ມູນຄອນເທນເນີ'
|
||||||
|
ContainerRecoverCreate: 'ສ້າງຄອນເທນເນີໃໝ່'
|
||||||
|
ContainerRecoverMounts: 'ກູ້ຄືນການ Mount ຂອງຄອນເທນເນີ'
|
||||||
|
ContainerRecoverStart: 'ເລີ່ມຄອນເທນເນີທີ່ກູ້ຄືນແລ້ວ'
|
||||||
|
ContainerRecoverCleanup: 'ທຳຄວາມສະອາດພື້ນທີ່ເຮັດວຽກການກູ້ຄືນຄອນເທນເນີ'
|
||||||
|
ComposeBackupPrepare: 'ກຽມພື້ນທີ່ເຮັດວຽກສຳລັບສຳຮອງຂໍ້ມູນ Compose'
|
||||||
|
ComposeBackupStop: 'ຢຸດ Compose ກ່ອນການສຳຮອງຂໍ້ມູນ'
|
||||||
|
ComposeBackupFiles: 'ສຳຮອງຂໍ້ມູນໄຟລ໌ Compose'
|
||||||
|
ComposeBackupContainers: 'ສຳຮອງຂໍ້ມູນຄອນເທນເນີ Compose'
|
||||||
|
ComposeBackupMeta: 'ຂຽນຂໍ້ມູນ Metadata ສຳລັບການສຳຮອງຂໍ້ມູນ Compose'
|
||||||
|
ComposeBackupStart: 'ເລີ່ມ Compose ຫຼັງຈາກສຳຮອງຂໍ້ມູນ'
|
||||||
|
ComposeBackupCleanup: 'ທຳຄວາມສະອາດພື້ນທີ່ເຮັດວຽກການສຳຮອງຂໍ້ມູນ Compose'
|
||||||
|
ComposeRecoverPrepare: 'ກຽມພື້ນທີ່ເຮັດວຽກສຳລັບການກູ້ຄືນ Compose'
|
||||||
|
ComposeRecoverExtract: 'ແຕກໄຟລ໌ສຳຮອງຂໍ້ມູນ Compose'
|
||||||
|
ComposeRecoverMeta: 'ໂຫຼດຂໍ້ມູນ Metadata ຂອງການສຳຮອງຂໍ້ມູນ Compose'
|
||||||
|
ComposeRecoverMetaLogName: 'Metadata ການກູ້ຄືນ Compose: name={{ .name }}'
|
||||||
|
ComposeRecoverMetaLogPath: 'Metadata ການກູ້ຄືນ Compose: backupPath={{ .backupPath }} targetDir={{ .targetDir }}'
|
||||||
|
ComposeRecoverMetaLogCount: 'Metadata ການກູ້ຄືນ Compose: files={{ .files }} containers={{ .containers }}'
|
||||||
|
ComposeRecoverFiles: 'ກູ້ຄືນໄຟລ໌ Compose'
|
||||||
|
ComposeRecoverContainers: 'ກູ້ຄືນຄອນເທນເນີ Compose'
|
||||||
|
ComposeRecoverRecord: 'ບັນທຶກຂໍ້ມູນ Compose'
|
||||||
|
ComposeRecoverCleanup: 'ທຳຄວາມສະອາດພື້ນທີ່ເຮັດວຽກການກູ້ຄືນ Compose'
|
||||||
|
|
||||||
|
#runtime
|
||||||
|
ErrFileNotExist: 'ບໍ່ພົບໄຟລ໌ຕົ້ນທາງ: {{ .detail }}'
|
||||||
|
ErrImageBuildErr: 'ການສ້າງຮູບພາບ (Image build) ລົ້ມເຫຼວ'
|
||||||
|
ErrImageExist: "ຮູບພາບມີຢູ່ໃນລະບົບແລ້ວ. ກະລຸນາໃຊ້ຊື່ຮູບພາບອື່ນ."
|
||||||
|
ErrDelWithWebsite: 'Runtime ຖືກເຊື່ອມໂຍງກັບເວັບໄຊ ແລະ ບໍ່ສາມາດລຶບໄດ້'
|
||||||
|
ErrRuntimeStart: 'ການເລີ່ມຕົ້ນລົ້ມເຫຼວ'
|
||||||
|
ErrPackageJsonNotFound: 'ບໍ່ມີໄຟລ໌ package.json ຢູ່'
|
||||||
|
ErrScriptsNotFound: 'ບໍ່ພົບພາກສ່ວນ scripts ໃນ package.json'
|
||||||
|
ErrContainerNameNotFound: 'ບໍ່ພົບຊື່ຄອນເທນເນີ; ກະລຸນາກວດສອບໄຟລ໌ .env'
|
||||||
|
ErrNodeModulesNotFound: 'ບໍ່ພົບ node_modules; ກະລຸນາແກ້ໄຂ runtime ຫຼື ລໍຖ້າການເລີ່ມຕົ້ນ'
|
||||||
|
ErrContainerNameIsNull: 'ຊື່ຄອນເທນເນີຫວ່າງເປົ່າ'
|
||||||
|
ErrPHPPortIsDefault: 'ພອດ 9000 ຖືກສະຫງວນໄວ້ເປັນຄ່າເລີ່ມຕົ້ນ; ກະລຸນາໃຊ້ພອດອື່ນ'
|
||||||
|
ErrPHPRuntimePortFailed: 'ພອດ {{ .name }} ຖືກໃຊ້ງານແລ້ວໂດຍ runtime ນີ້'
|
||||||
|
|
||||||
|
#tool
|
||||||
|
ErrConfigNotFound: 'ບໍ່ມີໄຟລ໌ການຕັ້ງຄ່າຢູ່'
|
||||||
|
ErrConfigParse: 'ຮູບແບບໄຟລ໌ການຕັ້ງຄ່າບໍ່ຖືກຕ້ອງ'
|
||||||
|
ErrConfigIsNull: 'ໄຟລ໌ການຕັ້ງຄ່າບໍ່ສາມາດຫວ່າງເປົ່າໄດ້'
|
||||||
|
ErrConfigDirNotFound: 'ບໍ່ພົບໂຟນເດີທີ່ໃຊ້ລັນງານ'
|
||||||
|
ErrConfigAlreadyExist: 'ໄຟລ໌ການຕັ້ງຄ່າທີ່ມີຊື່ດຽວກັນມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
ErrUserFindErr: 'ບໍ່ພົບຜູ້ໃຊ້ {{ .name }}: {{ .err }}'
|
||||||
|
|
||||||
|
#cronjob
|
||||||
|
CutWebsiteLogSuccess: 'ຕັດບັນທຶກ (Log) ເວັບໄຊສຳເລັດ: {{ .name }} (ສຳຮອງໄວ້ທີ່: {{ .path }})'
|
||||||
|
HandleShell: 'ລັນສະຄຣິບ {{ .name }}'
|
||||||
|
HandleCurl: 'ຮ້ອງຂໍ URL {{ .name }}'
|
||||||
|
HandleNtpSync: 'ຊິງຄ໌ເວລາລະບົບ'
|
||||||
|
HandleSystemClean: 'ທຳຄວາມສະອາດແຄຊ (Cache) ລະບົບ'
|
||||||
|
SystemLog: 'ບັນທຶກລະບົບ'
|
||||||
|
CutWebsiteLog: 'ຕັດບັນທຶກເວັບໄຊ'
|
||||||
|
FileOrDir: 'ໂຟນເດີ / ໄຟລ໌'
|
||||||
|
UploadFile: 'ອັບໂຫຼດໄຟລ໌ສຳຮອງ {{ .file }} ໄປທີ່ {{ .backup }}'
|
||||||
|
Upload: "ອັບໂຫຼດ"
|
||||||
|
IgnoreBackupErr: 'ສຳຮອງຂໍ້ມູນລົ້ມເຫຼວ: {{ .detail }} (ຖືກຂ້າມ)'
|
||||||
|
IgnoreUploadErr: 'ອັບໂຫຼດລົ້ມເຫຼວ: {{ .detail }} (ຖືກຂ້າມ)'
|
||||||
|
LoadBackupFailed: 'ການດຶງຂໍ້ມູນການເຊື່ອມຕໍ່ບັນຊີສຳຮອງລົ້ມເຫຼວ: {{ .detail }}'
|
||||||
|
InExecuting: 'ວຽກກຳລັງເຮັດວຽກຢູ່'
|
||||||
|
NoSuchResource: 'ບໍ່ພົບຂໍ້ມູນການສຳຮອງ; ຂ້າມການເຮັດວຽກ'
|
||||||
|
CleanLog: 'ທຳຄວາມສະອາດບັນທຶກ'
|
||||||
|
CleanLogByName: 'ທຳຄວາມສະອາດບັນທຶກ {{ .name }}'
|
||||||
|
WafIpGroupNotFound: "ບໍ່ພົບກຸ່ມ IP ຂອງ WAF. ກະລຸນາໄປທີ່ [Advanced Features -> WAF -> Black/White List -> IP Group] ແລະ ເພີ່ມໂດຍໃຊ້ Remote Download."
|
||||||
|
|
||||||
|
#toolbox
|
||||||
|
ErrNotExistUser: 'ຜູ້ໃຊ້ບໍ່ມີຢູ່ໃນລະບົບ'
|
||||||
|
ErrBanAction: 'ການຕັ້ງຄ່າລົ້ມເຫຼວ: ການບໍລິການ {{ .name }} ບໍ່ສາມາດໃຊ້ງານໄດ້'
|
||||||
|
ErrClamdscanNotFound: 'ບໍ່ພົບຄຳສັ່ງ clamdscan'
|
||||||
|
TaskSwapSet: 'ຕັ້ງຄ່າ Swap'
|
||||||
|
SetSwap: "ຕັ້ງຄ່າ Swap {{ .path }} - {{ .size }}"
|
||||||
|
CreateSwap: 'ສ້າງໄຟລ໌ Swap'
|
||||||
|
FormatSwap: 'ຟໍແມັດໄຟລ໌ Swap'
|
||||||
|
EnableSwap: 'ເປີດໃຊ້ Swap'
|
||||||
|
|
||||||
|
#tamper
|
||||||
|
CleanTamperSetting: 'ທຳຄວາມສະອາດການຕັ້ງຄ່າການປ້ອງກັນໃນອະດີດ'
|
||||||
|
SetTamperWithRules: 'ເປີດໃຊ້ການປ້ອງກັນໂຟນເດີຕາມກົດລະບຽບ'
|
||||||
|
ProtectDir: "ປ້ອງກັນໂຟນເດີ {{ .name }}"
|
||||||
|
IgnoreFile: "ປ້ອງກັນໄຟລ໌ {{ .name }}"
|
||||||
|
TamperSetSuccessful: 'ຕັ້ງຄ່າການປ້ອງກັນສຳເລັດ. ກຳລັງຕິດຕາມໂຟນເດີເຫຼົ່ານີ້:'
|
||||||
|
|
||||||
|
#waf
|
||||||
|
ErrScope: 'ການຕັ້ງຄ່ານີ້ບໍ່ສາມາດປ່ຽນແປງໄດ້'
|
||||||
|
ErrStateChange: 'ການປ່ຽນສະຖານະລົ້ມເຫຼວ'
|
||||||
|
ErrRuleExist: 'ກົດລະບຽບມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
ErrRuleNotExist: 'ບໍ່ມີກົດລະບຽບນີ້ຢູ່'
|
||||||
|
ErrParseIP: 'ຮູບແບບ IP ບໍ່ຖືກຕ້ອງ'
|
||||||
|
ErrDefaultIP: 'ຊື່ default ຖືກສະຫງວນໄວ້; ກະລຸນາໃຊ້ຊື່ອື່ນ'
|
||||||
|
ErrGroupInUse: 'ກຸ່ມ IP ກຳລັງຖືກໃຊ້ງານໂດຍກົດລະບຽບ Blacklist/Whitelist'
|
||||||
|
ErrIPGroupAclUse: 'ກຸ່ມ IP ກຳລັງຖືກໃຊ້ງານໂດຍກົດລະບຽບປັບແຕ່ງເອງຂອງເວັບໄຊ {{ .name }}'
|
||||||
|
ErrIPGroupOriginProtectionUse: 'ກຸ່ມ IP ຖືກໃຊ້ໂດຍການປ້ອງກັນ CDN Origin ຂອງເວັບໄຊ {{ .name }}'
|
||||||
|
ErrOriginProtectionIPGroupRequired: 'ກະລຸນາເລືອກຢ່າງໜ້ອຍໜຶ່ງກຸ່ມ IP ທີ່ບໍ່ຫວ່າງກ່ອນເປີດການປ້ອງກັນ Origin'
|
||||||
|
ErrOriginProtectionIPGroupInvalid: 'ກຸ່ມ IP {{ .name }} ບໍ່ມີຢູ່ ຫຼື ຫວ່າງເປົ່າ'
|
||||||
|
ErrGroupExist: 'ຊື່ກຸ່ມ IP ມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
ErrIPRange: 'ຊ່ວງ IP ບໍ່ຖືກຕ້ອງ'
|
||||||
|
ErrIPExist: 'IP ມີຢູ່ໃນລະບົບແລ້ວ'
|
||||||
|
urlDefense: 'ກົດລະບຽບ URL'
|
||||||
|
urlHelper: 'URL ທີ່ຖືກບລັອກ'
|
||||||
|
dirFilter: 'ການກັ່ນກອງໂຟນເດີ'
|
||||||
|
xss: 'XSS'
|
||||||
|
phpExec: 'ການປະຕິບັດສະຄຣິບ PHP'
|
||||||
|
oneWordTrojan: 'ໂທຣຈັນແບບຄຳດຽວ (One Word Trojan)'
|
||||||
|
appFilter: 'ການກັ່ນກອງໂຟນເດີອັນຕະລາຍຂອງແອັບ'
|
||||||
|
webShell: 'WebShell'
|
||||||
|
args: 'ກົດລະບຽບພາຣາມິເຕີ'
|
||||||
|
protocolFilter: 'ການກັ່ນກອງໂປຣໂຕຄໍ'
|
||||||
|
javaFilter: 'ການກັ່ນກອງໄຟລ໌ອັນຕະລາຍຂອງ Java'
|
||||||
|
scannerFilter: 'ການກັ່ນກອງເຄື່ອງມືສະແກນ'
|
||||||
|
escapeFilter: 'ການກັ່ນກອງ Escape'
|
||||||
|
customRule: 'ກົດລະບຽບປັບແຕ່ງເອງ'
|
||||||
|
httpMethod: 'ການກັ່ນກອງວິທີການ HTTP'
|
||||||
|
fileExt: 'ຂໍ້ຈຳກັດການອັບໂຫຼດໄຟລ໌'
|
||||||
|
defaultIpBlack: 'ກຸ່ມ IP ທີ່ເປັນອັນຕະລາຍ'
|
||||||
|
cookie: 'ກົດລະບຽບຄຸກກີ້ (Cookie)'
|
||||||
|
urlBlack: 'ລາຍຊື່ URL ທີ່ຖືກຫ້າມ'
|
||||||
|
uaBlack: 'ລາຍຊື່ User-Agent ທີ່ຖືກຫ້າມ'
|
||||||
|
attackCount: 'ຂີດຈຳກັດຄວາມຖີ່ຂອງການໂຈມຕີ'
|
||||||
|
fileExtCheck: 'ຂໍ້ຈຳກັດການອັບໂຫຼດໄຟລ໌'
|
||||||
|
geoRestrict: 'ຂໍ້ຈຳກັດການເຂົ້າເຖິງຕາມພາກພື້ນ'
|
||||||
|
unknownWebsite: 'ການເຂົ້າເຖິງຊື່ໂດເມນທີ່ບໍ່ໄດ້ຮັບອະນຸຍາດ'
|
||||||
|
notFoundCount: 'ຂີດຈຳກັດອັດຕາການເກີດ 404'
|
||||||
|
headerDefense: 'ກົດລະບຽບ Header'
|
||||||
|
defaultUaBlack: 'ກົດລະບຽບ User-Agent'
|
||||||
|
methodWhite: 'ກົດລະບຽບ HTTP'
|
||||||
|
captcha: 'ການຢືນຢັນຕົວຕົນ (CAPTCHA)'
|
||||||
|
fiveSeconds: 'ການຢືນຢັນ 5 ວິນາທີ'
|
||||||
|
vulnCheck: 'ກົດລະບຽບເພີ່ມເຕີມ'
|
||||||
|
acl: 'ກົດລະບຽບປັບແຕ່ງເອງ'
|
||||||
|
sql: 'SQL injection'
|
||||||
|
cc: 'ຂີດຈຳກັດຄວາມຖີ່ຂອງການເຂົ້າເຖິງ'
|
||||||
|
defaultUrlBlack: 'ກົດລະບຽບ URL'
|
||||||
|
sqlInject: 'SQL injection'
|
||||||
|
ErrDBNotExist: 'ຖານຂໍ້ມູນບໍ່ມີຢູ່'
|
||||||
|
allow: 'ອະນຸຍາດ'
|
||||||
|
deny: 'ປະຕິເສດ'
|
||||||
|
OpenrestyNotFound: 'ຍັງບໍ່ໄດ້ຕິດຕັ້ງ OpenResty'
|
||||||
|
remoteIpIsNull: "ລາຍຊື່ IP ຫວ່າງເປົ່າ"
|
||||||
|
OpenrestyVersionErr: 'ເວີຊັນ OpenResty ຕ່ຳເກີນໄປ; ກະລຸນາອັບເກຣດເປັນ 1.27.1.2-2-2-focal'
|
||||||
|
ErrFileTooLarge: 'ໄຟລ໌ມີຂະໜາດໃຫຍ່ກວ່າ 1 MB ແລະ ບໍ່ສາມາດອັບໂຫຼດໄດ້'
|
||||||
|
|
||||||
|
#task
|
||||||
|
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: 'ດຶງຂໍ້ມູນ (Pull)'
|
||||||
|
TaskCommit: 'ຄອມມິດ (Commit)'
|
||||||
|
TaskBuild: 'ສ້າງ (Build)'
|
||||||
|
TaskPush: 'ສົ່ງຂໍ້ມູນ (Push)'
|
||||||
|
TaskClean: "ທຳຄວາມສະອາດ"
|
||||||
|
TaskHandle: 'ປະຕິບັດ'
|
||||||
|
TaskImport: "ນຳເຂົ້າ"
|
||||||
|
TaskExport: "ສົ່ງອອກ"
|
||||||
|
Website: 'ເວັບໄຊ'
|
||||||
|
App: 'ແອັບພລິເຄຊັນ'
|
||||||
|
Runtime: 'Runtime'
|
||||||
|
Database: 'ຖານຂໍ້ມູນ'
|
||||||
|
ConfigFTP: 'ສ້າງຜູ້ໃຊ້ FTP {{ .name }}'
|
||||||
|
ConfigOpenresty: 'ສ້າງໄຟລ໌ການຕັ້ງຄ່າ OpenResty'
|
||||||
|
InstallAppSuccess: 'ຕິດຕັ້ງແອັບ {{ .name }} ສຳເລັດແລ້ວ'
|
||||||
|
ConfigRuntime: 'ຕັ້ງຄ່າ runtime'
|
||||||
|
ConfigApp: 'ຕັ້ງຄ່າແອັບ'
|
||||||
|
SuccessStatus: '{{ .name }} ສຳເລັດແລ້ວ'
|
||||||
|
FailedStatus: '{{ .name }} ລົ້ມເຫຼວ: {{ .err }}'
|
||||||
|
HandleLink: 'ປະມວນຜົນລິ້ງຂອງແອັບ'
|
||||||
|
HandleDatabaseApp: 'ປະມວນຜົນພາຣາມິເຕີຂອງແອັບ'
|
||||||
|
ExecShell: 'ລັນສະຄຣິບ {{ .name }}'
|
||||||
|
PullImage: 'ດຶງຮູບພາບ'
|
||||||
|
Start: 'ເລີ່ມ'
|
||||||
|
Run: 'ເລີ່ມ'
|
||||||
|
Stop: 'ຢຸດ'
|
||||||
|
Image: 'ຮູບພາບ'
|
||||||
|
Compose: 'Compose'
|
||||||
|
Container: 'ຄອນເທນເນີ'
|
||||||
|
AppLink: 'ແອັບພລິເຄຊັນທີ່ເຊື່ອມໂຍງ'
|
||||||
|
EnableSSL: 'ເປີດໃຊ້ HTTPS'
|
||||||
|
AppStore: 'App Store'
|
||||||
|
TaskSync: 'ຊິງຄ໌'
|
||||||
|
LocalApp: 'ແອັບພລິເຄຊັນພາຍໃນເຄື່ອງ'
|
||||||
|
SubTask: 'ວຽກຍ່ອຍ'
|
||||||
|
RuntimeExtension: 'ສ່ວນເສີມ Runtime'
|
||||||
|
TaskIsExecuting: 'ວຽກກຳລັງເຮັດວຽກຢູ່'
|
||||||
|
CustomAppstore: 'ຄັງແອັບພລິເຄຊັນສ່ວນຕົວ'
|
||||||
|
TaskExec: 'ປະຕິບັດ'
|
||||||
|
TaskBatch: "ການຈັດການແບບກຸ່ມ"
|
||||||
|
FileConvert: 'ການແປງໄຟລ໌'
|
||||||
|
|
||||||
|
# task - clam
|
||||||
|
Clamscan: "ສະແກນ {{ .name }}"
|
||||||
|
TaskScan: "ສະແກນ"
|
||||||
|
|
||||||
|
# task - ai
|
||||||
|
OllamaModelPull: 'ດຶງໂມເດລ Ollama {{ .name }}'
|
||||||
|
OllamaModelSize: 'ກວດສອບຂະໜາດໂມເດລ Ollama: {{ .name }}'
|
||||||
|
AIBenchmarkRun: 'ລັນການທົດສອບປະສິດທິພາບ AI'
|
||||||
|
|
||||||
|
# task-snapshot
|
||||||
|
Snapshot: 'ສະແນັບຊັອດ (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 escape'
|
||||||
|
RecoverApp: 'ກູ້ຄືນແອັບທີ່ຕິດຕັ້ງແລ້ວ'
|
||||||
|
RecoverWebsite: 'ກູ້ຄືນໂຟນເດີເວັບໄຊ'
|
||||||
|
RecoverAppImage: 'ກູ້ຄືນການສຳຮອງຮູບພາບສະແນັບຊັອດ'
|
||||||
|
RecoverCompose: 'ກູ້ຄືນໂຄງການ Compose ອື່ນໆ'
|
||||||
|
RecoverComposeList: 'ດຶງລາຍຊື່ໂຄງການ Compose ທັງໝົດທີ່ຈະກູ້ຄືນ'
|
||||||
|
RecoverComposeItem: 'ກູ້ຄືນ Compose {{ .name }}'
|
||||||
|
RecoverAppEmpty: 'ບໍ່ພົບການສຳຮອງຮູບພາບແອັບໃນສະແນັບຊັອດ'
|
||||||
|
RecoverBaseData: 'ກູ້ຄືນຂໍ້ມູນພື້ນຖານ ແລະ ໄຟລ໌'
|
||||||
|
RecoverDaemonJsonEmpty: 'ບໍ່ພົບ daemon.json ໃນສະແນັບຊັອດ ຫຼື ໃນເຄື່ອງປັດຈຸບັນ'
|
||||||
|
RecoverDaemonJson: 'ກູ້ຄືນ daemon.json'
|
||||||
|
RecoverDBData: 'ກູ້ຄືນຂໍ້ມູນຖານຂໍ້ມູນ'
|
||||||
|
RecoverBackups: 'ກູ້ຄືນໂຟນເດີສຳຮອງພາຍໃນເຄື່ອງ'
|
||||||
|
RecoverPanelData: 'ກູ້ຄືນໂຟນເດີຂໍ້ມູນ'
|
||||||
|
|
||||||
|
# task - container
|
||||||
|
ContainerImagePull: 'ດຶງຮູບພາບຄອນເທນເນີ {{ .name }}'
|
||||||
|
ContainerRemoveOld: 'ລຶບຄອນເທນເນີເດີມ {{ .name }}'
|
||||||
|
ContainerImageCheck: 'ກວດສອບຜົນການດຶງຮູບພາບ'
|
||||||
|
ContainerLoadInfo: 'ໂຫຼດຂໍ້ມູນຄອນເທນເນີ'
|
||||||
|
ContainerRecreate: 'ອັບເດດຄອນເທນເນີລົ້ມເຫຼວ; ກຳລັງກູ້ຄືນຄອນເທນເນີເດີມ'
|
||||||
|
ContainerCreate: 'ສ້າງຄອນເທນເນີໃໝ່ {{ .name }}'
|
||||||
|
ContainerCreateFailed: 'ສ້າງຄອນເທນເນີລົ້ມເຫຼວ; ກຳລັງລຶบคອນເທນເນີທີ່ລົ້ມເຫຼວ'
|
||||||
|
ContainerStartCheck: 'ກວດສອບການເລີ່ມຕົ້ນຂອງຄອນເທນເນີ'
|
||||||
|
ContainerAcquireLock: 'ລໍຖ້າລັອກການດຳເນີນງານຄອນເທນເນີ {{ .name }}'
|
||||||
|
ContainerUpgradeItem: 'ອັບເກຣດຄອນເທນເນີ {{ .name }}'
|
||||||
|
ContainerUpgradeSummary: 'ສະຫຼຸບຜົນການອັບເກຣດຄອນເທນເນີ'
|
||||||
|
ContainerCleanupWarning: 'ອັບເດດສຳເລັດ ແຕ່ລຶບຄອນເທນເນີສຳຮອງ {{ .name }} ບໍ່ສຳເລັດ. ຫຼັງຈາກຢືນຢັນວ່າຄອນເທນເນີໃໝ່ເຮັດວຽກປົກກະຕິ ໃຫ້ລຶບຄອນເທນເນີສຳຮອງດ້ວຍຕົນເອງ. ຂໍ້ຜິດພາດ: {{ .err }}'
|
||||||
|
ContainerStopOld: 'ຢຸດຄອນເທນເນີເດີມ {{ .name }}'
|
||||||
|
ContainerRenameOld: 'ປ່ຽນຊື່ຄອນເທນເນີເດີມ {{ .name }} ສຳລັບການກູ້ຄືນ'
|
||||||
|
ContainerDisconnectOld: 'ປ່ອຍທີ່ຢູ່ເຄືອຂ່າຍຂອງຄອນເທນເນີເດີມ {{ .name }}'
|
||||||
|
ContainerCreateReplacement: 'ສ້າງຄອນເທນເນີທົດແທນ {{ .name }}'
|
||||||
|
ContainerStartReplacement: 'ເລີ່ມຄອນເທນເນີທົດແທນ {{ .name }}'
|
||||||
|
ContainerWaitReplacement: 'ກວດສອບຄອນເທນເນີທົດແທນ {{ .name }} ວ່າພ້ອມແລ້ວ'
|
||||||
|
ContainerRollbackRestartOld: 'ເລີ່ມຄອນເທນເນີເດີມ {{ .name }} ຄືນໃໝ່'
|
||||||
|
ContainerRollbackRemoveReplacement: 'ລຶບຄອນເທນເນີທົດແທນ {{ .name }} ທີ່ລົ້ມເຫຼວ'
|
||||||
|
ContainerRollbackRenameOld: 'ກູ້ຄືນຊື່ຄອນເທນເນີເດີມ {{ .name }}'
|
||||||
|
ContainerRollbackReconnectOld: 'ກູ້ຄືນເຄືອຂ່າຍຂອງຄອນເທນເນີເດີມ {{ .name }}'
|
||||||
|
|
||||||
|
# task - image
|
||||||
|
ImageBuild: 'ສ້າງຮູບພາບ'
|
||||||
|
ImagePull: 'ດຶງຮູບພາບ'
|
||||||
|
ImageRepoAuthFromDB: 'ໂຫຼດຂໍ້ມູນການຢືນຢັນຕົວຕົນຂອງ Registry ຈາກຖານຂໍ້ມູນ'
|
||||||
|
ImagePush: 'ສົ່ງຮູບພາບ (Push)'
|
||||||
|
ImageRenameTag: 'ອັບເດດແທັກ (Tag) ຂອງຮູບພາບ'
|
||||||
|
ImageNewTag: 'ແທັກຮູບພາບໃໝ່ {{ .name }}'
|
||||||
|
ComposeCreate: 'ສ້າງໂຄງການ Compose'
|
||||||
|
|
||||||
|
# task - website
|
||||||
|
BackupNginxConfig: 'ສຳຮອງການຕັ້ງຄ່າ OpenResty ຂອງເວັບໄຊ'
|
||||||
|
CompressFileSuccess: 'ບີບອັດໂຟນເດີໄປທີ່ {{ .name }} ສຳເລັດແລ້ວ'
|
||||||
|
CompressDir: 'ບີບອັດໂຟນເດີ'
|
||||||
|
DeCompressFile: 'ແຕກໄຟລ໌ {{ .name }}'
|
||||||
|
ErrCheckValid: 'ການກວດສອບໄຟລ໌ສຳຮອງລົ້ມເຫຼວ: {{ .name }}'
|
||||||
|
Rollback: 'ຍ້ອນກັບ'
|
||||||
|
websiteDir: 'ໂຟນເດີເວັບໄຊ'
|
||||||
|
RecoverFailedStartRollBack: 'ການກູ້ຄືນລົ້ມເຫຼວ; ກຳລັງເລີ່ມການຍ້ອນກັບ'
|
||||||
|
AppBackupFileIncomplete: 'ໄຟລ໌ສຳຮອງບໍ່ສົມບູນ: ບໍ່ພົບ app.json ຫຼື app.tar.gz'
|
||||||
|
AppAttributesNotMatch: 'ປະເພດ ຫຼື ຊື່ຂອງແອັບພລິເຄຊັນບໍ່ກົງກັນ'
|
||||||
|
|
||||||
|
#alert
|
||||||
|
ErrAlertSync: 'ຊິງຄ໌ຂໍ້ຄວາມແຈ້ງເຕືອນລົ້ມເຫຼວ'
|
||||||
|
|
||||||
|
#task - runtime
|
||||||
|
ErrInstallExtension: 'ມີວຽກຕິດຕັ້ງສ່ວນເສີມອື່ນກຳລັງເຮັດວຽກຢູ່'
|
||||||
|
|
||||||
|
# alert mail template
|
||||||
|
PanelAlertTitle: "ການແຈ້ງເຕືອນຈາກແຜງຄວບຄຸມ"
|
||||||
|
TestAlertTitle: "ອີເມວທົດສອບ - ກວດສອບການເຊື່ອມຕໍ່ອີເມວ"
|
||||||
|
TestAlert: "ນີ້ແມ່ນອີເມວທົດສອບເພື່ອຢືນຢັນວ່າການຕັ້ງຄ່າການສົ່ງອີເມວຂອງທ່ານຖືກຕ້ອງ."
|
||||||
|
LicenseExpirationAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໃບອະນຸຍາດຈະໝົດອາຍຸໃນ {{ .day }} ວັນ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||||
|
CronJobFailedAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ວຽກທີ່ຕັ້ງເວລາ {{ .name }} ລົ້ມເຫຼວ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||||
|
ClamAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ການສະແກນໄວຣັສພົບ {{ .num }} ໄຟລ໌ທີ່ຕິດເຊື້ອ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||||
|
WebSiteAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ເວັບໄຊ {{ .num }} ແຫ່ງຈະໝົດອາຍຸໃນ {{ .day }} ວັນ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||||
|
SSLAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໃບຮັບຮອງ SSL {{ .num }} ໃບຈະໝົດອາຍຸໃນ {{ .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 }}. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||||
|
|
||||||
|
#disk
|
||||||
|
DeviceNotFound: "ບໍ່ພົບອຸປະກອນ {{ .name }}"
|
||||||
|
DeviceIsMounted: 'ອຸປະກອນ {{ .name }} ຖືກ Mount ຢູ່; ກະລຸນາຍົກເລີກການ Mount ກ່ອນ'
|
||||||
|
PartitionDiskErr: 'ການແບ່ງພາທິຊັນລົ້ມເຫຼວ: {{ .err }}'
|
||||||
|
FormatDiskErr: 'ການຟໍແມັດລົ້ມເຫຼວ: {{ .err }}'
|
||||||
|
MountDiskErr: 'ການ Mount ລົ້ມເຫຼວ: {{ .err }}'
|
||||||
|
XfsNotFound: 'ບໍ່ພົບ xfs; ກະລຸນາຕິດຕັ້ງ xfsprogs ກ່ອນ'
|
||||||
|
|
||||||
|
# terminal
|
||||||
|
TerminalAIBlockedRiskyCommand: 'ບລັອກຄຳສັ່ງທີ່ມີຄວາມສ່ຽງ: {{ .command }}'
|
||||||
|
TerminalAIThinking: 'AI ກຳລັງຄິດ...'
|
||||||
|
TerminalAIReadyToExecute: 'ຄິດສຳເລັດແລ້ວ, ກົດ Enter ເພື່ອປະຕິບັດ (ໃຊ້ເວລາ: {{ .duration }}, ໂທເຄັນ: {{ .tokens }})'
|
||||||
|
TerminalAIRequestFailed: 'ຄຳຮ້ອງຂໍ AI ລົ້ມເຫຼວ: {{ .err }}'
|
||||||
|
FileAISearchEmptyDir: 'ບໍ່ພົບໄຟລ໌ ຫຼື ໂຟນເດີພາຍໃຕ້ເສັ້ນທາງນີ້ (ຫຼື ລາຍການທັງໝົດຖືກກັ່ນກອງອອກ).'
|
||||||
|
|
||||||
|
# ai proxy
|
||||||
|
ErrAIProxyServiceNotInstalled: 'ຍັງບໍ່ໄດ້ຕິດຕັ້ງບໍລິການ {{ .name }}'
|
||||||
|
ErrAIProxyModelAccountNotFound: 'ບໍ່ພົບບັນຊີໂມເດລ {{ .id }}'
|
||||||
|
ErrAIProxyModelMapRequired: 'ຟິວ (Field) ນີ້ຈຳເປັນຕ້ອງມີ.'
|
||||||
|
ErrAIProxyModelMapInvalid: 'ການຈັບຄູ່ໂມເດລຕ້ອງເປັນວັດຖຸ JSON ທີ່ຖືກຕ້ອງ: {{ .err }}'
|
||||||
|
ErrAIProxyModelMapEmpty: 'ການຈັບຄູ່ໂມເດລບໍ່ສາມາດຫວ່າງເປົ່າໄດ້'
|
||||||
|
ErrAIProxyModelMapEmptyExternalModel: 'ການຈັບຄູ່ໂມເດລມີໂມເດລຄຳຮ້ອງຂໍທີ່ຫວ່າງເປົ່າ'
|
||||||
|
ErrAIProxyModelMapEmptyUpstreamModel: 'ໂມເດລ Upstream ສຳລັບ {{ .model }} ບໍ່ສາມາດຫວ່າງເປົ່າໄດ້'
|
||||||
|
AIProxyUserFallback: 'ຜູ້ໃຊ້ທີ່ຖືກລຶບ (ID: {{ .id }})'
|
||||||
@@ -1,10 +1,18 @@
|
|||||||
# ralat asas
|
# ralat asas
|
||||||
ErrInvalidParams: 'Permintaan tidak sah: {{ .detail }}'
|
ErrInvalidParams: 'Permintaan tidak sah: {{ .detail }}'
|
||||||
|
ErrMcpGatewayArgsParse: 'Format argumen tersuai MCP tidak sah'
|
||||||
|
ErrMcpGatewayArgsConflict: 'Argumen {{ .name }} diurus oleh 1Panel dan tidak boleh ditetapkan dalam argumen tersuai'
|
||||||
ErrTokenParse: 'Token tidak sah: {{ .detail }}'
|
ErrTokenParse: 'Token tidak sah: {{ .detail }}'
|
||||||
ErrInitialPassword: 'Kata laluan asal salah'
|
ErrInitialPassword: 'Kata laluan asal salah'
|
||||||
ErrInternalServer: 'Ralat pelayan: {{ .detail }}'
|
ErrInternalServer: 'Ralat pelayan: {{ .detail }}'
|
||||||
ErrRecordExist: 'Rekod sudah wujud'
|
ErrRecordExist: 'Rekod sudah wujud'
|
||||||
ErrRecordNotFound: 'Rekod tidak ditemui'
|
ErrRecordNotFound: 'Rekod tidak ditemui'
|
||||||
|
WafLogExport: 'Eksport log WAF'
|
||||||
|
MonitorLogExport: 'Eksport log pemantauan laman web'
|
||||||
|
LogExportNoData: 'Tiada log yang sepadan dengan penapis dipilih'
|
||||||
|
LogExportDiskSpace: 'Ruang tersedia kurang daripada 1 GiB; eksport dihentikan'
|
||||||
|
LogExportRetentionInvalid: 'Tempoh simpanan rekod eksport mestilah antara 1 hingga 30 hari'
|
||||||
|
ErrSystemLogKeywordFilterUnsupported: 'Versi sistem semasa tidak menyokong penapisan kata kunci log sistem (memerlukan systemd 237 atau lebih baharu)'
|
||||||
ErrStructTransform: 'Ralat penukaran: {{ .err }}'
|
ErrStructTransform: 'Ralat penukaran: {{ .err }}'
|
||||||
ErrNotSupportType: 'Jenis tidak disokong: {{ .name }}'
|
ErrNotSupportType: 'Jenis tidak disokong: {{ .name }}'
|
||||||
|
|
||||||
@@ -63,6 +71,7 @@ Decrypt: 'Dekripsi'
|
|||||||
# ejen
|
# ejen
|
||||||
ErrAgentAccountBound: 'Akaun terikat kepada 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.'
|
ErrTerminalAIAccountInUse: 'Akaun model ini sedang digunakan oleh Terminal AI. Tukar akaun dalam tetapan Terminal AI atau nyahaktifkan Terminal AI, kemudian cuba lagi.'
|
||||||
|
ErrFileAIAccountInUse: 'Akaun model ini sedang digunakan oleh AI pengurusan fail. Tukar akaun dalam tetapannya atau nyahaktifkan AI tersebut, kemudian cuba lagi.'
|
||||||
ErrAgentAccountUnavailable: 'Sambungan akaun tidak tersedia: {{ .err }}'
|
ErrAgentAccountUnavailable: 'Sambungan akaun tidak tersedia: {{ .err }}'
|
||||||
ErrAgentProviderNotSupported: 'Penyedia ejen tidak disokong'
|
ErrAgentProviderNotSupported: 'Penyedia ejen tidak disokong'
|
||||||
ErrAgentAccountRequired: 'Pilih akaun ejen terlebih dahulu'
|
ErrAgentAccountRequired: 'Pilih akaun ejen terlebih dahulu'
|
||||||
@@ -70,6 +79,7 @@ ErrAgentAccountNotVerified: 'Akaun ejen belum disahkan'
|
|||||||
ErrAgentAccountModelsRequired: 'Sila konfigur sekurang-kurangnya satu model'
|
ErrAgentAccountModelsRequired: 'Sila konfigur sekurang-kurangnya satu model'
|
||||||
ErrAgentAccountSingleInitialModel: 'Hanya satu model awal boleh dikonfigurasi semasa mencipta akaun'
|
ErrAgentAccountSingleInitialModel: 'Hanya satu model awal boleh dikonfigurasi semasa mencipta akaun'
|
||||||
ErrAgentModelNotInAccount: 'Model tidak dikonfigurasikan dalam akaun yang dipilih'
|
ErrAgentModelNotInAccount: 'Model tidak dikonfigurasikan dalam akaun yang dipilih'
|
||||||
|
ErrAgentVerifyModelInUse: 'Model ini digunakan untuk mengesahkan akaun. Pilih model pengesahan lain sebelum memadamkannya'
|
||||||
ErrAgentFallbackModelDuplicate: 'Model fallback tidak boleh berulang'
|
ErrAgentFallbackModelDuplicate: 'Model fallback tidak boleh berulang'
|
||||||
ErrAgentFallbackModelPrimary: 'Model fallback tidak boleh sama dengan model utama'
|
ErrAgentFallbackModelPrimary: 'Model fallback tidak boleh sama dengan model utama'
|
||||||
ErrAgentModelInUse: 'Masih ada ejen terikat yang menggunakan model ini: {{ .name }}'
|
ErrAgentModelInUse: 'Masih ada ejen terikat yang menggunakan model ini: {{ .name }}'
|
||||||
@@ -101,6 +111,7 @@ ErrAppLimit: 'Bilangan aplikasi yang dipasang telah melebihi had'
|
|||||||
ErrNotInstall: 'Aplikasi tidak dipasang'
|
ErrNotInstall: 'Aplikasi tidak dipasang'
|
||||||
ErrPortInOtherApp: 'Port {{ .port }} sudah diduduki oleh aplikasi {{ .apps }}'
|
ErrPortInOtherApp: 'Port {{ .port }} sudah diduduki oleh aplikasi {{ .apps }}'
|
||||||
ErrDbUserNotValid: 'Pangkalan data sedia ada, nama pengguna dan kata laluan tidak sepadan'
|
ErrDbUserNotValid: 'Pangkalan data sedia ada, nama pengguna dan kata laluan tidak sepadan'
|
||||||
|
ErrMysqlUserUsedByApps: 'Pengguna pangkalan data ini sedang digunakan oleh [{{ .detail }}]; hosnya tidak boleh diubah, dan pengguna ini tidak boleh dipadamkan atau dibatalkan kebenarannya'
|
||||||
ErrUpdateBuWebsite: 'Aplikasi telah berjaya dikemas kini, tetapi pengubahsuaian fail konfigurasi tapak web gagal. Sila semak konfigurasi'
|
ErrUpdateBuWebsite: 'Aplikasi telah berjaya dikemas kini, tetapi pengubahsuaian fail konfigurasi tapak web gagal. Sila semak konfigurasi'
|
||||||
Err1PanelNetworkFailed: 'Pembuatan rangkaian kontena lalai gagal {{ .detail }}'
|
Err1PanelNetworkFailed: 'Pembuatan rangkaian kontena lalai gagal {{ .detail }}'
|
||||||
ErrFileParse: 'Penghuraian fail karang docker aplikasi gagal'
|
ErrFileParse: 'Penghuraian fail karang docker aplikasi gagal'
|
||||||
@@ -121,6 +132,7 @@ ErrAppNameExist: 'Nama aplikasi sudah wujud'
|
|||||||
ErrAppInstallNameExist: 'Nama pemasangan aplikasi {{ .name }} sudah wujud'
|
ErrAppInstallNameExist: 'Nama pemasangan aplikasi {{ .name }} sudah wujud'
|
||||||
AppStoreIsSyncing: 'App Store sedang menyegerak, sila cuba sebentar lagi'
|
AppStoreIsSyncing: 'App Store sedang menyegerak, sila cuba sebentar lagi'
|
||||||
ErrGetCompose: 'Gagal mendapatkan fail docker-compose.yml {{ .detail }}'
|
ErrGetCompose: 'Gagal mendapatkan fail docker-compose.yml {{ .detail }}'
|
||||||
|
ErrAppVersionUnavailable: 'Versi aplikasi ini telah dialih keluar daripada perkhidmatan jauh. Sila pilih versi lain dan cuba lagi.'
|
||||||
ErrAppWarn: 'Status tidak normal, sila semak log'
|
ErrAppWarn: 'Status tidak normal, sila semak log'
|
||||||
ErrAppParamKey: 'Parameter {{ .name }} medan tidak normal'
|
ErrAppParamKey: 'Parameter {{ .name }} medan tidak normal'
|
||||||
ErrAppUpgrade: 'Peningkatan {{ .name }} aplikasi gagal {{ .err }}'
|
ErrAppUpgrade: 'Peningkatan {{ .name }} aplikasi gagal {{ .err }}'
|
||||||
@@ -372,6 +384,9 @@ ErrParseIP: 'Format IP salah'
|
|||||||
ErrDefaultIP: 'default ialah nama simpanan, sila tukar kepada nama lain'
|
ErrDefaultIP: 'default ialah nama simpanan, sila tukar kepada nama lain'
|
||||||
ErrGroupInUse: 'Kumpulan IP digunakan oleh senarai hitam/senarai putih dan tidak boleh dipadamkan'
|
ErrGroupInUse: 'Kumpulan IP digunakan oleh senarai hitam/senarai putih dan tidak boleh dipadamkan'
|
||||||
ErrIPGroupAclUse: 'Kumpulan IP digunakan oleh peraturan tersuai tapak web {{ .name }}, tidak boleh dipadamkan'
|
ErrIPGroupAclUse: 'Kumpulan IP digunakan oleh peraturan tersuai tapak web {{ .name }}, tidak boleh dipadamkan'
|
||||||
|
ErrIPGroupOriginProtectionUse: 'Kumpulan IP digunakan oleh perlindungan origin CDN laman web {{ .name }}'
|
||||||
|
ErrOriginProtectionIPGroupRequired: 'Pilih sekurang-kurangnya satu kumpulan IP yang tidak kosong sebelum mengaktifkan perlindungan origin'
|
||||||
|
ErrOriginProtectionIPGroupInvalid: 'Kumpulan IP {{ .name }} tidak wujud atau kosong'
|
||||||
ErrGroupExist: 'Nama kumpulan IP sudah wujud'
|
ErrGroupExist: 'Nama kumpulan IP sudah wujud'
|
||||||
ErrIPRange: 'Julat IP salah'
|
ErrIPRange: 'Julat IP salah'
|
||||||
ErrIPExist: 'IP sudah wujud'
|
ErrIPExist: 'IP sudah wujud'
|
||||||
@@ -550,6 +565,20 @@ ContainerRecreate: 'Kemas kini kontena gagal, kini mula memulihkan bekas asal'
|
|||||||
ContainerCreate: 'Buat bekas baharu {{ .name }}'
|
ContainerCreate: 'Buat bekas baharu {{ .name }}'
|
||||||
ContainerCreateFailed: 'Penciptaan bekas gagal, padamkan bekas yang gagal'
|
ContainerCreateFailed: 'Penciptaan bekas gagal, padamkan bekas yang gagal'
|
||||||
ContainerStartCheck: 'Periksa sama ada bekas telah dimulakan'
|
ContainerStartCheck: 'Periksa sama ada bekas telah dimulakan'
|
||||||
|
ContainerAcquireLock: 'Tunggu kunci operasi bekas {{ .name }}'
|
||||||
|
ContainerUpgradeItem: 'Naik taraf bekas {{ .name }}'
|
||||||
|
ContainerUpgradeSummary: 'Ringkaskan hasil naik taraf bekas'
|
||||||
|
ContainerCleanupWarning: 'Kemas kini berjaya tetapi bekas sandaran {{ .name }} tidak dapat dialih keluar. Selepas memastikan bekas baharu berfungsi, alih keluar bekas sandaran secara manual. Ralat: {{ .err }}'
|
||||||
|
ContainerStopOld: 'Hentikan bekas asal {{ .name }}'
|
||||||
|
ContainerRenameOld: 'Namakan semula bekas asal {{ .name }} untuk pemulihan'
|
||||||
|
ContainerDisconnectOld: 'Lepaskan alamat rangkaian bekas asal {{ .name }}'
|
||||||
|
ContainerCreateReplacement: 'Cipta bekas ganti {{ .name }}'
|
||||||
|
ContainerStartReplacement: 'Mulakan bekas ganti {{ .name }}'
|
||||||
|
ContainerWaitReplacement: 'Periksa kesediaan bekas ganti {{ .name }}'
|
||||||
|
ContainerRollbackRestartOld: 'Mulakan semula bekas asal {{ .name }}'
|
||||||
|
ContainerRollbackRemoveReplacement: 'Alih keluar bekas ganti yang gagal {{ .name }}'
|
||||||
|
ContainerRollbackRenameOld: 'Pulihkan nama bekas asal {{ .name }}'
|
||||||
|
ContainerRollbackReconnectOld: 'Pulihkan rangkaian bekas asal {{ .name }}'
|
||||||
|
|
||||||
# tugas - imej
|
# tugas - imej
|
||||||
ImageBuild: 'Membina Imej'
|
ImageBuild: 'Membina Imej'
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
# erros básicos
|
# erros básicos
|
||||||
ErrInvalidParams: 'Requisição inválida: {{ .detail }}'
|
ErrInvalidParams: 'Requisição inválida: {{ .detail }}'
|
||||||
|
ErrMcpGatewayArgsParse: 'Formato inválido dos argumentos personalizados do MCP'
|
||||||
|
ErrMcpGatewayArgsConflict: 'O argumento {{ .name }} é gerenciado pelo 1Panel e não pode ser definido nos argumentos personalizados'
|
||||||
ErrTokenParse: 'Erro de token: {{ .detail }}'
|
ErrTokenParse: 'Erro de token: {{ .detail }}'
|
||||||
ErrInitialPassword: 'Senha original incorreta'
|
ErrInitialPassword: 'Senha original incorreta'
|
||||||
ErrInternalServer: 'Erro interno: {{ .detail }}'
|
ErrInternalServer: 'Erro interno: {{ .detail }}'
|
||||||
ErrRecordExist: 'Registro já existe'
|
ErrRecordExist: 'Registro já existe'
|
||||||
ErrRecordNotFound: 'Registro não encontrado'
|
ErrRecordNotFound: 'Registro não encontrado'
|
||||||
|
WafLogExport: 'Exportação de logs do WAF'
|
||||||
|
MonitorLogExport: 'Exportação de logs de monitoramento de sites'
|
||||||
|
LogExportNoData: 'Nenhum log corresponde aos filtros selecionados'
|
||||||
|
LogExportDiskSpace: 'Há menos de 1 GiB disponível; a exportação foi interrompida'
|
||||||
|
LogExportRetentionInvalid: 'A retenção dos registros de exportação deve ser de 1 a 30 dias'
|
||||||
|
ErrSystemLogKeywordFilterUnsupported: 'A versão atual do sistema não oferece suporte à filtragem de logs do sistema por palavra-chave (requer systemd 237 ou posterior)'
|
||||||
ErrStructTransform: 'Erro de conversão: {{ .err }}'
|
ErrStructTransform: 'Erro de conversão: {{ .err }}'
|
||||||
ErrNotSupportType: 'Tipo não suportado: {{ .name }}'
|
ErrNotSupportType: 'Tipo não suportado: {{ .name }}'
|
||||||
|
|
||||||
@@ -63,6 +71,7 @@ Decrypt: 'Descriptografar'
|
|||||||
# agente
|
# agente
|
||||||
ErrAgentAccountBound: 'Conta vinculada a 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.'
|
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.'
|
||||||
|
ErrFileAIAccountInUse: 'Esta conta de modelo está sendo usada pela IA do gerenciador de arquivos. Troque a conta nas configurações ou desative a IA e tente novamente.'
|
||||||
ErrAgentAccountUnavailable: 'Conexão da conta indisponível: {{ .err }}'
|
ErrAgentAccountUnavailable: 'Conexão da conta indisponível: {{ .err }}'
|
||||||
ErrAgentProviderNotSupported: 'Provedor de agente não suportado'
|
ErrAgentProviderNotSupported: 'Provedor de agente não suportado'
|
||||||
ErrAgentAccountRequired: 'Selecione uma conta de agente primeiro'
|
ErrAgentAccountRequired: 'Selecione uma conta de agente primeiro'
|
||||||
@@ -70,6 +79,7 @@ ErrAgentAccountNotVerified: 'Conta de agente não verificada'
|
|||||||
ErrAgentAccountModelsRequired: 'Configure pelo menos um modelo'
|
ErrAgentAccountModelsRequired: 'Configure pelo menos um modelo'
|
||||||
ErrAgentAccountSingleInitialModel: 'Apenas um modelo inicial pode ser configurado ao criar a conta'
|
ErrAgentAccountSingleInitialModel: 'Apenas um modelo inicial pode ser configurado ao criar a conta'
|
||||||
ErrAgentModelNotInAccount: 'O modelo não está configurado na conta selecionada'
|
ErrAgentModelNotInAccount: 'O modelo não está configurado na conta selecionada'
|
||||||
|
ErrAgentVerifyModelInUse: 'Este modelo é usado para verificar a conta. Selecione outro modelo de verificação antes de excluí-lo'
|
||||||
ErrAgentFallbackModelDuplicate: 'Os modelos de fallback não podem ser duplicados'
|
ErrAgentFallbackModelDuplicate: 'Os modelos de fallback não podem ser duplicados'
|
||||||
ErrAgentFallbackModelPrimary: 'O modelo de fallback não pode ser igual ao modelo principal'
|
ErrAgentFallbackModelPrimary: 'O modelo de fallback não pode ser igual ao modelo principal'
|
||||||
ErrAgentModelInUse: 'Ainda há um agente vinculado usando este modelo: {{ .name }}'
|
ErrAgentModelInUse: 'Ainda há um agente vinculado usando este modelo: {{ .name }}'
|
||||||
@@ -101,6 +111,7 @@ ErrAppLimit: 'O número de aplicativos instalados excedeu o limite'
|
|||||||
ErrNotInstall: 'Aplicativo não instalado'
|
ErrNotInstall: 'Aplicativo não instalado'
|
||||||
ErrPortInOtherApp: 'A porta {{ .port }} já está ocupada pelo aplicativo {{ .apps }}'
|
ErrPortInOtherApp: 'A porta {{ .port }} já está ocupada pelo aplicativo {{ .apps }}'
|
||||||
ErrDbUserNotValid: 'Banco de dados existente, nome de usuário e senha não correspondem'
|
ErrDbUserNotValid: 'Banco de dados existente, nome de usuário e senha não correspondem'
|
||||||
|
ErrMysqlUserUsedByApps: 'Este usuário do banco de dados está sendo usado por [{{ .detail }}]; seu host não pode ser alterado, e ele não pode ser excluído nem ter sua autorização revogada'
|
||||||
ErrUpdateBuWebsite: 'O aplicativo foi atualizado com sucesso, mas a modificação do arquivo de configuração do site falhou., verifique a configuração'
|
ErrUpdateBuWebsite: 'O aplicativo foi atualizado com sucesso, mas a modificação do arquivo de configuração do site falhou., verifique a configuração'
|
||||||
Err1PanelNetworkFailed: 'Falha na criação da rede de contêiner padrão {{ .detalhe }}'
|
Err1PanelNetworkFailed: 'Falha na criação da rede de contêiner padrão {{ .detalhe }}'
|
||||||
ErrFileParse: 'Falha na análise do arquivo docker-compose do aplicativo'
|
ErrFileParse: 'Falha na análise do arquivo docker-compose do aplicativo'
|
||||||
@@ -121,6 +132,7 @@ ErrAppNameExist: 'O nome do aplicativo já existe'
|
|||||||
ErrAppInstallNameExist: 'O nome de instalação do aplicativo {{ .name }} já existe'
|
ErrAppInstallNameExist: 'O nome de instalação do aplicativo {{ .name }} já existe'
|
||||||
AppStoreIsSyncing: 'A App Store está sincronizando mais tarde'
|
AppStoreIsSyncing: 'A App Store está sincronizando mais tarde'
|
||||||
ErrGetCompose: 'Falha ao obter o arquivo docker-compose.yml {{ .detail }}'
|
ErrGetCompose: 'Falha ao obter o arquivo docker-compose.yml {{ .detail }}'
|
||||||
|
ErrAppVersionUnavailable: 'Esta versão do aplicativo foi removida do serviço remoto. Selecione outra versão e tente novamente.'
|
||||||
ErrAppWarn: 'Status anormal, verifique o log'
|
ErrAppWarn: 'Status anormal, verifique o log'
|
||||||
ErrAppParamKey: 'O campo de parâmetro {{ .name }} está anormal'
|
ErrAppParamKey: 'O campo de parâmetro {{ .name }} está anormal'
|
||||||
ErrAppUpgrade: 'Falha na atualização do aplicativo {{ .name }} {{ .err }}'
|
ErrAppUpgrade: 'Falha na atualização do aplicativo {{ .name }} {{ .err }}'
|
||||||
@@ -372,6 +384,9 @@ ErrParseIP: 'Formato de IP errado'
|
|||||||
ErrDefaultIP: 'padrão é um nome reservado, altere-o para outro nome'
|
ErrDefaultIP: 'padrão é um nome reservado, altere-o para outro nome'
|
||||||
ErrGroupInUse: 'O grupo de IP é usado pela lista negra/lista branca e não pode ser excluído'
|
ErrGroupInUse: 'O grupo de IP é usado pela lista negra/lista branca e não pode ser excluído'
|
||||||
ErrIPGroupAclUse: 'O grupo de IP está sendo usado por regras personalizadas do site {{ .name }}, não pode ser excluído'
|
ErrIPGroupAclUse: 'O grupo de IP está sendo usado por regras personalizadas do site {{ .name }}, não pode ser excluído'
|
||||||
|
ErrIPGroupOriginProtectionUse: 'O grupo de IP está sendo usado pela proteção de origem CDN do site {{ .name }}'
|
||||||
|
ErrOriginProtectionIPGroupRequired: 'Selecione pelo menos um grupo de IP não vazio antes de ativar a proteção de origem'
|
||||||
|
ErrOriginProtectionIPGroupInvalid: 'O grupo de IP {{ .name }} não existe ou está vazio'
|
||||||
ErrGroupExist: 'O nome do grupo IP já existe'
|
ErrGroupExist: 'O nome do grupo IP já existe'
|
||||||
ErrIPRange: 'Intervalo de IP errado'
|
ErrIPRange: 'Intervalo de IP errado'
|
||||||
ErrIPExist: 'IP já existe'
|
ErrIPExist: 'IP já existe'
|
||||||
@@ -550,6 +565,20 @@ ContainerRecreate: 'A atualização do contêiner falhou, agora iniciando a rest
|
|||||||
ContainerCreate: 'Criar um novo contêiner {{ .name }}'
|
ContainerCreate: 'Criar um novo contêiner {{ .name }}'
|
||||||
ContainerCreateFailed: 'Falha na criação do contêiner, exclua o contêiner com falha'
|
ContainerCreateFailed: 'Falha na criação do contêiner, exclua o contêiner com falha'
|
||||||
ContainerStartCheck: 'Verifique se o contêiner foi iniciado'
|
ContainerStartCheck: 'Verifique se o contêiner foi iniciado'
|
||||||
|
ContainerAcquireLock: 'Aguardar o bloqueio de operação do contêiner {{ .name }}'
|
||||||
|
ContainerUpgradeItem: 'Atualizar o contêiner {{ .name }}'
|
||||||
|
ContainerUpgradeSummary: 'Resumir os resultados da atualização de contêineres'
|
||||||
|
ContainerCleanupWarning: 'A atualização foi concluída, mas não foi possível remover o contêiner de backup {{ .name }}. Depois de confirmar que o novo contêiner está funcionando, remova manualmente o contêiner de backup. Erro: {{ .err }}'
|
||||||
|
ContainerStopOld: 'Parar o contêiner original {{ .name }}'
|
||||||
|
ContainerRenameOld: 'Renomear o contêiner original {{ .name }} para reversão'
|
||||||
|
ContainerDisconnectOld: 'Liberar os endereços de rede do contêiner original {{ .name }}'
|
||||||
|
ContainerCreateReplacement: 'Criar o contêiner substituto {{ .name }}'
|
||||||
|
ContainerStartReplacement: 'Iniciar o contêiner substituto {{ .name }}'
|
||||||
|
ContainerWaitReplacement: 'Verificar se o contêiner substituto {{ .name }} está pronto'
|
||||||
|
ContainerRollbackRestartOld: 'Reiniciar o contêiner original {{ .name }}'
|
||||||
|
ContainerRollbackRemoveReplacement: 'Remover o contêiner substituto com falha {{ .name }}'
|
||||||
|
ContainerRollbackRenameOld: 'Restaurar o nome do contêiner original {{ .name }}'
|
||||||
|
ContainerRollbackReconnectOld: 'Restaurar as redes do contêiner original {{ .name }}'
|
||||||
|
|
||||||
# tarefa - imagem
|
# tarefa - imagem
|
||||||
ImageBuild: 'Construção de imagem'
|
ImageBuild: 'Construção de imagem'
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
# базовые ошибки
|
# базовые ошибки
|
||||||
ErrInvalidParams: 'Неверный запрос: {{ .detail }}'
|
ErrInvalidParams: 'Неверный запрос: {{ .detail }}'
|
||||||
|
ErrMcpGatewayArgsParse: 'Неверный формат пользовательских аргументов MCP'
|
||||||
|
ErrMcpGatewayArgsConflict: 'Аргумент {{ .name }} управляется 1Panel и не может быть задан в пользовательских аргументах'
|
||||||
ErrTokenParse: 'Ошибка токена: {{ .detail }}'
|
ErrTokenParse: 'Ошибка токена: {{ .detail }}'
|
||||||
ErrInitialPassword: 'Исходный пароль неверен'
|
ErrInitialPassword: 'Исходный пароль неверен'
|
||||||
ErrInternalServer: 'Внутренняя ошибка: {{ .detail }}'
|
ErrInternalServer: 'Внутренняя ошибка: {{ .detail }}'
|
||||||
ErrRecordExist: 'Запись уже существует'
|
ErrRecordExist: 'Запись уже существует'
|
||||||
ErrRecordNotFound: 'Запись не найдена'
|
ErrRecordNotFound: 'Запись не найдена'
|
||||||
|
WafLogExport: 'Экспорт журналов WAF'
|
||||||
|
MonitorLogExport: 'Экспорт журналов мониторинга веб-сайтов'
|
||||||
|
LogExportNoData: 'Нет журналов, соответствующих выбранным фильтрам'
|
||||||
|
LogExportDiskSpace: 'Доступно менее 1 GiB; экспорт остановлен'
|
||||||
|
LogExportRetentionInvalid: 'Срок хранения истории экспорта должен быть от 1 до 30 дней'
|
||||||
|
ErrSystemLogKeywordFilterUnsupported: 'Текущая версия системы не поддерживает фильтрацию системных журналов по ключевым словам (требуется systemd 237 или новее)'
|
||||||
ErrStructTransform: 'Ошибка преобразования: {{ .err }}'
|
ErrStructTransform: 'Ошибка преобразования: {{ .err }}'
|
||||||
ErrNotSupportType: 'Тип не поддерживается: {{ .name }}'
|
ErrNotSupportType: 'Тип не поддерживается: {{ .name }}'
|
||||||
|
|
||||||
@@ -63,6 +71,7 @@ Decrypt: 'Расшифровать'
|
|||||||
# агент
|
# агент
|
||||||
ErrAgentAccountBound: 'Акаунт привязан к агенту'
|
ErrAgentAccountBound: 'Акаунт привязан к агенту'
|
||||||
ErrTerminalAIAccountInUse: 'Эта учетная запись модели сейчас используется Terminal AI. Смените учетную запись в настройках Terminal AI или отключите Terminal AI и повторите попытку.'
|
ErrTerminalAIAccountInUse: 'Эта учетная запись модели сейчас используется Terminal AI. Смените учетную запись в настройках Terminal AI или отключите Terminal AI и повторите попытку.'
|
||||||
|
ErrFileAIAccountInUse: 'Эта учетная запись модели используется ИИ файлового менеджера. Смените учетную запись в его настройках или отключите ИИ и повторите попытку.'
|
||||||
ErrAgentAccountUnavailable: 'Связь с аккаунтом недоступна: {{ .err }}'
|
ErrAgentAccountUnavailable: 'Связь с аккаунтом недоступна: {{ .err }}'
|
||||||
ErrAgentProviderNotSupported: 'Провайдер агента не поддерживается'
|
ErrAgentProviderNotSupported: 'Провайдер агента не поддерживается'
|
||||||
ErrAgentAccountRequired: 'Выберите аккаунт агента'
|
ErrAgentAccountRequired: 'Выберите аккаунт агента'
|
||||||
@@ -70,6 +79,7 @@ ErrAgentAccountNotVerified: 'Аккаунт не подтверждён'
|
|||||||
ErrAgentAccountModelsRequired: 'Настройте хотя бы одну модель'
|
ErrAgentAccountModelsRequired: 'Настройте хотя бы одну модель'
|
||||||
ErrAgentAccountSingleInitialModel: 'При создании аккаунта можно указать только одну начальную модель'
|
ErrAgentAccountSingleInitialModel: 'При создании аккаунта можно указать только одну начальную модель'
|
||||||
ErrAgentModelNotInAccount: 'Модель не настроена в выбранном аккаунте'
|
ErrAgentModelNotInAccount: 'Модель не настроена в выбранном аккаунте'
|
||||||
|
ErrAgentVerifyModelInUse: 'Эта модель используется для проверки аккаунта. Перед удалением выберите другую модель проверки'
|
||||||
ErrAgentFallbackModelDuplicate: 'Резервные модели не могут дублироваться'
|
ErrAgentFallbackModelDuplicate: 'Резервные модели не могут дублироваться'
|
||||||
ErrAgentFallbackModelPrimary: 'Резервная модель не может совпадать с основной'
|
ErrAgentFallbackModelPrimary: 'Резервная модель не может совпадать с основной'
|
||||||
ErrAgentModelInUse: 'Эта модель всё ещё используется агентом: {{ .name }}'
|
ErrAgentModelInUse: 'Эта модель всё ещё используется агентом: {{ .name }}'
|
||||||
@@ -101,6 +111,7 @@ ErrAppLimit: 'Количество установленных приложени
|
|||||||
ErrNotInstall: 'Приложение не установлено'
|
ErrNotInstall: 'Приложение не установлено'
|
||||||
ErrPortInOtherApp: 'Порт {{ .port }} уже занят приложением {{ .apps }}'
|
ErrPortInOtherApp: 'Порт {{ .port }} уже занят приложением {{ .apps }}'
|
||||||
ErrDbUserNotValid: 'Существующая база данных, имя пользователя и пароль не совпадают'
|
ErrDbUserNotValid: 'Существующая база данных, имя пользователя и пароль не совпадают'
|
||||||
|
ErrMysqlUserUsedByApps: 'Этот пользователь базы данных используется приложениями [{{ .detail }}]; его хост нельзя изменить, а самого пользователя нельзя удалить или лишить прав'
|
||||||
ErrUpdateBuWebsite: 'Приложение успешно обновлено, но изменение файла конфигурации веб-сайта не удалось. Пожалуйста, проверьте конфигурацию'
|
ErrUpdateBuWebsite: 'Приложение успешно обновлено, но изменение файла конфигурации веб-сайта не удалось. Пожалуйста, проверьте конфигурацию'
|
||||||
Err1PanelNetworkFailed: 'Создание сети контейнеров по умолчанию не удалось {{ .detail }}'
|
Err1PanelNetworkFailed: 'Создание сети контейнеров по умолчанию не удалось {{ .detail }}'
|
||||||
ErrFileParse: 'Ошибка анализа файла docker-compose приложения'
|
ErrFileParse: 'Ошибка анализа файла docker-compose приложения'
|
||||||
@@ -121,6 +132,7 @@ ErrAppNameExist: 'Имя приложения уже существует'
|
|||||||
ErrAppInstallNameExist: 'Имя установки приложения {{ .name }} уже существует'
|
ErrAppInstallNameExist: 'Имя установки приложения {{ .name }} уже существует'
|
||||||
AppStoreIsSyncing: 'App Store синхронизируется, повторите попытку позже'
|
AppStoreIsSyncing: 'App Store синхронизируется, повторите попытку позже'
|
||||||
ErrGetCompose: 'Не удалось получить файл docker-compose.yml {{ .detail }}'
|
ErrGetCompose: 'Не удалось получить файл docker-compose.yml {{ .detail }}'
|
||||||
|
ErrAppVersionUnavailable: 'Эта версия приложения удалена из удаленного сервиса. Выберите другую версию и повторите попытку.'
|
||||||
ErrAppWarn: 'Ненормальное состояние, проверьте журнал'
|
ErrAppWarn: 'Ненормальное состояние, проверьте журнал'
|
||||||
ErrAppParamKey: 'Поле параметра {{ .name }} ненормально'
|
ErrAppParamKey: 'Поле параметра {{ .name }} ненормально'
|
||||||
ErrAppUpgrade: 'Обновление приложения {{ .name }} не удалось {{ .err }}'
|
ErrAppUpgrade: 'Обновление приложения {{ .name }} не удалось {{ .err }}'
|
||||||
@@ -372,6 +384,9 @@ ErrParseIP: 'Неправильный формат IP'
|
|||||||
ErrDefaultIP: 'по умолчанию это зарезервированное имя, пожалуйста, измените его на другое имя'
|
ErrDefaultIP: 'по умолчанию это зарезервированное имя, пожалуйста, измените его на другое имя'
|
||||||
ErrGroupInUse: 'IP-группа используется черным/белым списком и не может быть удалена'
|
ErrGroupInUse: 'IP-группа используется черным/белым списком и не может быть удалена'
|
||||||
ErrIPGroupAclUse: 'Группа IP используется пользовательскими правилами сайта {{ .name }}, невозможно удалить'
|
ErrIPGroupAclUse: 'Группа IP используется пользовательскими правилами сайта {{ .name }}, невозможно удалить'
|
||||||
|
ErrIPGroupOriginProtectionUse: 'Группа IP используется защитой источника CDN сайта {{ .name }}'
|
||||||
|
ErrOriginProtectionIPGroupRequired: 'Перед включением защиты источника выберите хотя бы одну непустую группу IP'
|
||||||
|
ErrOriginProtectionIPGroupInvalid: 'Группа IP {{ .name }} не существует или пуста'
|
||||||
ErrGroupExist: 'Имя группы IP уже существует'
|
ErrGroupExist: 'Имя группы IP уже существует'
|
||||||
ErrIPRange: 'Неверный диапазон IP-адресов'
|
ErrIPRange: 'Неверный диапазон IP-адресов'
|
||||||
ErrIPExist: 'IP-адрес уже существует'
|
ErrIPExist: 'IP-адрес уже существует'
|
||||||
@@ -550,6 +565,20 @@ ContainerRecreate: 'Обновление контейнера не удалос
|
|||||||
ContainerCreate: 'Создать новый контейнер {{ .name }}'
|
ContainerCreate: 'Создать новый контейнер {{ .name }}'
|
||||||
ContainerCreateFailed: 'Создание контейнера не удалось, удалите неудавшийся контейнер'
|
ContainerCreateFailed: 'Создание контейнера не удалось, удалите неудавшийся контейнер'
|
||||||
ContainerStartCheck: 'Проверить, запущен ли контейнер'
|
ContainerStartCheck: 'Проверить, запущен ли контейнер'
|
||||||
|
ContainerAcquireLock: 'Ожидание блокировки операций контейнера {{ .name }}'
|
||||||
|
ContainerUpgradeItem: 'Обновление контейнера {{ .name }}'
|
||||||
|
ContainerUpgradeSummary: 'Сводка результатов обновления контейнеров'
|
||||||
|
ContainerCleanupWarning: 'Обновление завершено, но удалить резервный контейнер {{ .name }} не удалось. Убедитесь, что новый контейнер работает, затем удалите резервный контейнер вручную. Ошибка: {{ .err }}'
|
||||||
|
ContainerStopOld: 'Остановка исходного контейнера {{ .name }}'
|
||||||
|
ContainerRenameOld: 'Переименование исходного контейнера {{ .name }} для отката'
|
||||||
|
ContainerDisconnectOld: 'Освобождение сетевых адресов исходного контейнера {{ .name }}'
|
||||||
|
ContainerCreateReplacement: 'Создание нового контейнера {{ .name }}'
|
||||||
|
ContainerStartReplacement: 'Запуск нового контейнера {{ .name }}'
|
||||||
|
ContainerWaitReplacement: 'Проверка готовности нового контейнера {{ .name }}'
|
||||||
|
ContainerRollbackRestartOld: 'Повторный запуск исходного контейнера {{ .name }}'
|
||||||
|
ContainerRollbackRemoveReplacement: 'Удаление неудачно созданного контейнера {{ .name }}'
|
||||||
|
ContainerRollbackRenameOld: 'Восстановление имени исходного контейнера {{ .name }}'
|
||||||
|
ContainerRollbackReconnectOld: 'Восстановление сетей исходного контейнера {{ .name }}'
|
||||||
|
|
||||||
# задача - образ
|
# задача - образ
|
||||||
ImageBuild: 'Создание изображения'
|
ImageBuild: 'Создание изображения'
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
# temel hatalar
|
# temel hatalar
|
||||||
ErrInvalidParams: 'İstek geçersiz: {{ .detail }}'
|
ErrInvalidParams: 'İstek geçersiz: {{ .detail }}'
|
||||||
|
ErrMcpGatewayArgsParse: 'MCP özel bağımsız değişken biçimi geçersiz'
|
||||||
|
ErrMcpGatewayArgsConflict: '{{ .name }} bağımsız değişkeni 1Panel tarafından yönetilir ve özel bağımsız değişkenlerde ayarlanamaz'
|
||||||
ErrTokenParse: 'Token hatası: {{ .detail }}'
|
ErrTokenParse: 'Token hatası: {{ .detail }}'
|
||||||
ErrInitialPassword: 'Orijinal şifre yanlış'
|
ErrInitialPassword: 'Orijinal şifre yanlış'
|
||||||
ErrInternalServer: 'Sunucu hatası: {{ .detail }}'
|
ErrInternalServer: 'Sunucu hatası: {{ .detail }}'
|
||||||
ErrRecordExist: 'Kayıt zaten var'
|
ErrRecordExist: 'Kayıt zaten var'
|
||||||
ErrRecordNotFound: 'Kayıt bulunamadı'
|
ErrRecordNotFound: 'Kayıt bulunamadı'
|
||||||
|
WafLogExport: 'WAF günlüğü dışa aktarma'
|
||||||
|
MonitorLogExport: 'Web sitesi izleme günlüğü dışa aktarma'
|
||||||
|
LogExportNoData: 'Seçilen filtrelerle eşleşen günlük yok'
|
||||||
|
LogExportDiskSpace: 'Kullanılabilir alan 1 GiB altında; dışa aktarma durduruldu'
|
||||||
|
LogExportRetentionInvalid: 'Dışa aktarma kayıtları 1 ile 30 gün arasında saklanmalıdır'
|
||||||
|
ErrSystemLogKeywordFilterUnsupported: 'Mevcut sistem sürümü sistem günlüklerinde anahtar kelime filtrelemeyi desteklemiyor (systemd 237 veya üzeri gerekir)'
|
||||||
ErrStructTransform: 'Dönüşüm hatası: {{ .err }}'
|
ErrStructTransform: 'Dönüşüm hatası: {{ .err }}'
|
||||||
ErrNotSupportType: 'Bu tür desteklenmiyor: {{ .name }}'
|
ErrNotSupportType: 'Bu tür desteklenmiyor: {{ .name }}'
|
||||||
|
|
||||||
@@ -63,6 +71,7 @@ Decrypt: 'Şifre Çöz'
|
|||||||
# ajan
|
# ajan
|
||||||
ErrAgentAccountBound: 'Hesap bir ajana bağlı'
|
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.'
|
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.'
|
||||||
|
ErrFileAIAccountInUse: 'Bu model hesabı dosya yönetimi yapay zekası tarafından kullanılıyor. Ayarlardan hesabı değiştirin veya yapay zekayı devre dışı bırakıp yeniden deneyin.'
|
||||||
ErrAgentAccountUnavailable: 'Hesap bağlantısı yok: {{ .err }}'
|
ErrAgentAccountUnavailable: 'Hesap bağlantısı yok: {{ .err }}'
|
||||||
ErrAgentProviderNotSupported: 'Ajans sağlayıcısı desteklenmiyor'
|
ErrAgentProviderNotSupported: 'Ajans sağlayıcısı desteklenmiyor'
|
||||||
ErrAgentAccountRequired: 'Önce bir ajans hesabı seçin'
|
ErrAgentAccountRequired: 'Önce bir ajans hesabı seçin'
|
||||||
@@ -70,6 +79,7 @@ ErrAgentAccountNotVerified: 'Ajans hesabı doğrulanmadı'
|
|||||||
ErrAgentAccountModelsRequired: 'Lütfen en az bir model yapılandırın'
|
ErrAgentAccountModelsRequired: 'Lütfen en az bir model yapılandırın'
|
||||||
ErrAgentAccountSingleInitialModel: 'Hesap oluştururken yalnızca bir başlangıç modeli yapılandırılabilir'
|
ErrAgentAccountSingleInitialModel: 'Hesap oluştururken yalnızca bir başlangıç modeli yapılandırılabilir'
|
||||||
ErrAgentModelNotInAccount: 'Model seçilen hesapta yapılandırılmadı'
|
ErrAgentModelNotInAccount: 'Model seçilen hesapta yapılandırılmadı'
|
||||||
|
ErrAgentVerifyModelInUse: 'Bu model hesap doğrulaması için kullanılıyor. Silmeden önce başka bir doğrulama modeli seçin'
|
||||||
ErrAgentFallbackModelDuplicate: 'Yedek modeller tekrar edemez'
|
ErrAgentFallbackModelDuplicate: 'Yedek modeller tekrar edemez'
|
||||||
ErrAgentFallbackModelPrimary: 'Yedek model birincil modelle aynı olamaz'
|
ErrAgentFallbackModelPrimary: 'Yedek model birincil modelle aynı olamaz'
|
||||||
ErrAgentModelInUse: 'Bu modeli hâlâ kullanan bağlı bir ajan var: {{ .name }}'
|
ErrAgentModelInUse: 'Bu modeli hâlâ kullanan bağlı bir ajan var: {{ .name }}'
|
||||||
@@ -101,6 +111,7 @@ ErrAppLimit: 'Yüklenen uygulama sayısı sınırı aştı'
|
|||||||
ErrNotInstall: 'Uygulama yüklenmedi'
|
ErrNotInstall: 'Uygulama yüklenmedi'
|
||||||
ErrPortInOtherApp: '{{ .port }} portu zaten {{ .apps }} uygulaması tarafından kullanılıyor'
|
ErrPortInOtherApp: '{{ .port }} portu zaten {{ .apps }} uygulaması tarafından kullanılıyor'
|
||||||
ErrDbUserNotValid: 'Mevcut veritabanı, kullanıcı adı ve şifre eşleşmiyor'
|
ErrDbUserNotValid: 'Mevcut veritabanı, kullanıcı adı ve şifre eşleşmiyor'
|
||||||
|
ErrMysqlUserUsedByApps: 'Bu veritabanı kullanıcısı [{{ .detail }}] tarafından kullanılıyor; ana bilgisayarı değiştirilemez, kullanıcı silinemez veya yetkisi kaldırılamaz'
|
||||||
ErrUpdateBuWebsite: 'Uygulama başarıyla güncellendi, ancak web sitesi yapılandırma dosyası değiştirme başarısız. Lütfen yapılandırmayı kontrol edin'
|
ErrUpdateBuWebsite: 'Uygulama başarıyla güncellendi, ancak web sitesi yapılandırma dosyası değiştirme başarısız. Lütfen yapılandırmayı kontrol edin'
|
||||||
Err1PanelNetworkFailed: 'Varsayılan konteyner ağ oluşturma başarısız {{ .detail }}'
|
Err1PanelNetworkFailed: 'Varsayılan konteyner ağ oluşturma başarısız {{ .detail }}'
|
||||||
ErrFileParse: 'Uygulama docker-compose dosya ayrıştırma başarısız'
|
ErrFileParse: 'Uygulama docker-compose dosya ayrıştırma başarısız'
|
||||||
@@ -121,6 +132,7 @@ ErrAppNameExist: 'Uygulama adı zaten mevcut'
|
|||||||
ErrAppInstallNameExist: '{{ .name }} uygulama kurulum adı zaten mevcut'
|
ErrAppInstallNameExist: '{{ .name }} uygulama kurulum adı zaten mevcut'
|
||||||
AppStoreIsSyncing: 'Uygulama Mağazası senkronize ediliyor, lütfen daha sonra tekrar deneyin'
|
AppStoreIsSyncing: 'Uygulama Mağazası senkronize ediliyor, lütfen daha sonra tekrar deneyin'
|
||||||
ErrGetCompose: 'docker-compose.yml dosyası alınamadı {{ .detail }}'
|
ErrGetCompose: 'docker-compose.yml dosyası alınamadı {{ .detail }}'
|
||||||
|
ErrAppVersionUnavailable: 'Bu uygulama sürümü uzak hizmetten kaldırıldı. Lütfen başka bir sürüm seçip tekrar deneyin.'
|
||||||
ErrAppWarn: 'Anormal durum, lütfen günlüğü kontrol edin'
|
ErrAppWarn: 'Anormal durum, lütfen günlüğü kontrol edin'
|
||||||
ErrAppParamKey: 'Parametre {{ .name }} alanı anormal'
|
ErrAppParamKey: 'Parametre {{ .name }} alanı anormal'
|
||||||
ErrAppUpgrade: 'Uygulama {{ .name }} yükseltmesi başarısız {{ .err }}'
|
ErrAppUpgrade: 'Uygulama {{ .name }} yükseltmesi başarısız {{ .err }}'
|
||||||
@@ -372,6 +384,9 @@ ErrParseIP: 'Yanlış IP formatı'
|
|||||||
ErrDefaultIP: 'default ayrılmış bir isim, lütfen başka bir isme değiştirin'
|
ErrDefaultIP: 'default ayrılmış bir isim, lütfen başka bir isme değiştirin'
|
||||||
ErrGroupInUse: 'IP grubu kara liste/beyaz liste tarafından kullanılıyor ve silinemez'
|
ErrGroupInUse: 'IP grubu kara liste/beyaz liste tarafından kullanılıyor ve silinemez'
|
||||||
ErrIPGroupAclUse: 'IP grubu {{ .name }} web sitesinin özel kuralları tarafından kullanılıyor, silinemez'
|
ErrIPGroupAclUse: 'IP grubu {{ .name }} web sitesinin özel kuralları tarafından kullanılıyor, silinemez'
|
||||||
|
ErrIPGroupOriginProtectionUse: 'IP grubu {{ .name }} web sitesinin CDN kaynak koruması tarafından kullanılıyor'
|
||||||
|
ErrOriginProtectionIPGroupRequired: 'Kaynak korumasını etkinleştirmeden önce en az bir boş olmayan IP grubu seçin'
|
||||||
|
ErrOriginProtectionIPGroupInvalid: 'IP grubu {{ .name }} mevcut değil veya boş'
|
||||||
ErrGroupExist: 'IP grup adı zaten mevcut'
|
ErrGroupExist: 'IP grup adı zaten mevcut'
|
||||||
ErrIPRange: 'Yanlış IP aralığı'
|
ErrIPRange: 'Yanlış IP aralığı'
|
||||||
ErrIPExist: 'IP zaten mevcut'
|
ErrIPExist: 'IP zaten mevcut'
|
||||||
@@ -550,6 +565,20 @@ ContainerRecreate: 'Konteyner güncellemesi başarısız, şimdi orijinal kontey
|
|||||||
ContainerCreate: 'Yeni konteyner oluştur {{ .name }}'
|
ContainerCreate: 'Yeni konteyner oluştur {{ .name }}'
|
||||||
ContainerCreateFailed: 'Konteyner oluşturma başarısız, başarısız konteyneri sil'
|
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'
|
ContainerStartCheck: 'Konteynerin başlatılıp başlatılmadığını kontrol et'
|
||||||
|
ContainerAcquireLock: '{{ .name }} konteyneri işlem kilidini bekle'
|
||||||
|
ContainerUpgradeItem: '{{ .name }} konteynerini yükselt'
|
||||||
|
ContainerUpgradeSummary: 'Konteyner yükseltme sonuçlarını özetle'
|
||||||
|
ContainerCleanupWarning: 'Güncelleme tamamlandı ancak {{ .name }} yedek konteyneri kaldırılamadı. Yeni konteynerin düzgün çalıştığını doğruladıktan sonra yedek konteyneri elle kaldırın. Hata: {{ .err }}'
|
||||||
|
ContainerStopOld: 'Eski {{ .name }} konteynerini durdur'
|
||||||
|
ContainerRenameOld: 'Geri alma için eski {{ .name }} konteynerini yeniden adlandır'
|
||||||
|
ContainerDisconnectOld: 'Eski {{ .name }} konteynerinin ağ adreslerini serbest bırak'
|
||||||
|
ContainerCreateReplacement: 'Yeni {{ .name }} konteynerini oluştur'
|
||||||
|
ContainerStartReplacement: 'Yeni {{ .name }} konteynerini başlat'
|
||||||
|
ContainerWaitReplacement: 'Yeni {{ .name }} konteynerinin hazır olduğunu kontrol et'
|
||||||
|
ContainerRollbackRestartOld: 'Eski {{ .name }} konteynerini yeniden başlat'
|
||||||
|
ContainerRollbackRemoveReplacement: 'Başlatılamayan yeni {{ .name }} konteynerini kaldır'
|
||||||
|
ContainerRollbackRenameOld: 'Eski {{ .name }} konteynerinin adını geri yükle'
|
||||||
|
ContainerRollbackReconnectOld: 'Eski {{ .name }} konteynerinin ağlarını geri yükle'
|
||||||
|
|
||||||
# görev - imaj
|
# görev - imaj
|
||||||
ImageBuild: 'İmaj Oluşturma'
|
ImageBuild: 'İmaj Oluşturma'
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
# 基礎錯誤
|
# 基礎錯誤
|
||||||
ErrInvalidParams: '請求參數錯誤: {{ .detail }}'
|
ErrInvalidParams: '請求參數錯誤: {{ .detail }}'
|
||||||
|
ErrMcpGatewayArgsParse: 'MCP 自訂參數格式錯誤'
|
||||||
|
ErrMcpGatewayArgsConflict: '參數 {{ .name }} 由 1Panel 管理,不能在自訂參數中設定'
|
||||||
ErrTokenParse: 'Token 產生錯誤: {{ .detail }}'
|
ErrTokenParse: 'Token 產生錯誤: {{ .detail }}'
|
||||||
ErrInitialPassword: '原密碼錯誤'
|
ErrInitialPassword: '原密碼錯誤'
|
||||||
ErrInternalServer: '服務內部錯誤: {{ .detail }}'
|
ErrInternalServer: '服務內部錯誤: {{ .detail }}'
|
||||||
ErrRecordExist: '記錄已存在'
|
ErrRecordExist: '記錄已存在'
|
||||||
ErrRecordNotFound: '記錄未能找到'
|
ErrRecordNotFound: '記錄未能找到'
|
||||||
|
WafLogExport: 'WAF 日誌匯出'
|
||||||
|
MonitorLogExport: '網站監控日誌匯出'
|
||||||
|
LogExportNoData: '沒有符合條件的日誌'
|
||||||
|
LogExportDiskSpace: '可用磁碟空間不足 1 GiB,匯出任務已停止'
|
||||||
|
LogExportRetentionInvalid: '匯出記錄保留天數必須介於 1 到 30 天之間'
|
||||||
|
ErrSystemLogKeywordFilterUnsupported: '目前系統版本不支援系統日誌關鍵字篩選(需要 systemd 237 或更高版本)'
|
||||||
ErrStructTransform: '型別轉換失敗: {{ .err }}'
|
ErrStructTransform: '型別轉換失敗: {{ .err }}'
|
||||||
ErrNotSupportType: '系統暫不支援目前類型: {{ .name }}'
|
ErrNotSupportType: '系統暫不支援目前類型: {{ .name }}'
|
||||||
|
|
||||||
@@ -63,6 +71,7 @@ Decrypt: '解密'
|
|||||||
# 智慧體
|
# 智慧體
|
||||||
ErrAgentAccountBound: '該帳號已綁定到智能體,無法刪除,請重試。'
|
ErrAgentAccountBound: '該帳號已綁定到智能體,無法刪除,請重試。'
|
||||||
ErrTerminalAIAccountInUse: '該模型帳號正被終端 AI 使用,請在終端 AI 設定中更換帳號或關閉終端 AI 後再試。'
|
ErrTerminalAIAccountInUse: '該模型帳號正被終端 AI 使用,請在終端 AI 設定中更換帳號或關閉終端 AI 後再試。'
|
||||||
|
ErrFileAIAccountInUse: '該模型帳號正被檔案管理 AI 使用,請在檔案管理 AI 設定中更換帳號或關閉檔案管理 AI 後再試。'
|
||||||
ErrAgentAccountUnavailable: '帳號連線資訊不可用,錯誤:{{ .err }},請重試'
|
ErrAgentAccountUnavailable: '帳號連線資訊不可用,錯誤:{{ .err }},請重試'
|
||||||
ErrAgentProviderNotSupported: '暫不支援該智能體提供商,請重試'
|
ErrAgentProviderNotSupported: '暫不支援該智能體提供商,請重試'
|
||||||
ErrAgentAccountRequired: '請選擇智能體帳號後重試'
|
ErrAgentAccountRequired: '請選擇智能體帳號後重試'
|
||||||
@@ -70,6 +79,7 @@ ErrAgentAccountNotVerified: '帳號未驗證通過,請重試'
|
|||||||
ErrAgentAccountModelsRequired: '請至少配置一個模型'
|
ErrAgentAccountModelsRequired: '請至少配置一個模型'
|
||||||
ErrAgentAccountSingleInitialModel: '建立帳號時只能配置一個初始模型'
|
ErrAgentAccountSingleInitialModel: '建立帳號時只能配置一個初始模型'
|
||||||
ErrAgentModelNotInAccount: '所選模型未配置在當前帳號中,請重試'
|
ErrAgentModelNotInAccount: '所選模型未配置在當前帳號中,請重試'
|
||||||
|
ErrAgentVerifyModelInUse: '該模型正用於驗證帳號,請先選擇其他驗證模型後再刪除'
|
||||||
ErrAgentFallbackModelDuplicate: '備用模型不能重複'
|
ErrAgentFallbackModelDuplicate: '備用模型不能重複'
|
||||||
ErrAgentFallbackModelPrimary: '備用模型不能與主模型相同'
|
ErrAgentFallbackModelPrimary: '備用模型不能與主模型相同'
|
||||||
ErrAgentModelInUse: '仍有智能體正在使用該模型:{{ .name }}'
|
ErrAgentModelInUse: '仍有智能體正在使用該模型:{{ .name }}'
|
||||||
@@ -101,6 +111,7 @@ ErrAppLimit: '應用程式超出安裝數量限制'
|
|||||||
ErrNotInstall: '應用程式未安裝'
|
ErrNotInstall: '應用程式未安裝'
|
||||||
ErrPortInOtherApp: '{{ .port }} 連接埠已被應用程式{{ .apps }} 佔用!'
|
ErrPortInOtherApp: '{{ .port }} 連接埠已被應用程式{{ .apps }} 佔用!'
|
||||||
ErrDbUserNotValid: '存量資料庫,使用者名稱密碼不符!'
|
ErrDbUserNotValid: '存量資料庫,使用者名稱密碼不符!'
|
||||||
|
ErrMysqlUserUsedByApps: '該資料庫使用者已被【{{ .detail }}】應用使用,無法修改 Host、刪除或撤銷授權'
|
||||||
ErrUpdateBuWebsite: '應用程式更新成功,但網站設定檔修改失敗,請檢查設定!'
|
ErrUpdateBuWebsite: '應用程式更新成功,但網站設定檔修改失敗,請檢查設定!'
|
||||||
Err1PanelNetworkFailed: '預設容器網路建立失敗! {{ .detail }}'
|
Err1PanelNetworkFailed: '預設容器網路建立失敗! {{ .detail }}'
|
||||||
ErrFileParse: '應用docker-compose 檔解析失敗!'
|
ErrFileParse: '應用docker-compose 檔解析失敗!'
|
||||||
@@ -121,6 +132,7 @@ ErrAppNameExist: '應用程式名稱已存在'
|
|||||||
ErrAppInstallNameExist: '應用程式安裝名稱 {{ .name }} 已存在'
|
ErrAppInstallNameExist: '應用程式安裝名稱 {{ .name }} 已存在'
|
||||||
AppStoreIsSyncing: '應用程式商店正在同步中,請稍後重試'
|
AppStoreIsSyncing: '應用程式商店正在同步中,請稍後重試'
|
||||||
ErrGetCompose: 'docker-compose.yml 檔案取得失敗!{{ .detail }}'
|
ErrGetCompose: 'docker-compose.yml 檔案取得失敗!{{ .detail }}'
|
||||||
|
ErrAppVersionUnavailable: '目前應用程式版本已從遠端服務下架,請選擇其他版本後重試'
|
||||||
ErrAppWarn: '狀態異常,請檢視日誌'
|
ErrAppWarn: '狀態異常,請檢視日誌'
|
||||||
ErrAppParamKey: '參數{{ .name }} 欄位異常'
|
ErrAppParamKey: '參數{{ .name }} 欄位異常'
|
||||||
ErrAppUpgrade: '應用程式{{ .name }} 升級失敗{{ .err }}'
|
ErrAppUpgrade: '應用程式{{ .name }} 升級失敗{{ .err }}'
|
||||||
@@ -372,6 +384,9 @@ ErrParseIP: 'IP 格式錯誤'
|
|||||||
ErrDefaultIP: 'default 為保留名稱,請更換其他名稱'
|
ErrDefaultIP: 'default 為保留名稱,請更換其他名稱'
|
||||||
ErrGroupInUse: 'IP 群組被黑/白名單使用,無法刪除'
|
ErrGroupInUse: 'IP 群組被黑/白名單使用,無法刪除'
|
||||||
ErrIPGroupAclUse: 'IP 群組被網站 {{ .name }} 自訂規則使用,無法刪除'
|
ErrIPGroupAclUse: 'IP 群組被網站 {{ .name }} 自訂規則使用,無法刪除'
|
||||||
|
ErrIPGroupOriginProtectionUse: 'IP 群組被網站 {{ .name }} 的 CDN 來源站保護使用,無法刪除'
|
||||||
|
ErrOriginProtectionIPGroupRequired: '開啟來源站保護時必須選擇至少一個非空 IP 群組'
|
||||||
|
ErrOriginProtectionIPGroupInvalid: 'IP 群組 {{ .name }} 不存在或為空'
|
||||||
ErrGroupExist: 'IP 群組名稱已存在'
|
ErrGroupExist: 'IP 群組名稱已存在'
|
||||||
ErrIPRange: 'IP 範圍錯誤'
|
ErrIPRange: 'IP 範圍錯誤'
|
||||||
ErrIPExist: 'IP 已存在'
|
ErrIPExist: 'IP 已存在'
|
||||||
@@ -550,6 +565,20 @@ ContainerRecreate: '容器更新失敗,現在開始復原原容器'
|
|||||||
ContainerCreate: '建立新容器{{ .name }}'
|
ContainerCreate: '建立新容器{{ .name }}'
|
||||||
ContainerCreateFailed: '容器建立失敗,刪除失敗容器'
|
ContainerCreateFailed: '容器建立失敗,刪除失敗容器'
|
||||||
ContainerStartCheck: '檢查容器是否已啟動'
|
ContainerStartCheck: '檢查容器是否已啟動'
|
||||||
|
ContainerAcquireLock: '等待容器 {{ .name }} 操作鎖'
|
||||||
|
ContainerUpgradeItem: '升級容器 {{ .name }}'
|
||||||
|
ContainerUpgradeSummary: '彙總容器升級結果'
|
||||||
|
ContainerCleanupWarning: '容器更新成功,但備份容器 {{ .name }} 刪除失敗。請確認新容器運作正常後手動刪除該備份容器。錯誤:{{ .err }}'
|
||||||
|
ContainerStopOld: '停止原容器 {{ .name }}'
|
||||||
|
ContainerRenameOld: '重新命名原容器 {{ .name }} 以便回復'
|
||||||
|
ContainerDisconnectOld: '釋放原容器 {{ .name }} 的網路位址'
|
||||||
|
ContainerCreateReplacement: '建立替換容器 {{ .name }}'
|
||||||
|
ContainerStartReplacement: '啟動替換容器 {{ .name }}'
|
||||||
|
ContainerWaitReplacement: '檢查替換容器 {{ .name }} 是否就緒'
|
||||||
|
ContainerRollbackRestartOld: '重新啟動原容器 {{ .name }}'
|
||||||
|
ContainerRollbackRemoveReplacement: '刪除啟動失敗的替換容器 {{ .name }}'
|
||||||
|
ContainerRollbackRenameOld: '恢復原容器 {{ .name }} 的名稱'
|
||||||
|
ContainerRollbackReconnectOld: '恢復原容器 {{ .name }} 的網路'
|
||||||
|
|
||||||
# 任務 - 映像
|
# 任務 - 映像
|
||||||
ImageBuild: '映像建置'
|
ImageBuild: '映像建置'
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
# 基础错误
|
# 基础错误
|
||||||
ErrInvalidParams: "参数错误: {{ .detail }}"
|
ErrInvalidParams: "参数错误: {{ .detail }}"
|
||||||
|
ErrMcpGatewayArgsParse: "MCP 自定义参数格式错误"
|
||||||
|
ErrMcpGatewayArgsConflict: "参数 {{ .name }} 由 1Panel 管理,不能在自定义参数中设置"
|
||||||
ErrTokenParse: "Token 错误: {{ .detail }}"
|
ErrTokenParse: "Token 错误: {{ .detail }}"
|
||||||
ErrInitialPassword: "原密码错误"
|
ErrInitialPassword: "原密码错误"
|
||||||
ErrInternalServer: "服务错误: {{ .detail }}"
|
ErrInternalServer: "服务错误: {{ .detail }}"
|
||||||
ErrRecordExist: "记录已存在"
|
ErrRecordExist: "记录已存在"
|
||||||
ErrRecordNotFound: "记录不存在"
|
ErrRecordNotFound: "记录不存在"
|
||||||
|
WafLogExport: "WAF 日志导出"
|
||||||
|
MonitorLogExport: "网站监控日志导出"
|
||||||
|
LogExportNoData: "没有符合条件的日志"
|
||||||
|
LogExportDiskSpace: "可用磁盘空间不足 1 GiB,导出任务已停止"
|
||||||
|
LogExportRetentionInvalid: "导出记录保留天数必须在 1 到 30 天之间"
|
||||||
|
ErrSystemLogKeywordFilterUnsupported: "当前系统版本不支持系统日志关键词过滤(需要 systemd 237 或更高版本)"
|
||||||
ErrStructTransform: "类型转换失败: {{ .err }}"
|
ErrStructTransform: "类型转换失败: {{ .err }}"
|
||||||
ErrNotSupportType: "不支持当前类型: {{ .name }}"
|
ErrNotSupportType: "不支持当前类型: {{ .name }}"
|
||||||
|
|
||||||
@@ -63,6 +71,7 @@ Decrypt: "解密"
|
|||||||
# 智能体
|
# 智能体
|
||||||
ErrAgentAccountBound: "该账号已绑定智能体,无法删除"
|
ErrAgentAccountBound: "该账号已绑定智能体,无法删除"
|
||||||
ErrTerminalAIAccountInUse: "该模型账号正被终端 AI 使用,请在终端 AI 设置中更换账号或关闭终端 AI 后重试"
|
ErrTerminalAIAccountInUse: "该模型账号正被终端 AI 使用,请在终端 AI 设置中更换账号或关闭终端 AI 后重试"
|
||||||
|
ErrFileAIAccountInUse: "该模型账号正被文件管理 AI 使用,请在文件管理 AI 设置中更换账号或关闭文件管理 AI 后重试"
|
||||||
ErrAgentAccountUnavailable: "账号连接信息不可用: {{ .err }}"
|
ErrAgentAccountUnavailable: "账号连接信息不可用: {{ .err }}"
|
||||||
ErrAgentProviderNotSupported: "不支持该智能体提供商"
|
ErrAgentProviderNotSupported: "不支持该智能体提供商"
|
||||||
ErrAgentAccountRequired: "请选择智能体账号后重试"
|
ErrAgentAccountRequired: "请选择智能体账号后重试"
|
||||||
@@ -70,6 +79,7 @@ ErrAgentAccountNotVerified: "账号未通过验证"
|
|||||||
ErrAgentAccountModelsRequired: "请至少配置一个模型"
|
ErrAgentAccountModelsRequired: "请至少配置一个模型"
|
||||||
ErrAgentAccountSingleInitialModel: "创建账号时只能配置一个初始模型"
|
ErrAgentAccountSingleInitialModel: "创建账号时只能配置一个初始模型"
|
||||||
ErrAgentModelNotInAccount: "所选模型未配置在当前账号中"
|
ErrAgentModelNotInAccount: "所选模型未配置在当前账号中"
|
||||||
|
ErrAgentVerifyModelInUse: "该模型正用于验证账号,请先选择其他验证模型后再删除"
|
||||||
ErrAgentFallbackModelDuplicate: "备用模型不能重复"
|
ErrAgentFallbackModelDuplicate: "备用模型不能重复"
|
||||||
ErrAgentFallbackModelPrimary: "备用模型不能与主模型相同"
|
ErrAgentFallbackModelPrimary: "备用模型不能与主模型相同"
|
||||||
ErrAgentModelInUse: "仍有智能体正在使用该模型: {{ .name }}"
|
ErrAgentModelInUse: "仍有智能体正在使用该模型: {{ .name }}"
|
||||||
@@ -101,6 +111,7 @@ ErrAppLimit: "应用安装数量超限"
|
|||||||
ErrNotInstall: "应用未安装"
|
ErrNotInstall: "应用未安装"
|
||||||
ErrPortInOtherApp: "{{ .port }} 端口已被应用 {{ .apps }} 占用!"
|
ErrPortInOtherApp: "{{ .port }} 端口已被应用 {{ .apps }} 占用!"
|
||||||
ErrDbUserNotValid: "存量数据库用户名或密码不匹配"
|
ErrDbUserNotValid: "存量数据库用户名或密码不匹配"
|
||||||
|
ErrMysqlUserUsedByApps: "该数据库用户已被【{{ .detail }}】应用使用,无法修改 Host、删除或者撤销授权"
|
||||||
ErrUpdateBuWebsite: '应用更新成功,但网站配置文件修改失败,请检查配置'
|
ErrUpdateBuWebsite: '应用更新成功,但网站配置文件修改失败,请检查配置'
|
||||||
Err1PanelNetworkFailed: '默认容器网络创建失败!{{ .detail }}'
|
Err1PanelNetworkFailed: '默认容器网络创建失败!{{ .detail }}'
|
||||||
ErrFileParse: '应用 docker-compose 解析失败'
|
ErrFileParse: '应用 docker-compose 解析失败'
|
||||||
@@ -121,6 +132,7 @@ ErrAppNameExist: '应用名称已存在'
|
|||||||
ErrAppInstallNameExist: '应用安装名称 {{ .name }} 已存在'
|
ErrAppInstallNameExist: '应用安装名称 {{ .name }} 已存在'
|
||||||
AppStoreIsSyncing: '应用商店同步中,请稍后重试'
|
AppStoreIsSyncing: '应用商店同步中,请稍后重试'
|
||||||
ErrGetCompose: "docker-compose.yml 文件获取失败!{{ .detail }}"
|
ErrGetCompose: "docker-compose.yml 文件获取失败!{{ .detail }}"
|
||||||
|
ErrAppVersionUnavailable: "当前应用版本已从远程服务下架,请选择其他版本后重试"
|
||||||
ErrAppWarn: "状态异常,请查看日志"
|
ErrAppWarn: "状态异常,请查看日志"
|
||||||
ErrAppParamKey: "参数 {{ .name }} 字段异常"
|
ErrAppParamKey: "参数 {{ .name }} 字段异常"
|
||||||
ErrAppUpgrade: "应用 {{ .name }} 升级失败 {{ .err }}"
|
ErrAppUpgrade: "应用 {{ .name }} 升级失败 {{ .err }}"
|
||||||
@@ -372,6 +384,9 @@ ErrParseIP: "IP 格式错误"
|
|||||||
ErrDefaultIP: "default 为保留名称,请更换其他名称"
|
ErrDefaultIP: "default 为保留名称,请更换其他名称"
|
||||||
ErrGroupInUse: "IP 组被黑/白名单使用,无法删除"
|
ErrGroupInUse: "IP 组被黑/白名单使用,无法删除"
|
||||||
ErrIPGroupAclUse: "IP 组被网站 {{ .name }} 自定义规则使用,无法删除"
|
ErrIPGroupAclUse: "IP 组被网站 {{ .name }} 自定义规则使用,无法删除"
|
||||||
|
ErrIPGroupOriginProtectionUse: "IP 组被网站 {{ .name }} 的 CDN 源站保护使用,无法删除"
|
||||||
|
ErrOriginProtectionIPGroupRequired: "开启源站保护时必须选择至少一个非空 IP 组"
|
||||||
|
ErrOriginProtectionIPGroupInvalid: "IP 组 {{ .name }} 不存在或为空"
|
||||||
ErrGroupExist: "IP 组名称已存在"
|
ErrGroupExist: "IP 组名称已存在"
|
||||||
ErrIPRange: "IP 范围错误"
|
ErrIPRange: "IP 范围错误"
|
||||||
ErrIPExist: "IP 已存在"
|
ErrIPExist: "IP 已存在"
|
||||||
@@ -550,6 +565,20 @@ ContainerRecreate: "容器更新失败,现在开始恢复原容器"
|
|||||||
ContainerCreate: "创建新容器 {{ .name }}"
|
ContainerCreate: "创建新容器 {{ .name }}"
|
||||||
ContainerCreateFailed: "容器创建失败,删除失败容器"
|
ContainerCreateFailed: "容器创建失败,删除失败容器"
|
||||||
ContainerStartCheck: "检查容器是否已启动"
|
ContainerStartCheck: "检查容器是否已启动"
|
||||||
|
ContainerAcquireLock: "等待容器 {{ .name }} 操作锁"
|
||||||
|
ContainerUpgradeItem: "升级容器 {{ .name }}"
|
||||||
|
ContainerUpgradeSummary: "汇总容器升级结果"
|
||||||
|
ContainerCleanupWarning: "容器更新成功,但备份容器 {{ .name }} 删除失败。请确认新容器运行正常后手动删除该备份容器。错误:{{ .err }}"
|
||||||
|
ContainerStopOld: "停止原容器 {{ .name }}"
|
||||||
|
ContainerRenameOld: "重命名原容器 {{ .name }} 以便回滚"
|
||||||
|
ContainerDisconnectOld: "释放原容器 {{ .name }} 的网络地址"
|
||||||
|
ContainerCreateReplacement: "创建替换容器 {{ .name }}"
|
||||||
|
ContainerStartReplacement: "启动替换容器 {{ .name }}"
|
||||||
|
ContainerWaitReplacement: "检查替换容器 {{ .name }} 是否就绪"
|
||||||
|
ContainerRollbackRestartOld: "重新启动原容器 {{ .name }}"
|
||||||
|
ContainerRollbackRemoveReplacement: "删除启动失败的替换容器 {{ .name }}"
|
||||||
|
ContainerRollbackRenameOld: "恢复原容器 {{ .name }} 的名称"
|
||||||
|
ContainerRollbackReconnectOld: "恢复原容器 {{ .name }} 的网络"
|
||||||
|
|
||||||
# 任务 - 镜像
|
# 任务 - 镜像
|
||||||
ImageBuild: "镜像构建"
|
ImageBuild: "镜像构建"
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ func InitAgentDB() {
|
|||||||
migrations.NormalizeOllamaAccountAPIType,
|
migrations.NormalizeOllamaAccountAPIType,
|
||||||
migrations.InitAgentAccountModelPool,
|
migrations.InitAgentAccountModelPool,
|
||||||
migrations.AddAgentAccountMasterID,
|
migrations.AddAgentAccountMasterID,
|
||||||
|
migrations.NormalizeAgentAccountModelIDs,
|
||||||
|
migrations.AddAgentAccountVerifyModel,
|
||||||
|
migrations.AddAgentAccountAuthMode,
|
||||||
migrations.AddHostTable,
|
migrations.AddHostTable,
|
||||||
migrations.AddAITerminalSettings,
|
migrations.AddAITerminalSettings,
|
||||||
migrations.UpdateAgentQuickJumpTitle,
|
migrations.UpdateAgentQuickJumpTitle,
|
||||||
@@ -87,7 +90,9 @@ func InitAgentDB() {
|
|||||||
migrations.AddFileShareTable,
|
migrations.AddFileShareTable,
|
||||||
migrations.AddFileHistoryTable,
|
migrations.AddFileHistoryTable,
|
||||||
migrations.MigrateLegoV5,
|
migrations.MigrateLegoV5,
|
||||||
|
migrations.AddMcpServerGatewayArgs,
|
||||||
migrations.InitFirewallPortWhiteList,
|
migrations.InitFirewallPortWhiteList,
|
||||||
|
migrations.AddDatabaseUserTable,
|
||||||
})
|
})
|
||||||
if err := m.Migrate(); err != nil {
|
if err := m.Migrate(); err != nil {
|
||||||
global.LOG.Error(err)
|
global.LOG.Error(err)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"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/request"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/service"
|
"github.com/1Panel-dev/1Panel/agent/app/service"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
@@ -49,6 +50,8 @@ var AddTable = &gormigrate.Migration{
|
|||||||
&model.Cronjob{},
|
&model.Cronjob{},
|
||||||
&model.Database{},
|
&model.Database{},
|
||||||
&model.DatabaseMysql{},
|
&model.DatabaseMysql{},
|
||||||
|
&model.DatabaseUser{},
|
||||||
|
&model.DatabaseUserGrant{},
|
||||||
&model.DatabaseMongodb{},
|
&model.DatabaseMongodb{},
|
||||||
&model.DatabasePostgresql{},
|
&model.DatabasePostgresql{},
|
||||||
&model.Favorite{},
|
&model.Favorite{},
|
||||||
@@ -1211,6 +1214,57 @@ var AddAgentAccountMasterID = &gormigrate.Migration{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var NormalizeAgentAccountModelIDs = &gormigrate.Migration{
|
||||||
|
ID: "20260716-normalize-agent-account-model-ids",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
return migrationutils.NormalizeAgentAccountModelIDs(tx)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var AddAgentAccountVerifyModel = &gormigrate.Migration{
|
||||||
|
ID: "20260716-add-agent-account-verify-model",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
if err := tx.AutoMigrate(&model.AgentAccount{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var accounts []model.AgentAccount
|
||||||
|
if err := tx.Where("verify_model = '' OR verify_model IS NULL").Find(&accounts).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, account := range accounts {
|
||||||
|
var accountModel model.AgentAccountModel
|
||||||
|
err := tx.Where("account_id = ?", account.ID).Order("sort_order ASC, id ASC").First(&accountModel).Error
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Model(&model.AgentAccount{}).Where("id = ?", account.ID).Update("verify_model", accountModel.Model).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var AddAgentAccountAuthMode = &gormigrate.Migration{
|
||||||
|
ID: "20260716-add-agent-account-auth-mode",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
if err := tx.AutoMigrate(&model.AgentAccount{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Model(&model.AgentAccount{}).
|
||||||
|
Where("api_type = ? AND (auth_mode IS NULL OR auth_mode = '') AND provider IN ?", "anthropic-messages", []string{"bailian-coding-plan", "ark-coding-plan", "xiaomi"}).
|
||||||
|
Update("auth_mode", providercatalog.AuthModeBearer).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Model(&model.AgentAccount{}).
|
||||||
|
Where("api_type = ? AND (auth_mode IS NULL OR auth_mode = '')", "anthropic-messages").
|
||||||
|
Update("auth_mode", providercatalog.AuthModeXAPIKey).Error
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var AddHostTable = &gormigrate.Migration{
|
var AddHostTable = &gormigrate.Migration{
|
||||||
ID: "20260318-add-host-table",
|
ID: "20260318-add-host-table",
|
||||||
Migrate: func(tx *gorm.DB) error {
|
Migrate: func(tx *gorm.DB) error {
|
||||||
@@ -1390,7 +1444,7 @@ var AddAgentWebsiteBinding = &gormigrate.Migration{
|
|||||||
if err := tx.Where("type = ? AND app_install_id > 0", constant.Deployment).Find(&websites).Error; err != nil {
|
if err := tx.Where("type = ? AND app_install_id > 0", constant.Deployment).Find(&websites).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
websiteMap := service.UniqueDeploymentWebsiteMapForMigration(websites)
|
websiteMap := service.UniqueDeploymentWebsiteMapByAppInstall(websites)
|
||||||
for _, agent := range agents {
|
for _, agent := range agents {
|
||||||
if agent.WebsiteID != 0 || agent.AppInstallID == 0 {
|
if agent.WebsiteID != 0 || agent.AppInstallID == 0 {
|
||||||
continue
|
continue
|
||||||
@@ -1464,6 +1518,105 @@ var AddFileHistoryTable = &gormigrate.Migration{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadDatabaseUserTypesForMigration(tx *gorm.DB, mysqlName string) []string {
|
||||||
|
var database model.Database
|
||||||
|
if err := tx.Where("name = ?", mysqlName).First(&database).Error; err == nil && len(database.Type) != 0 {
|
||||||
|
return []string{database.Type}
|
||||||
|
}
|
||||||
|
|
||||||
|
var appKey string
|
||||||
|
_ = tx.Table("app_installs").
|
||||||
|
Select("apps.`key`").
|
||||||
|
Joins("JOIN apps ON apps.id = app_installs.app_id").
|
||||||
|
Where("app_installs.name = ?", mysqlName).
|
||||||
|
Scan(&appKey).Error
|
||||||
|
switch appKey {
|
||||||
|
case constant.AppMariaDB, constant.AppMysqlCluster:
|
||||||
|
return []string{appKey}
|
||||||
|
case constant.AppMysql:
|
||||||
|
return []string{constant.AppMysql}
|
||||||
|
default:
|
||||||
|
return []string{constant.AppMysql, constant.AppMariaDB}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeDatabaseUserHostsForMigration(permission string) []string {
|
||||||
|
hostSet := make(map[string]struct{})
|
||||||
|
for _, host := range strings.Split(permission, ",") {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
if len(host) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hostSet[host] = struct{}{}
|
||||||
|
}
|
||||||
|
if len(hostSet) == 0 {
|
||||||
|
return []string{"%"}
|
||||||
|
}
|
||||||
|
hosts := make([]string, 0, len(hostSet))
|
||||||
|
for host := range hostSet {
|
||||||
|
hosts = append(hosts, host)
|
||||||
|
}
|
||||||
|
return hosts
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDatabaseSystemUserForMigration(username string) bool {
|
||||||
|
switch strings.ToLower(username) {
|
||||||
|
case "root",
|
||||||
|
"mysql.session", "mysql.sys", "mysql.infoschema", "mysqlxsys",
|
||||||
|
"mariadb.sys", "mariadb-sys",
|
||||||
|
"debian-sys-maint":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var AddDatabaseUserTable = &gormigrate.Migration{
|
||||||
|
ID: "20260703-add-database-user-table",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
if err := tx.AutoMigrate(&model.DatabaseUser{}, &model.DatabaseUserGrant{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var mysqls []model.DatabaseMysql
|
||||||
|
if err := tx.Find(&mysqls).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, item := range mysqls {
|
||||||
|
if len(item.Username) == 0 || isDatabaseSystemUserForMigration(item.Username) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, dbType := range loadDatabaseUserTypesForMigration(tx, item.MysqlName) {
|
||||||
|
if len(dbType) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, host := range normalizeDatabaseUserHostsForMigration(item.Permission) {
|
||||||
|
user := model.DatabaseUser{
|
||||||
|
Type: dbType,
|
||||||
|
Database: item.MysqlName,
|
||||||
|
Username: item.Username,
|
||||||
|
Host: host,
|
||||||
|
Password: item.Password,
|
||||||
|
}
|
||||||
|
if err := tx.Where("`type` = ? AND database = ? AND username = ? AND host = ?", dbType, item.MysqlName, item.Username, host).FirstOrCreate(&user).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
grant := model.DatabaseUserGrant{
|
||||||
|
Type: dbType,
|
||||||
|
Database: item.MysqlName,
|
||||||
|
DBName: item.Name,
|
||||||
|
Username: item.Username,
|
||||||
|
Host: host,
|
||||||
|
}
|
||||||
|
if err := tx.Where("`type` = ? AND database = ? AND db_name = ? AND username = ? AND host = ?", dbType, item.MysqlName, item.Name, item.Username, host).FirstOrCreate(&grant).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// MigrateLegoV5 normalizes data persisted under lego v4 so that lego v5 can read it.
|
// MigrateLegoV5 normalizes data persisted under lego v4 so that lego v5 can read it.
|
||||||
//
|
//
|
||||||
// Two things changed in lego v5 that affect existing rows:
|
// Two things changed in lego v5 that affect existing rows:
|
||||||
@@ -1516,3 +1669,10 @@ var MigrateLegoV5 = &gormigrate.Migration{
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var AddMcpServerGatewayArgs = &gormigrate.Migration{
|
||||||
|
ID: "20260714-add-mcp-server-gateway-args",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
return tx.AutoMigrate(&model.McpServer{})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,19 +39,11 @@ func MigrateAgentAccountModelPool(tx *gorm.DB) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for index, item := range models {
|
for index, item := range models {
|
||||||
inputPayload, err := json.Marshal(item.Input)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
record := &model.AgentAccountModel{
|
record := &model.AgentAccountModel{
|
||||||
AccountID: account.ID,
|
AccountID: account.ID,
|
||||||
Model: item.ID,
|
Model: item.ID,
|
||||||
Name: item.Name,
|
Name: item.Name,
|
||||||
ContextWindow: item.ContextWindow,
|
SortOrder: index + 1,
|
||||||
MaxTokens: item.MaxTokens,
|
|
||||||
Reasoning: item.Reasoning,
|
|
||||||
Input: string(inputPayload),
|
|
||||||
SortOrder: index + 1,
|
|
||||||
}
|
}
|
||||||
if err := tx.Create(record).Error; err != nil {
|
if err := tx.Create(record).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -108,27 +100,47 @@ func buildMigratedAgentAccountModels(tx *gorm.DB, account *legacyAgentAccountMod
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
applyLegacyAgentAccountModelDefaults(account.Provider, models)
|
|
||||||
return models, nil
|
return models, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyLegacyAgentAccountModelDefaults(provider string, models []dto.AgentAccountModel) {
|
func NormalizeAgentAccountModelIDs(tx *gorm.DB) error {
|
||||||
if provider != "custom" && provider != "ollama" && provider != "vllm" {
|
var accounts []model.AgentAccount
|
||||||
return
|
if err := tx.Find(&accounts).Error; err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
meta, ok := providercatalog.Get(provider)
|
for _, account := range accounts {
|
||||||
if !ok {
|
var accountModels []model.AgentAccountModel
|
||||||
return
|
if err := tx.Where("account_id = ?", account.ID).Find(&accountModels).Error; err != nil {
|
||||||
}
|
return err
|
||||||
for i := range models {
|
|
||||||
if strings.TrimSpace(models[i].Name) == "" {
|
|
||||||
models[i].Name = strings.TrimSpace(models[i].ID)
|
|
||||||
}
|
}
|
||||||
if models[i].ContextWindow <= 0 {
|
for _, item := range accountModels {
|
||||||
models[i].ContextWindow = meta.Default.ContextWindow
|
normalized := providercatalog.NormalizeModelID(account.Provider, item.Model)
|
||||||
|
if normalized != item.Model {
|
||||||
|
if err := tx.Model(&model.AgentAccountModel{}).Where("id = ?", item.ID).Update("model", normalized).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if models[i].MaxTokens <= 0 {
|
|
||||||
models[i].MaxTokens = meta.Default.MaxTokens
|
var agents []model.Agent
|
||||||
|
if err := tx.Where("account_id = ?", account.ID).Find(&agents).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, agent := range agents {
|
||||||
|
normalized := providercatalog.NormalizeModelID(account.Provider, agent.Model)
|
||||||
|
if normalized != agent.Model {
|
||||||
|
if err := tx.Model(&model.Agent{}).Where("id = ?", agent.ID).Update("model", normalized).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := tx.Model(&model.AgentAccount{}).
|
||||||
|
Where("provider = ? AND api_type = ? AND (base_url = '' OR base_url = ? OR base_url = ?)", "deepseek", "anthropic-messages", "https://api.deepseek.com", "https://api.deepseek.com/v1").
|
||||||
|
Update("base_url", "https://api.deepseek.com/anthropic").Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Model(&model.AgentAccount{}).
|
||||||
|
Where("provider = ?", "gemini").
|
||||||
|
Update("api_type", "gemini-generate-content").Error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,5 +24,6 @@ func commonGroups() []CommonRouter {
|
|||||||
&AIToolsRouter{},
|
&AIToolsRouter{},
|
||||||
&GroupRouter{},
|
&GroupRouter{},
|
||||||
&AlertRouter{},
|
&AlertRouter{},
|
||||||
|
&RuntimeDiagnosticsRouter{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
aiToolsRouter.POST("/accounts/search", baseApi.PageAgentAccounts)
|
aiToolsRouter.POST("/accounts/search", baseApi.PageAgentAccounts)
|
||||||
aiToolsRouter.POST("/accounts/counts", baseApi.CountAgentAccountsByProviders)
|
aiToolsRouter.POST("/accounts/counts", baseApi.CountAgentAccountsByProviders)
|
||||||
aiToolsRouter.POST("/accounts/models", baseApi.GetAgentAccountModels)
|
aiToolsRouter.POST("/accounts/models", baseApi.GetAgentAccountModels)
|
||||||
|
aiToolsRouter.POST("/accounts/models/discover", baseApi.DiscoverAgentAccountModels)
|
||||||
aiToolsRouter.POST("/accounts/models/create", baseApi.CreateAgentAccountModel)
|
aiToolsRouter.POST("/accounts/models/create", baseApi.CreateAgentAccountModel)
|
||||||
aiToolsRouter.POST("/accounts/models/update", baseApi.UpdateAgentAccountModel)
|
aiToolsRouter.POST("/accounts/models/update", baseApi.UpdateAgentAccountModel)
|
||||||
aiToolsRouter.POST("/accounts/models/delete", baseApi.DeleteAgentAccountModel)
|
aiToolsRouter.POST("/accounts/models/delete", baseApi.DeleteAgentAccountModel)
|
||||||
|
|||||||
@@ -16,7 +16,16 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
cmdRouter.POST("/common/update/conf", baseApi.UpdateDBConfByFile)
|
cmdRouter.POST("/common/update/conf", baseApi.UpdateDBConfByFile)
|
||||||
|
|
||||||
cmdRouter.POST("", baseApi.CreateMysql)
|
cmdRouter.POST("", baseApi.CreateMysql)
|
||||||
cmdRouter.POST("/bind", baseApi.BindUser)
|
cmdRouter.POST("/users/search", baseApi.ListMysqlUsers)
|
||||||
|
cmdRouter.POST("/users", baseApi.CreateMysqlUser)
|
||||||
|
cmdRouter.POST("/users/update", baseApi.UpdateMysqlUser)
|
||||||
|
cmdRouter.POST("/users/password", baseApi.ChangeMysqlUserPassword)
|
||||||
|
cmdRouter.POST("/users/password/save", baseApi.SaveMysqlUserPassword)
|
||||||
|
cmdRouter.POST("/users/del", baseApi.DeleteMysqlUser)
|
||||||
|
cmdRouter.POST("/grants/search", baseApi.ListMysqlGrants)
|
||||||
|
cmdRouter.POST("/grants/summary", baseApi.ListMysqlGrantSummary)
|
||||||
|
cmdRouter.POST("/grants", baseApi.GrantMysqlUser)
|
||||||
|
cmdRouter.POST("/grants/del", baseApi.RevokeMysqlGrant)
|
||||||
cmdRouter.POST("load", baseApi.LoadDBFromRemote)
|
cmdRouter.POST("load", baseApi.LoadDBFromRemote)
|
||||||
cmdRouter.POST("/change/access", baseApi.ChangeMysqlAccess)
|
cmdRouter.POST("/change/access", baseApi.ChangeMysqlAccess)
|
||||||
cmdRouter.POST("/change/password", baseApi.ChangeMysqlPassword)
|
cmdRouter.POST("/change/password", baseApi.ChangeMysqlPassword)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ func (f *FileRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
fileRouter.POST("/batch/check", baseApi.BatchCheckFiles)
|
fileRouter.POST("/batch/check", baseApi.BatchCheckFiles)
|
||||||
fileRouter.POST("/upload", baseApi.UploadFiles)
|
fileRouter.POST("/upload", baseApi.UploadFiles)
|
||||||
fileRouter.POST("/chunkupload", baseApi.UploadChunkFiles)
|
fileRouter.POST("/chunkupload", baseApi.UploadChunkFiles)
|
||||||
|
fileRouter.POST("/chunkupload/stop", baseApi.StopChunkUpload)
|
||||||
fileRouter.POST("/rename", baseApi.ChangeFileName)
|
fileRouter.POST("/rename", baseApi.ChangeFileName)
|
||||||
fileRouter.POST("/wget", baseApi.WgetFile)
|
fileRouter.POST("/wget", baseApi.WgetFile)
|
||||||
fileRouter.POST("/wget/stop", baseApi.StopWget)
|
fileRouter.POST("/wget/stop", baseApi.StopWget)
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
hostRouter.POST("/ssh/update", baseApi.UpdateSSH)
|
hostRouter.POST("/ssh/update", baseApi.UpdateSSH)
|
||||||
hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs)
|
hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs)
|
||||||
hostRouter.POST("/ssh/log/export", baseApi.ExportSSHLogs)
|
hostRouter.POST("/ssh/log/export", baseApi.ExportSSHLogs)
|
||||||
|
hostRouter.POST("/ssh/log/clean", baseApi.CleanSSHLogs)
|
||||||
hostRouter.POST("/ssh/operate", baseApi.OperateSSH)
|
hostRouter.POST("/ssh/operate", baseApi.OperateSSH)
|
||||||
hostRouter.POST("/ssh/file", baseApi.LoadSSHFile)
|
hostRouter.POST("/ssh/file", baseApi.LoadSSHFile)
|
||||||
hostRouter.POST("/ssh/file/update", baseApi.UpdateSSHByFile)
|
hostRouter.POST("/ssh/file/update", baseApi.UpdateSSHByFile)
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ func (s *LogRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
baseApi := v2.ApiGroupApp.BaseApi
|
baseApi := v2.ApiGroupApp.BaseApi
|
||||||
{
|
{
|
||||||
operationRouter.GET("/system/files", baseApi.GetSystemFiles)
|
operationRouter.GET("/system/files", baseApi.GetSystemFiles)
|
||||||
|
operationRouter.GET("/system/status", baseApi.GetSystemLogStatus)
|
||||||
|
operationRouter.POST("/system/read", baseApi.ReadSystemLog)
|
||||||
|
operationRouter.GET("/system/services", baseApi.ListRunningServices)
|
||||||
operationRouter.POST("/tasks/search", baseApi.PageTasks)
|
operationRouter.POST("/tasks/search", baseApi.PageTasks)
|
||||||
operationRouter.POST("/tasks/read", baseApi.ReadTaskLogByLine)
|
operationRouter.POST("/tasks/read", baseApi.ReadTaskLogByLine)
|
||||||
operationRouter.GET("/tasks/executing/count", baseApi.CountExecutingTasks)
|
operationRouter.GET("/tasks/executing/count", baseApi.CountExecutingTasks)
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RuntimeDiagnosticsRouter struct{}
|
||||||
|
|
||||||
|
func (s *RuntimeDiagnosticsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||||
|
diagnosticsRouter := Router.Group("hosts/diagnostics")
|
||||||
|
baseApi := v2.ApiGroupApp.BaseApi
|
||||||
|
{
|
||||||
|
diagnosticsRouter.GET("/summary", baseApi.LoadRuntimeDiagnosticsSummary)
|
||||||
|
diagnosticsRouter.GET("/goroutines", baseApi.LoadRuntimeGoroutines)
|
||||||
|
diagnosticsRouter.POST("/profiles", baseApi.CreateRuntimeProfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -79,7 +80,7 @@ func (a aliClient) Delete(pathItem string) (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a aliClient) Upload(src, target string) (bool, error) {
|
func (a aliClient) Upload(ctx context.Context, src, target string) (bool, error) {
|
||||||
target = path.Join("/root", target)
|
target = path.Join("/root", target)
|
||||||
parentID := "root"
|
parentID := "root"
|
||||||
var err error
|
var err error
|
||||||
@@ -114,6 +115,7 @@ func (a aliClient) Upload(src, target string) (bool, error) {
|
|||||||
url := "https://api.alipan.com/v2/file/create"
|
url := "https://api.alipan.com/v2/file/create"
|
||||||
|
|
||||||
resp, err := client.R().
|
resp, err := client.R().
|
||||||
|
SetContext(ctx).
|
||||||
SetHeader("Authorization", a.token).
|
SetHeader("Authorization", a.token).
|
||||||
SetBody(data).
|
SetBody(data).
|
||||||
Post(url)
|
Post(url)
|
||||||
@@ -126,13 +128,13 @@ func (a aliClient) Upload(src, target string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
for _, part := range createResp.PartInfoList {
|
for _, part := range createResp.PartInfoList {
|
||||||
err = a.uploadPart(part.UploadURL, io.LimitReader(file, 1024*1024*1024))
|
err = a.uploadPart(ctx, part.UploadURL, io.LimitReader(file, 1024*1024*1024))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := a.completeUpload(createResp.UploadID, createResp.FileID); err != nil {
|
if err := a.completeUpload(ctx, createResp.UploadID, createResp.FileID); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
@@ -413,8 +415,8 @@ type createFileResp struct {
|
|||||||
PartInfoList []*partInfo `json:"part_info_list,omitempty"`
|
PartInfoList []*partInfo `json:"part_info_list,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a aliClient) uploadPart(uri string, reader io.Reader) error {
|
func (a aliClient) uploadPart(ctx context.Context, uri string, reader io.Reader) error {
|
||||||
req, err := http.NewRequest(http.MethodPut, uri, reader)
|
req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri, reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -469,7 +471,7 @@ func (a aliClient) handleDownload(uri string, target string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aliClient) completeUpload(uploadID, fileID string) error {
|
func (a *aliClient) completeUpload(ctx context.Context, uploadID, fileID string) error {
|
||||||
client := resty.New()
|
client := resty.New()
|
||||||
client.SetTLSClientConfig(&tls.Config{
|
client.SetTLSClientConfig(&tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
@@ -482,6 +484,7 @@ func (a *aliClient) completeUpload(uploadID, fileID string) error {
|
|||||||
|
|
||||||
url := "https://api.aliyundrive.com/v2/file/complete"
|
url := "https://api.aliyundrive.com/v2/file/complete"
|
||||||
resp, err := client.R().
|
resp, err := client.R().
|
||||||
|
SetContext(ctx).
|
||||||
SetHeader("Authorization", a.token).
|
SetHeader("Authorization", a.token).
|
||||||
SetBody(data).
|
SetBody(data).
|
||||||
Post(url)
|
Post(url)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user