mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 08:00:27 +00:00
Compare commits
149
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0be45b6a55 | ||
|
|
c922615183 | ||
|
|
8b2990fca4 | ||
|
|
37b5cd743a | ||
|
|
3b1324db08 | ||
|
|
f56bbb7dc1 | ||
|
|
cff649ae95 | ||
|
|
cb87ca4361 | ||
|
|
165d2b1290 | ||
|
|
189555e16c | ||
|
|
2d8250926f | ||
|
|
47e343712e | ||
|
|
11ffb4f2be | ||
|
|
e7670f96de | ||
|
|
342c04b30a | ||
|
|
5ab8377ac1 | ||
|
|
74eb2c8dd6 | ||
|
|
f136d97941 | ||
|
|
a5629e3f42 | ||
|
|
515d6ca500 | ||
|
|
c0ccc0318d | ||
|
|
8196c11010 | ||
|
|
170bd22062 | ||
|
|
097a33c68f | ||
|
|
3be3bf9926 | ||
|
|
038f2b4f54 | ||
|
|
f84e284c20 | ||
|
|
48e75b5916 | ||
|
|
a12ceaee6e | ||
|
|
9050680115 | ||
|
|
0022a9b508 | ||
|
|
3437d62f31 | ||
|
|
dc880835d0 | ||
|
|
1663bf8677 | ||
|
|
aed236cbc9 | ||
|
|
90be74504e | ||
|
|
bba8aab18c | ||
|
|
47d135ecca | ||
|
|
d96fe49993 | ||
|
|
ac396b61ce | ||
|
|
6798d0c7ee | ||
|
|
1490ed7541 | ||
|
|
48b3032de6 | ||
|
|
ed12bec460 | ||
|
|
3c4a1ea614 | ||
|
|
79eb0288b4 | ||
|
|
99775e1eb4 | ||
|
|
db848b41d2 | ||
|
|
b5b11dff86 | ||
|
|
9cf4bfc606 | ||
|
|
221162d44f | ||
|
|
617970a1f8 | ||
|
|
5610c73d67 | ||
|
|
51fe02661f | ||
|
|
8f491b2933 | ||
|
|
b7e16bd6d5 | ||
|
|
38ca2ddd01 | ||
|
|
402b41100b | ||
|
|
766beae4e1 | ||
|
|
05f72e9dcf | ||
|
|
23553c68ae | ||
|
|
4dd1eb7a1e | ||
|
|
db307732c6 | ||
|
|
33d4ade1d4 | ||
|
|
186075ff9a | ||
|
|
34df745d1c | ||
|
|
ec363ac157 | ||
|
|
193b0f3cd4 | ||
|
|
67c1bf2924 | ||
|
|
b1373ff77e | ||
|
|
55d0e27212 | ||
|
|
1c052b7c13 | ||
|
|
6c8d48c2dc | ||
|
|
b61dae638b | ||
|
|
872a93aa07 | ||
|
|
b1f337020c | ||
|
|
56858dd74c | ||
|
|
358415f737 | ||
|
|
5e73260103 | ||
|
|
e169f46784 | ||
|
|
39416abd47 | ||
|
|
640a0ca815 | ||
|
|
8e54cd5162 | ||
|
|
0f0099accb | ||
|
|
718ebeb203 | ||
|
|
4101bf691d | ||
|
|
a97deb3dca | ||
|
|
10203d5c0c | ||
|
|
a357d6697d | ||
|
|
555b0aa999 | ||
|
|
6dd0dbe2c3 | ||
|
|
c31bbae768 | ||
|
|
2bb0f15973 | ||
|
|
be229551e3 | ||
|
|
6283eb4c69 | ||
|
|
c329fb6599 | ||
|
|
c3685f6b74 | ||
|
|
f4f5a36475 | ||
|
|
ea1261b48e | ||
|
|
0c68f16a66 | ||
|
|
33e5c9230f | ||
|
|
96eefab36f | ||
|
|
b0b6bf458f | ||
|
|
f3d85d1309 | ||
|
|
39385ea0a0 | ||
|
|
e492c6573c | ||
|
|
10a7fb3be2 | ||
|
|
8bc7cfade3 | ||
|
|
ea044d6c32 | ||
|
|
f6ee3dc75b | ||
|
|
367a74503f | ||
|
|
53b1a7af4c | ||
|
|
078d7fbe1e | ||
|
|
c709e48ceb | ||
|
|
0741f60896 | ||
|
|
af8eef4a91 | ||
|
|
f233ef7069 | ||
|
|
1c199bd3d7 | ||
|
|
036c973fec | ||
|
|
8bb225d272 | ||
|
|
ab932ae154 | ||
|
|
89f969ec6e | ||
|
|
afa8cf22b1 | ||
|
|
67aa8471dc | ||
|
|
0617b81b3b | ||
|
|
98c535a12c | ||
|
|
ec46518936 | ||
|
|
a7b034387f | ||
|
|
7905dd00a6 | ||
|
|
74583aa1aa | ||
|
|
4be9d71edf | ||
|
|
57bfc38eb8 | ||
|
|
aaaa5980b5 | ||
|
|
4f57dfc76e | ||
|
|
2c31ffc66b | ||
|
|
b104a13b71 | ||
|
|
28633d2fef | ||
|
|
9052386c83 | ||
|
|
7bc45f2d77 | ||
|
|
2ef6ecb951 | ||
|
|
ca0dc71338 | ||
|
|
04a1ec9a9a | ||
|
|
999e5a779a | ||
|
|
99f62b7230 | ||
|
|
4dee4e1cc2 | ||
|
|
539addc656 | ||
|
|
1a35b307fa | ||
|
|
a04f4a8b96 | ||
|
|
6b88e1adf2 |
+1
-1
@@ -33,7 +33,7 @@ builds:
|
||||
- s390x
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
- formats: [ 'tar.gz' ]
|
||||
name_template: "1panel-v{{ .Version }}-{{ .Os }}-{{ .Arch }}{{- if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ PR are always welcome, even if they only contain small fixes like typos or a few
|
||||
|
||||
Please submit a PR broken down into small changes bit by bit. A PR consisting of a lot of features and code changes may be hard to review. It is recommended to submit PRs in an incremental fashion.
|
||||
|
||||
This [development guideline](https://docs.1panel.hk/dev_manual/dev_manual/) contains information about repository structure, how to set up development environment, how to run it, and more.
|
||||
This [development guideline](https://docs.1panel.pro/dev_manual/dev_manual/) contains information about repository structure, how to set up development environment, how to run it, and more.
|
||||
|
||||
Note: If you split your pull request to small changes, please make sure any of the changes goes to master will not break anything. Otherwise, it can not be merged until this feature complete.
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<p align="center"><a href="https://1panel.hk"><img src="https://resource.1panel.hk/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
|
||||
<p align="center"><b>Top-Rated Web-based Linux Server Management Tool</b></p>
|
||||
<p align="center"><a href="https://1panel.pro"><img src="https://resource.1panel.pro/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
|
||||
<p align="center"><b>Top-Rated Web-based Linux Server Management Tool</b><br>Best VPS control panel<br>新一代的 Linux 服务器运维管理面板</p>
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/2462" target="_blank"><img src="https://trendshift.io/api/badge/repositories/2462" alt="1Panel-dev%2F1Panel | Trendshift" style="width: 180px; height: auto;" /></a>
|
||||
<a href="https://www.producthunt.com/posts/1panel?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-1panel" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=639696&theme=light" alt="1Panel - Top-Rated web-based Linux server management tool | Product Hunt" style="width: 180px; height: auto;" /></a>
|
||||
<a href="https://trendshift.io/repositories/2462" target="_blank"><img src="https://trendshift.io/api/badge/repositories/2462" alt="1Panel-dev%2F1Panel | Trendshift" style="width: 240px; height: auto;" /></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://shields.io/github/license/1Panel-dev/1Panel?color=%231890FF" alt="License: GPL v3"></a>
|
||||
@@ -34,7 +33,7 @@
|
||||
|
||||
1Panel is an open-source, modern web-based control panel for Linux server management.
|
||||
|
||||
- **Efficient Management**: Through a user-friendly web graphical interface, 1Panel enables users to effortlessly manage their Linux servers. Key features include host monitoring, file management, database administration, and container management.
|
||||
- **Efficient Management**: Through a user-friendly web graphical interface, 1Panel enables users to effortlessly manage their Linux servers. Key features include host monitoring, file management, database administration, container management, LLMs management.
|
||||
- **Rapid Website Deployment**: With deep integration of the popular open-source website building software WordPress, 1Panel streamlines the process of domain binding and SSL certificate configuration, all achievable with just one click.
|
||||
- **Application Store**: 1Panel curates a wide range of high-quality open-source tools and applications, facilitating easy installation and updates for its users.
|
||||
- **Security and Reliability**: By leveraging containerization and secure application deployment practices, 1Panel minimizes vulnerability exposure. It further enhances security through integrated firewall management and log auditing capabilities.
|
||||
@@ -45,22 +44,30 @@
|
||||
Execute the script below and follow the prompts to install 1Panel:
|
||||
|
||||
```bash
|
||||
curl -sSL https://resource.1panel.hk/quick_start.sh -o quick_start.sh && bash quick_start.sh
|
||||
curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh
|
||||
```
|
||||
|
||||
Please refer to our [documentation](https://docs.1panel.hk/quick_start/) for more details.
|
||||
Please refer to our [documentation](https://docs.1panel.pro/quick_start/) for more details.
|
||||
|
||||
中国用户请使用这个 [安装脚本](https://1panel.cn/docs/installation/online_installation/),其应用数量比国际版本更丰富。
|
||||
|
||||
## Screenshot
|
||||
|
||||

|
||||

|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#1Panel-dev/1Panel&Date)
|
||||
|
||||
## Pro Edition
|
||||
|
||||
Compared to the OSS Edition, 1Panel Pro Edition provides users with a wealth of enhanced features and technical support services. Enhanced features include WAF enhancement, Website monitoring, Mobile APP, custom logo and theme, etc.
|
||||
|
||||
[Click to see Pro Edition details](https://1panel.pro/pricing).
|
||||
|
||||
## Security Information
|
||||
|
||||
If you discover any security issues, please contact us through: wanghe@fit2cloud.com.
|
||||
If you discover any security issues, please refer to [SECURITY.md](/SECURITY.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/xpu"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Create Ollama model
|
||||
// @Accept json
|
||||
// @Param request body dto.OllamaModelName true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/ollama/model [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 Ollama 模型 [name]","formatEN":"add Ollama model [name]"}
|
||||
func (b *BaseApi) CreateOllamaModel(c *gin.Context) {
|
||||
var req dto.OllamaModelName
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := AIToolService.Create(req.Name); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Rereate Ollama model
|
||||
// @Accept json
|
||||
// @Param request body dto.OllamaModelName true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/ollama/model/recreate [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 Ollama 模型重试 [name]","formatEN":"re-add Ollama model [name]"}
|
||||
func (b *BaseApi) RecreateOllamaModel(c *gin.Context) {
|
||||
var req dto.OllamaModelName
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := AIToolService.Recreate(req.Name); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Close Ollama model conn
|
||||
// @Accept json
|
||||
// @Param request body dto.OllamaModelName true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/ollama/model/close [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"关闭 Ollama 模型连接 [name]","formatEN":"close conn for Ollama model [name]"}
|
||||
func (b *BaseApi) CloseOllamaModel(c *gin.Context) {
|
||||
var req dto.OllamaModelName
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := AIToolService.Close(req.Name); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Sync Ollama model list
|
||||
// @Success 200 {array} dto.OllamaModelDropList
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/ollama/model/sync [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 Ollama 模型列表","formatEN":"sync Ollama model list"}
|
||||
func (b *BaseApi) SyncOllamaModel(c *gin.Context) {
|
||||
list, err := AIToolService.Sync()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Page Ollama models
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchWithPage true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/ollama/model/search [post]
|
||||
func (b *BaseApi) SearchOllamaModel(c *gin.Context) {
|
||||
var req dto.SearchWithPage
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := AIToolService.Search(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Page Ollama models
|
||||
// @Accept json
|
||||
// @Param request body dto.OllamaModelName true "request"
|
||||
// @Success 200 {string} details
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/ollama/model/load [post]
|
||||
func (b *BaseApi) LoadOllamaModelDetail(c *gin.Context) {
|
||||
var req dto.OllamaModelName
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
detail, err := AIToolService.LoadDetail(req.Name)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, detail)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Delete Ollama model
|
||||
// @Accept json
|
||||
// @Param request body dto.ForceDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/ollama/model/del [post]
|
||||
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"ollama_models","output_column":"name","output_value":"names"}],"formatZH":"删除 Ollama 模型 [names]","formatEN":"remove Ollama model [names]"}
|
||||
func (b *BaseApi) DeleteOllamaModel(c *gin.Context) {
|
||||
var req dto.ForceDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := AIToolService.Delete(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Load gpu / xpu info
|
||||
// @Accept json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/gpu/load [get]
|
||||
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
|
||||
ok, client := gpu.New()
|
||||
if ok {
|
||||
info, err := client.LoadGpuInfo()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
return
|
||||
}
|
||||
xpuOK, xpuClient := xpu.New()
|
||||
if xpuOK {
|
||||
info, err := xpuClient.LoadGpuInfo()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, &common.GpuInfo{})
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Bind domain
|
||||
// @Accept json
|
||||
// @Param request body dto.OllamaBindDomain true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/domain/bind [post]
|
||||
func (b *BaseApi) BindDomain(c *gin.Context) {
|
||||
var req dto.OllamaBindDomain
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := AIToolService.BindDomain(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get bind domain
|
||||
// @Accept json
|
||||
// @Param request body dto.OllamaBindDomainReq true "request"
|
||||
// @Success 200 {object} dto.OllamaBindDomainRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/domain/get [post]
|
||||
func (b *BaseApi) GetBindDomain(c *gin.Context) {
|
||||
var req dto.OllamaBindDomainReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := AIToolService.GetBindDomain(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// Tags AI
|
||||
// Summary Update bind domain
|
||||
// Accept json
|
||||
// Param request body dto.OllamaBindDomain true "request"
|
||||
// Success 200
|
||||
// Security ApiKeyAuth
|
||||
// Security Timestamp
|
||||
// Router /ai/domain/update [post]
|
||||
func (b *BaseApi) UpdateBindDomain(c *gin.Context) {
|
||||
var req dto.OllamaBindDomain
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := AIToolService.UpdateBindDomain(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func (b *BaseApi) SearchApp(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
list, err := appService.PageApp(req)
|
||||
list, err := appService.PageApp(c, req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
@@ -75,7 +75,7 @@ func (b *BaseApi) GetApp(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
appDTO, err := appService.GetApp(appKey)
|
||||
appDTO, err := appService.GetApp(c, appKey)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
@@ -173,7 +173,7 @@ func (b *BaseApi) InstallApp(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetAppTags(c *gin.Context) {
|
||||
tags, err := appService.GetAppTags()
|
||||
tags, err := appService.GetAppTags(c)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
|
||||
@@ -28,7 +28,10 @@ func (b *BaseApi) SearchAppInstalled(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, list)
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: int64(len(list)),
|
||||
})
|
||||
} else {
|
||||
total, list, err := appInstallService.Page(req)
|
||||
if err != nil {
|
||||
|
||||
@@ -166,7 +166,7 @@ func (b *BaseApi) SearchBackupRecords(c *gin.Context) {
|
||||
// @Summary Load backup records size
|
||||
// @Accept json
|
||||
// @Param request body dto.RecordSearch true "request"
|
||||
// @Success 200 {array} dto.dto.BackupFile
|
||||
// @Success 200 {array} dto.BackupFile
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/backup/record/size [post]
|
||||
@@ -215,7 +215,7 @@ func (b *BaseApi) SearchBackupRecordsByCronjob(c *gin.Context) {
|
||||
// @Summary Load backup records size for cronjob
|
||||
// @Accept json
|
||||
// @Param request body dto.RecordSearchByCronjob true "request"
|
||||
// @Success 200 {array} dto.dto.BackupFile
|
||||
// @Success 200 {array} dto.BackupFile
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/backup/record/size/bycronjob [post]
|
||||
|
||||
@@ -391,7 +391,7 @@ func (b *BaseApi) ContainerOperation(c *gin.Context) {
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Container stats
|
||||
// @Param id path integer true "container id"
|
||||
// @Param id path string true "container id"
|
||||
// @Success 200 {object} dto.ContainerStats
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
|
||||
@@ -15,6 +15,8 @@ var (
|
||||
appService = service.NewIAppService()
|
||||
appInstallService = service.NewIAppInstalledService()
|
||||
|
||||
AIToolService = service.NewIAIToolService()
|
||||
|
||||
containerService = service.NewIContainerService()
|
||||
composeTemplateService = service.NewIComposeTemplateService()
|
||||
imageRepoService = service.NewIImageRepoService()
|
||||
@@ -66,4 +68,6 @@ var (
|
||||
favoriteService = service.NewIFavoriteService()
|
||||
|
||||
websiteCAService = service.NewIWebsiteCAService()
|
||||
|
||||
mcpServerService = service.NewIMcpServerService()
|
||||
)
|
||||
|
||||
@@ -410,11 +410,37 @@ func (b *BaseApi) CheckFile(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err := os.Stat(req.Path); err != nil {
|
||||
helper.SuccessWithData(c, false)
|
||||
fileOp := files.NewFileOp()
|
||||
if fileOp.Stat(req.Path) {
|
||||
helper.SuccessWithData(c, true)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, true)
|
||||
if req.WithInit {
|
||||
if err := fileOp.CreateDir(req.Path, 0644); err != nil {
|
||||
helper.SuccessWithData(c, false)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, true)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, false)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Batch check file exist
|
||||
// @Accept json
|
||||
// @Param request body request.FilePathsCheck true "request"
|
||||
// @Success 200 {array} response.ExistFileInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/batch/check [post]
|
||||
func (b *BaseApi) BatchCheckFiles(c *gin.Context) {
|
||||
var req request.FilePathsCheck
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
fileList := fileService.BatchCheckFiles(req)
|
||||
helper.SuccessWithData(c, fileList)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// @Success 200 {object} response.HostToolRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool [post]
|
||||
// @Router /hosts/tool [post]
|
||||
func (b *BaseApi) GetToolStatus(c *gin.Context) {
|
||||
var req request.HostToolReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -36,7 +36,7 @@ func (b *BaseApi) GetToolStatus(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/create [post]
|
||||
// @Router /hosts/tool/create [post]
|
||||
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 [type] 配置","formatEN":"create [type] config"}
|
||||
func (b *BaseApi) InitToolConfig(c *gin.Context) {
|
||||
var req request.HostToolCreate
|
||||
@@ -58,7 +58,7 @@ func (b *BaseApi) InitToolConfig(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/operate [post]
|
||||
// @Router /hosts/tool/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operate","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] [type] ","formatEN":"[operate] [type]"}
|
||||
func (b *BaseApi) OperateTool(c *gin.Context) {
|
||||
var req request.HostToolReq
|
||||
@@ -80,7 +80,7 @@ func (b *BaseApi) OperateTool(c *gin.Context) {
|
||||
// @Success 200 {object} response.HostToolConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/config [post]
|
||||
// @Router /hosts/tool/config [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 主机工具配置文件 ","formatEN":"[operate] tool config"}
|
||||
func (b *BaseApi) OperateToolConfig(c *gin.Context) {
|
||||
var req request.HostToolConfig
|
||||
@@ -103,7 +103,7 @@ func (b *BaseApi) OperateToolConfig(c *gin.Context) {
|
||||
// @Success 200 {string} logContent
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/log [post]
|
||||
// @Router /hosts/tool/log [post]
|
||||
func (b *BaseApi) GetToolLog(c *gin.Context) {
|
||||
var req request.HostToolLogReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -125,7 +125,7 @@ func (b *BaseApi) GetToolLog(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/supervisor/process [post]
|
||||
// @Router /hosts/tool/supervisor/process [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 守护进程 ","formatEN":"[operate] process"}
|
||||
func (b *BaseApi) OperateProcess(c *gin.Context) {
|
||||
var req request.SupervisorProcessConfig
|
||||
@@ -147,7 +147,7 @@ func (b *BaseApi) OperateProcess(c *gin.Context) {
|
||||
// @Success 200 {object} response.SupervisorProcessConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/supervisor/process [get]
|
||||
// @Router /hosts/tool/supervisor/process [get]
|
||||
func (b *BaseApi) GetProcess(c *gin.Context) {
|
||||
configs, err := hostToolService.GetSupervisorProcessConfig()
|
||||
if err != nil {
|
||||
@@ -164,7 +164,7 @@ func (b *BaseApi) GetProcess(c *gin.Context) {
|
||||
// @Success 200 {string} content
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/supervisor/process/file [post]
|
||||
// @Router /hosts/tool/supervisor/process/file [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] Supervisor 进程文件 ","formatEN":"[operate] Supervisor Process Config file"}
|
||||
func (b *BaseApi) GetProcessFile(c *gin.Context) {
|
||||
var req request.SupervisorProcessFileReq
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary List mcp servers
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerSearch true "request"
|
||||
// @Success 200 {object} response.McpServersRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/search [post]
|
||||
func (b *BaseApi) PageMcpServers(c *gin.Context) {
|
||||
var req request.McpServerSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
list := mcpServerService.Page(req)
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Create mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server [post]
|
||||
func (b *BaseApi) CreateMcpServer(c *gin.Context) {
|
||||
var req request.McpServerCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.Create(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Update mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server/update [post]
|
||||
func (b *BaseApi) UpdateMcpServer(c *gin.Context) {
|
||||
var req request.McpServerUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.Update(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Delete mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server/del [post]
|
||||
func (b *BaseApi) DeleteMcpServer(c *gin.Context) {
|
||||
var req request.McpServerDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.Delete(req.ID)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Operate mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server/op [post]
|
||||
func (b *BaseApi) OperateMcpServer(c *gin.Context) {
|
||||
var req request.McpServerOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.Operate(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Bind Domain for mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpBindDomain true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/domain/bind [post]
|
||||
func (b *BaseApi) BindMcpDomain(c *gin.Context) {
|
||||
var req request.McpBindDomain
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.BindDomain(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Update bind Domain for mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpBindDomainUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/domain/update [post]
|
||||
func (b *BaseApi) UpdateMcpBindDomain(c *gin.Context) {
|
||||
var req request.McpBindDomainUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.UpdateBindDomain(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Get bin Domain for mcp server
|
||||
// @Accept json
|
||||
// @Success 200 {object} response.McpBindDomainRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/domain/get [get]
|
||||
func (b *BaseApi) GetMcpBindDomain(c *gin.Context) {
|
||||
res, err := mcpServerService.GetBindDomain()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// @Summary Page Extensions
|
||||
// @Accept json
|
||||
// @Param request body request.PHPExtensionsSearch true "request"
|
||||
// @Success 200 {array} response.PHPExtensionsDTO
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/php/extensions/search [post]
|
||||
@@ -27,7 +27,10 @@ func (b *BaseApi) PagePHPExtensions(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, list)
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Total: int64(len(list)),
|
||||
Items: list,
|
||||
})
|
||||
} else {
|
||||
total, list, err := phpExtensionsService.Page(req)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,8 +3,10 @@ package v1
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@@ -53,11 +55,21 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if req.Key == "SecurityEntrance" {
|
||||
if !checkEntrancePattern(req.Value) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("the format of the security entrance %s is incorrect.", req.Value))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := settingService.Update(req.Key, req.Value); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
if req.Key == "SecurityEntrance" {
|
||||
entranceValue := base64.StdEncoding.EncodeToString([]byte(req.Value))
|
||||
c.SetCookie("SecurityEntrance", entranceValue, 0, "", "", false, true)
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
@@ -391,3 +403,11 @@ func (b *BaseApi) UpdateApiConfig(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func checkEntrancePattern(val string) bool {
|
||||
if len(val) == 0 {
|
||||
return true
|
||||
}
|
||||
result, _ := regexp.MatchString("^[a-zA-Z0-9]{5,116}$", val)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// @Success 200 {object} dto.SSHInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/search [post]
|
||||
// @Router /hosts/ssh/search [post]
|
||||
func (b *BaseApi) GetSSHInfo(c *gin.Context) {
|
||||
info, err := sshService.GetSSHInfo()
|
||||
if err != nil {
|
||||
@@ -29,7 +29,7 @@ func (b *BaseApi) GetSSHInfo(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/operate [post]
|
||||
// @Router /hosts/ssh/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] SSH ","formatEN":"[operation] SSH"}
|
||||
func (b *BaseApi) OperateSSH(c *gin.Context) {
|
||||
var req dto.Operate
|
||||
@@ -51,7 +51,7 @@ func (b *BaseApi) OperateSSH(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/update [post]
|
||||
// @Router /hosts/ssh/update [post]
|
||||
// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置 [key] => [value]","formatEN":"update SSH setting [key] => [value]"}
|
||||
func (b *BaseApi) UpdateSSH(c *gin.Context) {
|
||||
var req dto.SSHUpdate
|
||||
@@ -73,7 +73,7 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/conffile/update [post]
|
||||
// @Router /hosts/conffile/update [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置文件","formatEN":"update SSH conf"}
|
||||
func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
|
||||
var req dto.SSHConf
|
||||
@@ -95,7 +95,7 @@ func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/generate [post]
|
||||
// @Router /hosts/ssh/generate [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"}
|
||||
func (b *BaseApi) GenerateSSH(c *gin.Context) {
|
||||
var req dto.GenerateSSH
|
||||
@@ -117,7 +117,7 @@ func (b *BaseApi) GenerateSSH(c *gin.Context) {
|
||||
// @Success 200 {string} data
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/secret [post]
|
||||
// @Router /hosts/ssh/secret [post]
|
||||
func (b *BaseApi) LoadSSHSecret(c *gin.Context) {
|
||||
var req dto.GenerateLoad
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -139,7 +139,7 @@ func (b *BaseApi) LoadSSHSecret(c *gin.Context) {
|
||||
// @Success 200 {object} dto.SSHLog
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/log [post]
|
||||
// @Router /hosts/ssh/log [post]
|
||||
func (b *BaseApi) LoadSSHLogs(c *gin.Context) {
|
||||
var req dto.SearchSSHLog
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -159,7 +159,7 @@ func (b *BaseApi) LoadSSHLogs(c *gin.Context) {
|
||||
// @Success 200 {string} data
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/conf [get]
|
||||
// @Router /hosts/ssh/conf [get]
|
||||
func (b *BaseApi) LoadSSHConf(c *gin.Context) {
|
||||
data, err := sshService.LoadSSHConf()
|
||||
if err != nil {
|
||||
|
||||
@@ -68,12 +68,11 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
|
||||
|
||||
<-quitChan
|
||||
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
dt := time.Now().Add(time.Second)
|
||||
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
|
||||
}
|
||||
|
||||
func (b *BaseApi) RedisWsSsh(c *gin.Context) {
|
||||
func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
@@ -95,38 +94,30 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
name := c.Query("name")
|
||||
from := c.Query("from")
|
||||
commands := []string{"redis-cli"}
|
||||
database, err := databaseService.Get(name)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) {
|
||||
return
|
||||
}
|
||||
if from == "local" {
|
||||
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"})
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) {
|
||||
source := c.Query("source")
|
||||
var containerID string
|
||||
var initCmd []string
|
||||
switch source {
|
||||
case "redis":
|
||||
containerID, initCmd, err = loadRedisInitCmd(c)
|
||||
case "ollama":
|
||||
containerID, initCmd, err = loadOllamaInitCmd(c)
|
||||
case "container":
|
||||
containerID, initCmd, err = loadContainerInitCmd(c)
|
||||
default:
|
||||
if wshandleError(wsConn, fmt.Errorf("not support such source %s", source)) {
|
||||
return
|
||||
}
|
||||
name = redisInfo.ContainerName
|
||||
if len(database.Password) != 0 {
|
||||
commands = []string{"redis-cli", "-a", database.Password, "--no-auth-warning"}
|
||||
}
|
||||
} else {
|
||||
itemPort := fmt.Sprintf("%v", database.Port)
|
||||
commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort}
|
||||
if len(database.Password) != 0 {
|
||||
commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort, "-a", database.Password, "--no-auth-warning"}
|
||||
}
|
||||
name = "1Panel-redis-cli-tools"
|
||||
}
|
||||
|
||||
pidMap := loadMapFromDockerTop(name)
|
||||
itemCmds := append([]string{"exec", "-it", name}, commands...)
|
||||
slave, err := terminal.NewCommand(itemCmds)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(name, strings.Join(commands, " "), pidMap)
|
||||
pidMap := loadMapFromDockerTop(containerID)
|
||||
slave, err := terminal.NewCommand(initCmd)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(containerID, strings.ReplaceAll(strings.Join(initCmd, " "), fmt.Sprintf("exec -it %s ", containerID), ""), pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
@@ -141,83 +132,68 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
dt := time.Now().Add(time.Second)
|
||||
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
|
||||
|
||||
}
|
||||
|
||||
func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
func loadRedisInitCmd(c *gin.Context) (string, []string, error) {
|
||||
name := c.Query("name")
|
||||
from := c.Query("from")
|
||||
commands := []string{"exec", "-it"}
|
||||
database, err := databaseService.Get(name)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return
|
||||
return "", nil, fmt.Errorf("no such database in db, err: %v", err)
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
if global.CONF.System.IsDemo {
|
||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||
return
|
||||
if from == "local" {
|
||||
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"})
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("no such app in db, err: %v", err)
|
||||
}
|
||||
name = redisInfo.ContainerName
|
||||
commands = append(commands, []string{name, "redis-cli"}...)
|
||||
if len(database.Password) != 0 {
|
||||
commands = append(commands, []string{"-a", database.Password, "--no-auth-warning"}...)
|
||||
}
|
||||
} else {
|
||||
name = "1Panel-redis-cli-tools"
|
||||
commands = append(commands, []string{name, "redis-cli", "-h", database.Address, "-p", fmt.Sprintf("%v", database.Port)}...)
|
||||
if len(database.Password) != 0 {
|
||||
commands = append(commands, []string{"-a", database.Password, "--no-auth-warning"}...)
|
||||
}
|
||||
}
|
||||
return name, commands, nil
|
||||
}
|
||||
|
||||
func loadOllamaInitCmd(c *gin.Context) (string, []string, error) {
|
||||
name := c.Query("name")
|
||||
if cmd.CheckIllegal(name) {
|
||||
return "", nil, fmt.Errorf("ollama model %s contains illegal characters", name)
|
||||
}
|
||||
ollamaInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: "", Type: "ollama"})
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("no such app in db, err: %v", err)
|
||||
}
|
||||
containerName := ollamaInfo.ContainerName
|
||||
return containerName, []string{"exec", "-it", containerName, "ollama", "run", name}, nil
|
||||
}
|
||||
|
||||
func loadContainerInitCmd(c *gin.Context) (string, []string, error) {
|
||||
containerID := c.Query("containerid")
|
||||
command := c.Query("command")
|
||||
user := c.Query("user")
|
||||
if len(command) == 0 || len(containerID) == 0 {
|
||||
if wshandleError(wsConn, errors.New("error param of command or containerID")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
|
||||
cmds := []string{"exec", containerID, command}
|
||||
if len(user) != 0 {
|
||||
cmds = []string{"exec", "-u", user, containerID, command}
|
||||
}
|
||||
if cmd.CheckIllegal(user, containerID, command) {
|
||||
if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) {
|
||||
return
|
||||
}
|
||||
return "", nil, fmt.Errorf("the command contains illegal characters. command: %s, user: %s, containerID: %s", command, user, containerID)
|
||||
}
|
||||
stdout, err := cmd.ExecWithCheck("docker", cmds...)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, stdout)) {
|
||||
return
|
||||
if len(command) == 0 || len(containerID) == 0 {
|
||||
return "", nil, fmt.Errorf("error param of command: %s or containerID: %s", command, containerID)
|
||||
}
|
||||
|
||||
commands := []string{"exec", "-it", containerID, command}
|
||||
if len(user) != 0 {
|
||||
commands = []string{"exec", "-it", "-u", user, containerID, command}
|
||||
}
|
||||
pidMap := loadMapFromDockerTop(containerID)
|
||||
slave, err := terminal.NewCommand(commands)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(containerID, command, pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, true)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
tty.Start(quitChan)
|
||||
go slave.Wait(quitChan)
|
||||
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
return containerID, commands, nil
|
||||
}
|
||||
|
||||
func wshandleError(ws *websocket.Conn, err error) bool {
|
||||
|
||||
@@ -571,6 +571,28 @@ func (b *BaseApi) GetProxyConfig(c *gin.Context) {
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Delete proxy conf
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteProxyDel true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/proxies/del [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"删除网站 [domain] 反向代理配置","formatEN":"Delete domain [domain] proxy config"}
|
||||
func (b *BaseApi) DeleteProxyConfig(c *gin.Context) {
|
||||
var req request.WebsiteProxyDel
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := websiteService.DeleteProxy(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Update proxy conf
|
||||
// @Accept json
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type OllamaModelInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Size string `json:"size"`
|
||||
From string `json:"from"`
|
||||
LogFileExist bool `json:"logFileExist"`
|
||||
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type OllamaModelDropList struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type OllamaModelName struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type OllamaBindDomain struct {
|
||||
Domain string `json:"domain" validate:"required"`
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
SSLID uint `json:"sslID"`
|
||||
WebsiteID uint `json:"websiteID"`
|
||||
IPList string `json:"ipList"`
|
||||
}
|
||||
|
||||
type OllamaBindDomainReq struct {
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
}
|
||||
|
||||
type OllamaBindDomainRes struct {
|
||||
Domain string `json:"domain"`
|
||||
SSLID uint `json:"sslID"`
|
||||
AcmeAccountID uint `json:"acmeAccountID"`
|
||||
AllowIPs []string `json:"allowIPs"`
|
||||
WebsiteID uint `json:"websiteID"`
|
||||
ConnUrl string `json:"connUrl"`
|
||||
}
|
||||
+21
-10
@@ -1,9 +1,5 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
)
|
||||
|
||||
type AppDatabase struct {
|
||||
ServiceName string `json:"PANEL_DB_HOST"`
|
||||
DbName string `json:"PANEL_DB_NAME"`
|
||||
@@ -62,7 +58,7 @@ type AppDefine struct {
|
||||
}
|
||||
|
||||
type LocalAppAppDefine struct {
|
||||
AppProperty model.App `json:"additionalProperties" yaml:"additionalProperties"`
|
||||
AppProperty AppProperty `json:"additionalProperties" yaml:"additionalProperties"`
|
||||
}
|
||||
|
||||
type LocalAppParam struct {
|
||||
@@ -82,8 +78,9 @@ type AppProperty struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Tags []string `json:"tags"`
|
||||
ShortDescZh string `json:"shortDescZh"`
|
||||
ShortDescEn string `json:"shortDescEn"`
|
||||
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh"`
|
||||
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn"`
|
||||
Description Locale `json:"description"`
|
||||
Key string `json:"key"`
|
||||
Required []string `json:"Required"`
|
||||
CrossVersionUpdate bool `json:"crossVersionUpdate"`
|
||||
@@ -93,6 +90,7 @@ type AppProperty struct {
|
||||
Github string `json:"github"`
|
||||
Document string `json:"document"`
|
||||
Version float64 `json:"version"`
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
}
|
||||
|
||||
type AppConfigVersion struct {
|
||||
@@ -104,9 +102,21 @@ type AppConfigVersion struct {
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
Sort int `json:"sort"`
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
Sort int `json:"sort"`
|
||||
Locales Locale `json:"locales"`
|
||||
}
|
||||
|
||||
type Locale struct {
|
||||
En string `json:"en"`
|
||||
Ja string `json:"ja"`
|
||||
Ms string `json:"ms"`
|
||||
PtBr string `json:"pt-br" yaml:"pt-br"`
|
||||
Ru string `json:"ru"`
|
||||
ZhHant string `json:"zh-hant" yaml:"zh-hant"`
|
||||
Zh string `json:"zh"`
|
||||
Ko string `json:"ko"`
|
||||
}
|
||||
|
||||
type AppForm struct {
|
||||
@@ -118,6 +128,7 @@ type AppFormFields struct {
|
||||
Type string `json:"type"`
|
||||
LabelZh string `json:"labelZh"`
|
||||
LabelEn string `json:"labelEn"`
|
||||
Label Locale `json:"label"`
|
||||
Required bool `json:"required"`
|
||||
Default interface{} `json:"default"`
|
||||
EnvKey string `json:"envKey"`
|
||||
|
||||
@@ -29,7 +29,7 @@ type Login struct {
|
||||
Captcha string `json:"captcha"`
|
||||
CaptchaID string `json:"captchaID"`
|
||||
AuthMethod string `json:"authMethod" validate:"required,oneof=jwt session"`
|
||||
Language string `json:"language" validate:"required,oneof=zh en tw ja ru ms 'pt-BR'"`
|
||||
Language string `json:"language" validate:"required,oneof=zh en tw ja ko ru ms 'pt-BR'"`
|
||||
}
|
||||
|
||||
type MFALogin struct {
|
||||
|
||||
@@ -52,3 +52,8 @@ type OperationWithNameAndType struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
}
|
||||
|
||||
type ForceDelete struct {
|
||||
IDs []uint `json:"ids"`
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
}
|
||||
|
||||
@@ -71,14 +71,16 @@ type CronjobDownload struct {
|
||||
}
|
||||
|
||||
type CronjobClean struct {
|
||||
IsDelete bool `json:"isDelete"`
|
||||
CleanData bool `json:"cleanData"`
|
||||
CronjobID uint `json:"cronjobID" validate:"required"`
|
||||
IsDelete bool `json:"isDelete"`
|
||||
CleanData bool `json:"cleanData"`
|
||||
CleanRemoteData bool `json:"cleanRemoteData"`
|
||||
CronjobID uint `json:"cronjobID" validate:"required"`
|
||||
}
|
||||
|
||||
type CronjobBatchDelete struct {
|
||||
CleanData bool `json:"cleanData"`
|
||||
IDs []uint `json:"ids" validate:"required"`
|
||||
CleanData bool `json:"cleanData"`
|
||||
CleanRemoteData bool `json:"cleanRemoteData"`
|
||||
IDs []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
|
||||
type CronjobInfo struct {
|
||||
|
||||
@@ -30,7 +30,8 @@ type PortRuleOperate struct {
|
||||
}
|
||||
|
||||
type ForwardRuleOperate struct {
|
||||
Rules []struct {
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
Rules []struct {
|
||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||
Num string `json:"num"`
|
||||
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package dto
|
||||
|
||||
type DockerComposeService struct {
|
||||
Image string `yaml:"image"`
|
||||
ContainerName string `yaml:"container_name"`
|
||||
Restart string `yaml:"restart"`
|
||||
Ports []string `yaml:"ports"`
|
||||
Environment []string `yaml:"environment"`
|
||||
Command []string `yaml:"command"`
|
||||
}
|
||||
@@ -33,6 +33,7 @@ type AppContainerConfig struct {
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
HostMode bool `json:"hostMode"`
|
||||
PullImage bool `json:"pullImage"`
|
||||
GpuConfig bool `json:"gpuConfig"`
|
||||
}
|
||||
|
||||
type AppInstalledSearch struct {
|
||||
|
||||
@@ -76,7 +76,12 @@ type FileRename struct {
|
||||
}
|
||||
|
||||
type FilePathCheck struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
WithInit bool `json:"withInit"`
|
||||
}
|
||||
|
||||
type FilePathsCheck struct {
|
||||
Paths []string `json:"paths" validate:"required"`
|
||||
}
|
||||
|
||||
type FileWget struct {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package request
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
|
||||
type McpServerSearch struct {
|
||||
dto.PageInfo
|
||||
Name string `json:"name"`
|
||||
Sync bool `json:"sync"`
|
||||
}
|
||||
|
||||
type McpServerCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Command string `json:"command" validate:"required"`
|
||||
Environments []Environment `json:"environments"`
|
||||
Volumes []Volume `json:"volumes"`
|
||||
Port int `json:"port" validate:"required"`
|
||||
ContainerName string `json:"containerName"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
SsePath string `json:"ssePath"`
|
||||
HostIP string `json:"hostIP"`
|
||||
}
|
||||
|
||||
type McpServerUpdate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
McpServerCreate
|
||||
}
|
||||
|
||||
type Environment struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Volume struct {
|
||||
Source string `json:"source"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
type McpServerDelete struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
type McpServerOperate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
}
|
||||
|
||||
type McpBindDomain struct {
|
||||
Domain string `json:"domain" validate:"required"`
|
||||
SSLID uint `json:"sslID"`
|
||||
IPList string `json:"ipList"`
|
||||
}
|
||||
|
||||
type McpBindDomainUpdate struct {
|
||||
WebsiteID uint `json:"websiteID" validate:"required"`
|
||||
SSLID uint `json:"sslID"`
|
||||
IPList string `json:"ipList"`
|
||||
}
|
||||
@@ -193,6 +193,11 @@ type WebsiteProxyConfig struct {
|
||||
ProxySSLName string `json:"proxySSLName"`
|
||||
}
|
||||
|
||||
type WebsiteProxyDel struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type WebsiteProxyReq struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -23,29 +23,31 @@ type AppUpdateRes struct {
|
||||
|
||||
type AppDTO struct {
|
||||
model.App
|
||||
Installed bool `json:"installed"`
|
||||
Versions []string `json:"versions"`
|
||||
Tags []model.Tag `json:"tags"`
|
||||
Installed bool `json:"installed"`
|
||||
Versions []string `json:"versions"`
|
||||
Tags []TagDTO `json:"tags"`
|
||||
}
|
||||
|
||||
type AppItem struct {
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
ID uint `json:"id"`
|
||||
ShortDescZh string `json:"shortDescZh"`
|
||||
ShortDescEn string `json:"shortDescEn"`
|
||||
Icon string `json:"icon"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Resource string `json:"resource"`
|
||||
Installed bool `json:"installed"`
|
||||
Versions []string `json:"versions"`
|
||||
Limit int `json:"limit"`
|
||||
Tags []model.Tag `json:"tags"`
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
ID uint `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Resource string `json:"resource"`
|
||||
Installed bool `json:"installed"`
|
||||
Versions []string `json:"versions"`
|
||||
Limit int `json:"limit"`
|
||||
Tags []TagDTO `json:"tags"`
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
}
|
||||
|
||||
type TagDTO struct {
|
||||
model.Tag
|
||||
ID uint `json:"id"`
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type AppInstalledCheck struct {
|
||||
@@ -65,10 +67,11 @@ type AppInstalledCheck struct {
|
||||
|
||||
type AppDetailDTO struct {
|
||||
model.AppDetail
|
||||
Enable bool `json:"enable"`
|
||||
Params interface{} `json:"params"`
|
||||
Image string `json:"image"`
|
||||
HostMode bool `json:"hostMode"`
|
||||
Enable bool `json:"enable"`
|
||||
Params interface{} `json:"params"`
|
||||
Image string `json:"image"`
|
||||
HostMode bool `json:"hostMode"`
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
}
|
||||
|
||||
type IgnoredApp struct {
|
||||
@@ -89,9 +92,10 @@ type AppInstalledDTO struct {
|
||||
}
|
||||
|
||||
type AppDetail struct {
|
||||
Website string `json:"website"`
|
||||
Document string `json:"document"`
|
||||
Github string `json:"github"`
|
||||
Website string `json:"website"`
|
||||
Document string `json:"document"`
|
||||
Github string `json:"github"`
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
}
|
||||
|
||||
type AppInstallDTO struct {
|
||||
|
||||
@@ -2,6 +2,7 @@ package response
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FileInfo struct {
|
||||
@@ -46,3 +47,10 @@ type FileLineContent struct {
|
||||
type FileExist struct {
|
||||
Exist bool `json:"exist"`
|
||||
}
|
||||
|
||||
type ExistFileInfo struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Size float64 `json:"size"`
|
||||
ModTime time.Time `json:"modTime"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
)
|
||||
|
||||
type McpServersRes struct {
|
||||
Items []McpServerDTO `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type McpServerDTO struct {
|
||||
model.McpServer
|
||||
Environments []request.Environment `json:"environments"`
|
||||
Volumes []request.Volume `json:"volumes"`
|
||||
}
|
||||
|
||||
type McpBindDomainRes struct {
|
||||
Domain string `json:"domain"`
|
||||
SSLID uint `json:"sslID"`
|
||||
AcmeAccountID uint `json:"acmeAccountID"`
|
||||
AllowIPs []string `json:"allowIPs"`
|
||||
WebsiteID uint `json:"websiteID"`
|
||||
ConnUrl string `json:"connUrl"`
|
||||
}
|
||||
@@ -71,6 +71,7 @@ type SettingInfo struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
IpWhiteList string `json:"ipWhiteList"`
|
||||
ApiKeyValidityTime string `json:"apiKeyValidityTime"`
|
||||
LicenseVerify string `json:"licenseVerify"`
|
||||
}
|
||||
|
||||
type SettingUpdate struct {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package model
|
||||
|
||||
type OllamaModel struct {
|
||||
BaseModel
|
||||
|
||||
Name string `json:"name"`
|
||||
Size string `json:"size"`
|
||||
From string `json:"from"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
@@ -12,10 +15,12 @@ type App struct {
|
||||
Key string `json:"key" gorm:"type:varchar(64);not null;"`
|
||||
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh" gorm:"type:longtext;"`
|
||||
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn" gorm:"type:longtext;"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon" gorm:"type:longtext;"`
|
||||
Type string `json:"type" gorm:"type:varchar(64);not null"`
|
||||
Status string `json:"status" gorm:"type:varchar(64);not null"`
|
||||
Required string `json:"required" gorm:"type:varchar(64);"`
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
CrossVersionUpdate bool `json:"crossVersionUpdate" yaml:"crossVersionUpdate"`
|
||||
Limit int `json:"limit" gorm:"type:Integer;not null"`
|
||||
Website string `json:"website" gorm:"type:varchar(64);not null"`
|
||||
@@ -36,8 +41,20 @@ func (i *App) IsLocalApp() bool {
|
||||
}
|
||||
func (i *App) GetAppResourcePath() string {
|
||||
if i.IsLocalApp() {
|
||||
//这里要去掉本地应用的local前缀
|
||||
return filepath.Join(constant.LocalAppResourceDir, strings.TrimPrefix(i.Key, "local"))
|
||||
}
|
||||
return filepath.Join(constant.RemoteAppResourceDir, i.Key)
|
||||
}
|
||||
|
||||
func (i *App) GetDescription(ctx *gin.Context) string {
|
||||
var translations = make(map[string]string)
|
||||
_ = json.Unmarshal([]byte(i.Description), &translations)
|
||||
lang := strings.ToLower(common.GetLang(ctx))
|
||||
if desc, ok := translations[lang]; ok && desc != "" {
|
||||
return desc
|
||||
}
|
||||
if lang == "zh" {
|
||||
return i.ShortDescZh
|
||||
}
|
||||
return i.ShortDescEn
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
type McpServer struct {
|
||||
BaseModel
|
||||
Name string `json:"name"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
Command string `json:"command"`
|
||||
ContainerName string `json:"containerName"`
|
||||
Message string `json:"message"`
|
||||
Port int `json:"port"`
|
||||
Status string `json:"status"`
|
||||
Env string `json:"env"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
SsePath string `json:"ssePath"`
|
||||
WebsiteID int `json:"websiteID"`
|
||||
Dir string `json:"dir"`
|
||||
HostIP string `json:"hostIP"`
|
||||
}
|
||||
@@ -2,7 +2,8 @@ package model
|
||||
|
||||
type Tag struct {
|
||||
BaseModel
|
||||
Key string `json:"key" gorm:"type:varchar(64);not null"`
|
||||
Name string `json:"name" gorm:"type:varchar(64);not null"`
|
||||
Sort int `json:"sort" gorm:"type:int;not null;default:1"`
|
||||
Key string `json:"key" gorm:"type:varchar(64);not null"`
|
||||
Name string `json:"name" gorm:"type:varchar(64);not null"`
|
||||
Translations string `json:"translations"`
|
||||
Sort int `json:"sort" gorm:"type:int;not null;default:1"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
type AiRepo struct{}
|
||||
|
||||
type IAiRepo interface {
|
||||
Get(opts ...DBOption) (model.OllamaModel, error)
|
||||
List(opts ...DBOption) ([]model.OllamaModel, error)
|
||||
Page(limit, offset int, opts ...DBOption) (int64, []model.OllamaModel, error)
|
||||
Create(cronjob *model.OllamaModel) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
}
|
||||
|
||||
func NewIAiRepo() IAiRepo {
|
||||
return &AiRepo{}
|
||||
}
|
||||
|
||||
func (u *AiRepo) Get(opts ...DBOption) (model.OllamaModel, error) {
|
||||
var item model.OllamaModel
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&item).Error
|
||||
return item, err
|
||||
}
|
||||
|
||||
func (u *AiRepo) List(opts ...DBOption) ([]model.OllamaModel, error) {
|
||||
var list []model.OllamaModel
|
||||
db := global.DB.Model(&model.OllamaModel{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (u *AiRepo) Page(page, size int, opts ...DBOption) (int64, []model.OllamaModel, error) {
|
||||
var list []model.OllamaModel
|
||||
db := global.DB.Model(&model.OllamaModel{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&list).Error
|
||||
return count, list, err
|
||||
}
|
||||
|
||||
func (u *AiRepo) Create(item *model.OllamaModel) error {
|
||||
return global.DB.Create(item).Error
|
||||
}
|
||||
|
||||
func (u *AiRepo) Update(id uint, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.OllamaModel{}).Where("id = ?", id).Updates(vars).Error
|
||||
}
|
||||
|
||||
func (u *AiRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.OllamaModel{}).Error
|
||||
}
|
||||
@@ -15,6 +15,7 @@ type DBOption func(*gorm.DB) *gorm.DB
|
||||
type ICommonRepo interface {
|
||||
WithByID(id uint) DBOption
|
||||
WithByName(name string) DBOption
|
||||
WithByLowerName(name string) DBOption
|
||||
WithByType(tp string) DBOption
|
||||
WithOrderBy(orderStr string) DBOption
|
||||
WithOrderRuleBy(orderBy, order string) DBOption
|
||||
@@ -45,6 +46,12 @@ func (c *CommonRepo) WithByName(name string) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithByLowerName(name string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("LOWER(name) = LOWER(?)", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithByDate(startTime, endTime time.Time) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("start_time > ? AND start_time < ?", startTime, endTime)
|
||||
|
||||
@@ -16,6 +16,7 @@ type IGroupRepo interface {
|
||||
Delete(opts ...DBOption) error
|
||||
CancelDefault(groupType string) error
|
||||
WithByHostDefault() DBOption
|
||||
WithByWebsiteDefault() DBOption
|
||||
}
|
||||
|
||||
func NewIGroupRepo() IGroupRepo {
|
||||
@@ -56,6 +57,12 @@ func (u *GroupRepo) WithByHostDefault() DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *GroupRepo) WithByWebsiteDefault() DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("is_default = ? AND type = ?", 1, "website")
|
||||
}
|
||||
}
|
||||
|
||||
func (u *GroupRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package repo
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
|
||||
type McpServerRepo struct {
|
||||
}
|
||||
|
||||
type IMcpServerRepo interface {
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.McpServer, error)
|
||||
GetFirst(opts ...DBOption) (*model.McpServer, error)
|
||||
Create(mcpServer *model.McpServer) error
|
||||
Save(mcpServer *model.McpServer) error
|
||||
DeleteBy(opts ...DBOption) error
|
||||
List(opts ...DBOption) ([]model.McpServer, error)
|
||||
}
|
||||
|
||||
func NewIMcpServerRepo() IMcpServerRepo {
|
||||
return &McpServerRepo{}
|
||||
}
|
||||
|
||||
func (m McpServerRepo) Page(page, size int, opts ...DBOption) (int64, []model.McpServer, error) {
|
||||
var servers []model.McpServer
|
||||
db := getDb(opts...).Model(&model.McpServer{})
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&servers).Error
|
||||
return count, servers, err
|
||||
}
|
||||
|
||||
func (m McpServerRepo) GetFirst(opts ...DBOption) (*model.McpServer, error) {
|
||||
var mcpServer model.McpServer
|
||||
if err := getDb(opts...).First(&mcpServer).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mcpServer, nil
|
||||
}
|
||||
|
||||
func (m McpServerRepo) List(opts ...DBOption) ([]model.McpServer, error) {
|
||||
var mcpServers []model.McpServer
|
||||
if err := getDb(opts...).Find(&mcpServers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mcpServers, nil
|
||||
}
|
||||
|
||||
func (m McpServerRepo) Create(mcpServer *model.McpServer) error {
|
||||
return getDb().Create(mcpServer).Error
|
||||
}
|
||||
|
||||
func (m McpServerRepo) Save(mcpServer *model.McpServer) error {
|
||||
return getDb().Save(mcpServer).Error
|
||||
}
|
||||
|
||||
func (m McpServerRepo) DeleteBy(opts ...DBOption) error {
|
||||
return getDb(opts...).Delete(&model.McpServer{}).Error
|
||||
}
|
||||
@@ -16,6 +16,7 @@ type ISettingRepo interface {
|
||||
Create(key, value string) error
|
||||
Update(key, value string) error
|
||||
WithByKey(key string) DBOption
|
||||
UpdateOrCreate(key, value string) error
|
||||
|
||||
CreateMonitorBase(model model.MonitorBase) error
|
||||
BatchCreateMonitorIO(ioList []model.MonitorIO) error
|
||||
@@ -85,3 +86,7 @@ func (u *SettingRepo) DelMonitorIO(timeForDelete time.Time) error {
|
||||
func (u *SettingRepo) DelMonitorNet(timeForDelete time.Time) error {
|
||||
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
|
||||
}
|
||||
|
||||
func (u *SettingRepo) UpdateOrCreate(key, value string) error {
|
||||
return global.DB.Model(&model.Setting{}).Where("key = ?", key).Assign(model.Setting{Key: key, Value: value}).FirstOrCreate(&model.Setting{}).Error
|
||||
}
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type AIToolService struct{}
|
||||
|
||||
type IAIToolService interface {
|
||||
Search(search dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error)
|
||||
Create(name string) error
|
||||
Close(name string) error
|
||||
Recreate(name string) error
|
||||
Delete(req dto.ForceDelete) error
|
||||
Sync() ([]dto.OllamaModelDropList, error)
|
||||
LoadDetail(name string) (string, error)
|
||||
BindDomain(req dto.OllamaBindDomain) error
|
||||
GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error)
|
||||
UpdateBindDomain(req dto.OllamaBindDomain) error
|
||||
}
|
||||
|
||||
func NewIAIToolService() IAIToolService {
|
||||
return &AIToolService{}
|
||||
}
|
||||
|
||||
func (u *AIToolService) Search(req dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error) {
|
||||
var options []repo.DBOption
|
||||
if len(req.Info) != 0 {
|
||||
options = append(options, commonRepo.WithLikeName(req.Info))
|
||||
}
|
||||
total, list, err := aiRepo.Page(req.Page, req.PageSize, options...)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var dtoLists []dto.OllamaModelInfo
|
||||
for _, itemModel := range list {
|
||||
var item dto.OllamaModelInfo
|
||||
if err := copier.Copy(&item, &itemModel); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
logPath := path.Join(global.CONF.System.DataDir, "log", "AITools", itemModel.Name)
|
||||
if _, err := os.Stat(logPath); err == nil {
|
||||
item.LogFileExist = true
|
||||
}
|
||||
dtoLists = append(dtoLists, item)
|
||||
}
|
||||
return int64(total), dtoLists, err
|
||||
}
|
||||
|
||||
func (u *AIToolService) LoadDetail(name string) (string, error) {
|
||||
if cmd.CheckIllegal(name) {
|
||||
return "", buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
containerName, err := LoadContainerName()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stdout, err := cmd.Execf("docker exec %s ollama show %s", containerName, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return stdout, err
|
||||
}
|
||||
|
||||
func (u *AIToolService) Create(name string) error {
|
||||
if cmd.CheckIllegal(name) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
modelInfo, _ := aiRepo.Get(commonRepo.WithByName(name))
|
||||
if modelInfo.ID != 0 {
|
||||
return constant.ErrRecordExist
|
||||
}
|
||||
containerName, err := LoadContainerName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logItem := path.Join(global.CONF.System.DataDir, "log", "AITools", name)
|
||||
if _, err := os.Stat(path.Dir(logItem)); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(path.Dir(logItem), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
info := model.OllamaModel{
|
||||
Name: name,
|
||||
From: "local",
|
||||
Status: constant.StatusWaiting,
|
||||
}
|
||||
if err := aiRepo.Create(&info); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go pullOllamaModel(file, containerName, info)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *AIToolService) Close(name string) error {
|
||||
if cmd.CheckIllegal(name) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
containerName, err := LoadContainerName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdout, err := cmd.Execf("docker exec %s ollama stop %s", containerName, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handle ollama stop %s failed, stdout: %s, err: %v", name, stdout, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *AIToolService) Recreate(name string) error {
|
||||
if cmd.CheckIllegal(name) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
modelInfo, _ := aiRepo.Get(commonRepo.WithByName(name))
|
||||
if modelInfo.ID == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
containerName, err := LoadContainerName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusWaiting, "from": "local"}); err != nil {
|
||||
return err
|
||||
}
|
||||
logItem := path.Join(global.CONF.System.DataDir, "log", "AITools", name)
|
||||
if _, err := os.Stat(path.Dir(logItem)); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(path.Dir(logItem), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go pullOllamaModel(file, containerName, modelInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *AIToolService) Delete(req dto.ForceDelete) error {
|
||||
ollamaList, _ := aiRepo.List(commonRepo.WithIdsIn(req.IDs))
|
||||
if len(ollamaList) == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
containerName, err := LoadContainerName()
|
||||
if err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
for _, item := range ollamaList {
|
||||
if item.Status != constant.StatusDeleted {
|
||||
stdout, err := cmd.Execf("docker exec %s ollama rm %s", containerName, item.Name)
|
||||
if err != nil && !req.ForceDelete {
|
||||
return fmt.Errorf("handle ollama rm %s failed, stdout: %s, err: %v", item.Name, stdout, err)
|
||||
}
|
||||
}
|
||||
_ = aiRepo.Delete(commonRepo.WithByID(item.ID))
|
||||
logItem := path.Join(global.CONF.System.DataDir, "log", "AITools", item.Name)
|
||||
_ = os.Remove(logItem)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *AIToolService) Sync() ([]dto.OllamaModelDropList, error) {
|
||||
containerName, err := LoadContainerName()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stdout, err := cmd.Execf("docker exec %s ollama list", containerName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var list []model.OllamaModel
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 5 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == "NAME" {
|
||||
continue
|
||||
}
|
||||
list = append(list, model.OllamaModel{Name: parts[0], Size: parts[2] + " " + parts[3]})
|
||||
}
|
||||
listInDB, _ := aiRepo.List()
|
||||
var dropList []dto.OllamaModelDropList
|
||||
for _, itemModel := range listInDB {
|
||||
isExit := false
|
||||
for i := 0; i < len(list); i++ {
|
||||
if list[i].Name == itemModel.Name {
|
||||
_ = aiRepo.Update(itemModel.ID, map[string]interface{}{"status": constant.StatusSuccess, "message": "", "size": list[i].Size})
|
||||
list = append(list[:i], list[(i+1):]...)
|
||||
isExit = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isExit && itemModel.Status != constant.StatusWaiting {
|
||||
_ = aiRepo.Update(itemModel.ID, map[string]interface{}{"status": constant.StatusDeleted, "message": "not exist", "size": ""})
|
||||
dropList = append(dropList, dto.OllamaModelDropList{ID: itemModel.ID, Name: itemModel.Name})
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, item := range list {
|
||||
item.Status = constant.StatusSuccess
|
||||
item.From = "remote"
|
||||
_ = aiRepo.Create(&item)
|
||||
}
|
||||
|
||||
return dropList, nil
|
||||
}
|
||||
|
||||
func (u *AIToolService) BindDomain(req dto.OllamaBindDomain) error {
|
||||
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
|
||||
if nginxInstall.ID == 0 {
|
||||
return buserr.New("ErrOpenrestyInstall")
|
||||
}
|
||||
var (
|
||||
ipList []string
|
||||
err error
|
||||
)
|
||||
if len(req.IPList) > 0 {
|
||||
ipList, err = common.HandleIPList(req.IPList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
ssl, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ssl.Pem == "" {
|
||||
return buserr.New("ErrSSL")
|
||||
}
|
||||
}
|
||||
createWebsiteReq := request.WebsiteCreate{
|
||||
PrimaryDomain: req.Domain,
|
||||
Alias: strings.ToLower(req.Domain),
|
||||
Type: constant.Deployment,
|
||||
AppType: constant.InstalledApp,
|
||||
AppInstallID: req.AppInstallID,
|
||||
}
|
||||
websiteService := NewIWebsiteService()
|
||||
if err := websiteService.CreateWebsite(createWebsiteReq); err != nil {
|
||||
return err
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(strings.ToLower(req.Domain)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ipList) > 0 {
|
||||
if err = ConfigAllowIPs(ipList, website); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
sslReq := request.WebsiteHTTPSOp{
|
||||
WebsiteID: website.ID,
|
||||
Enable: true,
|
||||
Type: "existed",
|
||||
WebsiteSSLID: req.SSLID,
|
||||
HttpConfig: "HTTPSOnly",
|
||||
}
|
||||
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = ConfigAIProxy(website); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *AIToolService) GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error) {
|
||||
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.AppInstallID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := &dto.OllamaBindDomainRes{}
|
||||
website, _ := websiteRepo.GetFirst(websiteRepo.WithAppInstallId(install.ID))
|
||||
if website.ID == 0 {
|
||||
return res, nil
|
||||
}
|
||||
res.WebsiteID = website.ID
|
||||
res.Domain = website.PrimaryDomain
|
||||
if website.WebsiteSSLID > 0 {
|
||||
res.SSLID = website.WebsiteSSLID
|
||||
ssl, _ := websiteSSLRepo.GetFirst(commonRepo.WithByID(website.WebsiteSSLID))
|
||||
res.AcmeAccountID = ssl.AcmeAccountID
|
||||
}
|
||||
res.ConnUrl = fmt.Sprintf("%s://%s", strings.ToLower(website.Protocol), website.PrimaryDomain)
|
||||
res.AllowIPs = GetAllowIps(website)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (u *AIToolService) UpdateBindDomain(req dto.OllamaBindDomain) error {
|
||||
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
|
||||
if nginxInstall.ID == 0 {
|
||||
return buserr.New("ErrOpenrestyInstall")
|
||||
}
|
||||
var (
|
||||
ipList []string
|
||||
err error
|
||||
)
|
||||
if len(req.IPList) > 0 {
|
||||
ipList, err = common.HandleIPList(req.IPList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
ssl, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ssl.Pem == "" {
|
||||
return buserr.New("ErrSSL")
|
||||
}
|
||||
}
|
||||
websiteService := NewIWebsiteService()
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ConfigAllowIPs(ipList, website); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
sslReq := request.WebsiteHTTPSOp{
|
||||
WebsiteID: website.ID,
|
||||
Enable: true,
|
||||
Type: "existed",
|
||||
WebsiteSSLID: req.SSLID,
|
||||
HttpConfig: "HTTPSOnly",
|
||||
}
|
||||
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if website.WebsiteSSLID > 0 && req.SSLID == 0 {
|
||||
sslReq := request.WebsiteHTTPSOp{
|
||||
WebsiteID: website.ID,
|
||||
Enable: false,
|
||||
}
|
||||
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadContainerName() (string, error) {
|
||||
ollamaBaseInfo, err := appInstallRepo.LoadBaseInfo("ollama", "")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ollama service is not found, err: %v", err)
|
||||
}
|
||||
if ollamaBaseInfo.Status != constant.Running {
|
||||
return "", fmt.Errorf("container %s of ollama is not running, please check and retry!", ollamaBaseInfo.ContainerName)
|
||||
}
|
||||
return ollamaBaseInfo.ContainerName, nil
|
||||
}
|
||||
|
||||
func pullOllamaModel(file *os.File, containerName string, info model.OllamaModel) {
|
||||
defer file.Close()
|
||||
cmd := exec.Command("docker", "exec", containerName, "ollama", "pull", info.Name)
|
||||
multiWriter := io.MultiWriter(os.Stdout, file)
|
||||
cmd.Stdout = multiWriter
|
||||
cmd.Stderr = multiWriter
|
||||
_ = cmd.Run()
|
||||
itemSize, err := loadModelSize(info.Name, containerName)
|
||||
if len(itemSize) != 0 {
|
||||
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
|
||||
} else {
|
||||
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
}
|
||||
_, _ = file.WriteString("ollama pull completed!")
|
||||
}
|
||||
|
||||
func loadModelSize(name string, containerName string) (string, error) {
|
||||
stdout, err := cmd.Execf("docker exec %s ollama list | grep %s", containerName, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
lines := strings.Split(string(stdout), "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 5 {
|
||||
continue
|
||||
}
|
||||
return parts[2] + " " + parts[3], nil
|
||||
}
|
||||
return "", fmt.Errorf("no such model %s in ollama list, std: %s", name, string(stdout))
|
||||
}
|
||||
+51
-33
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -34,9 +35,9 @@ type AppService struct {
|
||||
}
|
||||
|
||||
type IAppService interface {
|
||||
PageApp(req request.AppSearch) (interface{}, error)
|
||||
GetAppTags() ([]response.TagDTO, error)
|
||||
GetApp(key string) (*response.AppDTO, error)
|
||||
PageApp(ctx *gin.Context, req request.AppSearch) (interface{}, error)
|
||||
GetAppTags(ctx *gin.Context) ([]response.TagDTO, error)
|
||||
GetApp(ctx *gin.Context, key string) (*response.AppDTO, error)
|
||||
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
|
||||
Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error)
|
||||
SyncAppListFromRemote() error
|
||||
@@ -50,7 +51,7 @@ func NewIAppService() IAppService {
|
||||
return &AppService{}
|
||||
}
|
||||
|
||||
func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
|
||||
func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (interface{}, error) {
|
||||
var opts []repo.DBOption
|
||||
opts = append(opts, appRepo.OrderByRecommend())
|
||||
if req.Name != "" {
|
||||
@@ -90,30 +91,23 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
|
||||
return nil, err
|
||||
}
|
||||
var appDTOs []*response.AppItem
|
||||
lang := strings.ToLower(common.GetLang(ctx))
|
||||
for _, ap := range apps {
|
||||
appDTO := &response.AppItem{
|
||||
ID: ap.ID,
|
||||
Name: ap.Name,
|
||||
Key: ap.Key,
|
||||
Type: ap.Type,
|
||||
Icon: ap.Icon,
|
||||
ShortDescZh: ap.ShortDescZh,
|
||||
ShortDescEn: ap.ShortDescEn,
|
||||
Resource: ap.Resource,
|
||||
Limit: ap.Limit,
|
||||
ID: ap.ID,
|
||||
Name: ap.Name,
|
||||
Key: ap.Key,
|
||||
Type: ap.Type,
|
||||
Icon: ap.Icon,
|
||||
Resource: ap.Resource,
|
||||
Limit: ap.Limit,
|
||||
GpuSupport: ap.GpuSupport,
|
||||
}
|
||||
appDTO.Description = ap.GetDescription(ctx)
|
||||
appDTOs = append(appDTOs, appDTO)
|
||||
appTags, err := appTagRepo.GetByAppId(ap.ID)
|
||||
tags, err := getAppTags(ap.ID, lang)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var tagIds []uint
|
||||
for _, at := range appTags {
|
||||
tagIds = append(tagIds, at.TagId)
|
||||
}
|
||||
tags, err := tagRepo.GetByIds(tagIds)
|
||||
if err != nil {
|
||||
continue
|
||||
return nil, err
|
||||
}
|
||||
appDTO.Tags = tags
|
||||
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(ap.ID))
|
||||
@@ -125,18 +119,32 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (a AppService) GetAppTags() ([]response.TagDTO, error) {
|
||||
func (a AppService) GetAppTags(ctx *gin.Context) ([]response.TagDTO, error) {
|
||||
tags, _ := tagRepo.All()
|
||||
res := make([]response.TagDTO, 0)
|
||||
lang := strings.ToLower(common.GetLang(ctx))
|
||||
for _, tag := range tags {
|
||||
res = append(res, response.TagDTO{
|
||||
Tag: tag,
|
||||
})
|
||||
tagDTO := response.TagDTO{
|
||||
ID: tag.ID,
|
||||
Key: tag.Key,
|
||||
}
|
||||
var translations = make(map[string]string)
|
||||
_ = json.Unmarshal([]byte(tag.Translations), &translations)
|
||||
if name, ok := translations[lang]; ok && name != "" {
|
||||
tagDTO.Name = name
|
||||
} else {
|
||||
if lang == "zh" || lang == "zh-Hant" {
|
||||
tagDTO.Name = tag.Name
|
||||
} else {
|
||||
tagDTO.Name = tag.Key
|
||||
}
|
||||
}
|
||||
res = append(res, tagDTO)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (a AppService) GetApp(key string) (*response.AppDTO, error) {
|
||||
func (a AppService) GetApp(ctx *gin.Context, key string) (*response.AppDTO, error) {
|
||||
var appDTO response.AppDTO
|
||||
if key == "postgres" {
|
||||
key = "postgresql"
|
||||
@@ -146,6 +154,7 @@ func (a AppService) GetApp(key string) (*response.AppDTO, error) {
|
||||
return nil, err
|
||||
}
|
||||
appDTO.App = app
|
||||
appDTO.App.Description = app.GetDescription(ctx)
|
||||
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(app.ID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -155,7 +164,12 @@ func (a AppService) GetApp(key string) (*response.AppDTO, error) {
|
||||
versionsRaw = append(versionsRaw, detail.Version)
|
||||
}
|
||||
appDTO.Versions = common.GetSortedVersions(versionsRaw)
|
||||
|
||||
tags, err := getAppTags(app.ID, strings.ToLower(common.GetLang(ctx)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appDTO.GpuSupport = app.GpuSupport
|
||||
appDTO.Tags = tags
|
||||
return &appDTO, nil
|
||||
}
|
||||
|
||||
@@ -252,6 +266,7 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
||||
if err := checkLimit(app); err != nil {
|
||||
appDetailDTO.Enable = false
|
||||
}
|
||||
appDetailDTO.GpuSupport = app.GpuSupport
|
||||
return appDetailDTO, nil
|
||||
}
|
||||
func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) {
|
||||
@@ -296,7 +311,8 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
|
||||
err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
if list, _ := appInstallRepo.ListBy(commonRepo.WithByName(req.Name)); len(list) > 0 {
|
||||
|
||||
if list, _ := appInstallRepo.ListBy(commonRepo.WithByLowerName(req.Name)); len(list) > 0 {
|
||||
err = buserr.New(constant.ErrAppNameExist)
|
||||
return
|
||||
}
|
||||
@@ -827,10 +843,11 @@ func (a AppService) SyncAppListFromRemote() (err error) {
|
||||
oldAppIds []uint
|
||||
)
|
||||
for _, t := range list.Extra.Tags {
|
||||
translations, _ := json.Marshal(t.Locales)
|
||||
tags = append(tags, &model.Tag{
|
||||
Key: t.Key,
|
||||
Name: t.Name,
|
||||
Sort: t.Sort,
|
||||
Key: t.Key,
|
||||
Translations: string(translations),
|
||||
Sort: t.Sort,
|
||||
})
|
||||
}
|
||||
oldApps, err := appRepo.GetBy(appRepo.WithResource(constant.AppResourceRemote))
|
||||
@@ -848,6 +865,7 @@ func (a AppService) SyncAppListFromRemote() (err error) {
|
||||
global.LOG.Infof("Starting synchronization of application details...")
|
||||
for _, l := range list.Apps {
|
||||
app := appsMap[l.AppProperty.Key]
|
||||
app.GpuSupport = l.AppProperty.GpuSupport
|
||||
|
||||
if l.AppProperty.Version > 0 && common.CompareVersion(strconv.FormatFloat(l.AppProperty.Version, 'f', -1, 64), setting.SystemVersion) {
|
||||
delete(appsMap, l.AppProperty.Key)
|
||||
|
||||
@@ -750,6 +750,7 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
}
|
||||
res.AppContainerConfig = config
|
||||
res.HostMode = isHostModel(install.DockerCompose)
|
||||
res.GpuConfig = isGpuConfig(install.DockerCompose)
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
@@ -838,6 +839,9 @@ func updateInstallInfoInDB(appKey, appName, param string, value interface{}) err
|
||||
"param": strings.ReplaceAll(appInstall.Param, oldVal, newVal),
|
||||
"env": strings.ReplaceAll(appInstall.Env, oldVal, newVal),
|
||||
}, commonRepo.WithByID(appInstall.ID))
|
||||
if appKey == "mysql" || appKey == "postgresql" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if param == "user-password" {
|
||||
oldVal = fmt.Sprintf("\"PANEL_DB_USER_PASSWORD\":\"%v\"", appInstall.UserPassword)
|
||||
|
||||
@@ -1051,6 +1051,8 @@ func getApps(oldApps []model.App, items []dto.AppDefine) map[string]model.App {
|
||||
app.Key = key
|
||||
app.ShortDescZh = config.ShortDescZh
|
||||
app.ShortDescEn = config.ShortDescEn
|
||||
description, _ := json.Marshal(config.Description)
|
||||
app.Description = string(description)
|
||||
app.Website = config.Website
|
||||
app.Document = config.Document
|
||||
app.Github = config.Github
|
||||
@@ -1150,14 +1152,31 @@ func handleLocalApp(appDir string) (app *model.App, err error) {
|
||||
err = buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
|
||||
return
|
||||
}
|
||||
app = &localAppDefine.AppProperty
|
||||
appDefine := localAppDefine.AppProperty
|
||||
app = &model.App{}
|
||||
app.Name = appDefine.Name
|
||||
app.TagsKey = append(appDefine.Tags, "Local")
|
||||
app.Type = appDefine.Type
|
||||
app.CrossVersionUpdate = appDefine.CrossVersionUpdate
|
||||
app.Limit = appDefine.Limit
|
||||
app.Recommend = appDefine.Recommend
|
||||
app.Website = appDefine.Website
|
||||
app.Github = appDefine.Github
|
||||
app.Document = appDefine.Document
|
||||
if appDefine.ShortDescZh != "" {
|
||||
app.ShortDescZh = appDefine.ShortDescZh
|
||||
}
|
||||
if appDefine.ShortDescEn != "" {
|
||||
app.ShortDescEn = appDefine.ShortDescEn
|
||||
}
|
||||
desc, _ := json.Marshal(appDefine.Description)
|
||||
app.Description = string(desc)
|
||||
|
||||
app.Key = "local" + appDefine.Key
|
||||
app.Resource = constant.AppResourceLocal
|
||||
app.Status = constant.AppNormal
|
||||
app.Recommend = 9999
|
||||
app.TagsKey = append(app.TagsKey, "Local")
|
||||
app.Key = "local" + app.Key
|
||||
readMePath := path.Join(appDir, "README.md")
|
||||
readMeByte, err := fileOp.GetContent(readMePath)
|
||||
readMeByte, err := fileOp.GetContent(path.Join(appDir, "README.md"))
|
||||
if err == nil {
|
||||
app.ReadMe = string(readMeByte)
|
||||
}
|
||||
@@ -1319,9 +1338,10 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
|
||||
Path: installed.GetPath(),
|
||||
CreatedAt: installed.CreatedAt,
|
||||
App: response.AppDetail{
|
||||
Github: installed.App.Github,
|
||||
Website: installed.App.Website,
|
||||
Document: installed.App.Document,
|
||||
Github: installed.App.Github,
|
||||
Website: installed.App.Website,
|
||||
Document: installed.App.Document,
|
||||
GpuSupport: installed.App.GpuSupport,
|
||||
},
|
||||
}
|
||||
if updated {
|
||||
@@ -1480,6 +1500,20 @@ func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName
|
||||
deploy["resources"] = resource
|
||||
serviceValue["deploy"] = deploy
|
||||
|
||||
if req.GpuConfig {
|
||||
resource["reservations"] = map[string]interface{}{
|
||||
"devices": []map[string]interface{}{
|
||||
{
|
||||
"driver": "nvidia",
|
||||
"count": "all",
|
||||
"capabilities": []string{"gpu"},
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
delete(resource, "reservations")
|
||||
}
|
||||
|
||||
ports, ok := serviceValue["ports"].([]interface{})
|
||||
if ok {
|
||||
for i, port := range ports {
|
||||
@@ -1581,9 +1615,74 @@ func isHostModel(dockerCompose string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func isGpuConfig(dockerCompose string) bool {
|
||||
composeMap := make(map[string]interface{})
|
||||
_ = yaml.Unmarshal([]byte(dockerCompose), &composeMap)
|
||||
services, serviceValid := composeMap["services"].(map[string]interface{})
|
||||
if !serviceValid {
|
||||
return false
|
||||
}
|
||||
for _, service := range services {
|
||||
serviceValue := service.(map[string]interface{})
|
||||
deploy := map[string]interface{}{}
|
||||
if de, ok := serviceValue["deploy"]; ok {
|
||||
deploy = de.(map[string]interface{})
|
||||
}
|
||||
resource := map[string]interface{}{}
|
||||
if res, ok := deploy["resources"]; ok {
|
||||
resource = res.(map[string]interface{})
|
||||
}
|
||||
if reservations, ok := resource["reservations"]; ok {
|
||||
reservationsMap := reservations.(map[string]interface{})
|
||||
if _, dOk := reservationsMap["devices"]; dOk {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func RequestDownloadCallBack(downloadCallBackUrl string) {
|
||||
if downloadCallBackUrl == "" {
|
||||
return
|
||||
}
|
||||
_, _, _ = httpUtil.HandleGet(downloadCallBackUrl, http.MethodGet, constant.TimeOut5s)
|
||||
}
|
||||
|
||||
func getAppTags(appID uint, lang string) ([]response.TagDTO, error) {
|
||||
appTags, err := appTagRepo.GetByAppId(appID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tagIds []uint
|
||||
for _, at := range appTags {
|
||||
tagIds = append(tagIds, at.TagId)
|
||||
}
|
||||
tags, err := tagRepo.GetByIds(tagIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []response.TagDTO
|
||||
for _, t := range tags {
|
||||
if t.Name != "" {
|
||||
tagDTO := response.TagDTO{
|
||||
ID: t.ID,
|
||||
Key: t.Key,
|
||||
Name: t.Name,
|
||||
}
|
||||
res = append(res, tagDTO)
|
||||
} else {
|
||||
var translations = make(map[string]string)
|
||||
_ = json.Unmarshal([]byte(t.Translations), &translations)
|
||||
if name, ok := translations[lang]; ok {
|
||||
tagDTO := response.TagDTO{
|
||||
ID: t.ID,
|
||||
Key: t.Key,
|
||||
Name: name,
|
||||
}
|
||||
res = append(res, tagDTO)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
+31
-19
@@ -38,16 +38,11 @@ func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*d
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
||||
}
|
||||
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
||||
}
|
||||
pass, err := encrypt.StringDecrypt(passwordSetting.Value)
|
||||
if err != nil {
|
||||
if nameSetting.Value != info.Name {
|
||||
return nil, constant.ErrAuth
|
||||
}
|
||||
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
|
||||
return nil, constant.ErrAuth
|
||||
if err = checkPassword(info.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
||||
if err != nil {
|
||||
@@ -83,17 +78,12 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance strin
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
||||
}
|
||||
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
||||
}
|
||||
pass, err := encrypt.StringDecrypt(passwordSetting.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
|
||||
if nameSetting.Value != info.Name {
|
||||
return nil, constant.ErrAuth
|
||||
}
|
||||
if err = checkPassword(info.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -214,8 +204,30 @@ func (u *AuthService) GetSecurityEntrance() string {
|
||||
func (u *AuthService) IsLogin(c *gin.Context) bool {
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
_, err := global.SESSION.Get(sID)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func checkPassword(password string) error {
|
||||
priKey, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PRIVATE_KEY"))
|
||||
|
||||
privateKey, err := encrypt.ParseRSAPrivateKey(priKey.Value)
|
||||
if err != nil {
|
||||
return false
|
||||
return err
|
||||
}
|
||||
return true
|
||||
loginPassword, err := encrypt.DecryptPassword(password, privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
|
||||
if err != nil {
|
||||
return errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
||||
}
|
||||
existPassword, err := encrypt.StringDecrypt(passwordSetting.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hmac.Equal([]byte(loginPassword), []byte(existPassword)) {
|
||||
return constant.ErrAuth
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@@ -169,8 +170,9 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err)
|
||||
} else {
|
||||
global.LOG.Infof("rollback mysql db %s from %s successful", req.DetailName, rollbackFile)
|
||||
}
|
||||
global.LOG.Infof("rollback mysql db %s from %s successful", req.DetailName, rollbackFile)
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
} else {
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
@@ -186,6 +188,7 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("recover mysql db %s from %s failed, err: %v", req.DetailName, req.File, err)
|
||||
return err
|
||||
}
|
||||
isOk = true
|
||||
|
||||
@@ -2,13 +2,14 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
pgclient "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
|
||||
@@ -157,8 +158,9 @@ func handlePostgresqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("rollback postgresql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err)
|
||||
} else {
|
||||
global.LOG.Infof("rollback postgresql db %s from %s successful", req.DetailName, rollbackFile)
|
||||
}
|
||||
global.LOG.Infof("rollback postgresql db %s from %s successful", req.DetailName, rollbackFile)
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
} else {
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
@@ -171,6 +173,7 @@ func handlePostgresqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
Username: dbInfo.Username,
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("recover postgresql db %s from %s failed, err: %v", req.DetailName, req.File, err)
|
||||
return err
|
||||
}
|
||||
isOk = true
|
||||
|
||||
@@ -255,14 +255,14 @@ func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var datas []dto.ContainerListStats
|
||||
datas := make([]dto.ContainerListStats, len(list))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(list))
|
||||
for i := 0; i < len(list); i++ {
|
||||
go func(item types.Container) {
|
||||
datas = append(datas, loadCpuAndMem(client, item.ID))
|
||||
go func(index int, item types.Container) {
|
||||
datas[index] = loadCpuAndMem(client, item.ID)
|
||||
wg.Done()
|
||||
}(list[i])
|
||||
}(i, list[i])
|
||||
}
|
||||
wg.Wait()
|
||||
return datas, nil
|
||||
@@ -450,12 +450,14 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
||||
networkSettings := oldContainer.NetworkSettings
|
||||
bridgeNetworkSettings := networkSettings.Networks[data.Network]
|
||||
if bridgeNetworkSettings.IPAMConfig != nil {
|
||||
ipv4Address := bridgeNetworkSettings.IPAMConfig.IPv4Address
|
||||
data.Ipv4 = ipv4Address
|
||||
ipv6Address := bridgeNetworkSettings.IPAMConfig.IPv6Address
|
||||
data.Ipv6 = ipv6Address
|
||||
if data.Network != "bridge" {
|
||||
data.Ipv4 = bridgeNetworkSettings.IPAMConfig.IPv4Address
|
||||
data.Ipv6 = bridgeNetworkSettings.IPAMConfig.IPv6Address
|
||||
}
|
||||
} else {
|
||||
data.Ipv4 = bridgeNetworkSettings.IPAddress
|
||||
if data.Network != "bridge" {
|
||||
data.Ipv4 = bridgeNetworkSettings.IPAddress
|
||||
}
|
||||
}
|
||||
|
||||
data.Cmd = oldContainer.Config.Cmd
|
||||
@@ -937,15 +939,27 @@ func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
|
||||
}
|
||||
return cpuPercent
|
||||
}
|
||||
|
||||
func calculateMemPercentUnix(memStats types.MemoryStats) float64 {
|
||||
memPercent := 0.0
|
||||
memUsage := float64(memStats.Usage)
|
||||
memLimit := float64(memStats.Limit)
|
||||
memUsage := calculateMemUsageUnixNoCache(memStats)
|
||||
memLimit := memStats.Limit
|
||||
if memUsage > 0.0 && memLimit > 0.0 {
|
||||
memPercent = (memUsage / memLimit) * 100.0
|
||||
memPercent = (float64(memUsage) / float64(memLimit)) * 100.0
|
||||
}
|
||||
return memPercent
|
||||
}
|
||||
|
||||
func calculateMemUsageUnixNoCache(mem types.MemoryStats) uint64 {
|
||||
if v, isCgroup1 := mem.Stats["total_inactive_file"]; isCgroup1 && v < mem.Usage {
|
||||
return mem.Usage - v
|
||||
}
|
||||
if v := mem.Stats["inactive_file"]; v < mem.Usage {
|
||||
return mem.Usage - v
|
||||
}
|
||||
return mem.Usage
|
||||
}
|
||||
|
||||
func calculateBlockIO(blkio types.BlkioStats) (blkRead float64, blkWrite float64) {
|
||||
for _, bioEntry := range blkio.IoServiceBytesRecursive {
|
||||
switch strings.ToLower(bioEntry.Op) {
|
||||
@@ -1063,7 +1077,7 @@ func loadCpuAndMem(client *client.Client, container string) dto.ContainerListSta
|
||||
data.PercpuUsage = len(stats.CPUStats.CPUUsage.PercpuUsage)
|
||||
|
||||
data.MemoryCache = stats.MemoryStats.Stats["cache"]
|
||||
data.MemoryUsage = stats.MemoryStats.Usage
|
||||
data.MemoryUsage = calculateMemUsageUnixNoCache(stats.MemoryStats)
|
||||
data.MemoryLimit = stats.MemoryStats.Limit
|
||||
|
||||
data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats)
|
||||
@@ -1183,9 +1197,10 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
|
||||
for _, volume := range req.Volumes {
|
||||
if volume.Type == "volume" {
|
||||
hostConf.Mounts = append(hostConf.Mounts, mount.Mount{
|
||||
Type: mount.Type(volume.Type),
|
||||
Source: volume.SourceDir,
|
||||
Target: volume.ContainerDir,
|
||||
Type: mount.Type(volume.Type),
|
||||
Source: volume.SourceDir,
|
||||
Target: volume.ContainerDir,
|
||||
ReadOnly: volume.Mode == "ro",
|
||||
})
|
||||
config.Volumes[volume.ContainerDir] = struct{}{}
|
||||
} else {
|
||||
@@ -1262,12 +1277,12 @@ func loadContainerPortForInfo(itemPorts []types.Port) []dto.PortHelper {
|
||||
var exposedPorts []dto.PortHelper
|
||||
samePortMap := make(map[string]dto.PortHelper)
|
||||
ports := transPortToStr(itemPorts)
|
||||
var itemPort dto.PortHelper
|
||||
for _, item := range ports {
|
||||
itemStr := strings.Split(item, "->")
|
||||
if len(itemStr) < 2 {
|
||||
continue
|
||||
}
|
||||
var itemPort dto.PortHelper
|
||||
lastIndex := strings.LastIndex(itemStr[0], ":")
|
||||
if lastIndex == -1 {
|
||||
itemPort.HostPort = itemStr[0]
|
||||
|
||||
@@ -114,8 +114,17 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !req.CleanRemoteData {
|
||||
for key := range accountMap {
|
||||
if key != constant.Local {
|
||||
delete(accountMap, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
cronjob.RetainCopies = 0
|
||||
u.removeExpiredBackup(cronjob, accountMap, model.BackupRecord{})
|
||||
if len(accountMap) != 0 {
|
||||
u.removeExpiredBackup(cronjob, accountMap, model.BackupRecord{})
|
||||
}
|
||||
} else {
|
||||
u.removeExpiredLog(cronjob)
|
||||
}
|
||||
@@ -249,7 +258,7 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
|
||||
global.Cron.Remove(cron.EntryID(idItem))
|
||||
}
|
||||
global.LOG.Infof("stop cronjob entryID: %s", cronjob.EntryIDs)
|
||||
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData, IsDelete: true}); err != nil {
|
||||
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData, CleanRemoteData: req.CleanRemoteData, IsDelete: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cronjobRepo.Delete(commonRepo.WithByID(id)); err != nil {
|
||||
|
||||
@@ -322,7 +322,8 @@ func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, accountMap
|
||||
if len(account) != 0 {
|
||||
global.LOG.Debugf("start upload file to %s, dir: %s", account, path.Join(accountMap[account].backupPath, cloudSrc))
|
||||
if _, err := accountMap[account].client.Upload(file, path.Join(accountMap[account].backupPath, cloudSrc)); err != nil {
|
||||
return "", err
|
||||
global.LOG.Errorf("upload file to %s failed, err: %v", account, err)
|
||||
continue
|
||||
}
|
||||
global.LOG.Debugf("upload successful!")
|
||||
}
|
||||
@@ -349,12 +350,18 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
|
||||
if cronjob.Type == "snapshot" {
|
||||
for _, account := range accounts {
|
||||
if len(account) != 0 {
|
||||
if _, ok := accountMap[account]; !ok {
|
||||
continue
|
||||
}
|
||||
_, _ = accountMap[account].client.Delete(path.Join(accountMap[account].backupPath, "system_snapshot", records[i].FileName))
|
||||
}
|
||||
}
|
||||
_ = snapshotRepo.Delete(commonRepo.WithByName(strings.TrimSuffix(records[i].FileName, ".tar.gz")))
|
||||
} else {
|
||||
for _, account := range accounts {
|
||||
if _, ok := accountMap[account]; !ok {
|
||||
continue
|
||||
}
|
||||
if len(account) != 0 {
|
||||
_, _ = accountMap[account].client.Delete(path.Join(accountMap[account].backupPath, records[i].FileDir, records[i].FileName))
|
||||
}
|
||||
|
||||
@@ -10,19 +10,19 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/xpu"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/copier"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
)
|
||||
|
||||
type DashboardService struct{}
|
||||
@@ -341,7 +341,17 @@ func loadDiskInfo() []dto.DiskInfo {
|
||||
}
|
||||
|
||||
func loadGPUInfo() []dto.GPUInfo {
|
||||
list := xpack.LoadGpuInfo()
|
||||
ok, client := gpu.New()
|
||||
var list []interface{}
|
||||
if ok {
|
||||
info, err := client.LoadGpuInfo()
|
||||
if err != nil || len(info.GPUs) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, item := range info.GPUs {
|
||||
list = append(list, item)
|
||||
}
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -359,7 +369,17 @@ func loadGPUInfo() []dto.GPUInfo {
|
||||
}
|
||||
|
||||
func loadXpuInfo() []dto.XPUInfo {
|
||||
list := xpack.LoadXpuInfo()
|
||||
var list []interface{}
|
||||
ok, xpuClient := xpu.New()
|
||||
if ok {
|
||||
xpus, err := xpuClient.LoadDashData()
|
||||
if err != nil || len(xpus) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, item := range xpus {
|
||||
list = append(list, item)
|
||||
}
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -50,10 +50,6 @@ func (u *DBCommonService) LoadDatabaseFile(req dto.OperationWithNameAndType) (st
|
||||
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/postgresql/%s/data/postgresql.conf", req.Name))
|
||||
case "redis-conf":
|
||||
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/redis/%s/conf/redis.conf", req.Name))
|
||||
case "mysql-slow-logs":
|
||||
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name))
|
||||
case "mariadb-slow-logs":
|
||||
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mariadb/%s/db/data/1Panel-slow.log", req.Name))
|
||||
}
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
return "", buserr.New("ErrHttpReqNotFound")
|
||||
|
||||
@@ -12,6 +12,8 @@ var (
|
||||
appInstallRepo = repo.NewIAppInstallRepo()
|
||||
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
|
||||
|
||||
aiRepo = repo.NewIAiRepo()
|
||||
|
||||
mysqlRepo = repo.NewIMysqlRepo()
|
||||
postgresqlRepo = repo.NewIPostgresqlRepo()
|
||||
databaseRepo = repo.NewIDatabaseRepo()
|
||||
@@ -44,4 +46,6 @@ var (
|
||||
phpExtensionsRepo = repo.NewIPHPExtensionsRepo()
|
||||
|
||||
favoriteRepo = repo.NewIFavoriteRepo()
|
||||
|
||||
mcpServerRepo = repo.NewIMcpServerRepo()
|
||||
)
|
||||
|
||||
@@ -50,6 +50,7 @@ type IFileService interface {
|
||||
ChangeMode(op request.FileCreate) error
|
||||
BatchChangeModeAndOwner(op request.FileRoleReq) error
|
||||
ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error)
|
||||
BatchCheckFiles(req request.FilePathsCheck) []response.ExistFileInfo
|
||||
}
|
||||
|
||||
var filteredPaths = []string{
|
||||
@@ -478,6 +479,12 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
||||
}
|
||||
case "image-pull", "image-push", "image-build", "compose-create":
|
||||
logFilePath = path.Join(global.CONF.System.TmpDir, fmt.Sprintf("docker_logs/%s", req.Name))
|
||||
case "ollama-model":
|
||||
logFilePath = path.Join(global.CONF.System.DataDir, "log", "AITools", req.Name)
|
||||
case "mysql-slow-logs":
|
||||
logFilePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name))
|
||||
case "mariadb-slow-logs":
|
||||
logFilePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mariadb/%s/db/data/1Panel-slow.log", req.Name))
|
||||
}
|
||||
|
||||
lines, isEndOfFile, total, err := files.ReadFileByLine(logFilePath, req.Page, req.PageSize, req.Latest)
|
||||
@@ -501,3 +508,18 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (f *FileService) BatchCheckFiles(req request.FilePathsCheck) []response.ExistFileInfo {
|
||||
fileList := make([]response.ExistFileInfo, 0, len(req.Paths))
|
||||
for _, filePath := range req.Paths {
|
||||
if info, err := os.Stat(filePath); err == nil {
|
||||
fileList = append(fileList, response.ExistFileInfo{
|
||||
Size: float64(info.Size()),
|
||||
Name: info.Name(),
|
||||
Path: filePath,
|
||||
ModTime: info.ModTime(),
|
||||
})
|
||||
}
|
||||
}
|
||||
return fileList
|
||||
}
|
||||
|
||||
@@ -381,6 +381,10 @@ func (u *FirewallService) OperateForwardRule(req dto.ForwardRuleOperate) error {
|
||||
TargetIP: r.TargetIP,
|
||||
TargetPort: r.TargetPort,
|
||||
}, r.Operation); err != nil {
|
||||
if req.ForceDelete {
|
||||
global.LOG.Error(err)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,643 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser"
|
||||
"github.com/1Panel-dev/1Panel/cmd/server/mcp"
|
||||
"github.com/1Panel-dev/1Panel/cmd/server/nginx_conf"
|
||||
"github.com/subosito/gotenv"
|
||||
"gopkg.in/yaml.v3"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type McpServerService struct{}
|
||||
|
||||
type IMcpServerService interface {
|
||||
Page(req request.McpServerSearch) response.McpServersRes
|
||||
Create(create request.McpServerCreate) error
|
||||
Update(req request.McpServerUpdate) error
|
||||
Delete(id uint) error
|
||||
Operate(req request.McpServerOperate) error
|
||||
GetBindDomain() (response.McpBindDomainRes, error)
|
||||
BindDomain(req request.McpBindDomain) error
|
||||
UpdateBindDomain(req request.McpBindDomainUpdate) error
|
||||
}
|
||||
|
||||
func NewIMcpServerService() IMcpServerService {
|
||||
return &McpServerService{}
|
||||
}
|
||||
|
||||
func (m McpServerService) Page(req request.McpServerSearch) response.McpServersRes {
|
||||
var (
|
||||
res response.McpServersRes
|
||||
items []response.McpServerDTO
|
||||
)
|
||||
|
||||
total, data, _ := mcpServerRepo.Page(req.PageInfo.Page, req.PageInfo.PageSize)
|
||||
for _, item := range data {
|
||||
_ = syncMcpServerContainerStatus(&item)
|
||||
serverDTO := response.McpServerDTO{
|
||||
McpServer: item,
|
||||
Environments: make([]request.Environment, 0),
|
||||
Volumes: make([]request.Volume, 0),
|
||||
}
|
||||
project, _ := docker.GetComposeProject(item.Name, path.Join(constant.McpDir, item.Name), []byte(item.DockerCompose), []byte(item.Env), true)
|
||||
for _, service := range project.Services {
|
||||
if service.Environment != nil {
|
||||
for key, value := range service.Environment {
|
||||
serverDTO.Environments = append(serverDTO.Environments, request.Environment{
|
||||
Key: key,
|
||||
Value: *value,
|
||||
})
|
||||
}
|
||||
}
|
||||
if service.Volumes != nil {
|
||||
for _, volume := range service.Volumes {
|
||||
serverDTO.Volumes = append(serverDTO.Volumes, request.Volume{
|
||||
Source: volume.Source,
|
||||
Target: volume.Target,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
items = append(items, serverDTO)
|
||||
}
|
||||
res.Total = total
|
||||
res.Items = items
|
||||
return res
|
||||
}
|
||||
|
||||
func (m McpServerService) Update(req request.McpServerUpdate) error {
|
||||
mcpServer, err := mcpServerRepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mcpServer.Port != req.Port {
|
||||
if err := checkPortExist(req.Port); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if mcpServer.ContainerName != req.ContainerName {
|
||||
if err := checkContainerName(req.ContainerName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
mcpServer.Name = req.Name
|
||||
mcpServer.ContainerName = req.ContainerName
|
||||
mcpServer.Port = req.Port
|
||||
mcpServer.Command = req.Command
|
||||
mcpServer.BaseURL = req.BaseURL
|
||||
mcpServer.SsePath = req.SsePath
|
||||
mcpServer.HostIP = req.HostIP
|
||||
if err := handleCreateParams(mcpServer, req.Environments, req.Volumes); err != nil {
|
||||
return err
|
||||
}
|
||||
env := handleEnv(mcpServer)
|
||||
mcpDir := path.Join(constant.McpDir, mcpServer.Name)
|
||||
envPath := path.Join(mcpDir, ".env")
|
||||
if err := gotenv.Write(env, envPath); err != nil {
|
||||
return err
|
||||
}
|
||||
dockerComposePath := path.Join(mcpDir, "docker-compose.yml")
|
||||
if err := files.NewFileOp().SaveFile(dockerComposePath, mcpServer.DockerCompose, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
mcpServer.Status = constant.RuntimeStarting
|
||||
if err := mcpServerRepo.Save(mcpServer); err != nil {
|
||||
return err
|
||||
}
|
||||
go startMcp(mcpServer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m McpServerService) Create(create request.McpServerCreate) error {
|
||||
servers, _ := mcpServerRepo.List()
|
||||
for _, server := range servers {
|
||||
if server.Port == create.Port {
|
||||
return buserr.New("ErrPortInUsed")
|
||||
}
|
||||
if server.ContainerName == create.ContainerName {
|
||||
return buserr.New("ErrContainerName")
|
||||
}
|
||||
if server.Name == create.Name {
|
||||
return buserr.New(constant.ErrNameIsExist)
|
||||
}
|
||||
if server.SsePath == create.SsePath {
|
||||
return buserr.New("ErrSsePath")
|
||||
}
|
||||
}
|
||||
if err := checkPortExist(create.Port); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkContainerName(create.ContainerName); err != nil {
|
||||
return err
|
||||
}
|
||||
mcpDir := path.Join(constant.McpDir, create.Name)
|
||||
mcpServer := &model.McpServer{
|
||||
Name: create.Name,
|
||||
ContainerName: create.ContainerName,
|
||||
Port: create.Port,
|
||||
Command: create.Command,
|
||||
Status: constant.RuntimeStarting,
|
||||
BaseURL: create.BaseURL,
|
||||
SsePath: create.SsePath,
|
||||
Dir: mcpDir,
|
||||
HostIP: create.HostIP,
|
||||
}
|
||||
if err := handleCreateParams(mcpServer, create.Environments, create.Volumes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env := handleEnv(mcpServer)
|
||||
filesOP := files.NewFileOp()
|
||||
if !filesOP.Stat(mcpDir) {
|
||||
_ = filesOP.CreateDir(mcpDir, 0644)
|
||||
}
|
||||
envPath := path.Join(mcpDir, ".env")
|
||||
if err := gotenv.Write(env, envPath); err != nil {
|
||||
return err
|
||||
}
|
||||
dockerComposePath := path.Join(mcpDir, "docker-compose.yml")
|
||||
if err := filesOP.SaveFile(dockerComposePath, mcpServer.DockerCompose, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mcpServerRepo.Create(mcpServer); err != nil {
|
||||
return err
|
||||
}
|
||||
addProxy(mcpServer)
|
||||
go startMcp(mcpServer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m McpServerService) Delete(id uint) error {
|
||||
mcpServer, err := mcpServerRepo.GetFirst(commonRepo.WithByID(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
composePath := path.Join(constant.McpDir, mcpServer.Name, "docker-compose.yml")
|
||||
_, _ = compose.Down(composePath)
|
||||
_ = files.NewFileOp().DeleteDir(path.Join(constant.McpDir, mcpServer.Name))
|
||||
|
||||
websiteID := GetWebsiteID()
|
||||
if websiteID > 0 {
|
||||
websiteService := NewIWebsiteService()
|
||||
delProxyReq := request.WebsiteProxyDel{
|
||||
ID: websiteID,
|
||||
Name: mcpServer.Name,
|
||||
}
|
||||
_ = websiteService.DeleteProxy(delProxyReq)
|
||||
}
|
||||
return mcpServerRepo.DeleteBy(commonRepo.WithByID(id))
|
||||
}
|
||||
|
||||
func (m McpServerService) Operate(req request.McpServerOperate) error {
|
||||
mcpServer, err := mcpServerRepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
composePath := path.Join(mcpServer.Dir, "docker-compose.yml")
|
||||
var out string
|
||||
switch req.Operate {
|
||||
case "start":
|
||||
out, err = compose.Up(composePath)
|
||||
mcpServer.Status = constant.RuntimeRunning
|
||||
case "stop":
|
||||
out, err = compose.Down(composePath)
|
||||
mcpServer.Status = constant.RuntimeStopped
|
||||
case "restart":
|
||||
out, err = compose.Restart(composePath)
|
||||
mcpServer.Status = constant.RuntimeRunning
|
||||
}
|
||||
if err != nil {
|
||||
mcpServer.Status = constant.RuntimeError
|
||||
mcpServer.Message = out
|
||||
}
|
||||
return mcpServerRepo.Save(mcpServer)
|
||||
}
|
||||
|
||||
func (m McpServerService) GetBindDomain() (response.McpBindDomainRes, error) {
|
||||
var res response.McpBindDomainRes
|
||||
websiteID := GetWebsiteID()
|
||||
if websiteID == 0 {
|
||||
return res, nil
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
res.WebsiteID = website.ID
|
||||
res.Domain = website.PrimaryDomain
|
||||
if website.WebsiteSSLID > 0 {
|
||||
res.SSLID = website.WebsiteSSLID
|
||||
ssl, _ := websiteSSLRepo.GetFirst(commonRepo.WithByID(website.WebsiteSSLID))
|
||||
res.AcmeAccountID = ssl.AcmeAccountID
|
||||
}
|
||||
res.ConnUrl = fmt.Sprintf("%s://%s", strings.ToLower(website.Protocol), website.PrimaryDomain)
|
||||
res.AllowIPs = GetAllowIps(website)
|
||||
return res, nil
|
||||
|
||||
}
|
||||
|
||||
func (m McpServerService) BindDomain(req request.McpBindDomain) error {
|
||||
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
|
||||
if nginxInstall.ID == 0 {
|
||||
return buserr.New("ErrOpenrestyInstall")
|
||||
}
|
||||
var (
|
||||
ipList []string
|
||||
err error
|
||||
)
|
||||
if len(req.IPList) > 0 {
|
||||
ipList, err = common.HandleIPList(req.IPList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
ssl, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ssl.Pem == "" {
|
||||
return buserr.New("ErrSSL")
|
||||
}
|
||||
}
|
||||
group, _ := groupRepo.Get(groupRepo.WithByWebsiteDefault())
|
||||
createWebsiteReq := request.WebsiteCreate{
|
||||
PrimaryDomain: req.Domain,
|
||||
Alias: strings.ToLower(req.Domain),
|
||||
Type: constant.Static,
|
||||
WebsiteGroupID: group.ID,
|
||||
}
|
||||
websiteService := NewIWebsiteService()
|
||||
if err := websiteService.CreateWebsite(createWebsiteReq); err != nil {
|
||||
return err
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(strings.ToLower(req.Domain)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = settingRepo.UpdateOrCreate("MCP_WEBSITE_ID", fmt.Sprintf("%d", website.ID))
|
||||
if len(ipList) > 0 {
|
||||
if err = ConfigAllowIPs(ipList, website); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
sslReq := request.WebsiteHTTPSOp{
|
||||
WebsiteID: website.ID,
|
||||
Enable: true,
|
||||
Type: "existed",
|
||||
WebsiteSSLID: req.SSLID,
|
||||
HttpConfig: "HTTPSOnly",
|
||||
}
|
||||
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = addMCPProxy(website.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m McpServerService) UpdateBindDomain(req request.McpBindDomainUpdate) error {
|
||||
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
|
||||
if nginxInstall.ID == 0 {
|
||||
return buserr.New("ErrOpenrestyInstall")
|
||||
}
|
||||
var (
|
||||
ipList []string
|
||||
err error
|
||||
)
|
||||
if len(req.IPList) > 0 {
|
||||
ipList, err = common.HandleIPList(req.IPList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
ssl, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ssl.Pem == "" {
|
||||
return buserr.New("ErrSSL")
|
||||
}
|
||||
}
|
||||
websiteService := NewIWebsiteService()
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ConfigAllowIPs(ipList, website); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
sslReq := request.WebsiteHTTPSOp{
|
||||
WebsiteID: website.ID,
|
||||
Enable: true,
|
||||
Type: "existed",
|
||||
WebsiteSSLID: req.SSLID,
|
||||
HttpConfig: "HTTPSOnly",
|
||||
}
|
||||
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if website.WebsiteSSLID > 0 && req.SSLID == 0 {
|
||||
sslReq := request.WebsiteHTTPSOp{
|
||||
WebsiteID: website.ID,
|
||||
Enable: false,
|
||||
}
|
||||
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
go updateMcpConfig(website.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateMcpConfig(websiteID uint) {
|
||||
servers, _ := mcpServerRepo.List()
|
||||
if len(servers) == 0 {
|
||||
return
|
||||
}
|
||||
website, _ := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
|
||||
websiteDomain := website.Domains[0]
|
||||
var baseUrl string
|
||||
if website.Protocol == constant.ProtocolHTTP {
|
||||
baseUrl = fmt.Sprintf("http://%s", websiteDomain.Domain)
|
||||
} else {
|
||||
baseUrl = fmt.Sprintf("https://%s", websiteDomain.Domain)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for _, server := range servers {
|
||||
if server.BaseURL != baseUrl {
|
||||
server.BaseURL = baseUrl
|
||||
server.HostIP = "127.0.0.1"
|
||||
_ = updateMcpServer(&server)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func addProxy(server *model.McpServer) {
|
||||
websiteID := GetWebsiteID()
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("[mcp] add proxy failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("[mcp] add proxy failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
|
||||
if !fileOp.Stat(includeDir) {
|
||||
if err = fileOp.CreateDir(includeDir, 0644); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
config, err := parser.NewStringParser(string(nginx_conf.SSE)).Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
includePath := path.Join(includeDir, server.Name+".conf")
|
||||
config.FilePath = includePath
|
||||
directives := config.Directives
|
||||
location, ok := directives[0].(*components.Location)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, server.SsePath)})
|
||||
location.ChangePath("^~", server.SsePath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
global.LOG.Errorf("write config failed, err: %v", buserr.WithErr(constant.ErrUpdateBuWebsite, err))
|
||||
return
|
||||
}
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
|
||||
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
|
||||
global.LOG.Errorf("update nginx config failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func addMCPProxy(websiteID uint) error {
|
||||
servers, _ := mcpServerRepo.List()
|
||||
if len(servers) == 0 {
|
||||
return nil
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
|
||||
if !fileOp.Stat(includeDir) {
|
||||
if err = fileOp.CreateDir(includeDir, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
config, err := parser.NewStringParser(string(nginx_conf.SSE)).Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
websiteDomain := website.Domains[0]
|
||||
var baseUrl string
|
||||
if website.Protocol == constant.ProtocolHTTP {
|
||||
baseUrl = fmt.Sprintf("http://%s", websiteDomain.Domain)
|
||||
} else {
|
||||
baseUrl = fmt.Sprintf("https://%s", websiteDomain.Domain)
|
||||
}
|
||||
if websiteDomain.Port != 80 && websiteDomain.Port != 443 {
|
||||
baseUrl = fmt.Sprintf("%s:%d", baseUrl, websiteDomain.Port)
|
||||
}
|
||||
for _, server := range servers {
|
||||
includePath := path.Join(includeDir, server.Name+".conf")
|
||||
config.FilePath = includePath
|
||||
directives := config.Directives
|
||||
location, ok := directives[0].(*components.Location)
|
||||
if !ok {
|
||||
err = errors.New("error")
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, server.SsePath)})
|
||||
location.ChangePath("^~", server.SsePath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
|
||||
}
|
||||
server.BaseURL = baseUrl
|
||||
server.HostIP = "127.0.0.1"
|
||||
go updateMcpServer(&server)
|
||||
}
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
|
||||
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateMcpServer(mcpServer *model.McpServer) error {
|
||||
env := handleEnv(mcpServer)
|
||||
if err := gotenv.Write(env, path.Join(mcpServer.Dir, ".env")); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = mcpServerRepo.Save(mcpServer)
|
||||
composePath := path.Join(constant.McpDir, mcpServer.Name, "docker-compose.yml")
|
||||
_, _ = compose.Down(composePath)
|
||||
if _, err := compose.Up(composePath); err != nil {
|
||||
mcpServer.Status = constant.RuntimeError
|
||||
mcpServer.Message = err.Error()
|
||||
}
|
||||
return mcpServerRepo.Save(mcpServer)
|
||||
}
|
||||
|
||||
func handleEnv(mcpServer *model.McpServer) gotenv.Env {
|
||||
env := make(gotenv.Env)
|
||||
env["CONTAINER_NAME"] = mcpServer.ContainerName
|
||||
env["COMMAND"] = mcpServer.Command
|
||||
env["PANEL_APP_PORT_HTTP"] = strconv.Itoa(mcpServer.Port)
|
||||
env["BASE_URL"] = mcpServer.BaseURL
|
||||
env["SSE_PATH"] = mcpServer.SsePath
|
||||
env["HOST_IP"] = mcpServer.HostIP
|
||||
envStr, _ := gotenv.Marshal(env)
|
||||
mcpServer.Env = envStr
|
||||
return env
|
||||
}
|
||||
|
||||
func handleCreateParams(mcpServer *model.McpServer, environments []request.Environment, volumes []request.Volume) error {
|
||||
var composeContent []byte
|
||||
if mcpServer.ID == 0 {
|
||||
composeContent = mcp.DefaultMcpCompose
|
||||
} else {
|
||||
composeContent = []byte(mcpServer.DockerCompose)
|
||||
}
|
||||
composeMap := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal(composeContent, &composeMap); err != nil {
|
||||
return err
|
||||
}
|
||||
services, serviceValid := composeMap["services"].(map[string]interface{})
|
||||
if !serviceValid {
|
||||
return buserr.New(constant.ErrFileParse)
|
||||
}
|
||||
serviceName := ""
|
||||
serviceValue := make(map[string]interface{})
|
||||
|
||||
if mcpServer.ID > 0 {
|
||||
serviceName = mcpServer.Name
|
||||
serviceValue = services[serviceName].(map[string]interface{})
|
||||
} else {
|
||||
for name, service := range services {
|
||||
serviceName = name
|
||||
serviceValue = service.(map[string]interface{})
|
||||
break
|
||||
}
|
||||
delete(services, serviceName)
|
||||
}
|
||||
delete(serviceValue, "environment")
|
||||
if len(environments) > 0 {
|
||||
envMap := make(map[string]string)
|
||||
for _, env := range environments {
|
||||
envMap[env.Key] = env.Value
|
||||
}
|
||||
serviceValue["environment"] = envMap
|
||||
}
|
||||
delete(serviceValue, "volumes")
|
||||
if len(volumes) > 0 {
|
||||
volumeList := make([]string, 0)
|
||||
for _, volume := range volumes {
|
||||
volumeList = append(volumeList, fmt.Sprintf("%s:%s", volume.Source, volume.Target))
|
||||
}
|
||||
serviceValue["volumes"] = volumeList
|
||||
}
|
||||
|
||||
services[mcpServer.Name] = serviceValue
|
||||
composeByte, err := yaml.Marshal(composeMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mcpServer.DockerCompose = string(composeByte)
|
||||
return nil
|
||||
}
|
||||
|
||||
func startMcp(mcpServer *model.McpServer) {
|
||||
composePath := path.Join(constant.McpDir, mcpServer.Name, "docker-compose.yml")
|
||||
if mcpServer.Status != constant.RuntimeNormal {
|
||||
_, _ = compose.Down(composePath)
|
||||
}
|
||||
if out, err := compose.Up(composePath); err != nil {
|
||||
mcpServer.Status = constant.RuntimeError
|
||||
mcpServer.Message = out
|
||||
} else {
|
||||
mcpServer.Status = constant.RuntimeRunning
|
||||
mcpServer.Message = ""
|
||||
}
|
||||
_ = syncMcpServerContainerStatus(mcpServer)
|
||||
}
|
||||
|
||||
func syncMcpServerContainerStatus(mcpServer *model.McpServer) error {
|
||||
containerNames := []string{mcpServer.ContainerName}
|
||||
cli, err := docker.NewClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
containers, err := cli.ListContainersByName(containerNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
mcpServer.Status = constant.RuntimeStopped
|
||||
return mcpServerRepo.Save(mcpServer)
|
||||
}
|
||||
container := containers[0]
|
||||
switch container.State {
|
||||
case "exited":
|
||||
mcpServer.Status = constant.RuntimeError
|
||||
case "running":
|
||||
mcpServer.Status = constant.RuntimeRunning
|
||||
case "paused":
|
||||
mcpServer.Status = constant.RuntimeStopped
|
||||
case "restarting":
|
||||
mcpServer.Status = constant.RuntimeReStarting
|
||||
default:
|
||||
if mcpServer.Status != constant.RuntimeBuildIng {
|
||||
mcpServer.Status = constant.RuntimeStopped
|
||||
}
|
||||
}
|
||||
return mcpServerRepo.Save(mcpServer)
|
||||
}
|
||||
|
||||
func GetWebsiteID() uint {
|
||||
websiteID, _ := settingRepo.Get(settingRepo.WithByKey("MCP_WEBSITE_ID"))
|
||||
if websiteID.Value == "" {
|
||||
return 0
|
||||
}
|
||||
websiteIDUint, _ := strconv.ParseUint(websiteID.Value, 10, 64)
|
||||
return uint(websiteIDUint)
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
@@ -42,6 +44,7 @@ type ISettingService interface {
|
||||
HandlePasswordExpired(c *gin.Context, old, new string) error
|
||||
GenerateApiKey() (string, error)
|
||||
UpdateApiConfig(req dto.ApiInterfaceConfig) error
|
||||
GenerateRSAKey() error
|
||||
}
|
||||
|
||||
func NewISettingService() ISettingService {
|
||||
@@ -516,3 +519,47 @@ func (u *SettingService) UpdateApiConfig(req dto.ApiInterfaceConfig) error {
|
||||
global.CONF.System.ApiKeyValidityTime = req.ApiKeyValidityTime
|
||||
return nil
|
||||
}
|
||||
|
||||
func exportPrivateKeyToPEM(privateKey *rsa.PrivateKey) string {
|
||||
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
|
||||
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: privateKeyBytes,
|
||||
})
|
||||
return string(privateKeyPEM)
|
||||
}
|
||||
|
||||
func exportPublicKeyToPEM(publicKey *rsa.PublicKey) (string, error) {
|
||||
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "PUBLIC KEY",
|
||||
Bytes: publicKeyBytes,
|
||||
})
|
||||
return string(publicKeyPEM), nil
|
||||
}
|
||||
|
||||
func (u *SettingService) GenerateRSAKey() error {
|
||||
priKey, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PRIVATE_KEY"))
|
||||
pubKey, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PUBLIC_KEY"))
|
||||
if priKey.Value != "" && pubKey.Value != "" {
|
||||
return nil
|
||||
}
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privateKeyPEM := exportPrivateKeyToPEM(privateKey)
|
||||
publicKeyPEM, err := exportPublicKeyToPEM(&privateKey.PublicKey)
|
||||
err = settingRepo.UpdateOrCreate("PASSWORD_PRIVATE_KEY", privateKeyPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = settingRepo.UpdateOrCreate("PASSWORD_PUBLIC_KEY", publicKeyPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -87,13 +87,18 @@ type IWebsiteService interface {
|
||||
LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error)
|
||||
UpdateSiteDir(req request.WebsiteUpdateDir) error
|
||||
UpdateSitePermission(req request.WebsiteUpdateDirPermission) error
|
||||
|
||||
OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
GetProxies(id uint) (res []request.WebsiteProxyConfig, err error)
|
||||
UpdateProxyFile(req request.NginxProxyUpdate) (err error)
|
||||
DeleteProxy(req request.WebsiteProxyDel) (err error)
|
||||
|
||||
GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error)
|
||||
UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
|
||||
|
||||
GetAntiLeech(id uint) (*response.NginxAntiLeechRes, error)
|
||||
UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err error)
|
||||
|
||||
OperateRedirect(req request.NginxRedirectReq) (err error)
|
||||
GetRedirect(id uint) (res []response.NginxRedirectConfig, err error)
|
||||
UpdateRedirectFile(req request.NginxRedirectUpdate) (err error)
|
||||
@@ -422,7 +427,9 @@ func (w WebsiteService) UpdateWebsite(req request.WebsiteUpdate) error {
|
||||
}
|
||||
}
|
||||
website.PrimaryDomain = req.PrimaryDomain
|
||||
website.WebsiteGroupID = req.WebsiteGroupID
|
||||
if req.WebsiteGroupID > 0 {
|
||||
website.WebsiteGroupID = req.WebsiteGroupID
|
||||
}
|
||||
website.Remark = req.Remark
|
||||
website.IPV6 = req.IPV6
|
||||
|
||||
@@ -491,6 +498,10 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
|
||||
if err := websiteDomainRepo.DeleteBy(ctx, websiteDomainRepo.WithWebsiteId(req.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
websiteID := GetWebsiteID()
|
||||
if req.ID == websiteID {
|
||||
_ = settingRepo.Update("MCP_WEBSITE_ID", "0")
|
||||
}
|
||||
tx.Commit()
|
||||
|
||||
uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/website/%s", website.Alias))
|
||||
@@ -1553,6 +1564,29 @@ func (w WebsiteService) UpdateSitePermission(req request.WebsiteUpdateDirPermiss
|
||||
return websiteRepo.Save(context.Background(), &website)
|
||||
}
|
||||
|
||||
func (w WebsiteService) DeleteProxy(req request.WebsiteProxyDel) (err error) {
|
||||
fileOp := files.NewFileOp()
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
|
||||
if !fileOp.Stat(includeDir) {
|
||||
_ = fileOp.CreateDir(includeDir, 0755)
|
||||
}
|
||||
fileName := fmt.Sprintf("%s.conf", req.Name)
|
||||
includePath := path.Join(includeDir, fileName)
|
||||
backName := fmt.Sprintf("%s.bak", req.Name)
|
||||
backPath := path.Join(includeDir, backName)
|
||||
_ = fileOp.DeleteFile(includePath)
|
||||
_ = fileOp.DeleteFile(backPath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
}
|
||||
|
||||
func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error) {
|
||||
var (
|
||||
website model.Website
|
||||
@@ -1736,11 +1770,22 @@ func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, e
|
||||
}
|
||||
directives := config.GetDirectives()
|
||||
|
||||
location, ok := directives[0].(*components.Location)
|
||||
if !ok {
|
||||
err = errors.New("error")
|
||||
return
|
||||
var (
|
||||
location *components.Location
|
||||
ok bool
|
||||
)
|
||||
for _, directive := range directives {
|
||||
if directive.GetName() == "location" {
|
||||
location, ok = directive.(*components.Location)
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if location == nil {
|
||||
return nil, buserr.New("ErrConfigParse")
|
||||
}
|
||||
|
||||
proxyConfig.ProxyPass = location.ProxyPass
|
||||
proxyConfig.Cache = location.Cache
|
||||
if location.CacheTime > 0 {
|
||||
@@ -2043,7 +2088,19 @@ func (w WebsiteService) UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err e
|
||||
}
|
||||
newBlock.Directives = append(newBlock.Directives, ifDir)
|
||||
newDirective.Block = newBlock
|
||||
block.Directives = append(block.Directives, &newDirective)
|
||||
|
||||
index := -1
|
||||
for i, directive := range block.Directives {
|
||||
if directive.GetName() == "include" {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index != -1 {
|
||||
block.Directives = append(block.Directives[:index], append([]components.IDirective{&newDirective}, block.Directives[index:]...)...)
|
||||
} else {
|
||||
block.Directives = append(block.Directives, &newDirective)
|
||||
}
|
||||
}
|
||||
|
||||
if err = nginx.WriteConfig(nginxFull.SiteConfig.Config, nginx.IndentedStyle); err != nil {
|
||||
|
||||
@@ -642,9 +642,15 @@ func applySSL(website model.Website, websiteSSL model.WebsiteSSL, req request.We
|
||||
}
|
||||
if param.Name == "ssl_protocols" {
|
||||
nginxParams[i].Params = req.SSLProtocol
|
||||
if len(req.SSLProtocol) == 0 {
|
||||
nginxParams[i].Params = []string{"TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1"}
|
||||
}
|
||||
}
|
||||
if param.Name == "ssl_ciphers" {
|
||||
nginxParams[i].Params = []string{req.Algorithm}
|
||||
if len(req.Algorithm) == 0 {
|
||||
nginxParams[i].Params = []string{"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED"}
|
||||
}
|
||||
}
|
||||
}
|
||||
if req.Hsts {
|
||||
@@ -1106,3 +1112,55 @@ func getResourceContent(fileOp files.FileOp, resourcePath string) (string, error
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func ConfigAllowIPs(ips []string, website model.Website) error {
|
||||
nginxFull, err := getNginxFull(&website)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nginxConfig := nginxFull.SiteConfig
|
||||
config := nginxFull.SiteConfig.Config
|
||||
server := config.FindServers()[0]
|
||||
server.RemoveDirective("allow", nil)
|
||||
server.RemoveDirective("deny", nil)
|
||||
if len(ips) > 0 {
|
||||
server.UpdateAllowIPs(ips)
|
||||
}
|
||||
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
return nginxCheckAndReload(nginxConfig.OldContent, config.FilePath, nginxFull.Install.ContainerName)
|
||||
}
|
||||
|
||||
func GetAllowIps(website model.Website) []string {
|
||||
nginxFull, err := getNginxFull(&website)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
config := nginxFull.SiteConfig.Config
|
||||
server := config.FindServers()[0]
|
||||
dirs := server.GetDirectives()
|
||||
var ips []string
|
||||
for _, dir := range dirs {
|
||||
if dir.GetName() == "allow" {
|
||||
ips = append(ips, dir.GetParameters()...)
|
||||
}
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
func ConfigAIProxy(website model.Website) error {
|
||||
nginxFull, err := getNginxFull(&website)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
config := nginxFull.SiteConfig.Config
|
||||
server := config.FindServers()[0]
|
||||
dirs := server.GetDirectives()
|
||||
for _, dir := range dirs {
|
||||
if dir.GetName() == "location" && dir.GetParameters()[0] == "/" {
|
||||
server.UpdateRootProxyForAi([]string{fmt.Sprintf("http://%s", website.Proxy)})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ type System struct {
|
||||
Language string `mapstructure:"language"`
|
||||
IsDemo bool `mapstructure:"is_demo"`
|
||||
IsIntl bool `mapstructure:"is_intl"`
|
||||
LicenseVerify string `mapstructure:"license_verify"`
|
||||
AppRepo string `mapstructure:"app_repo"`
|
||||
ChangeUserInfo string `mapstructure:"change_user_info"`
|
||||
OneDriveID string `mapstructure:"one_drive_id"`
|
||||
|
||||
@@ -32,6 +32,11 @@ var WebUrlMap = map[string]struct{}{
|
||||
"/apps/installed": {},
|
||||
"/apps/upgrade": {},
|
||||
|
||||
"/ai": {},
|
||||
"/ai/model": {},
|
||||
"/ai/gpu": {},
|
||||
"/ai/mcp": {},
|
||||
|
||||
"/containers": {},
|
||||
"/containers/container": {},
|
||||
"/containers/image": {},
|
||||
@@ -113,11 +118,11 @@ var WebUrlMap = map[string]struct{}{
|
||||
"/xpack/monitor/rank": {},
|
||||
"/xpack/monitor/log": {},
|
||||
"/xpack/tamper": {},
|
||||
"/xpack/gpu": {},
|
||||
"/xpack/alert/dashboard": {},
|
||||
"/xpack/alert/log": {},
|
||||
"/xpack/alert/setting": {},
|
||||
"/xpack/setting": {},
|
||||
"/xpack/app": {},
|
||||
}
|
||||
|
||||
var DynamicRoutes = []string{
|
||||
|
||||
@@ -17,4 +17,5 @@ var (
|
||||
RuntimeDir = path.Join(DataDir, "runtime")
|
||||
RecycleBinDir = "/.1panel_clash"
|
||||
SSLLogDir = path.Join(global.CONF.System.DataDir, "log", "ssl")
|
||||
McpDir = path.Join(DataDir, "mcp")
|
||||
)
|
||||
|
||||
@@ -182,3 +182,10 @@ var (
|
||||
ErrAlertSync = "ErrAlertSync"
|
||||
ErrAlertRemote = "ErrAlertRemote"
|
||||
)
|
||||
|
||||
// mobile app
|
||||
var (
|
||||
ErrVerifyToken = "ErrVerifyToken"
|
||||
ErrInvalidToken = "ErrInvalidToken"
|
||||
ErrExpiredToken = "ErrExpiredToken"
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
RuntimeStopped = "stopped"
|
||||
RuntimeUnhealthy = "unhealthy"
|
||||
RuntimeCreating = "creating"
|
||||
RuntimeReStarting = "restarting"
|
||||
|
||||
RuntimePHP = "php"
|
||||
RuntimeNode = "node"
|
||||
|
||||
@@ -6,6 +6,8 @@ const (
|
||||
StatusWaiting = "Waiting"
|
||||
StatusSuccess = "Success"
|
||||
StatusFailed = "Failed"
|
||||
StatusDeleted = "Deleted"
|
||||
StatusCanceled = "Canceled"
|
||||
StatusUploading = "Uploading"
|
||||
StatusEnable = "Enable"
|
||||
StatusDisable = "Disable"
|
||||
|
||||
@@ -13,8 +13,10 @@ func NewAppStoreJob() *app {
|
||||
|
||||
func (a *app) Run() {
|
||||
global.LOG.Info("AppStore scheduled task in progress ...")
|
||||
if err := service.NewIAppService().SyncAppListFromRemote(); err != nil {
|
||||
appService := service.NewIAppService()
|
||||
if err := appService.SyncAppListFromRemote(); err != nil {
|
||||
global.LOG.Errorf("AppStore sync failed %s", err.Error())
|
||||
}
|
||||
appService.SyncAppListFromLocal()
|
||||
global.LOG.Info("AppStore scheduled task has completed")
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ func Init() {
|
||||
_, _ = bundle.LoadMessageFileFS(fs, "lang/ja.yaml")
|
||||
_, _ = bundle.LoadMessageFileFS(fs, "lang/ru.yaml")
|
||||
_, _ = bundle.LoadMessageFileFS(fs, "lang/ms.yaml")
|
||||
_, _ = bundle.LoadMessageFileFS(fs, "lang/ko.yaml")
|
||||
}
|
||||
|
||||
func UseI18nForCmd(lang string) {
|
||||
|
||||
@@ -284,3 +284,13 @@ UserInfoPassHelp: "Tip: To change the password, you can execute the command: "
|
||||
DBConnErr: "Error: Failed to initialize database connection, {{ .err }}"
|
||||
SystemVersion: "version: "
|
||||
SystemMode: "mode: "
|
||||
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: 'Please install Openresty first'
|
||||
ErrSSL: "Certificate content is empty, please check the certificate!"
|
||||
ErrSsePath: "SSE path is duplicated"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: 'Token verification error, please reset and scan again.'
|
||||
ErrInvalidToken: 'Invalid token, please reset and scan again.'
|
||||
ErrExpiredToken: 'Token has expired, please reset and scan again.'
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#cmd
|
||||
AppVersion: "Lotfan noskhe-ye barname ra vared konid"
|
||||
AppCommands: "Dasturat-e marbut be barname"
|
||||
AppInit: "Barname ra shoru' konid"
|
||||
AppKeyVal: "Kelid-e barname (faghat az zabane englisi poshtibani mikonad)"
|
||||
AppCreateFileErr: "Khalgh-e file {{ .name }} namovaffagh bud {{ .err }}"
|
||||
AppCreateDirErr: "Khalgh-e folder {{ .name }} namovaffagh bud {{ .err }}"
|
||||
AppMissKey: "Kelid-e barname mojood nist, az -k estefade konid"
|
||||
AppMissVersion: "Noskhe-ye barname mojood nist, az -v estefade konid"
|
||||
AppVersionExist: "Noskhe ghablan mojood ast!"
|
||||
AppCreateSuccessful: "Khalgh ba movaffaghiat anjam shod!"
|
||||
AppWriteErr: "Neveshtan file {{ .name }} namovaffagh bud {{ .err }}"
|
||||
SudoHelper: "Lotfan in eskript ra be onvane karbare root ya ba mojavez sudo ejra konid"
|
||||
ListenIPCommands: "IP goosh dadan ra taghir dahid"
|
||||
ListenIPv4: "Goosh dadan bar IPv4"
|
||||
ListenIPv6: "Goosh dadan bar IPv6"
|
||||
ListenChangeSuccessful: "Taghir movaffagh! Hala bar {{ .value }} goosh midahad"
|
||||
ResetCommands: "E'ade setadad etela'at-e system"
|
||||
ResetMFA: "Fa'al-sazi do marhale'i 1Panel ra laghv konid"
|
||||
ResetHttps: "Voroud HTTPS baraye 1Panel ra laghv konid"
|
||||
ResetEntrance: "Voroud amin 1Panel ra laghv konid"
|
||||
ResetIPs: "Mahdudiyat IP-haye mojaz 1Panel ra laghv konid"
|
||||
ResetDomain: "Sazgari domain 1Panel ra laghv konid"
|
||||
RestoreCommands: "Bargasht be khadamate 1Panel va data"
|
||||
RestoreNoSuchFile: "Hich file mojood baraye bargasht nist"
|
||||
RestoreStep1: "(1/5) Shoru' bargasht be khadamate 1Panel va data az directory {{ .name }} ..."
|
||||
RestoreStep2: "(2/5) Bargasht be binary 1Panel ba movaffaghiat anjam shod"
|
||||
RestoreStep3: "(3/5) Bargasht be script 1Panel ba movaffaghiat anjam shod"
|
||||
RestoreStep4: "(4/5) Bargasht be khadamat 1Panel ba movaffaghiat anjam shod"
|
||||
RestoreStep5: "(5/5) Bargasht be data 1Panel ba movaffaghiat anjam shod"
|
||||
RestoreSuccessful: "Bargasht ba movaffaghiat anjam shod! Khadamat dar hale restart ast, lotfan montazer bashid..."
|
||||
UpdateCommands: "Be'rooz resani etela'at-e panel"
|
||||
UpdateUser: "Be'rooz resani karbare panel"
|
||||
UpdatePassword: "Be'rooz resani ramz-e panel"
|
||||
UpdatePort: "Be'rooz resani port-e panel"
|
||||
UpdateUserNull: "Khatta: karbare panel khalist!"
|
||||
UpdateUserBlank: "Khatta: karbare panel shamel faseleh ast!"
|
||||
UpdateUserFormat: "Khatta: format-e karbare panel na dorost ast! Faghat az englisi, chine'i, adad va _, ba tool-e 3-30 poshtibani mikonad"
|
||||
UpdateUserErr: "Khatta: be'rooz resani karbare panel namovaffagh bud, {{ .err }}"
|
||||
UpdateSuccessful: "Be'rooz resani ba movaffaghiat anjam shod!"
|
||||
UpdateUserResult: "Karbare panel: {{ .name }}"
|
||||
UpdatePasswordRead: "Khatta: khatayi dar khandan etela'at-e ramz-e panel, {{ .err }}"
|
||||
UpdatePasswordNull: "Khatta: ramz-e panel khalist!"
|
||||
UpdateUPasswordBlank: "Khatta: ramz-e panel shamel faseleh ast!"
|
||||
UpdatePasswordFormat: "Khatta: ramz-e panel faghat az harf, adad, karakter-haye vizhe !@#$%*_,.? ba tool-e 8-30 poshtibani mikonad!"
|
||||
UpdatePasswordLen: "Khatta: lotfan ramzi bishtar az 6 karakter vared konid!"
|
||||
UpdatePasswordRe: "Teyid-e ramz:"
|
||||
UpdatePasswordErr: "Khatta: be'rooz resani ramz-e panel namovaffagh bud, {{ .err }}"
|
||||
UpdatePasswordSame: "Khatta: do ramz ba ham yeksan nistand, lotfan barrasi konid va dobare talash konid!"
|
||||
UpdatePasswordResult: "Ramz-e panel: {{ .name }}"
|
||||
UpdatePortFormat: "Khatta: shomareye port bayad bein 1 ta 65535 bashad!"
|
||||
UpdatePortUsed: "Khatta: shomareye port ghablan estefade shode ast, lotfan barrasi konid va dobare talash konid!"
|
||||
UpdatePortErr: "Khatta: be'rooz resani port-e panel namovaffagh bud, {{ .err }}"
|
||||
UpdatePortResult: "Port-e panel: {{ .name }}"
|
||||
UpdatePortFirewallAdd: "Ezafe kardane ghanoun-e port firewall namovaffagh bud, {{ .err }}, lotfan port {{ .name }} ra be dast az qavaneen firewall ezafe konid."
|
||||
UpdatePortFirewallDel: "Khatta: hazf-e port firewall namovaffagh bud, {{ .err }}"
|
||||
UpdatePortFirewallReload: "Reload firewall namovaffagh bud, {{ .err }}, lotfan firewall ra be dast reload konid."
|
||||
UserInfo: "Daryaft etela'at-e panel"
|
||||
UserInfoAddr: "Address-e panel: "
|
||||
UserInfoPassHelp: "Nokte: baraye taghiri ramz, mitavanid dastur zir ra anjam dahid: "
|
||||
DBConnErr: "Khatta: etesal be database namovaffagh bud, {{ .err }}"
|
||||
SystemVersion: "noskhe: "
|
||||
SystemMode: "halat: "
|
||||
+50
-39
@@ -1,17 +1,17 @@
|
||||
ErrInvalidParams: "リクエストパラメーターエラー:{{.Detail}}"
|
||||
ErrTokenParse: "トークン生成エラー:{{.Detail}}"
|
||||
ErrInvalidParams: "リクエストパラメーターエラー: {{.Detail}}"
|
||||
ErrTokenParse: "トークン生成エラー: {{.Detail}}"
|
||||
ErrInitialPassword: "初期パスワードエラー"
|
||||
ErrInternalServer: "サービス内部エラー:{{.Detail}}"
|
||||
ErrInternalServer: "サービス内部エラー: {{.Detail}}"
|
||||
ErrRecordExist: "レコードはすでに存在します"
|
||||
ErrRecordNotFound: "記録が見つかりません"
|
||||
ErrStructTransform: "タイプ変換障害:{{.Detail}}"
|
||||
ErrNotLogin: "ユーザーはログインしません:{{.Detail}}"
|
||||
ErrPasswordExpired: "現在のパスワードが期限切れになっています:{{.Detail}}"
|
||||
ErrNotSupportType: "システムは現在のタイプをサポートしていません:{{.Detail}}"
|
||||
ErrApiConfigStatusInvalid: "APIインターフェイスアクセス禁止:{{.Detail}}"
|
||||
ErrApiConfigKeyInvalid: "APIインターフェイスキーエラー:{{.Detail}}"
|
||||
ErrApiConfigIPInvalid: "APIインターフェイスIPはホワイトリストにありません:{{.Detail}}"
|
||||
ErrApiConfigDisable: "このインターフェイスは、APIインターフェイスコールの使用を禁止しています:{{.Detail}}"
|
||||
ErrStructTransform: "タイプ変換障害: {{.Detail}}"
|
||||
ErrNotLogin: "ユーザーはログインしません: {{.Detail}}"
|
||||
ErrPasswordExpired: "現在のパスワードが期限切れになっています: {{.Detail}}"
|
||||
ErrNotSupportType: "システムは現在のタイプをサポートしていません: {{.Detail}}"
|
||||
ErrApiConfigStatusInvalid: "APIインターフェイスアクセス禁止: {{.Detail}}"
|
||||
ErrApiConfigKeyInvalid: "APIインターフェイスキーエラー: {{.Detail}}"
|
||||
ErrApiConfigIPInvalid: "APIインターフェイスIPはホワイトリストにありません: {{.Detail}}"
|
||||
ErrApiConfigDisable: "このインターフェイスは、APIインターフェイスコールの使用を禁止しています: {{.Detail}}"
|
||||
|
||||
#common
|
||||
ErrNameIsExist: "名前はすでに存在しています"
|
||||
@@ -92,10 +92,10 @@ ErrDomainIsExist: "ドメインはすでに存在しています"
|
||||
ErrAliasIsExist: "エイリアスはすでに存在しています"
|
||||
ErrAppDelete: '他のウェブサイトはこのアプリを使用します'
|
||||
ErrGroupIsUsed: "グループは使用されており、削除することはできません"
|
||||
ErrBackupMatch: "バックアップファイルは、ウェブサイトの現在の部分的なデータと一致しません:{{.Detail}}"
|
||||
ErrBackupMatch: "バックアップファイルは、ウェブサイトの現在の部分的なデータと一致しません: {{.Detail}}"
|
||||
ErrBackupExist: 'バックアップファイルは、存在しない元のデータの一部に対応しています。{{.Detail}}'
|
||||
ErrPHPResource: 'ローカルランタイムはスイッチングをサポートしません!'
|
||||
ErrPathPermission: 'インデックスディレクトリで1000:1000以外のアクセス許可を持つフォルダーが検出されたため、Webサイトにアクセスするとアクセスが拒否された場合があります。上の[保存]ボタンをクリックしてください'
|
||||
ErrPathPermission: 'インデックスディレクトリで1000: 1000以外のアクセス許可を持つフォルダーが検出されたため、Webサイトにアクセスするとアクセスが拒否された場合があります。上の[保存]ボタンをクリックしてください'
|
||||
ErrDomainIsUsed: "ドメインはすでにWebサイトで使用されています{{.Name}}"
|
||||
ErrDomainFormat: "{{.name}}ドメイン形式エラー"
|
||||
ErrDefaultAlias: "デフォルトは予約済みのコード名です。別のコード名を使用してください"
|
||||
@@ -124,7 +124,7 @@ ErrDeleteCAWithSSL: "現在の組織の下に発行された証明書があり
|
||||
ErrDeleteWithPanelSSL: "パネルSSL構成はこの証明書を使用しており、削除できません"
|
||||
ErrDefaultCA: "デフォルトの証明書当局は削除できません"
|
||||
ApplyWebSiteSSLLog: "{{.name}} Webサイト証明書の更新を開始します"
|
||||
ErrUpdateWebsiteSSL: "{{.name}}ウェブサイトは証明書の更新に失敗しました:{{.err}}"
|
||||
ErrUpdateWebsiteSSL: "{{.name}}ウェブサイトは証明書の更新に失敗しました: {{.err}}"
|
||||
ApplyWebSiteSSLSuccess: "ウェブサイトの証明書を正常に更新します"
|
||||
ErrExecShell: "スクリプトの実行に失敗した{{.err}}"
|
||||
ExecShellStart: "スクリプトの実行を開始します"
|
||||
@@ -146,7 +146,7 @@ ErrTypeOfRedis: "回復ファイルのタイプは、現在の永続性モード
|
||||
ErrInUsed: "{{.Detail}}が使用されており、削除できません"
|
||||
ErrObjectInUsed: "このオブジェクトは使用されており、削除することはできません"
|
||||
ErrPortRules: "ポートの数が一致しません。再入力してください!"
|
||||
ErrPgImagePull: "画像プルタイムアウト。画像の加速を構成するか、Postgres:16.0-Alpine画像を手動で引いて、再試行してください"
|
||||
ErrPgImagePull: "画像プルタイムアウト。画像の加速を構成するか、Postgres: 16.0-Alpine画像を手動で引いて、再試行してください"
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: "ビルドフォルダーは存在しません!ファイルの整合性を確認してください!"
|
||||
@@ -253,31 +253,42 @@ UpdateCommands: "パネル情報を更新します"
|
||||
UpdateUser: "パネルユーザーを更新します"
|
||||
UpdatePassword: "パスワードを更新します"
|
||||
UpdatePort: "パネルポートを更新します"
|
||||
UpdateUserNull: "エラー:パネルユーザーは空です!"
|
||||
UpdateUserBlank: "エラー:パネルユーザーにはスペースが含まれています!"
|
||||
UpdateUserFormat: "エラー:無効なパネルユーザー形式!英語、中国語、数字、_、長さ3〜30のみをサポートしています"
|
||||
UpdateUserErr: "エラー:パネルユーザーの更新に失敗しました{{.err}}"
|
||||
UpdateUserNull: "エラー: パネルユーザーは空です!"
|
||||
UpdateUserBlank: "エラー: パネルユーザーにはスペースが含まれています!"
|
||||
UpdateUserFormat: "エラー: 無効なパネルユーザー形式!英語、中国語、数字、_、長さ3〜30のみをサポートしています"
|
||||
UpdateUserErr: "エラー: パネルユーザーの更新に失敗しました{{.err}}"
|
||||
UpdateSuccessful: "更新成功!"
|
||||
UpdateUserResult: "パネルユーザー:{{.name}}"
|
||||
UpdatePasswordRead: "エラー:パネルパスワード情報の読み取りに失敗しました、{{.err}}"
|
||||
UpdatePasswordNull: "エラー:パネルパスワードは空です!"
|
||||
UpdateUPasswordBlank: "エラー:パスワードにはスペースが含まれています!"
|
||||
UpdatePasswordFormat: "エラー:パネルパスワードは、文字、数字、特殊文字のみをサポートします!@#$%*_、。、、長さ8-30!"
|
||||
UpdatePasswordLen: "エラー:6文字より長くパスワードを入力してください!"
|
||||
UpdatePasswordRe: "パスワードを認証する:"
|
||||
UpdatePasswordErr: "エラー:{{.err}}のパスワードの更新に失敗しました"
|
||||
UpdatePasswordSame: "エラー:2つのパスワードが一致しません。チェックして再試行してください!"
|
||||
UpdatePasswordResult: "パネルパスワード:{{.name}}"
|
||||
UpdatePortFormat: "エラー:入力ポート番号は1〜65535でなければなりません!"
|
||||
UpdatePortUsed: "エラー:ポート番号はすでに使用されています。チェックして再試行してください!"
|
||||
UpdatePortErr: "エラー:パネルポートの更新に失敗しました{{.err}}"
|
||||
UpdatePortResult: "パネルポート:{{.name}}"
|
||||
UpdateUserResult: "パネルユーザー: {{.name}}"
|
||||
UpdatePasswordRead: "エラー: パネルパスワード情報の読み取りに失敗しました、{{.err}}"
|
||||
UpdatePasswordNull: "エラー: パネルパスワードは空です!"
|
||||
UpdateUPasswordBlank: "エラー: パスワードにはスペースが含まれています!"
|
||||
UpdatePasswordFormat: "エラー: パネルパスワードは、文字、数字、特殊文字のみをサポートします!@#$%*_、。、、長さ8-30!"
|
||||
UpdatePasswordLen: "エラー: 6文字より長くパスワードを入力してください!"
|
||||
UpdatePasswordRe: "パスワードを認証する: "
|
||||
UpdatePasswordErr: "エラー: {{.err}}のパスワードの更新に失敗しました"
|
||||
UpdatePasswordSame: "エラー: 2つのパスワードが一致しません。チェックして再試行してください!"
|
||||
UpdatePasswordResult: "パネルパスワード: {{.name}}"
|
||||
UpdatePortFormat: "エラー: 入力ポート番号は1〜65535でなければなりません!"
|
||||
UpdatePortUsed: "エラー: ポート番号はすでに使用されています。チェックして再試行してください!"
|
||||
UpdatePortErr: "エラー: パネルポートの更新に失敗しました{{.err}}"
|
||||
UpdatePortResult: "パネルポート: {{.name}}"
|
||||
UpdatePortFirewallAdd: "ファイアウォールポートルールの追加に失敗した{{.err}}、ファイアウォールルールに{{.name}}ポートを手動で追加してください。"
|
||||
UpdatePortFirewallDel: "エラー:ファイアウォールポートの削除に失敗しました、{{.err}}"
|
||||
UpdatePortFirewallDel: "エラー: ファイアウォールポートの削除に失敗しました、{{.err}}"
|
||||
UpdatePortFirewallReload: "ファイアウォールのリロードに失敗しました{{.err}}、ファイアウォールを手動でリロードしてください。"
|
||||
UserInfo: "パネル情報を取得します"
|
||||
UserInfoAddr: "パネルアドレス:"
|
||||
UserInfoPassHelp: "ヒント:パスワードを変更するには、コマンドを実行できます。"
|
||||
DBConnErr: "エラー:データベース接続の初期化に失敗しました、{{.err}}"
|
||||
SystemVersion: "バージョン:"
|
||||
SystemMode: "モード:"
|
||||
UserInfoAddr: "パネルアドレス: "
|
||||
UserInfoPassHelp: "ヒント: パスワードを変更するには、コマンドを実行できます。"
|
||||
DBConnErr: "エラー: データベース接続の初期化に失敗しました、{{.err}}"
|
||||
SystemVersion: "バージョン: "
|
||||
SystemMode: "モード: "
|
||||
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: 'まず Openresty をインストールしてください'
|
||||
ErrSSL: "証明書の内容が空です。証明書を確認してください!"
|
||||
ErrSsePath: "SSE パスが重複しています"
|
||||
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: 'トークンの検証エラーです。リセット後、再度QRコードをスキャンしてください。'
|
||||
ErrInvalidToken: '無効なトークンです。リセット後、再度QRコードをスキャンしてください。'
|
||||
ErrExpiredToken: 'トークンの有効期限が切れました。リセット後、再度QRコードをスキャンしてください。'
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
ErrInvalidParams: "요청 매개변수 오류: {{ .detail }}"
|
||||
ErrTokenParse: "토큰 생성 오류: {{ .detail }}"
|
||||
ErrInitialPassword: "초기 비밀번호 오류"
|
||||
ErrInternalServer: "서버 내부 오류: {{ .detail }}"
|
||||
ErrRecordExist: "기록이 이미 존재합니다"
|
||||
ErrRecordNotFound: "기록을 찾을 수 없습니다"
|
||||
ErrStructTransform: "형식 변환 실패: {{ .detail }}"
|
||||
ErrNotLogin: "사용자가 로그인되지 않았습니다: {{ .detail }}"
|
||||
ErrPasswordExpired: "현재 비밀번호가 만료되었습니다: {{ .detail }}"
|
||||
ErrNotSupportType: "시스템에서 현재 유형을 지원하지 않습니다: {{ .detail }}"
|
||||
ErrApiConfigStatusInvalid: "API 인터페이스 액세스 금지: {{ .detail }}"
|
||||
ErrApiConfigKeyInvalid: "API 인터페이스 키 오류: {{ .detail }}"
|
||||
ErrApiConfigIPInvalid: "API 인터페이스 IP가 화이트리스트에 없습니다: {{ .detail }}"
|
||||
ErrApiConfigDisable: "이 인터페이스는 API 호출을 금지합니다: {{ .detail }}"
|
||||
ErrApiConfigKeyTimeInvalid: "API 인터페이스 타임스탬프 오류: {{ .detail }}"
|
||||
|
||||
# 공통
|
||||
ErrNameIsExist: "이름이 이미 존재합니다"
|
||||
ErrDemoEnvironment: "데모 서버에서는 이 작업이 금지되어 있습니다!"
|
||||
ErrCmdTimeout: "명령어 실행 시간이 초과되었습니다!"
|
||||
ErrCmdIllegal: "명령어에 잘못된 문자가 포함되어 있습니다. 수정 후 다시 시도하세요!"
|
||||
ErrPortExist: '{{ .port }} 포트가 {{ .type }} [{{ .name }}]에 의해 이미 사용 중입니다'
|
||||
TYPE_APP: "애플리케이션"
|
||||
TYPE_RUNTIME: "실행 환경"
|
||||
TYPE_DOMAIN: "도메인 이름"
|
||||
ErrTypePort: '포트 {{ .name }} 형식 오류'
|
||||
ErrTypePortRange: '포트 범위는 1-65535 사이여야 합니다'
|
||||
Success: "성공"
|
||||
Failed: "실패"
|
||||
SystemRestart: "시스템 재시작으로 인해 작업이 중단되었습니다"
|
||||
|
||||
# 애플리케이션
|
||||
ErrPortInUsed: "{{ .detail }} 포트가 이미 사용 중입니다"
|
||||
ErrAppLimit: "앱 설치 제한을 초과했습니다"
|
||||
ErrAppRequired: "{{ .detail }} 앱이 필요합니다"
|
||||
ErrNotInstall: "앱이 설치되지 않았습니다"
|
||||
ErrPortInOtherApp: "{{ .port }} 포트가 {{ .apps }} 앱에 의해 이미 사용 중입니다"
|
||||
ErrDbUserNotValid: "데이터베이스 사용자 이름과 비밀번호가 일치하지 않습니다!"
|
||||
ErrDockerComposeNotValid: "docker-compose 파일 형식 오류!"
|
||||
ErrUpdateBuWebsite: "애플리케이션이 성공적으로 업데이트되었지만, 웹사이트 구성 파일 수정에 실패했습니다. 구성을 확인하세요!"
|
||||
Err1PanelNetworkFailed: "기본 컨테이너 네트워크 생성 실패! {{ .detail }}"
|
||||
ErrFileParse: "애플리케이션 docker-compose 파일 분석 실패!"
|
||||
ErrInstallDirNotFound: "설치 디렉터리가 존재하지 않습니다"
|
||||
AppStoreIsUpToDate: "최신 상태입니다"
|
||||
LocalAppVersionNull: "{{.name}} 앱 버전이 동기화되지 않았습니다! 애플리케이션 목록에 추가할 수 없습니다"
|
||||
LocalAppVersionErr: "{{.name}} 버전 {{.version}} 동기화 실패! {{.err}}"
|
||||
ErrFileNotFound: "{{.name}} 파일이 존재하지 않습니다"
|
||||
ErrFileParseApp: "{{.name}} 파일 분석 실패 {{.err}}"
|
||||
ErrAppDirNull: "버전 폴더가 존재하지 않습니다"
|
||||
LocalAppErr: "앱 {{.name}} 동기화 실패! {{.err}}"
|
||||
ErrContainerName: "컨테이너 이름이 이미 존재합니다"
|
||||
ErrAppSystemRestart: "1Panel 재시작으로 인해 작업이 중단되었습니다"
|
||||
ErrCreateHttpClient: "HTTP 요청 생성 실패 {{.err}}"
|
||||
ErrHttpReqTimeOut: "요청 시간이 초과되었습니다 {{.err}}"
|
||||
ErrHttpReqFailed: "요청 실패 {{.err}}"
|
||||
ErrHttpReqNotFound: "파일이 존재하지 않습니다"
|
||||
ErrNoSuchHost: "네트워크 연결 실패"
|
||||
ErrImagePullTimeOut: "이미지 가져오기 시간 초과"
|
||||
ErrContainerNotFound: "{{ .name }} 컨테이너가 존재하지 않습니다"
|
||||
ErrContainerMsg: "{{ .name }} 컨테이너가 비정상적입니다. 자세한 내용은 컨테이너 페이지의 로그를 확인하세요"
|
||||
ErrAppBackup: "{{ .name }} 애플리케이션 백업 실패 오류 {{.err}}"
|
||||
ErrImagePull: "{{ .name }} 이미지 가져오기 실패 오류 {{.err}}"
|
||||
ErrVersionTooLow: "현재 1Panel 버전이 낮아 앱 스토어를 업데이트할 수 없습니다. 버전을 업그레이드하세요"
|
||||
ErrAppNameExist: "앱 이름이 이미 존재합니다"
|
||||
AppStoreIsSyncing: "앱 스토어가 동기화 중입니다. 나중에 다시 시도하세요"
|
||||
ErrGetCompose: "docker-compose.yml 파일 가져오기 실패! {{ .detail }}"
|
||||
ErrAppWarn: "비정상 상태, 로그를 확인하세요"
|
||||
ErrAppParamKey: "매개변수 {{ .name }} 필드 예외"
|
||||
ErrAppUpgrade: "애플리케이션 {{ .name }} 업그레이드 실패 {{ .err }}"
|
||||
AppRecover: "앱 {{ .name }} 롤백 완료"
|
||||
PullImageStart: "이미지 {{ .name }} 가져오기 시작"
|
||||
PullImageSuccess: "이미지 가져오기 성공"
|
||||
UpgradeAppStart: "애플리케이션 {{ .name }} 업그레이드 시작"
|
||||
UpgradeAppSuccess: "앱 {{ .name }} 업그레이드 성공"
|
||||
|
||||
# 파일
|
||||
ErrFileCanNotRead: "파일을 읽을 수 없습니다"
|
||||
ErrFileToLarge: "파일이 너무 큽니다"
|
||||
ErrPathNotFound: "경로를 찾을 수 없습니다"
|
||||
ErrMovePathFailed: "대상 경로가 원래 경로를 포함할 수 없습니다!"
|
||||
ErrLinkPathNotFound: "대상 경로가 존재하지 않습니다!"
|
||||
ErrFileIsExist: "파일 또는 디렉터리가 이미 존재합니다!"
|
||||
ErrFileUpload: "파일 업로드 실패 {{.name}} {{.detail}}"
|
||||
ErrFileDownloadDir: "다운로드 폴더는 지원되지 않습니다"
|
||||
ErrCmdNotFound: "{{ .name }} 명령이 존재하지 않습니다. 호스트에 이 명령을 먼저 설치하세요"
|
||||
ErrSourcePathNotFound: "소스 디렉터리가 존재하지 않습니다"
|
||||
ErrFavoriteExist: "이 경로는 이미 즐겨찾기에 추가되었습니다"
|
||||
ErrInvalidChar: "잘못된 문자는 금지됩니다"
|
||||
ErrPathNotDelete: "선택한 디렉터리는 삭제할 수 없습니다"
|
||||
|
||||
# 웹사이트
|
||||
ErrDomainIsExist: "도메인 이름이 이미 존재합니다"
|
||||
ErrAliasIsExist: "별칭이 이미 존재합니다"
|
||||
ErrAppDelete: "다른 웹사이트에서 이 애플리케이션을 사용 중입니다"
|
||||
ErrGroupIsUsed: "그룹이 사용 중이므로 삭제할 수 없습니다"
|
||||
ErrBackupMatch: "백업 파일이 현재 웹사이트 데이터와 일치하지 않습니다: {{ .detail }}"
|
||||
ErrBackupExist: "백업 파일이 원본 데이터의 일부와 일치하지 않습니다: {{ .detail }}"
|
||||
ErrPHPResource: "로컬 런타임에서 전환을 지원하지 않습니다!"
|
||||
ErrPathPermission: "인덱스 디렉터리에 1000:1000 권한이 아닌 폴더가 감지되었습니다. 웹사이트 액세스 시 '접근 거부' 오류가 발생할 수 있습니다. 상단의 저장 버튼을 클릭하세요"
|
||||
ErrDomainIsUsed: "도메인이 웹사이트 {{ .name }}에서 이미 사용 중입니다"
|
||||
ErrDomainFormat: "{{ .name }} 도메인 형식 오류"
|
||||
ErrDefaultAlias: "기본은 예약된 코드 이름입니다. 다른 이름을 사용하세요"
|
||||
ErrImageNotExist: "실행 환경 {{.name}} 이미지가 존재하지 않습니다. 실행 환경을 다시 편집하세요"
|
||||
|
||||
# SSL 관련
|
||||
ErrSSLCannotDelete: "인증서 {{ .name }}은(는) 웹사이트에서 사용 중이므로 삭제할 수 없습니다."
|
||||
ErrAccountCannotDelete: "계정과 연결된 인증서는 삭제할 수 없습니다."
|
||||
ErrSSLApply: "인증서가 성공적으로 서명되었지만 Openresty 다시 로드에 실패했습니다. 구성을 확인하세요!"
|
||||
ErrEmailIsExist: "이메일이 이미 존재합니다."
|
||||
ErrSSLKeyNotFound: "개인 키 파일이 존재하지 않습니다."
|
||||
ErrSSLCertificateNotFound: "인증서 파일이 존재하지 않습니다."
|
||||
ErrSSLKeyFormat: "개인 키 파일 검증 오류"
|
||||
ErrSSLCertificateFormat: "인증서 파일 형식 오류입니다. pem 형식을 사용하세요."
|
||||
ErrEabKidOrEabHmacKeyCannotBlank: "EabKid 또는 EabHmacKey 는 비워둘 수 없습니다."
|
||||
ErrOpenrestyNotFound: "HTTP 모드는 먼저 Openresty 를 설치해야 합니다."
|
||||
ApplySSLStart: "인증서를 신청하기 시작합니다. 도메인 이름 [{{ .domain }}], 신청 방법 [{{ .type }}]"
|
||||
dnsAccount: "DNS 자동"
|
||||
dnsManual: "DNS 수동"
|
||||
http: "HTTP"
|
||||
ApplySSLFailed: "도메인 [{{ .domain }}] 인증서 신청 실패, {{ .detail }}"
|
||||
ApplySSLSuccess: "도메인 [{{ .domain }}] 인증서 신청 성공!"
|
||||
DNSAccountName: "DNS 계정 [{{ .name }}] 제조사 [{{ .type }}]"
|
||||
PushDirLog: "인증서를 디렉토리 [{{ .path }}]에 푸시했습니다. {{ .status }}"
|
||||
ErrDeleteCAWithSSL: "현재 조직에 발급된 인증서가 있으므로 삭제할 수 없습니다."
|
||||
ErrDeleteWithPanelSSL: "패널 SSL 구성이 이 인증서를 사용 중이므로 삭제할 수 없습니다."
|
||||
ErrDefaultCA: "기본 인증 기관은 삭제할 수 없습니다."
|
||||
ApplyWebSiteSSLLog: "{{ .name }} 웹사이트 인증서를 업데이트하기 시작합니다."
|
||||
ErrUpdateWebsiteSSL: "{{ .name }} 웹사이트 인증서 업데이트 실패: {{ .err }}"
|
||||
ApplyWebSiteSSLSuccess: "웹사이트 인증서 업데이트 성공"
|
||||
ErrExecShell: "스크립트 실행 실패 {{ .err }}"
|
||||
ExecShellStart: "스크립트 실행 시작"
|
||||
ExecShellSuccess: "스크립트 실행 성공"
|
||||
StartUpdateSystemSSL: "시스템 인증서 업데이트 시작"
|
||||
UpdateSystemSSLSuccess: "시스템 인증서 업데이트 성공"
|
||||
|
||||
# MySQL
|
||||
ErrUserIsExist: "현재 사용자가 이미 존재합니다. 새로운 사용자를 입력하세요."
|
||||
ErrDatabaseIsExist: "현재 데이터베이스가 이미 존재합니다. 새로운 데이터베이스를 입력하세요."
|
||||
ErrExecTimeOut: "SQL 실행이 시간 초과되었습니다. 데이터베이스를 확인하세요."
|
||||
ErrRemoteExist: "원격 데이터베이스에 동일한 이름이 이미 존재합니다. 이름을 수정한 후 다시 시도하세요."
|
||||
ErrLocalExist: "로컬 데이터베이스에 동일한 이름이 이미 존재합니다. 이름을 수정한 후 다시 시도하세요."
|
||||
|
||||
# Redis
|
||||
ErrTypeOfRedis: "복구 파일 형식이 현재 지속성 모드와 일치하지 않습니다. 파일 형식을 수정한 후 다시 시도하세요."
|
||||
|
||||
# Container
|
||||
ErrInUsed: "{{ .detail }}이(가) 사용 중이므로 삭제할 수 없습니다."
|
||||
ErrObjectInUsed: "이 객체는 사용 중이므로 삭제할 수 없습니다."
|
||||
ErrObjectBeDependent: "이 이미지는 다른 이미지에 의해 의존되고 있어 삭제할 수 없습니다."
|
||||
ErrPortRules: "포트 수가 일치하지 않습니다. 다시 입력하세요!"
|
||||
ErrPgImagePull: "이미지 가져오기 시간이 초과되었습니다. 이미지 가속 구성을 설정하거나 postgres:16.0-alpine 이미지를 수동으로 가져온 후 다시 시도하세요."
|
||||
|
||||
# Runtime
|
||||
ErrDirNotFound: "빌드 폴더가 존재하지 않습니다! 파일의 무결성을 확인하세요!"
|
||||
ErrFileNotExist: "{{ .detail }} 파일이 존재하지 않습니다! 소스 파일의 무결성을 확인하세요!"
|
||||
ErrImageBuildErr: "이미지 빌드 실패"
|
||||
ErrImageExist: "이미지가 이미 존재합니다!"
|
||||
ErrDelWithWebsite: "운영 환경이 웹사이트와 연결되어 있어 삭제할 수 없습니다."
|
||||
ErrRuntimeStart: "실행 시작 실패"
|
||||
ErrPackageJsonNotFound: "package.json 파일이 존재하지 않습니다."
|
||||
ErrScriptsNotFound: "package.json 에서 스크립트 구성 항목을 찾을 수 없습니다."
|
||||
ErrContainerNameNotFound: "컨테이너 이름을 가져올 수 없습니다. .env 파일을 확인하세요."
|
||||
ErrNodeModulesNotFound: "node_modules 폴더가 존재하지 않습니다! 실행 환경을 편집하거나 실행 환경이 성공적으로 시작되기를 기다리세요."
|
||||
|
||||
# Setting
|
||||
ErrBackupInUsed: "백업 계정이 크론 작업에 사용 중이므로 삭제할 수 없습니다."
|
||||
ErrBackupCheck: "백업 계정 연결 테스트 실패 {{ .err }}"
|
||||
ErrOSSConn: "최신 버전을 가져올 수 없습니다. 서버가 외부 네트워크에 연결할 수 있는지 확인하세요."
|
||||
ErrEntrance: "보안 입구 정보 오류. 확인 후 다시 시도하세요!"
|
||||
|
||||
# Tool
|
||||
ErrConfigNotFound: "구성 파일이 존재하지 않습니다."
|
||||
ErrConfigParse: "구성 파일 형식 오류"
|
||||
ErrConfigIsNull: "구성 파일은 비워둘 수 없습니다."
|
||||
ErrConfigDirNotFound: "실행 디렉토리가 존재하지 않습니다."
|
||||
ErrConfigAlreadyExist: "동일한 이름의 구성 파일이 이미 존재합니다."
|
||||
ErrUserFindErr: "사용자 {{ .name }} 찾기 실패 {{ .err }}"
|
||||
|
||||
# SSH
|
||||
ErrFirewallNone: "시스템에서 firewalld 또는 ufw 서비스를 감지하지 못했습니다. 확인 후 다시 시도하세요!"
|
||||
ErrFirewallBoth: "시스템에서 firewalld 와 ufw 서비스가 모두 감지되었습니다. 충돌을 방지하려면 하나를 제거한 후 다시 시도하세요!"
|
||||
|
||||
# Cronjob
|
||||
ErrBashExecute: "스크립트 실행 오류. 작업 출력 텍스트 영역에서 구체적인 정보를 확인하세요."
|
||||
ErrCutWebsiteLog: "{{ .name }} 웹사이트 로그 자르기 실패, 오류 {{ .err }}"
|
||||
CutWebsiteLogSuccess: "{{ .name }} 웹사이트 로그 자르기 성공, 백업 경로 {{ .path }}"
|
||||
|
||||
# Toolbox
|
||||
ErrNotExistUser: "현재 사용자가 존재하지 않습니다. 수정 후 다시 시도하세요!"
|
||||
ErrBanAction: "설정 실패, 현재 {{ .name }} 서비스가 사용할 수 없습니다. 확인 후 다시 시도하세요!"
|
||||
ErrClamdscanNotFound: "clamdscan 명령을 감지하지 못했습니다. 설치 문서를 참조하세요!"
|
||||
|
||||
# WAF
|
||||
ErrScope: "이 구성의 수정은 지원되지 않습니다."
|
||||
ErrStateChange: "상태 수정 실패"
|
||||
ErrRuleExist: "규칙이 이미 존재합니다."
|
||||
ErrRuleNotExist: "규칙이 존재하지 않습니다."
|
||||
ErrParseIP: "IP 형식 오류"
|
||||
ErrDefaultIP: "default 는 예약된 이름입니다. 다른 이름으로 변경하세요."
|
||||
ErrGroupInUse: "IP 그룹이 블랙/화이트 리스트에서 사용 중이며 삭제할 수 없습니다."
|
||||
ErrGroupExist: "IP 그룹 이름이 이미 존재합니다."
|
||||
ErrIPRange: "잘못된 IP 범위"
|
||||
ErrIPExist: "IP가 이미 존재합니다."
|
||||
|
||||
# License
|
||||
ErrLicense: "라이선스 형식 오류, 확인 후 다시 시도하세요!"
|
||||
ErrLicenseCheck: "라이선스 검증 실패, 확인 후 다시 시도하세요!"
|
||||
ErrXpackVersion: "라이선스 검증 실패, 이 라이선스는 버전 제한이 있어 성공적으로 가져올 수 없습니다. 확인 후 다시 시도하세요!"
|
||||
ErrLicenseSave: "라이선스 정보 저장 실패, 오류 {{ .err }}. 다시 시도하세요!"
|
||||
ErrLicenseSync: "라이선스 정보 동기화 실패, 데이터베이스에서 라이선스 정보를 감지하지 못했습니다!"
|
||||
ErrXpackNotFound: "이 섹션은 전문가 버전 기능입니다. Panel 설정 - 라이선스 인터페이스에서 먼저 라이선스를 가져오세요."
|
||||
ErrXpackNotActive: "이 섹션은 전문가 버전 기능입니다. Panel 설정 - 라이선스 인터페이스에서 먼저 라이선스 상태를 동기화하세요."
|
||||
ErrXpackOutOfDate: "현재 라이선스가 만료되었습니다. Panel 설정 - 라이선스 인터페이스에서 라이선스를 다시 가져오세요."
|
||||
ErrXpackLost: "라이선스가 최대 재시도 횟수에 도달했습니다. [설정] - [라이선스] 페이지로 이동하여 동기화 버튼을 수동으로 클릭해 전문가 버전 기능을 정상적으로 작동시키세요."
|
||||
ErrXpackTimeout: "요청 시간이 초과되었습니다. 네트워크 연결이 불안정할 수 있으니 나중에 다시 시도하세요!"
|
||||
|
||||
# Alert
|
||||
ErrAlert: "알림 정보 형식 오류, 확인 후 다시 시도하세요!"
|
||||
ErrAlertPush: "알림 전송 오류, 확인 후 다시 시도하세요!"
|
||||
ErrAlertSave: "알림 저장 오류, 확인 후 다시 시도하세요!"
|
||||
ErrAlertSync: "알림 동기화 오류, 확인 후 다시 시도하세요!"
|
||||
ErrAlertRemote: "원격 알림 오류, 확인 후 다시 시도하세요!"
|
||||
|
||||
# CMD
|
||||
AppVersion: "앱 버전"
|
||||
AppCommands: "앱 관련 명령어"
|
||||
AppInit: "앱 초기화"
|
||||
AppKeyVal: "앱 키 (영어만 지원)"
|
||||
AppCreateFileErr: "파일 {{ .name }} 생성 실패 {{ .err }}"
|
||||
AppCreateDirErr: "폴더 {{ .name }} 생성 실패 {{ .err }}"
|
||||
AppMissKey: "앱 키가 없습니다. -k 옵션으로 지정하세요."
|
||||
AppMissVersion: "앱 버전이 없습니다. -v 옵션으로 지정하세요."
|
||||
AppVersionExist: "버전이 이미 존재합니다!"
|
||||
AppCreateSuccessful: "생성 성공!"
|
||||
AppWriteErr: "파일 {{ .name }} 쓰기 실패 {{ .err }}"
|
||||
SudoHelper: "{{ .cmd }} 명령을 사용하거나 root 사용자로 전환하세요."
|
||||
ListenIPCommands: "IP 변경"
|
||||
ListenIPv4: "IPv4 에서 수신 대기"
|
||||
ListenIPv6: "IPv6 에서 수신 대기"
|
||||
ListenChangeSuccessful: "변경 성공! 현재 {{ .value }}에서 수신 대기 중입니다."
|
||||
ResetCommands: "시스템 정보 초기화"
|
||||
ResetMFA: "1Panel 이중 인증 취소"
|
||||
ResetHttps: "1Panel HTTPS 로그인 취소"
|
||||
ResetEntrance: "1Panel 보안 입구 취소"
|
||||
ResetIPs: "1Panel 허용 IP 제한 취소"
|
||||
ResetDomain: "1Panel 도메인 바인딩 취소"
|
||||
RestoreCommands: "1Panel 서비스 및 데이터 복구"
|
||||
RestoreNoSuchFile: "복구 가능한 파일이 없습니다."
|
||||
RestoreStep1: "(1/5) {{ .name }} 디렉토리에서 1Panel 서비스 및 데이터 복구 시작..."
|
||||
RestoreStep2: "(2/5) 1Panel 바이너리 복구 성공"
|
||||
RestoreStep3: "(3/5) 1Panel 스크립트 복구 성공"
|
||||
RestoreStep4: "(4/5) 1Panel 서비스 복구 성공"
|
||||
RestoreStep5: "(5/5) 1Panel 데이터 복구 성공"
|
||||
RestoreSuccessful: "복구 성공! 서비스를 다시 시작합니다. 잠시만 기다려주세요..."
|
||||
UpdateCommands: "패널 정보 업데이트"
|
||||
UpdateUser: "패널 사용자 업데이트"
|
||||
UpdatePassword: "패널 비밀번호 업데이트"
|
||||
UpdatePort: "패널 포트 업데이트"
|
||||
UpdateUserNull: "오류: 패널 사용자가 비어 있습니다!"
|
||||
UpdateUserBlank: "오류: 패널 사용자에 공백이 포함되어 있습니다!"
|
||||
UpdateUserFormat: "오류: 잘못된 패널 사용자 형식! 영어, 중국어, 숫자, _만 지원하며 길이는 3~30자입니다."
|
||||
UpdateUserErr: "오류: 패널 사용자 업데이트 실패 {{ .err }}"
|
||||
UpdateSuccessful: "업데이트 성공!"
|
||||
UpdateUserResult: "패널 사용자: {{ .name }}"
|
||||
UpdatePasswordRead: "오류: 패널 비밀번호 정보 읽기 실패 {{ .err }}"
|
||||
UpdatePasswordNull: "오류: 패널 비밀번호가 비어 있습니다!"
|
||||
UpdateUPasswordBlank: "오류: 패널 비밀번호에 공백이 포함되어 있습니다!"
|
||||
UpdatePasswordFormat: "오류: 패널 비밀번호는 문자, 숫자, 특수 문자 !@#$%*_,.?만 지원하며 길이는 8~30자입니다!"
|
||||
UpdatePasswordLen: "오류: 6자 이상의 비밀번호를 입력하세요!"
|
||||
UpdatePasswordRe: "비밀번호 확인:"
|
||||
UpdatePasswordErr: "오류: 패널 비밀번호 업데이트 실패 {{ .err }}"
|
||||
UpdatePasswordSame: "오류: 두 비밀번호가 일치하지 않습니다. 확인 후 다시 시도하세요!"
|
||||
UpdatePasswordResult: "패널 비밀번호: {{ .name }}"
|
||||
UpdatePortFormat: "오류: 입력한 포트 번호는 1~65535 사이여야 합니다!"
|
||||
UpdatePortUsed: "오류: 포트 번호가 이미 사용 중입니다. 확인 후 다시 시도하세요!"
|
||||
UpdatePortErr: "오류: 패널 포트 업데이트 실패 {{ .err }}"
|
||||
UpdatePortResult: "패널 포트: {{ .name }}"
|
||||
UpdatePortFirewallAdd: "방화벽 포트 규칙 추가 실패 {{ .err }}, 방화벽 규칙에 {{ .name }} 포트를 수동으로 추가하세요."
|
||||
UpdatePortFirewallDel: "오류: 방화벽 포트 삭제 실패 {{ .err }}"
|
||||
UpdatePortFirewallReload: "방화벽 다시 로드 실패 {{ .err }}, 방화벽을 수동으로 다시 로드하세요."
|
||||
UserInfo: "패널 정보 가져오기"
|
||||
UserInfoAddr: "패널 주소: "
|
||||
UserInfoPassHelp: "팁: 비밀번호를 변경하려면 다음 명령어를 실행할 수 있습니다: "
|
||||
DBConnErr: "오류: 데이터베이스 연결 초기화 실패 {{ .err }}"
|
||||
SystemVersion: "버전: "
|
||||
SystemMode: "모드: "
|
||||
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: '먼저 Openresty를 설치하세요'
|
||||
ErrSSL: "인증서 내용이 비어 있습니다. 인증서를 확인하세요!"
|
||||
ErrSsePath: "SSE 경로가 중복되었습니다"
|
||||
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: '토큰 검증 오류가 발생했습니다. 재설정 후 다시 스캔해 주세요.'
|
||||
ErrInvalidToken: '유효하지 않은 토큰입니다. 재설정 후 다시 스캔해 주세요.'
|
||||
ErrExpiredToken: '토큰이 만료되었습니다. 재설정 후 다시 스캔해 주세요.'
|
||||
@@ -283,3 +283,13 @@ UserInfoPassHelp: "Petua: Untuk menukar kata laluan, anda boleh menjalankan arah
|
||||
DBConnErr: "Ralat: Gagal memulakan sambungan pangkalan data, {{ .err }}"
|
||||
SystemVersion: "Versi: "
|
||||
SystemMode: "Mod: "
|
||||
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: 'Sila pasang Openresty terlebih dahulu'
|
||||
ErrSSL: "Kandungan sijil kosong, sila periksa sijil!"
|
||||
ErrSsePath: "Laluan SSE bertindan"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: 'Ralat pengesahan token, sila tetapkan semula dan imbas semula.'
|
||||
ErrInvalidToken: 'Token tidak sah, sila tetapkan semula dan imbas semula.'
|
||||
ErrExpiredToken: 'Token telah tamat tempoh, sila tetapkan semula dan imbas semula.'
|
||||
|
||||
@@ -280,4 +280,14 @@ UserInfoAddr: "Endereço do painel: "
|
||||
UserInfoPassHelp: "Dica: Para alterar a senha, você pode executar o comando: "
|
||||
DBConnErr: "Erro: Falha ao inicializar a conexão com o banco de dados, {{ .err }}"
|
||||
SystemVersion: "versão: "
|
||||
SystemMode: "modo: "
|
||||
SystemMode: "modo: "
|
||||
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: 'Por favor, instale o Openresty primeiro'
|
||||
ErrSSL: "O conteúdo do certificado está vazio, por favor, verifique o certificado!"
|
||||
ErrSsePath: "Caminho SSE duplicado"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: 'Erro de verificação do token, por favor, reinicie e escaneie novamente.'
|
||||
ErrInvalidToken: 'Token inválido, por favor, reinicie e escaneie novamente.'
|
||||
ErrExpiredToken: 'O token expirou, por favor, reinicie e escaneie novamente.'
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#cmd
|
||||
AppVersion: "Versão do aplicativo"
|
||||
AppCommands: "Comandos relacionados ao aplicativo"
|
||||
AppInit: "Inicializar aplicativo"
|
||||
AppKeyVal: "Chave do aplicativo (suporta apenas inglês)"
|
||||
AppCreateFileErr: "Falha na criação do arquivo {{ .name }} {{ .err }}"
|
||||
AppCreateDirErr: "Falha na criação da pasta {{ .name }} {{ .err }}"
|
||||
AppMissKey: "Chave do aplicativo ausente, use -k para especificar"
|
||||
AppMissVersion: "Versão do aplicativo ausente, use -v para especificar"
|
||||
AppVersionExist: "Versão já existente!"
|
||||
AppCreateSuccessful: "Criação bem-sucedida!"
|
||||
AppWriteErr: "Falha na gravação do arquivo {{ .name }} {{ .err }}"
|
||||
SudoHelper: "Por favor, use {{ .cmd }} ou troque para o usuário root"
|
||||
ListenIPCommands: "Alterar IP de escuta"
|
||||
ListenIPv4: "Escutar no IPv4"
|
||||
ListenIPv6: "Escutar no IPv6"
|
||||
ListenChangeSuccessful: "Alteração bem-sucedida! Agora escutando em {{ .value }}"
|
||||
ResetCommands: "Redefinir informações do sistema"
|
||||
ResetMFA: "Cancelar autenticação de dois fatores do 1Panel"
|
||||
ResetHttps: "Cancelar login HTTPS do 1Panel"
|
||||
ResetEntrance: "Cancelar entrada segura do 1Panel"
|
||||
ResetIPs: "Cancelar restrições de IP autorizados do 1Panel"
|
||||
ResetDomain: "Cancelar vinculação de domínio do 1Panel"
|
||||
RestoreCommands: "Restaurar serviço e dados do 1Panel"
|
||||
RestoreNoSuchFile: "Nenhum arquivo disponível para restauração"
|
||||
RestoreStep1: "(1/5) Iniciando a restauração do serviço e dados do 1Panel a partir do diretório {{ .name }}..."
|
||||
RestoreStep2: "(2/5) Restauração do binário do 1Panel bem-sucedida"
|
||||
RestoreStep3: "(3/5) Restauração do script do 1Panel bem-sucedida"
|
||||
RestoreStep4: "(4/5) Restauração do serviço do 1Panel bem-sucedida"
|
||||
RestoreStep5: "(5/5) Restauração dos dados do 1Panel bem-sucedida"
|
||||
RestoreSuccessful: "Restauração bem-sucedida! Reiniciando o serviço, aguarde..."
|
||||
UpdateCommands: "Atualizar informações do painel"
|
||||
UpdateUser: "Atualizar usuário do painel"
|
||||
UpdatePassword: "Atualizar senha do painel"
|
||||
UpdatePort: "Atualizar porta do painel"
|
||||
UpdateUserNull: "Erro: usuário do painel está vazio!"
|
||||
UpdateUserBlank: "Erro: usuário do painel contém espaços!"
|
||||
UpdateUserFormat: "Erro: Formato de usuário inválido! Aceita apenas inglês, chinês, números e _, comprimento de 3-30"
|
||||
UpdateUserErr: "Erro: Falha ao atualizar usuário do painel, {{ .err }}"
|
||||
UpdateSuccessful: "Atualização bem-sucedida!"
|
||||
UpdateUserResult: "Usuário do painel: {{ .name }}"
|
||||
UpdatePasswordRead: "Erro: Falha ao ler informações da senha do painel, {{ .err }}"
|
||||
UpdatePasswordNull: "Erro: Senha do painel está vazia!"
|
||||
UpdateUPasswordBlank: "Erro: Senha do painel contém espaços!"
|
||||
UpdatePasswordFormat: "Erro: A senha do painel aceita apenas letras, números, caracteres especiais !@#$%*_,.?, comprimento de 8-30!"
|
||||
UpdatePasswordLen: "Erro: Por favor, insira uma senha com mais de 6 caracteres!"
|
||||
UpdatePasswordRe: "Confirmar senha:"
|
||||
UpdatePasswordErr: "Erro: Falha ao atualizar senha do painel, {{ .err }}"
|
||||
UpdatePasswordSame: "Erro: As duas senhas não coincidem, por favor, verifique e tente novamente!"
|
||||
UpdatePasswordResult: "Senha do painel: {{ .name }}"
|
||||
UpdatePortFormat: "Erro: O número da porta deve estar entre 1 e 65535!"
|
||||
UpdatePortUsed: "Erro: O número da porta já está em uso, por favor, verifique e tente novamente!"
|
||||
UpdatePortErr: "Erro: Falha ao atualizar a porta do painel, {{ .err }}"
|
||||
UpdatePortResult: "Porta do painel: {{ .name }}"
|
||||
UpdatePortFirewallAdd: "Falha ao adicionar regra de porta no firewall, {{ .err }}, adicione manualmente a porta {{ .name }} às regras do firewall."
|
||||
UpdatePortFirewallDel: "Erro: Falha ao excluir porta do firewall, {{ .err }}"
|
||||
UpdatePortFirewallReload: "Falha ao recarregar o firewall, {{ .err }}, recarregue o firewall manualmente."
|
||||
UserInfo: "Obter informações do painel"
|
||||
UserInfoAddr: "Endereço do painel: "
|
||||
UserInfoPassHelp: "Dica: Para alterar a senha, execute o comando: "
|
||||
DBConnErr: "Erro: Falha ao inicializar conexão com o banco de dados, {{ .err }}"
|
||||
SystemVersion: "versão: "
|
||||
SystemMode: "modo: "
|
||||
@@ -284,3 +284,13 @@ UserInfoPassHelp: "Подсказка: чтобы изменить пароль,
|
||||
DBConnErr: "Ошибка: не удалось инициализировать подключение к базе данных, {{ .err }}"
|
||||
SystemVersion: "версия: "
|
||||
SystemMode: "режим: "
|
||||
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: "Пожалуйста, установите Openresty сначала"
|
||||
ErrSSL: "Содержимое сертификата пустое, пожалуйста, проверьте сертификат!"
|
||||
ErrSsePath: "Путь SSE дублируется"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: 'шибка проверки токена, пожалуйста, сбросьте и отсканируйте снова.'
|
||||
ErrInvalidToken: 'Неверный токен, пожалуйста, сбросьте и отсканируйте снова.'
|
||||
ErrExpiredToken: 'Токен истек, пожалуйста, сбросьте и отсканируйте снова.'
|
||||
|
||||
+105
-95
@@ -8,7 +8,7 @@ ErrStructTransform: "類型轉換失敗: {{ .detail }}"
|
||||
ErrNotLogin: "用戶未登入: {{ .detail }}"
|
||||
ErrPasswordExpired: "目前密碼已過期: {{ .detail }}"
|
||||
ErrNotSupportType: "系統暫不支援目前類型: {{ .detail }}"
|
||||
ErrApiConfigStatusInvalid: "API 介面禁止訪問: {{ .detail }}"
|
||||
ErrApiConfigStatusInvalid: "API 介面禁止瀏覽: {{ .detail }}"
|
||||
ErrApiConfigKeyInvalid: "API 介面金鑰錯誤: {{ .detail }}"
|
||||
ErrApiConfigIPInvalid: "呼叫 API 介面 IP 不在白名單: {{ .detail }}"
|
||||
ErrApiConfigDisable: "此介面禁止使用 API 介面呼叫: {{ .detail }}"
|
||||
@@ -18,7 +18,7 @@ ErrApiConfigKeyTimeInvalid: "API 介面時間戳記錯誤: {{ .detail }}"
|
||||
ErrNameIsExist: "名稱已存在"
|
||||
ErrDemoEnvironment: "演示伺服器,禁止此操作!"
|
||||
ErrCmdTimeout: "指令執行超時!"
|
||||
ErrCmdIllegal: "執行命令中存在不合法字符,請修改後重試!"
|
||||
ErrCmdIllegal: "執行指令中存在不合法字元,請修改後重試!"
|
||||
ErrPortExist: '{{ .port }} 埠已被 {{ .type }} [{{ .name }}] 佔用'
|
||||
TYPE_APP: "應用"
|
||||
TYPE_RUNTIME: "運作環境"
|
||||
@@ -37,30 +37,30 @@ ErrAppRequired: "請先安裝 {{ .detail }} 應用"
|
||||
ErrNotInstall: "應用未安裝"
|
||||
ErrPortInOtherApp: "{{ .port }} 埠已被應用 {{ .apps }} 佔用!"
|
||||
ErrDbUserNotValid: "儲存資料庫,用戶名密碼不匹配!"
|
||||
ErrDockerComposeNotValid: "docker-compose 文件格式錯誤"
|
||||
ErrDockerComposeNotValid: "docker-compose 檔案格式錯誤"
|
||||
ErrUpdateBuWebsite: '應用更新成功,但是網站配置文件修改失敗,請檢查配置!'
|
||||
Err1PanelNetworkFailed: '預設容器網路建立失敗!{{ .detail }}'
|
||||
ErrFileParse: '應用 docker-compose 文件解析失敗!'
|
||||
ErrFileParse: '應用 docker-compose 檔案解析失敗!'
|
||||
ErrInstallDirNotFound: '安裝目錄不存在'
|
||||
AppStoreIsUpToDate: '應用商店已經是最新版本'
|
||||
LocalAppVersionNull: '{{.name}} 應用未同步到版本!無法添加到應用列表'
|
||||
LocalAppVersionErr: '{{.name}} 同步版本 {{.version}} 失敗!{{.err}}'
|
||||
ErrFileNotFound: '{{.name}} 文件不存在'
|
||||
ErrFileParseApp: '{{.name}} 文件解析失敗 {{.err}}'
|
||||
LocalAppVersionNull: '{{ .name }} 應用未同步到版本!無法新增到應用列表'
|
||||
LocalAppVersionErr: '{{ .name }} 同步版本 {{ .version }} 失敗!{{ .err }}'
|
||||
ErrFileNotFound: '{{ .name }} 檔案不存在'
|
||||
ErrFileParseApp: '{{ .name }} 檔案解析失敗 {{ .err }}'
|
||||
ErrAppDirNull: '版本資料夾不存在'
|
||||
LocalAppErr: "應用 {{.name}} 同步失敗!{{.err}}"
|
||||
LocalAppErr: "應用 {{ .name }} 同步失敗!{{ .err }}"
|
||||
ErrContainerName: "容器名稱已存在"
|
||||
ErrAppSystemRestart: "1Panel 重啟導致任務中斷"
|
||||
ErrCreateHttpClient: "建立HTTP請求失敗 {{.err}}"
|
||||
ErrHttpReqTimeOut: "請求超時 {{.err}}"
|
||||
ErrHttpReqFailed: "請求失敗 {{.err}}"
|
||||
ErrHttpReqNotFound: "文件不存在"
|
||||
ErrNoSuchHost: "網路連接失敗"
|
||||
ErrCreateHttpClient: "建立HTTP請求失敗 {{ .err }}"
|
||||
ErrHttpReqTimeOut: "請求超時 {{ .err }}"
|
||||
ErrHttpReqFailed: "請求失敗 {{ .err }}"
|
||||
ErrHttpReqNotFound: "檔案不存在"
|
||||
ErrNoSuchHost: "網路連線失敗"
|
||||
ErrImagePullTimeOut: "鏡像拉取超時"
|
||||
ErrContainerNotFound: '{{ .name }} 容器不存在'
|
||||
ErrContainerMsg: '{{ .name }} 容器異常,具體請在容器頁面查看日誌'
|
||||
ErrAppBackup: '{{ .name }} 應用備份失敗 err {{.err}}'
|
||||
ErrImagePull: '{{ .name }} 鏡像拉取失敗 err {{.err}}'
|
||||
ErrAppBackup: '{{ .name }} 應用備份失敗 err {{ .err }}'
|
||||
ErrImagePull: '{{ .name }} 鏡像拉取失敗 err {{ .err }}'
|
||||
ErrVersionTooLow: '目前 1Panel 版本過低,無法更新應用商店,請升級版本之後操作'
|
||||
ErrAppNameExist: '應用名稱已存在'
|
||||
AppStoreIsSyncing: '應用程式商店正在同步中,請稍後再試'
|
||||
@@ -68,22 +68,22 @@ ErrGetCompose: "docker-compose.yml 檔案取得失敗!{{ .detail }}"
|
||||
ErrAppWarn: "狀態異常,請查看日誌"
|
||||
ErrAppParamKey: "參數 {{ .name }} 欄位異常"
|
||||
ErrAppUpgrade: "應用程式 {{ .name }} 升級失敗 {{ .err }}"
|
||||
AppRecover: "應用程式 {{ .name }} 回滾 "
|
||||
AppRecover: "應用程式 {{ .name }} 轉返 "
|
||||
PullImageStart: "開始拉取鏡像 {{ .name }}"
|
||||
PullImageSuccess: "鏡像拉取成功"
|
||||
UpgradeAppStart: "開始升級應用程式 {{ .name }}"
|
||||
UpgradeAppSuccess: "應用程式 {{ .name }} 升級成功"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支援預覽"
|
||||
ErrFileToLarge: "文件超過10M,無法打開"
|
||||
ErrFileCanNotRead: "此檔案不支援預覽"
|
||||
ErrFileToLarge: "檔案超過10M,無法打開"
|
||||
ErrPathNotFound: "目錄不存在"
|
||||
ErrMovePathFailed: "目標路徑不能包含原路徑!"
|
||||
ErrLinkPathNotFound: "目標路徑不存在!"
|
||||
ErrFileIsExist: "文件或資料夾已存在!"
|
||||
ErrFileUpload: "{{ .name }} 上傳文件失敗 {{ .detail}}"
|
||||
ErrFileIsExist: "檔案或資料夾已存在!"
|
||||
ErrFileUpload: "{{ .name }} 上傳檔案失敗 {{ .detail }}"
|
||||
ErrFileDownloadDir: "不支援下載資料夾"
|
||||
ErrCmdNotFound: "{{ .name}} 命令不存在,請先在宿主機安裝此命令"
|
||||
ErrCmdNotFound: "{{ .name}} 指令不存在,請先在宿主機安裝此指令"
|
||||
ErrSourcePathNotFound: "源目錄不存在"
|
||||
ErrFavoriteExist: "已收藏此路徑"
|
||||
ErrPathNotDelete: "所選目錄不可刪除"
|
||||
@@ -93,33 +93,33 @@ ErrDomainIsExist: "域名已存在"
|
||||
ErrAliasIsExist: "代號已存在"
|
||||
ErrAppDelete: '其他網站使用此應用,無法刪除'
|
||||
ErrGroupIsUsed: '分組正在使用中,無法刪除'
|
||||
ErrBackupMatch: '該備份文件與目前網站部分數據不匹配: {{ .detail}}'
|
||||
ErrBackupExist: '該備份文件對應部分原數據不存在: {{ .detail}}'
|
||||
ErrPHPResource: '本地運行環境不支援切換!'
|
||||
ErrPathPermission: 'index 目錄下偵測到非 1000:1000 權限資料夾,可能導致網站存取 Access denied 錯誤,請點擊上方儲存按鈕'
|
||||
ErrBackupMatch: '該備份檔案與目前網站部分資料不匹配: {{ .detail }}'
|
||||
ErrBackupExist: '該備份檔案對應部分原資料不存在: {{ .detail }}'
|
||||
ErrPHPResource: '本機執行環境不支援切換!'
|
||||
ErrPathPermission: 'index 目錄下偵測到非 1000:1000 權限資料夾,可能導致網站存取 Access denied 錯誤,請點選上方儲存按鈕'
|
||||
ErrDomainIsUsed: "域名已被網站【{{ .name }}】使用"
|
||||
ErrDomainFormat: "{{ .name }} 域名格式不正確"
|
||||
ErrDefaultAlias: "default 為保留代號,請使用其他代號"
|
||||
ErrImageNotExist: "執行環境 {{.name}} 鏡像不存在,請重新編輯執行環境"
|
||||
ErrImageNotExist: "執行環境 {{ .name }} 鏡像不存在,請重新編輯執行環境"
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "{{ .name }} 證書正在被網站使用,無法刪除"
|
||||
ErrAccountCannotDelete: "帳號關聯證書,無法刪除"
|
||||
ErrSSLApply: "證書續簽成功,openresty reload失敗,請檢查配置!"
|
||||
ErrEmailIsExist: '郵箱已存在'
|
||||
ErrSSLKeyNotFound: '私鑰文件不存在'
|
||||
ErrSSLCertificateNotFound: '證書文件不存在'
|
||||
ErrSSLKeyFormat: '私鑰文件校驗錯誤'
|
||||
ErrSSLCertificateFormat: '證書文件格式錯誤,請使用 pem 格式'
|
||||
ErrEmailIsExist: '信箱已存在'
|
||||
ErrSSLKeyNotFound: '私鑰檔案不存在'
|
||||
ErrSSLCertificateNotFound: '證書檔案不存在'
|
||||
ErrSSLKeyFormat: '私鑰檔案校驗錯誤'
|
||||
ErrSSLCertificateFormat: '證書檔案格式錯誤,請使用 pem 格式'
|
||||
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能為空'
|
||||
ErrOpenrestyNotFound: 'Http 模式需要先安裝 Openresty'
|
||||
ApplySSLStart: '開始申請憑證,網域 [{{ .domain }}] 申請方式 [{{ .type }}] '
|
||||
dnsAccount: "DNS 自動"
|
||||
dnsManual: "DNS 手排"
|
||||
http: "HTTP"
|
||||
ApplySSLFailed: '申請 [{{ .domain }}] 憑證失敗, {{.detail}} '
|
||||
ApplySSLFailed: '申請 [{{ .domain }}] 憑證失敗, {{ .detail }} '
|
||||
ApplySSLSuccess: '申請 [{{ .domain }}] 憑證成功! ! '
|
||||
DNSAccountName: 'DNS 帳號 [{{ .name }}] 廠商 [{{.type}}]'
|
||||
DNSAccountName: 'DNS 帳號 [{{ .name }}] 廠商 [{{ .type }}]'
|
||||
PushDirLog: '憑證推送到目錄 [{{ .path }}] {{ .status }}'
|
||||
ErrDeleteCAWithSSL: "目前機構下存在已簽發證書,無法刪除"
|
||||
ErrDeleteWithPanelSSL: "面板 SSL 配置使用此證書,無法刪除"
|
||||
@@ -137,34 +137,34 @@ UpdateSystemSSLSuccess: "更新系統證書成功"
|
||||
ErrUserIsExist: "目前用戶已存在,請重新輸入"
|
||||
ErrDatabaseIsExist: "目前資料庫已存在,請重新輸入"
|
||||
ErrExecTimeOut: "SQL 執行超時,請檢查資料庫"
|
||||
ErrRemoteExist: "遠程資料庫已存在該名稱,請修改後重試"
|
||||
ErrLocalExist: "本地資料庫已存在該名稱,請修改後重試"
|
||||
ErrRemoteExist: "遠端資料庫已存在該名稱,請修改後重試"
|
||||
ErrLocalExist: "本機資料庫已存在該名稱,請修改後重試"
|
||||
|
||||
#redis
|
||||
ErrTypeOfRedis: "恢復文件類型與目前持久化方式不匹配,請修改後重試"
|
||||
ErrTypeOfRedis: "復原檔案類型與目前持久化方式不匹配,請修改後重試"
|
||||
|
||||
#container
|
||||
ErrInUsed: "{{ .detail }} 正被使用,無法刪除"
|
||||
ErrObjectInUsed: "該對象正被使用,無法刪除"
|
||||
ErrObjectInUsed: "該物件正被使用,無法刪除"
|
||||
ErrObjectBeDependent: "該鏡像依賴於其他鏡像,無法刪除"
|
||||
ErrPortRules: "埠數目不匹配,請重新輸入!"
|
||||
ErrPgImagePull: "鏡像拉取超時,請配置鏡像加速或手動拉取 postgres:16.0-alpine 鏡像後重試"
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: "build 資料夾不存在!請檢查文件完整性!"
|
||||
ErrFileNotExist: "{{ .detail }} 文件不存在!請檢查源文件完整性!"
|
||||
ErrDirNotFound: "build 資料夾不存在!請檢查檔案完整性!"
|
||||
ErrFileNotExist: "{{ .detail }} 檔案不存在!請檢查源文件完整性!"
|
||||
ErrImageBuildErr: "鏡像 build 失敗"
|
||||
ErrImageExist: "鏡像已存在!"
|
||||
ErrDelWithWebsite: "運行環境已經關聯網站,無法刪除"
|
||||
ErrDelWithWebsite: "執行環境已經關聯網站,無法刪除"
|
||||
ErrRuntimeStart: "啟動失敗"
|
||||
ErrPackageJsonNotFound: "package.json 文件不存在"
|
||||
ErrPackageJsonNotFound: "package.json 檔案不存在"
|
||||
ErrScriptsNotFound: "沒有在 package.json 中找到 scripts 配置項"
|
||||
ErrContainerNameNotFound: "無法取得容器名稱,請檢查 .env 文件"
|
||||
ErrNodeModulesNotFound: "node_modules 資料夾不存在!請編輯運行環境或者等待運行環境啟動成功"
|
||||
ErrContainerNameNotFound: "無法取得容器名稱,請檢查 .env 檔案"
|
||||
ErrNodeModulesNotFound: "node_modules 資料夾不存在!請編輯執行環境或者等待執行環境啟動成功"
|
||||
|
||||
#setting
|
||||
ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除"
|
||||
ErrBackupCheck: "備份帳號測試連接失敗 {{ .err}}"
|
||||
ErrBackupCheck: "備份帳號測試連線失敗 {{ .err }}"
|
||||
ErrOSSConn: "無法取得最新版本,請確認伺服器是否能夠連接外部網路。"
|
||||
ErrEntrance: "安全入口資訊錯誤,請檢查後重試!"
|
||||
|
||||
@@ -172,16 +172,16 @@ ErrEntrance: "安全入口資訊錯誤,請檢查後重試!"
|
||||
ErrConfigNotFound: "配置文件不存在"
|
||||
ErrConfigParse: "配置文件格式有誤"
|
||||
ErrConfigIsNull: "配置文件不允許為空"
|
||||
ErrConfigDirNotFound: "運行目錄不存在"
|
||||
ErrConfigDirNotFound: "執行目錄不存在"
|
||||
ErrConfigAlreadyExist: "已存在同名配置文件"
|
||||
ErrUserFindErr: "用戶 {{ .name }} 查找失敗 {{ .err }}"
|
||||
ErrUserFindErr: "用戶 {{ .name }} 尋找失敗 {{ .err }}"
|
||||
|
||||
#ssh
|
||||
ErrFirewallNone: "未檢測到系統 firewalld 或 ufw 服務,請檢查後重試!"
|
||||
ErrFirewallBoth: "檢測到系統同時存在 firewalld 或 ufw 服務,為避免衝突,請移除後重試!"
|
||||
|
||||
#cronjob
|
||||
ErrBashExecute: "腳本執行錯誤,請在任務輸出文本域中查看具體資訊。"
|
||||
ErrBashExecute: "腳本執行錯誤,請在任務輸出文字區域中查看具體資訊。"
|
||||
ErrCutWebsiteLog: "{{ .name }} 網站日誌切割失敗,錯誤 {{ .err }}"
|
||||
CutWebsiteLogSuccess: "{{ .name }} 網站日誌切割成功,備份路徑 {{ .path }}"
|
||||
|
||||
@@ -205,82 +205,92 @@ ErrIPExist: "IP 已存在"
|
||||
#license
|
||||
ErrLicense: "許可證格式錯誤,請檢查後重試!"
|
||||
ErrLicenseCheck: "許可證校驗失敗,請檢查後重試!"
|
||||
ErrXpackVersion: "許可證校驗失敗,該許可證受版本限制,無法成功導入,請檢查後重試!"
|
||||
ErrLicenseSave: "許可證資訊保存失敗,錯誤 {{ .err }}, 請重試!"
|
||||
ErrXpackVersion: "許可證校驗失敗,該許可證受版本限制,無法成功匯入,請檢查後重試!"
|
||||
ErrLicenseSave: "許可證資訊儲存失敗,錯誤 {{ .err }}, 請重試!"
|
||||
ErrLicenseSync: "許可證資訊同步失敗,資料庫中未檢測到許可證資訊!"
|
||||
ErrXpackNotFound: "該部分為專業版功能,請先在 面板設定-許可證 界面導入許可證"
|
||||
ErrXpackNotActive: "該部分為專業版功能,請先在 面板設定-許可證 界面同步許可證狀態"
|
||||
ErrXpackOutOfDate: "目前許可證已過期,請重新在 面板設定-許可證 界面導入許可證"
|
||||
ErrXpackLost: "許可證已達到最大重試次數,請進入【面板設定】【許可證】頁面手動點擊同步按鈕,以確保專業版功能正常使用"
|
||||
ErrXpackTimeout: "請求超時,網路連接可能不穩定,請稍後再試!"
|
||||
ErrXpackNotFound: "該部分為專業版功能,請先在 面板設定-許可證 介面匯入許可證"
|
||||
ErrXpackNotActive: "該部分為專業版功能,請先在 面板設定-許可證 介面同步許可證狀態"
|
||||
ErrXpackOutOfDate: "目前許可證已過期,請重新在 面板設定-許可證 介面匯入許可證"
|
||||
ErrXpackLost: "許可證已達到最大重試次數,請進入【面板設定】【許可證】頁面手動點選同步按鈕,以確保專業版功能正常使用"
|
||||
ErrXpackTimeout: "請求超時,網路連線可能不穩定,請稍後再試!"
|
||||
|
||||
#license
|
||||
ErrAlert: "告警資訊格式錯誤,請檢查後重試!"
|
||||
ErrAlertPush: "告警資訊推送錯誤,請檢查後重試!"
|
||||
ErrAlertSave: "告警資訊保存錯誤,請檢查後重試!"
|
||||
ErrAlertSave: "告警資訊儲存錯誤,請檢查後重試!"
|
||||
ErrAlertSync: "告警資訊同步錯誤,請檢查後重試!"
|
||||
ErrAlertRemote: "告警資訊遠端錯誤,請檢查後重試!"
|
||||
|
||||
#cmd
|
||||
AppVersion: "應用版本"
|
||||
AppCommands: "應用相關命令"
|
||||
AppCommands: "應用相關指令"
|
||||
AppInit: "初始化應用"
|
||||
AppKeyVal: "應用的 key(僅支援英文)"
|
||||
AppCreateFileErr: "文件 {{ .name }} 建立失敗 {{ .err }}"
|
||||
AppCreateFileErr: "檔案 {{ .name }} 建立失敗 {{ .err }}"
|
||||
AppCreateDirErr: "資料夾 {{ .name }} 建立失敗 {{ .err }}"
|
||||
AppMissKey: "應用的 key 缺失,使用 -k 指定"
|
||||
AppMissVersion: "應用版本缺失,使用 -v 指定"
|
||||
AppVersionExist: "版本已存在!"
|
||||
AppCreateSuccessful: "建立成功!"
|
||||
AppWriteErr: "文件 {{ .name }} 寫入失敗 {{ .err }}"
|
||||
AppWriteErr: "檔案 {{ .name }} 寫入失敗 {{ .err }}"
|
||||
SudoHelper: "請使用 {{ .cmd }} 或者切換到 root 用戶"
|
||||
ListenIPCommands: "切換監聽 IP"
|
||||
ListenIPv4: "監聽 IPv4"
|
||||
ListenIPv6: "監聽 IPv6"
|
||||
ListenChangeSuccessful: "切換成功!已切換至監聽 {{ .value }}"
|
||||
ResetCommands: "重置系統資訊"
|
||||
ResetCommands: "重設系統資訊"
|
||||
ResetMFA: "取消 1Panel 兩步驗證"
|
||||
ResetHttps: "取消 1Panel https 方式登錄"
|
||||
ResetHttps: "取消 1Panel https 方式登入"
|
||||
ResetEntrance: "取消 1Panel 安全入口"
|
||||
ResetIPs: "取消 1Panel 授權 IP 限制"
|
||||
ResetDomain: "取消 1Panel 訪問域名綁定"
|
||||
RestoreCommands: "回滾 1Panel 服務及數據"
|
||||
RestoreNoSuchFile: "暫無可回滾文件"
|
||||
RestoreStep1: "(1/5)開始從 {{ .name }} 目錄回滾 1Panel 服務及數據... "
|
||||
RestoreStep2: "(2/5)1Panel 二進制回滾成功"
|
||||
RestoreStep3: "(3/5)1Panel 腳本回滾成功"
|
||||
RestoreStep4: "(4/5)1Panel 服務回滾成功"
|
||||
RestoreStep5: "(5/5)1Panel 數據回滾成功"
|
||||
RestoreSuccessful: "回滾成功!正在重啟服務,請稍候..."
|
||||
ResetDomain: "取消 1Panel 瀏覽域名綁定"
|
||||
RestoreCommands: "轉返 1Panel 服務及資料"
|
||||
RestoreNoSuchFile: "暫無可轉返文件"
|
||||
RestoreStep1: "(1/5)開始從 {{ .name }} 目錄轉返 1Panel 服務及資料... "
|
||||
RestoreStep2: "(2/5)1Panel 二進制轉返成功"
|
||||
RestoreStep3: "(3/5)1Panel 腳本轉返成功"
|
||||
RestoreStep4: "(4/5)1Panel 服務轉返成功"
|
||||
RestoreStep5: "(5/5)1Panel 資料轉返成功"
|
||||
RestoreSuccessful: "轉返成功!正在重啟服務,請稍候..."
|
||||
UpdateCommands: "修改面板資訊"
|
||||
UpdateUser: "修改面板用戶"
|
||||
UpdatePassword: "修改面板密碼"
|
||||
UpdatePort: "修改面板埠"
|
||||
UpdateUserNull: "錯誤:輸入面板用戶為空!"
|
||||
UpdateUserBlank: "錯誤:輸入面板用戶中包含空格字符!"
|
||||
UpdateUserFormat: "錯誤:輸入面板用戶錯誤!僅支援英文、中文、數字和_,長度3-30"
|
||||
UpdateUserErr: "錯誤:面板用戶修改失敗,{{ .err }}"
|
||||
UpdateUserNull: "錯誤: 輸入面板用戶為空!"
|
||||
UpdateUserBlank: "錯誤: 輸入面板用戶中包含空格字元!"
|
||||
UpdateUserFormat: "錯誤: 輸入面板用戶錯誤!僅支援英文、中文、數字和_,長度3-30"
|
||||
UpdateUserErr: "錯誤: 面板用戶修改失敗,{{ .err }}"
|
||||
UpdateSuccessful: "修改成功!"
|
||||
UpdateUserResult: "面板用戶:{{ .name }}"
|
||||
UpdatePasswordRead: "錯誤:面板密碼資訊讀取錯誤,{{ .err }}"
|
||||
UpdatePasswordNull: "錯誤:輸入面板密碼為空!"
|
||||
UpdateUPasswordBlank: "錯誤:輸入面板密碼中包含空格字符!"
|
||||
UpdatePasswordFormat: "錯誤:面板密碼僅支援字母、數字、特殊字符(!@#$%*_,.?),長度 8-30 位!"
|
||||
UpdatePasswordLen: "錯誤:請輸入 6 位以上密碼!"
|
||||
UpdatePasswordRe: "確認密碼:"
|
||||
UpdatePasswordErr: "錯誤:面板密碼修改失敗,{{ .err }}"
|
||||
UpdatePasswordSame: "錯誤:兩次密碼不匹配,請檢查後重試!"
|
||||
UpdatePasswordResult: "面板密碼:{{ .name }}"
|
||||
UpdatePortFormat: "錯誤:輸入的埠號必須在 1 到 65535 之間!"
|
||||
UpdatePortUsed: "錯誤:該埠號正被佔用,請檢查後重試!"
|
||||
UpdatePortErr: "錯誤:面板埠修改失敗,{{ .err }}"
|
||||
UpdatePortResult: "面板埠:{{ .name }}"
|
||||
UpdatePortFirewallAdd: "添加防火牆埠規則失敗,{{ .err }},請您手動將 {{ .name }} 埠添加至防火牆規則中。"
|
||||
UpdatePortFirewallDel: "錯誤:防火牆埠刪除失敗,{{ .err }}"
|
||||
UpdateUserResult: "面板用戶: {{ .name }}"
|
||||
UpdatePasswordRead: "錯誤: 面板密碼資訊讀取錯誤,{{ .err }}"
|
||||
UpdatePasswordNull: "錯誤: 輸入面板密碼為空!"
|
||||
UpdateUPasswordBlank: "錯誤: 輸入面板密碼中包含空格字元!"
|
||||
UpdatePasswordFormat: "錯誤: 面板密碼僅支援字母、數字、特殊字元(!@#$%*_,.?),長度 8-30 位!"
|
||||
UpdatePasswordLen: "錯誤: 請輸入 6 位以上密碼!"
|
||||
UpdatePasswordRe: "確認密碼: "
|
||||
UpdatePasswordErr: "錯誤: 面板密碼修改失敗,{{ .err }}"
|
||||
UpdatePasswordSame: "錯誤: 兩次密碼不匹配,請檢查後重試!"
|
||||
UpdatePasswordResult: "面板密碼: {{ .name }}"
|
||||
UpdatePortFormat: "錯誤: 輸入的埠號必須在 1 到 65535 之間!"
|
||||
UpdatePortUsed: "錯誤: 該埠號正被佔用,請檢查後重試!"
|
||||
UpdatePortErr: "錯誤: 面板埠修改失敗,{{ .err }}"
|
||||
UpdatePortResult: "面板埠: {{ .name }}"
|
||||
UpdatePortFirewallAdd: "新增防火牆埠規則失敗,{{ .err }},請您手動將 {{ .name }} 埠新增至防火牆規則中。"
|
||||
UpdatePortFirewallDel: "錯誤: 防火牆埠刪除失敗,{{ .err }}"
|
||||
UpdatePortFirewallReload: "防火牆重載失敗,{{ .err }},請您手動重載防火牆。"
|
||||
UserInfo: "取得面板資訊"
|
||||
UserInfoAddr: "面板地址:"
|
||||
UserInfoPassHelp: "提示:修改密碼可執行命令:"
|
||||
DBConnErr: "錯誤:初始化資料庫連接失敗,{{ .err }}"
|
||||
SystemVersion: "版本:"
|
||||
SystemMode: "模式:"
|
||||
UserInfoAddr: "面板地址: "
|
||||
UserInfoPassHelp: "提示: 修改密碼可執行指令: "
|
||||
DBConnErr: "錯誤: 初始化資料庫連線失敗,{{ .err }}"
|
||||
SystemVersion: "版本: "
|
||||
SystemMode: "模式: "
|
||||
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: "請先安裝 Openresty"
|
||||
ErrSSL: "證書內容為空,請檢查證書!"
|
||||
ErrSsePath: "SSE 路徑重複"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: '令牌驗證錯誤,請重置後再次掃碼'
|
||||
ErrInvalidToken: '無效的令牌,請重置後再次掃碼'
|
||||
ErrExpiredToken: '令牌過期,請重置後再次掃碼'
|
||||
|
||||
+51
-41
@@ -42,24 +42,24 @@ Err1PanelNetworkFailed: '默认容器网络创建失败!{{ .detail }}'
|
||||
ErrFileParse: '应用 docker-compose 文件解析失败!'
|
||||
ErrInstallDirNotFound: '安装目录不存在'
|
||||
AppStoreIsUpToDate: '应用商店已经是最新版本'
|
||||
LocalAppVersionNull: '{{.name}} 应用未同步到版本!无法添加到应用列表'
|
||||
LocalAppVersionErr: '{{.name}} 同步版本 {{.version}} 失败!{{.err}}'
|
||||
ErrFileNotFound: '{{.name}} 文件不存在'
|
||||
ErrFileParseApp: '{{.name}} 文件解析失败 {{.err}}'
|
||||
LocalAppVersionNull: '{{ .name }} 应用未同步到版本!无法添加到应用列表'
|
||||
LocalAppVersionErr: '{{ .name }} 同步版本 {{ .version }} 失败!{{ .err }}'
|
||||
ErrFileNotFound: '{{ .name }} 文件不存在'
|
||||
ErrFileParseApp: '{{ .name }} 文件解析失败 {{ .err }}'
|
||||
ErrAppDirNull: '版本文件夹不存在'
|
||||
LocalAppErr: "应用 {{.name}} 同步失败!{{.err}}"
|
||||
LocalAppErr: "应用 {{ .name }} 同步失败!{{ .err }}"
|
||||
ErrContainerName: "容器名称已存在"
|
||||
ErrAppSystemRestart: "1Panel 重启导致任务终止"
|
||||
ErrCreateHttpClient: "创建HTTP请求失败 {{.err}}"
|
||||
ErrHttpReqTimeOut: "请求超时 {{.err}}"
|
||||
ErrHttpReqFailed: "请求失败 {{.err}}"
|
||||
ErrCreateHttpClient: "创建HTTP请求失败 {{ .err }}"
|
||||
ErrHttpReqTimeOut: "请求超时 {{ .err }}"
|
||||
ErrHttpReqFailed: "请求失败 {{ .err }}"
|
||||
ErrHttpReqNotFound: "文件不存在"
|
||||
ErrNoSuchHost: "网络连接失败"
|
||||
ErrImagePullTimeOut: '镜像拉取超时'
|
||||
ErrContainerNotFound: '{{ .name }} 容器不存在'
|
||||
ErrContainerMsg: '{{ .name }} 容器异常,具体请在容器页面查看日志'
|
||||
ErrAppBackup: '{{ .name }} 应用备份失败 err {{.err}}'
|
||||
ErrImagePull: '镜像拉取失败 {{.err}}'
|
||||
ErrAppBackup: '{{ .name }} 应用备份失败 err {{ .err }}'
|
||||
ErrImagePull: '镜像拉取失败 {{ .err }}'
|
||||
ErrVersionTooLow: '当前 1Panel 版本过低,无法更新应用商店,请升级版本之后操作'
|
||||
ErrAppNameExist: '应用名称已存在'
|
||||
AppStoreIsSyncing: '应用商店正在同步中,请稍后再试'
|
||||
@@ -80,7 +80,7 @@ ErrPathNotFound: "目录不存在"
|
||||
ErrMovePathFailed: "目标路径不能包含原路径!"
|
||||
ErrLinkPathNotFound: "目标路径不存在!"
|
||||
ErrFileIsExist: "文件或文件夹已存在!"
|
||||
ErrFileUpload: "{{ .name }} 上传文件失败 {{ .detail}}"
|
||||
ErrFileUpload: "{{ .name }} 上传文件失败 {{ .detail }}"
|
||||
ErrFileDownloadDir: "不支持下载文件夹"
|
||||
ErrCmdNotFound: "{{ .name}} 命令不存在,请先在宿主机安装此命令"
|
||||
ErrSourcePathNotFound: "源目录不存在"
|
||||
@@ -93,14 +93,14 @@ ErrDomainIsExist: "域名已存在"
|
||||
ErrAliasIsExist: "代号已存在"
|
||||
ErrAppDelete: '其他网站使用此应用,无法删除'
|
||||
ErrGroupIsUsed: '分组正在使用中,无法删除'
|
||||
ErrBackupMatch: '该备份文件与当前网站部分数据不匹配 {{ .detail}}'
|
||||
ErrBackupExist: '该备份文件对应部分源数据不存在 {{ .detail}}'
|
||||
ErrBackupMatch: '该备份文件与当前网站部分数据不匹配 {{ .detail }}'
|
||||
ErrBackupExist: '该备份文件对应部分源数据不存在 {{ .detail }}'
|
||||
ErrPHPResource: '本地运行环境不支持切换!'
|
||||
ErrPathPermission: 'index 目录下检测到非 1000:1000 权限文件夹,可能导致网站访问 Access denied 错误,请点击上方保存按钮'
|
||||
ErrDomainIsUsed: "域名已被网站【{{ .name }}】使用"
|
||||
ErrDomainFormat: "{{ .name }} 域名格式不正确"
|
||||
ErrDefaultAlias: "default 为保留代号,请使用其他代号"
|
||||
ErrImageNotExist: "运行环境 {{.name}} 镜像不存在,请重新编辑运行环境"
|
||||
ErrImageNotExist: "运行环境 {{ .name }} 镜像不存在,请重新编辑运行环境"
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"
|
||||
@@ -117,9 +117,9 @@ ApplySSLStart: '开始申请证书,域名 [{{ .domain }}] 申请方式 [{{ .ty
|
||||
dnsAccount: "DNS 自动"
|
||||
dnsManual: "DNS 手动"
|
||||
http: "HTTP"
|
||||
ApplySSLFailed: '申请 [{{ .domain }}] 证书失败, {{.detail}} '
|
||||
ApplySSLFailed: '申请 [{{ .domain }}] 证书失败, {{ .detail }} '
|
||||
ApplySSLSuccess: '申请 [{{ .domain }}] 证书成功!!'
|
||||
DNSAccountName: 'DNS 账号 [{{ .name }}] 厂商 [{{.type}}]'
|
||||
DNSAccountName: 'DNS 账号 [{{ .name }}] 厂商 [{{ .type }}]'
|
||||
PushDirLog: '证书推送到目录 [{{ .path }}] {{ .status }}'
|
||||
ErrDeleteCAWithSSL: "当前机构下存在已签发证书,无法删除"
|
||||
ErrDeleteWithPanelSSL: "面板 SSL 配置使用此证书,无法删除"
|
||||
@@ -164,7 +164,7 @@ ErrNodeModulesNotFound: "node_modules 文件夹不存在!请编辑运行环境
|
||||
|
||||
#setting
|
||||
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
|
||||
ErrBackupCheck: "备份账号测试连接失败 {{ .err}}"
|
||||
ErrBackupCheck: "备份账号测试连接失败 {{ .err }}"
|
||||
ErrOSSConn: "无法获取最新版本,请确认服务器是否能够连接外部网络。"
|
||||
ErrEntrance: "安全入口信息错误,请检查后重试!"
|
||||
|
||||
@@ -256,31 +256,41 @@ UpdateCommands: "修改面板信息"
|
||||
UpdateUser: "修改面板用户"
|
||||
UpdatePassword: "修改面板密码"
|
||||
UpdatePort: "修改面板端口"
|
||||
UpdateUserNull: "错误:输入面板用户为空!"
|
||||
UpdateUserBlank: "错误:输入面板用户中包含空格字符!"
|
||||
UpdateUserFormat: "错误:输入面板用户错误!仅支持英文、中文、数字和_,长度3-30"
|
||||
UpdateUserErr: "错误:面板用户修改失败,{{ .err }}"
|
||||
UpdateUserNull: "错误: 输入面板用户为空!"
|
||||
UpdateUserBlank: "错误: 输入面板用户中包含空格字符!"
|
||||
UpdateUserFormat: "错误: 输入面板用户错误!仅支持英文、中文、数字和_,长度3-30"
|
||||
UpdateUserErr: "错误: 面板用户修改失败,{{ .err }}"
|
||||
UpdateSuccessful: "修改成功!"
|
||||
UpdateUserResult: "面板用户:{{ .name }}"
|
||||
UpdatePasswordRead: "错误:面板密码信息读取错误,{{ .err }}"
|
||||
UpdatePasswordNull: "错误:输入面板密码为空!"
|
||||
UpdateUPasswordBlank: "错误:输入面板密码中包含空格字符!"
|
||||
UpdatePasswordFormat: "错误:面板密码仅支持字母、数字、特殊字符(!@#$%*_,.?),长度 8-30 位!"
|
||||
UpdatePasswordLen: "错误:请输入 6 位以上密码!"
|
||||
UpdatePasswordRe: "确认密码:"
|
||||
UpdatePasswordErr: "错误:面板密码修改失败,{{ .err }}"
|
||||
UpdatePasswordSame: "错误:两次密码不匹配,请检查后重试!"
|
||||
UpdatePasswordResult: "面板密码:{{ .name }}"
|
||||
UpdatePortFormat: "错误:输入的端口号必须在 1 到 65535 之间!"
|
||||
UpdatePortUsed: "错误:该端口号正被占用,请检查后重试!"
|
||||
UpdatePortErr: "错误:面板端口修改失败,{{ .err }}"
|
||||
UpdatePortResult: "面板端口:{{ .name }}"
|
||||
UpdateUserResult: "面板用户: {{ .name }}"
|
||||
UpdatePasswordRead: "错误: 面板密码信息读取错误,{{ .err }}"
|
||||
UpdatePasswordNull: "错误: 输入面板密码为空!"
|
||||
UpdateUPasswordBlank: "错误: 输入面板密码中包含空格字符!"
|
||||
UpdatePasswordFormat: "错误: 面板密码仅支持字母、数字、特殊字符(!@#$%*_,.?),长度 8-30 位!"
|
||||
UpdatePasswordLen: "错误: 请输入 6 位以上密码!"
|
||||
UpdatePasswordRe: "确认密码: "
|
||||
UpdatePasswordErr: "错误: 面板密码修改失败,{{ .err }}"
|
||||
UpdatePasswordSame: "错误: 两次密码不匹配,请检查后重试!"
|
||||
UpdatePasswordResult: "面板密码: {{ .name }}"
|
||||
UpdatePortFormat: "错误: 输入的端口号必须在 1 到 65535 之间!"
|
||||
UpdatePortUsed: "错误: 该端口号正被占用,请检查后重试!"
|
||||
UpdatePortErr: "错误: 面板端口修改失败,{{ .err }}"
|
||||
UpdatePortResult: "面板端口: {{ .name }}"
|
||||
UpdatePortFirewallAdd: "添加防火墙端口规则失败,{{ .err }},请您手动将 {{ .name }} 端口添加至防火墙规则中。"
|
||||
UpdatePortFirewallDel: "错误:防火墙端口删除失败,{{ .err }}"
|
||||
UpdatePortFirewallDel: "错误: 防火墙端口删除失败,{{ .err }}"
|
||||
UpdatePortFirewallReload: "防火墙重载失败,{{ .err }},请您手动重载防火墙。"
|
||||
UserInfo: "获取面板信息"
|
||||
UserInfoAddr: "面板地址:"
|
||||
UserInfoPassHelp: "提示:修改密码可执行命令:"
|
||||
DBConnErr: "错误:初始化数据库连接失败,{{ .err }}"
|
||||
SystemVersion: "版本:"
|
||||
SystemMode: "模式:"
|
||||
UserInfoAddr: "面板地址: "
|
||||
UserInfoPassHelp: "提示: 修改密码可执行命令: "
|
||||
DBConnErr: "错误: 初始化数据库连接失败,{{ .err }}"
|
||||
SystemVersion: "版本: "
|
||||
SystemMode: "模式: "
|
||||
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: '请先安装 Openresty'
|
||||
ErrSSL: "证书内容为空,请检查证书!"
|
||||
ErrSsePath: "SSE 路径重复"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: '令牌验证错误,请重置后再次扫码'
|
||||
ErrInvalidToken: '无效的令牌,请重置后再次扫码'
|
||||
ErrExpiredToken: '令牌过期,请重置后再次扫码'
|
||||
|
||||
@@ -23,8 +23,11 @@ func Init() {
|
||||
|
||||
constant.SSLLogDir = path.Join(global.CONF.System.DataDir, "log", "ssl")
|
||||
|
||||
constant.McpDir = path.Join(constant.DataDir, "mcp")
|
||||
|
||||
dirs := []string{constant.DataDir, constant.ResourceDir, constant.AppResourceDir, constant.AppInstallDir,
|
||||
global.CONF.System.Backup, constant.RuntimeDir, constant.LocalAppResourceDir, constant.RemoteAppResourceDir, constant.SSLLogDir}
|
||||
global.CONF.System.Backup, constant.RuntimeDir, constant.LocalAppResourceDir, constant.RemoteAppResourceDir,
|
||||
constant.SSLLogDir, constant.McpDir}
|
||||
|
||||
fileOp := files.NewFileOp()
|
||||
for _, dir := range dirs {
|
||||
|
||||
@@ -11,6 +11,7 @@ func Init() {
|
||||
go syncInstalledApp()
|
||||
go syncRuntime()
|
||||
go syncSSL()
|
||||
generateKey()
|
||||
}
|
||||
|
||||
func syncApp() {
|
||||
@@ -38,3 +39,9 @@ func syncSSL() {
|
||||
global.LOG.Errorf("sync ssl status error : %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func generateKey() {
|
||||
if err := service.NewISettingService().GenerateRSAKey(); err != nil {
|
||||
global.LOG.Errorf("generate rsa key error : %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,9 +84,19 @@ func Init() {
|
||||
global.CONF.System.ApiKeyValidityTime = apiKeyValidityTimeSetting.Value
|
||||
}
|
||||
|
||||
if global.CONF.System.LicenseVerify == "" {
|
||||
licenseVerify, err := settingRepo.Get(settingRepo.WithByKey("LicenseVerify"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load service license verify from setting failed, err: %v", err)
|
||||
}
|
||||
global.CONF.System.LicenseVerify = licenseVerify.Value
|
||||
}
|
||||
handleLicenseVerify(global.CONF.System.LicenseVerify, settingRepo)
|
||||
|
||||
handleUserInfo(global.CONF.System.ChangeUserInfo, settingRepo)
|
||||
|
||||
handleCronjobStatus()
|
||||
handleOllamaModelStatus()
|
||||
handleSnapStatus()
|
||||
loadLocalDir()
|
||||
initDir()
|
||||
@@ -150,6 +160,11 @@ func handleSnapStatus() {
|
||||
}
|
||||
}
|
||||
|
||||
func handleOllamaModelStatus() {
|
||||
message := "the task was interrupted due to the restart of the 1panel service"
|
||||
_ = global.DB.Model(&model.OllamaModel{}).Where("status = ?", constant.StatusWaiting).Updates(map[string]interface{}{"status": constant.StatusCanceled, "message": message}).Error
|
||||
}
|
||||
|
||||
func handleCronjobStatus() {
|
||||
var jobRecords []model.JobRecords
|
||||
_ = global.DB.Where("status = ?", constant.StatusWaiting).Find(&jobRecords).Error
|
||||
@@ -201,6 +216,12 @@ func loadLocalDir() {
|
||||
global.LOG.Errorf("error type dir: %T", varMap["dir"])
|
||||
}
|
||||
|
||||
func handleLicenseVerify(licenseVerify string, settingRepo repo.ISettingRepo) {
|
||||
if err := settingRepo.Update("LicenseVerify", licenseVerify); err != nil {
|
||||
global.LOG.Fatalf("init license verify before start failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func handleUserInfo(tags string, settingRepo repo.ISettingRepo) {
|
||||
if len(tags) == 0 {
|
||||
return
|
||||
|
||||
@@ -99,6 +99,15 @@ func Init() {
|
||||
migrations.AddAutoRestart,
|
||||
migrations.AddApiInterfaceConfig,
|
||||
migrations.AddApiKeyValidityTime,
|
||||
|
||||
migrations.UpdateAppTag,
|
||||
migrations.UpdateApp,
|
||||
migrations.AddOllamaModel,
|
||||
migrations.AddAppMenu,
|
||||
migrations.AddAppPanelName,
|
||||
migrations.AddLicenseVerify,
|
||||
|
||||
migrations.AddMcpServer,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -360,3 +360,112 @@ var AddApiKeyValidityTime = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateAppTag = &gormigrate.Migration{
|
||||
ID: "20250114-update-app-tag",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.Tag{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateApp = &gormigrate.Migration{
|
||||
ID: "20250213-update-app",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.App{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddOllamaModel = &gormigrate.Migration{
|
||||
ID: "20250218-add-ollama-model",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.OllamaModel{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddAppMenu = &gormigrate.Migration{
|
||||
ID: "20250217-update-xpack-hide-menu",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var (
|
||||
setting model.Setting
|
||||
menu dto.XpackHideMenu
|
||||
)
|
||||
|
||||
tx.Model(&model.Setting{}).Where("key = ?", "XpackHideMenu").First(&setting)
|
||||
|
||||
if err := json.Unmarshal([]byte(setting.Value), &menu); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var newChildren []dto.XpackHideMenu
|
||||
for _, item := range menu.Children {
|
||||
if item.ID != "4" {
|
||||
newChildren = append(newChildren, item)
|
||||
}
|
||||
}
|
||||
menu.Children = newChildren
|
||||
|
||||
appIsCheck := false
|
||||
for _, item := range menu.Children {
|
||||
if item.IsCheck {
|
||||
appIsCheck = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
menu.Children = append(menu.Children, dto.XpackHideMenu{
|
||||
ID: "8",
|
||||
Title: "xpack.app.app",
|
||||
Path: "/xpack/app",
|
||||
Label: "XApp",
|
||||
IsCheck: appIsCheck,
|
||||
})
|
||||
|
||||
data, err := json.Marshal(menu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Model(&model.Setting{}).Where("key = ?", "XpackHideMenu").Updates(map[string]interface{}{"value": string(data)}).Error
|
||||
},
|
||||
}
|
||||
|
||||
var AddAppPanelName = &gormigrate.Migration{
|
||||
ID: "20250218-add-app-panel-name",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
|
||||
if err := tx.Create(&model.Setting{Key: "AppPanelName", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddLicenseVerify = &gormigrate.Migration{
|
||||
ID: "20250226-add-license-verify",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
|
||||
if err := tx.Create(&model.Setting{Key: "LicenseVerify", Value: "LX"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddMcpServer = &gormigrate.Migration{
|
||||
ID: "20250401-add-mcp-server",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.McpServer{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -109,13 +109,20 @@ func handleNoRoute(c *gin.Context) {
|
||||
c.Data(statusCode, "text/html; charset=utf-8", data)
|
||||
}
|
||||
|
||||
func checkSession(c *gin.Context) bool {
|
||||
sId, err := c.Cookie(constant.SessionName)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = global.SESSION.Get(sId)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func setWebStatic(rootRouter *gin.RouterGroup) {
|
||||
rootRouter.StaticFS("/public", http.FS(web.Favicon))
|
||||
rootRouter.StaticFS("/favicon.ico", http.FS(web.Favicon))
|
||||
rootRouter.Static("/api/v1/images", "./uploads")
|
||||
rootRouter.Use(func(c *gin.Context) {
|
||||
c.Next()
|
||||
})
|
||||
|
||||
rootRouter.GET("/assets/*filepath", func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Cache-Control", fmt.Sprintf("private, max-age=%d", 3600))
|
||||
staticServer := http.FileServer(http.FS(web.Assets))
|
||||
@@ -138,10 +145,15 @@ func setWebStatic(rootRouter *gin.RouterGroup) {
|
||||
})
|
||||
}
|
||||
rootRouter.GET("/", func(c *gin.Context) {
|
||||
if !checkEntrance(c) {
|
||||
if !checkEntrance(c) && !checkSession(c) {
|
||||
handleNoRoute(c)
|
||||
return
|
||||
}
|
||||
entrance = authService.GetSecurityEntrance()
|
||||
if entrance != "" {
|
||||
entranceValue := base64.StdEncoding.EncodeToString([]byte(entrance))
|
||||
c.SetCookie("SecurityEntrance", entranceValue, 0, "", "", false, true)
|
||||
}
|
||||
staticServer := http.FileServer(http.FS(web.IndexHtml))
|
||||
staticServer.ServeHTTP(c.Writer, c.Request)
|
||||
})
|
||||
@@ -158,6 +170,7 @@ func Routers() *gin.Engine {
|
||||
|
||||
Router.Use(middleware.WhiteAllow())
|
||||
Router.Use(middleware.BindDomain())
|
||||
Router.Use(middleware.SetPasswordPublicKey())
|
||||
|
||||
Router.NoRoute(func(c *gin.Context) {
|
||||
if checkFrontendPath(c) {
|
||||
@@ -175,7 +188,12 @@ func Routers() *gin.Engine {
|
||||
|
||||
swaggerRouter := Router.Group("1panel")
|
||||
docs.SwaggerInfo.BasePath = "/api/v1"
|
||||
swaggerRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
swaggerRouter.GET("/swagger/*any", func(c *gin.Context) {
|
||||
if !checkSession(c) {
|
||||
handleNoRoute(c)
|
||||
return
|
||||
}
|
||||
}, ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
PublicGroup := Router.Group("")
|
||||
{
|
||||
PublicGroup.GET("/health", func(c *gin.Context) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package viper
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
@@ -103,6 +104,7 @@ func Init() {
|
||||
global.CONF.System.Entrance = entrance
|
||||
global.CONF.System.Language = language
|
||||
global.CONF.System.ChangeUserInfo = loadChangeInfo()
|
||||
global.CONF.System.LicenseVerify = os.Getenv("LXWARE_LICENSE_VERIFY")
|
||||
global.Viper = v
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetPasswordPublicKey() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
cookieKey, _ := c.Cookie("panel_public_key")
|
||||
settingRepo := repo.NewISettingRepo()
|
||||
key, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PUBLIC_KEY"))
|
||||
base64Key := base64.StdEncoding.EncodeToString([]byte(key.Value))
|
||||
if base64Key == cookieKey {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
c.SetCookie("panel_public_key", base64Key, 7*24*60*60, "/", "", false, false)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -71,18 +71,25 @@ func SessionAuth() gin.HandlerFunc {
|
||||
func isValid1PanelTimestamp(panelTimestamp string) bool {
|
||||
apiKeyValidityTime := global.CONF.System.ApiKeyValidityTime
|
||||
apiTime, err := strconv.Atoi(apiKeyValidityTime)
|
||||
if err != nil {
|
||||
if err != nil || apiTime < 0 {
|
||||
global.LOG.Errorf("apiTime %d, err: %v", apiTime, err)
|
||||
return false
|
||||
}
|
||||
if apiTime == 0 {
|
||||
return true
|
||||
}
|
||||
panelTime, err := strconv.ParseInt(panelTimestamp, 10, 64)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("panelTimestamp %s, panelTime %d, apiTime %d, err: %v", panelTimestamp, apiTime, panelTime, err)
|
||||
return false
|
||||
}
|
||||
nowTime := time.Now().Unix()
|
||||
if panelTime > nowTime {
|
||||
tolerance := int64(60)
|
||||
if panelTime > nowTime+tolerance {
|
||||
global.LOG.Errorf("Valid Panel Timestamp, apiTime %d, panelTime %d, nowTime %d, err: %v", apiTime, panelTime, nowTime, err)
|
||||
return false
|
||||
}
|
||||
return apiTime == 0 || nowTime-panelTime <= int64(apiTime*60)
|
||||
return nowTime-panelTime <= int64(apiTime)*60+tolerance
|
||||
}
|
||||
|
||||
func isValid1PanelToken(panelToken string, panelTimestamp string) bool {
|
||||
@@ -95,27 +102,38 @@ func isValid1PanelToken(panelToken string, panelTimestamp string) bool {
|
||||
|
||||
func isIPInWhiteList(clientIP string) bool {
|
||||
ipWhiteString := global.CONF.System.IpWhiteList
|
||||
ipWhiteList := strings.Split(ipWhiteString, "\n")
|
||||
if len(ipWhiteString) == 0 {
|
||||
global.LOG.Error("IP whitelist is empty")
|
||||
return false
|
||||
}
|
||||
ipWhiteList, ipErr := common.HandleIPList(ipWhiteString)
|
||||
if ipErr != nil {
|
||||
global.LOG.Errorf("Failed to handle IP list: %v", ipErr)
|
||||
return false
|
||||
}
|
||||
clientParsedIP := net.ParseIP(clientIP)
|
||||
if clientParsedIP == nil {
|
||||
return false
|
||||
}
|
||||
iPv4 := clientParsedIP.To4()
|
||||
iPv6 := clientParsedIP.To16()
|
||||
for _, cidr := range ipWhiteList {
|
||||
if cidr == "0.0.0.0" {
|
||||
if (iPv4 != nil && (cidr == "0.0.0.0" || cidr == "0.0.0.0/0" || iPv4.String() == cidr)) || (iPv6 != nil && (cidr == "::/0" || iPv6.String() == cidr)) {
|
||||
return true
|
||||
}
|
||||
_, ipNet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
if cidr == clientIP {
|
||||
return true
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ipNet.Contains(net.ParseIP(clientIP)) {
|
||||
if (iPv4 != nil && ipNet.Contains(iPv4)) || (iPv6 != nil && ipNet.Contains(iPv6)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GenerateMD5(input string) string {
|
||||
func GenerateMD5(param string) string {
|
||||
hash := md5.New()
|
||||
hash.Write([]byte(input))
|
||||
hash.Write([]byte(param))
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
||||
|
||||
@@ -23,5 +23,7 @@ func commonGroups() []CommonRouter {
|
||||
&RuntimeRouter{},
|
||||
&ProcessRouter{},
|
||||
&WebsiteCARouter{},
|
||||
&AIToolsRouter{},
|
||||
&McpServerRouter{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AIToolsRouter struct {
|
||||
}
|
||||
|
||||
func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter := Router.Group("ai")
|
||||
aiToolsRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
|
||||
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
aiToolsRouter.POST("/ollama/close", baseApi.CloseOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/model", baseApi.CreateOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/model/search", baseApi.SearchOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/model/sync", baseApi.SyncOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/model/load", baseApi.LoadOllamaModelDetail)
|
||||
aiToolsRouter.POST("/ollama/model/del", baseApi.DeleteOllamaModel)
|
||||
aiToolsRouter.GET("/gpu/load", baseApi.LoadGpuInfo)
|
||||
aiToolsRouter.POST("/domain/bind", baseApi.BindDomain)
|
||||
aiToolsRouter.POST("/domain/get", baseApi.GetBindDomain)
|
||||
aiToolsRouter.POST("/domain/update", baseApi.UpdateBindDomain)
|
||||
|
||||
aiToolsRouter.POST("/mcp/search", baseApi.PageMcpServers)
|
||||
aiToolsRouter.POST("/mcp/server", baseApi.CreateMcpServer)
|
||||
aiToolsRouter.POST("/mcp/server/update", baseApi.UpdateMcpServer)
|
||||
aiToolsRouter.POST("/mcp/server/del", baseApi.DeleteMcpServer)
|
||||
aiToolsRouter.POST("/mcp/server/op", baseApi.OperateMcpServer)
|
||||
aiToolsRouter.POST("/mcp/domain/bind", baseApi.BindMcpDomain)
|
||||
aiToolsRouter.GET("/mcp/domain/get", baseApi.GetMcpBindDomain)
|
||||
aiToolsRouter.POST("/mcp/domain/update", baseApi.UpdateMcpBindDomain)
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
Use(middleware.PasswordExpired())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
baRouter.GET("/exec", baseApi.ContainerWsSsh)
|
||||
baRouter.GET("/exec", baseApi.ContainerWsSSH)
|
||||
baRouter.GET("/stats/:id", baseApi.ContainerStats)
|
||||
|
||||
baRouter.POST("", baseApi.ContainerCreate)
|
||||
|
||||
@@ -38,7 +38,6 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.POST("/redis/persistence/conf", baseApi.LoadPersistenceConf)
|
||||
cmdRouter.POST("/redis/status", baseApi.LoadRedisStatus)
|
||||
cmdRouter.POST("/redis/conf", baseApi.LoadRedisConf)
|
||||
cmdRouter.GET("/redis/exec", baseApi.RedisWsSsh)
|
||||
cmdRouter.GET("/redis/check", baseApi.CheckHasCli)
|
||||
cmdRouter.POST("/redis/install/cli", baseApi.InstallCli)
|
||||
cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword)
|
||||
|
||||
@@ -27,6 +27,7 @@ func (f *FileRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
fileRouter.POST("/content", baseApi.GetContent)
|
||||
fileRouter.POST("/save", baseApi.SaveContent)
|
||||
fileRouter.POST("/check", baseApi.CheckFile)
|
||||
fileRouter.POST("/batch/check", baseApi.BatchCheckFiles)
|
||||
fileRouter.POST("/upload", baseApi.UploadFiles)
|
||||
fileRouter.POST("/chunkupload", baseApi.UploadChunkFiles)
|
||||
fileRouter.POST("/rename", baseApi.ChangeFileName)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type McpServerRouter struct {
|
||||
}
|
||||
|
||||
func (m *McpServerRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
mcpRouter := Router.Group("mcp")
|
||||
mcpRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
|
||||
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
mcpRouter.POST("/search", baseApi.PageMcpServers)
|
||||
mcpRouter.POST("/server", baseApi.CreateMcpServer)
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
websiteRouter.POST("/proxies", baseApi.GetProxyConfig)
|
||||
websiteRouter.POST("/proxies/update", baseApi.UpdateProxyConfig)
|
||||
websiteRouter.POST("/proxies/file", baseApi.UpdateProxyConfigFile)
|
||||
websiteRouter.POST("/proxies/del", baseApi.DeleteProxyConfig)
|
||||
|
||||
websiteRouter.POST("/auths", baseApi.GetAuthConfig)
|
||||
websiteRouter.POST("/auths/update", baseApi.UpdateAuthConfig)
|
||||
|
||||
@@ -47,9 +47,9 @@ func Start() {
|
||||
session.Init()
|
||||
gin.SetMode(gin.DebugMode)
|
||||
cron.Run()
|
||||
hook.Init()
|
||||
InitOthers()
|
||||
business.Init()
|
||||
hook.Init()
|
||||
|
||||
rootRouter := router.Routers()
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package common
|
||||
|
||||
type GpuInfo struct {
|
||||
CudaVersion string `json:"cudaVersion"`
|
||||
DriverVersion string `json:"driverVersion"`
|
||||
Type string `json:"type"`
|
||||
|
||||
GPUs []GPU `json:"gpu"`
|
||||
}
|
||||
|
||||
type GPU struct {
|
||||
Index uint `json:"index"`
|
||||
ProductName string `json:"productName"`
|
||||
PersistenceMode string `json:"persistenceMode"`
|
||||
BusID string `json:"busID"`
|
||||
DisplayActive string `json:"displayActive"`
|
||||
ECC string `json:"ecc"`
|
||||
FanSpeed string `json:"fanSpeed"`
|
||||
|
||||
Temperature string `json:"temperature"`
|
||||
PerformanceState string `json:"performanceState"`
|
||||
PowerDraw string `json:"powerDraw"`
|
||||
MaxPowerLimit string `json:"maxPowerLimit"`
|
||||
MemUsed string `json:"memUsed"`
|
||||
MemTotal string `json:"memTotal"`
|
||||
GPUUtil string `json:"gpuUtil"`
|
||||
ComputeMode string `json:"computeMode"`
|
||||
MigMode string `json:"migMode"`
|
||||
Processes []Process `json:"processes"`
|
||||
}
|
||||
|
||||
type Process struct {
|
||||
Pid string `json:"pid"`
|
||||
Type string `json:"type"`
|
||||
ProcessName string `json:"processName"`
|
||||
UsedMemory string `json:"usedMemory"`
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu/schema"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
)
|
||||
|
||||
type NvidiaSMI struct{}
|
||||
|
||||
func New() (bool, NvidiaSMI) {
|
||||
return cmd.Which("nvidia-smi"), NvidiaSMI{}
|
||||
}
|
||||
|
||||
func (n NvidiaSMI) LoadGpuInfo() (*common.GpuInfo, error) {
|
||||
std, err := cmd.ExecWithTimeOut("nvidia-smi -q -x", 5*time.Second)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("calling nvidia-smi failed, err: %v", std)
|
||||
}
|
||||
data := []byte(std)
|
||||
version := "v11"
|
||||
|
||||
buf := bytes.NewBuffer(data)
|
||||
decoder := xml.NewDecoder(buf)
|
||||
for {
|
||||
token, err := decoder.Token()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return nil, fmt.Errorf("reading token failed: %w", err)
|
||||
}
|
||||
d, ok := token.(xml.Directive)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
directive := string(d)
|
||||
if !strings.HasPrefix(directive, "DOCTYPE") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(directive, " ")
|
||||
s := strings.Trim(parts[len(parts)-1], "\" ")
|
||||
if strings.HasPrefix(s, "nvsmi_device_") && strings.HasSuffix(s, ".dtd") {
|
||||
version = strings.TrimSuffix(strings.TrimPrefix(s, "nvsmi_device_"), ".dtd")
|
||||
} else {
|
||||
global.LOG.Debugf("Cannot find schema version in %q", directive)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if version == "v12" || version == "v11" {
|
||||
return schema.Parse(data, version)
|
||||
} else {
|
||||
global.LOG.Errorf("don't support such schema version %s", version)
|
||||
}
|
||||
|
||||
return &common.GpuInfo{}, nil
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu/common"
|
||||
)
|
||||
|
||||
func Parse(buf []byte, version string) (*common.GpuInfo, error) {
|
||||
var (
|
||||
s smi
|
||||
info common.GpuInfo
|
||||
)
|
||||
if err := xml.Unmarshal(buf, &s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info.Type = "nvidia"
|
||||
info.CudaVersion = s.CudaVersion
|
||||
info.DriverVersion = s.DriverVersion
|
||||
if len(s.Gpu) == 0 {
|
||||
return &info, nil
|
||||
}
|
||||
for i := 0; i < len(s.Gpu); i++ {
|
||||
var gpuItem common.GPU
|
||||
gpuItem.Index = uint(i)
|
||||
gpuItem.ProductName = s.Gpu[i].ProductName
|
||||
gpuItem.PersistenceMode = s.Gpu[i].PersistenceMode
|
||||
gpuItem.BusID = s.Gpu[i].ID
|
||||
gpuItem.DisplayActive = s.Gpu[i].DisplayActive
|
||||
gpuItem.ECC = s.Gpu[i].EccMode.CurrentEcc
|
||||
if gpuItem.ECC == "Enabled" {
|
||||
gpuItem.ECC = s.Gpu[i].EccErrors.Volatile.DramUncorrectable
|
||||
}
|
||||
gpuItem.FanSpeed = s.Gpu[i].FanSpeed
|
||||
|
||||
gpuItem.Temperature = s.Gpu[i].Temperature.GpuTemp
|
||||
gpuItem.PerformanceState = s.Gpu[i].PerformanceState
|
||||
if version == "v12" {
|
||||
gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.PowerDraw
|
||||
gpuItem.MaxPowerLimit = s.Gpu[i].GpuPowerReadings.MaxPowerLimit
|
||||
} else {
|
||||
gpuItem.PowerDraw = s.Gpu[i].PowerReadings.PowerDraw
|
||||
gpuItem.MaxPowerLimit = s.Gpu[i].PowerReadings.MaxPowerLimit
|
||||
}
|
||||
gpuItem.MemUsed = s.Gpu[i].FbMemoryUsage.Used
|
||||
gpuItem.MemTotal = s.Gpu[i].FbMemoryUsage.Total
|
||||
gpuItem.GPUUtil = s.Gpu[i].Utilization.GpuUtil
|
||||
gpuItem.ComputeMode = s.Gpu[i].ComputeMode
|
||||
gpuItem.MigMode = s.Gpu[i].MigMode.CurrentMig
|
||||
|
||||
for _, process := range s.Gpu[i].Processes.ProcessInfo {
|
||||
gpuItem.Processes = append(gpuItem.Processes, common.Process{
|
||||
Pid: process.Pid,
|
||||
Type: process.Type,
|
||||
ProcessName: process.ProcessName,
|
||||
UsedMemory: process.UsedMemory,
|
||||
})
|
||||
}
|
||||
info.GPUs = append(info.GPUs, gpuItem)
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
package schema
|
||||
|
||||
type smi struct {
|
||||
AttachedGpus string `xml:"attached_gpus"`
|
||||
CudaVersion string `xml:"cuda_version"`
|
||||
DriverVersion string `xml:"driver_version"`
|
||||
Gpu []struct {
|
||||
ID string `xml:"id,attr"`
|
||||
AccountedProcesses struct{} `xml:"accounted_processes"`
|
||||
AccountingMode string `xml:"accounting_mode"`
|
||||
AccountingModeBufferSize string `xml:"accounting_mode_buffer_size"`
|
||||
AddressingMode string `xml:"addressing_mode"`
|
||||
ApplicationsClocks struct {
|
||||
GraphicsClock string `xml:"graphics_clock"`
|
||||
MemClock string `xml:"mem_clock"`
|
||||
} `xml:"applications_clocks"`
|
||||
Bar1MemoryUsage struct {
|
||||
Free string `xml:"free"`
|
||||
Total string `xml:"total"`
|
||||
Used string `xml:"used"`
|
||||
} `xml:"bar1_memory_usage"`
|
||||
BoardID string `xml:"board_id"`
|
||||
BoardPartNumber string `xml:"board_part_number"`
|
||||
CcProtectedMemoryUsage struct {
|
||||
Free string `xml:"free"`
|
||||
Total string `xml:"total"`
|
||||
Used string `xml:"used"`
|
||||
} `xml:"cc_protected_memory_usage"`
|
||||
ClockPolicy struct {
|
||||
AutoBoost string `xml:"auto_boost"`
|
||||
AutoBoostDefault string `xml:"auto_boost_default"`
|
||||
} `xml:"clock_policy"`
|
||||
Clocks struct {
|
||||
GraphicsClock string `xml:"graphics_clock"`
|
||||
MemClock string `xml:"mem_clock"`
|
||||
SmClock string `xml:"sm_clock"`
|
||||
VideoClock string `xml:"video_clock"`
|
||||
} `xml:"clocks"`
|
||||
ClocksEventReasons struct {
|
||||
ClocksEventReasonApplicationsClocksSetting string `xml:"clocks_event_reason_applications_clocks_setting"`
|
||||
ClocksEventReasonDisplayClocksSetting string `xml:"clocks_event_reason_display_clocks_setting"`
|
||||
ClocksEventReasonGpuIdle string `xml:"clocks_event_reason_gpu_idle"`
|
||||
ClocksEventReasonHwPowerBrakeSlowdown string `xml:"clocks_event_reason_hw_power_brake_slowdown"`
|
||||
ClocksEventReasonHwSlowdown string `xml:"clocks_event_reason_hw_slowdown"`
|
||||
ClocksEventReasonHwThermalSlowdown string `xml:"clocks_event_reason_hw_thermal_slowdown"`
|
||||
ClocksEventReasonSwPowerCap string `xml:"clocks_event_reason_sw_power_cap"`
|
||||
ClocksEventReasonSwThermalSlowdown string `xml:"clocks_event_reason_sw_thermal_slowdown"`
|
||||
ClocksEventReasonSyncBoost string `xml:"clocks_event_reason_sync_boost"`
|
||||
} `xml:"clocks_event_reasons"`
|
||||
ComputeMode string `xml:"compute_mode"`
|
||||
DefaultApplicationsClocks struct {
|
||||
GraphicsClock string `xml:"graphics_clock"`
|
||||
MemClock string `xml:"mem_clock"`
|
||||
} `xml:"default_applications_clocks"`
|
||||
DeferredClocks struct {
|
||||
MemClock string `xml:"mem_clock"`
|
||||
} `xml:"deferred_clocks"`
|
||||
DisplayActive string `xml:"display_active"`
|
||||
DisplayMode string `xml:"display_mode"`
|
||||
DriverModel struct {
|
||||
CurrentDm string `xml:"current_dm"`
|
||||
PendingDm string `xml:"pending_dm"`
|
||||
} `xml:"driver_model"`
|
||||
EccErrors struct {
|
||||
Aggregate struct {
|
||||
DramCorrectable string `xml:"dram_correctable"`
|
||||
DramUncorrectable string `xml:"dram_uncorrectable"`
|
||||
SramCorrectable string `xml:"sram_correctable"`
|
||||
SramUncorrectable string `xml:"sram_uncorrectable"`
|
||||
} `xml:"aggregate"`
|
||||
Volatile struct {
|
||||
DramCorrectable string `xml:"dram_correctable"`
|
||||
DramUncorrectable string `xml:"dram_uncorrectable"`
|
||||
SramCorrectable string `xml:"sram_correctable"`
|
||||
SramUncorrectable string `xml:"sram_uncorrectable"`
|
||||
} `xml:"volatile"`
|
||||
} `xml:"ecc_errors"`
|
||||
EccMode struct {
|
||||
CurrentEcc string `xml:"current_ecc"`
|
||||
PendingEcc string `xml:"pending_ecc"`
|
||||
} `xml:"ecc_mode"`
|
||||
EncoderStats struct {
|
||||
AverageFps string `xml:"average_fps"`
|
||||
AverageLatency string `xml:"average_latency"`
|
||||
SessionCount string `xml:"session_count"`
|
||||
} `xml:"encoder_stats"`
|
||||
Fabric struct {
|
||||
State string `xml:"state"`
|
||||
Status string `xml:"status"`
|
||||
} `xml:"fabric"`
|
||||
FanSpeed string `xml:"fan_speed"`
|
||||
FbMemoryUsage struct {
|
||||
Free string `xml:"free"`
|
||||
Reserved string `xml:"reserved"`
|
||||
Total string `xml:"total"`
|
||||
Used string `xml:"used"`
|
||||
} `xml:"fb_memory_usage"`
|
||||
FbcStats struct {
|
||||
AverageFps string `xml:"average_fps"`
|
||||
AverageLatency string `xml:"average_latency"`
|
||||
SessionCount string `xml:"session_count"`
|
||||
} `xml:"fbc_stats"`
|
||||
GpuFruPartNumber string `xml:"gpu_fru_part_number"`
|
||||
GpuModuleID string `xml:"gpu_module_id"`
|
||||
GpuOperationMode struct {
|
||||
CurrentGom string `xml:"current_gom"`
|
||||
PendingGom string `xml:"pending_gom"`
|
||||
} `xml:"gpu_operation_mode"`
|
||||
GpuPartNumber string `xml:"gpu_part_number"`
|
||||
GpuPowerReadings struct {
|
||||
CurrentPowerLimit string `xml:"current_power_limit"`
|
||||
DefaultPowerLimit string `xml:"default_power_limit"`
|
||||
MaxPowerLimit string `xml:"max_power_limit"`
|
||||
MinPowerLimit string `xml:"min_power_limit"`
|
||||
PowerDraw string `xml:"power_draw"`
|
||||
PowerState string `xml:"power_state"`
|
||||
RequestedPowerLimit string `xml:"requested_power_limit"`
|
||||
} `xml:"gpu_power_readings"`
|
||||
GpuResetStatus struct {
|
||||
DrainAndResetRecommended string `xml:"drain_and_reset_recommended"`
|
||||
ResetRequired string `xml:"reset_required"`
|
||||
} `xml:"gpu_reset_status"`
|
||||
GpuVirtualizationMode struct {
|
||||
HostVgpuMode string `xml:"host_vgpu_mode"`
|
||||
VirtualizationMode string `xml:"virtualization_mode"`
|
||||
} `xml:"gpu_virtualization_mode"`
|
||||
GspFirmwareVersion string `xml:"gsp_firmware_version"`
|
||||
Ibmnpu struct {
|
||||
RelaxedOrderingMode string `xml:"relaxed_ordering_mode"`
|
||||
} `xml:"ibmnpu"`
|
||||
InforomVersion struct {
|
||||
EccObject string `xml:"ecc_object"`
|
||||
ImgVersion string `xml:"img_version"`
|
||||
OemObject string `xml:"oem_object"`
|
||||
PwrObject string `xml:"pwr_object"`
|
||||
} `xml:"inforom_version"`
|
||||
MaxClocks struct {
|
||||
GraphicsClock string `xml:"graphics_clock"`
|
||||
MemClock string `xml:"mem_clock"`
|
||||
SmClock string `xml:"sm_clock"`
|
||||
VideoClock string `xml:"video_clock"`
|
||||
} `xml:"max_clocks"`
|
||||
MaxCustomerBoostClocks struct {
|
||||
GraphicsClock string `xml:"graphics_clock"`
|
||||
} `xml:"max_customer_boost_clocks"`
|
||||
MigDevices struct {
|
||||
MigDevice []struct {
|
||||
Index string `xml:"index"`
|
||||
GpuInstanceID string `xml:"gpu_instance_id"`
|
||||
ComputeInstanceID string `xml:"compute_instance_id"`
|
||||
EccErrorCount struct {
|
||||
Text string `xml:",chardata" json:"text"`
|
||||
VolatileCount struct {
|
||||
SramUncorrectable string `xml:"sram_uncorrectable"`
|
||||
} `xml:"volatile_count" json:"volatile_count"`
|
||||
} `xml:"ecc_error_count" json:"ecc_error_count"`
|
||||
FbMemoryUsage struct {
|
||||
Total string `xml:"total"`
|
||||
Reserved string `xml:"reserved"`
|
||||
Used string `xml:"used"`
|
||||
Free string `xml:"free"`
|
||||
} `xml:"fb_memory_usage" json:"fb_memory_usage"`
|
||||
Bar1MemoryUsage struct {
|
||||
Total string `xml:"total"`
|
||||
Used string `xml:"used"`
|
||||
Free string `xml:"free"`
|
||||
} `xml:"bar1_memory_usage" json:"bar1_memory_usage"`
|
||||
} `xml:"mig_device" json:"mig_device"`
|
||||
} `xml:"mig_devices" json:"mig_devices"`
|
||||
MigMode struct {
|
||||
CurrentMig string `xml:"current_mig"`
|
||||
PendingMig string `xml:"pending_mig"`
|
||||
} `xml:"mig_mode"`
|
||||
MinorNumber string `xml:"minor_number"`
|
||||
ModulePowerReadings struct {
|
||||
CurrentPowerLimit string `xml:"current_power_limit"`
|
||||
DefaultPowerLimit string `xml:"default_power_limit"`
|
||||
MaxPowerLimit string `xml:"max_power_limit"`
|
||||
MinPowerLimit string `xml:"min_power_limit"`
|
||||
PowerDraw string `xml:"power_draw"`
|
||||
PowerState string `xml:"power_state"`
|
||||
RequestedPowerLimit string `xml:"requested_power_limit"`
|
||||
} `xml:"module_power_readings"`
|
||||
MultigpuBoard string `xml:"multigpu_board"`
|
||||
Pci struct {
|
||||
AtomicCapsInbound string `xml:"atomic_caps_inbound"`
|
||||
AtomicCapsOutbound string `xml:"atomic_caps_outbound"`
|
||||
PciBridgeChip struct {
|
||||
BridgeChipFw string `xml:"bridge_chip_fw"`
|
||||
BridgeChipType string `xml:"bridge_chip_type"`
|
||||
} `xml:"pci_bridge_chip"`
|
||||
PciBus string `xml:"pci_bus"`
|
||||
PciBusID string `xml:"pci_bus_id"`
|
||||
PciDevice string `xml:"pci_device"`
|
||||
PciDeviceID string `xml:"pci_device_id"`
|
||||
PciDomain string `xml:"pci_domain"`
|
||||
PciGpuLinkInfo struct {
|
||||
LinkWidths struct {
|
||||
CurrentLinkWidth string `xml:"current_link_width"`
|
||||
MaxLinkWidth string `xml:"max_link_width"`
|
||||
} `xml:"link_widths"`
|
||||
PcieGen struct {
|
||||
CurrentLinkGen string `xml:"current_link_gen"`
|
||||
DeviceCurrentLinkGen string `xml:"device_current_link_gen"`
|
||||
MaxDeviceLinkGen string `xml:"max_device_link_gen"`
|
||||
MaxHostLinkGen string `xml:"max_host_link_gen"`
|
||||
MaxLinkGen string `xml:"max_link_gen"`
|
||||
} `xml:"pcie_gen"`
|
||||
} `xml:"pci_gpu_link_info"`
|
||||
PciSubSystemID string `xml:"pci_sub_system_id"`
|
||||
ReplayCounter string `xml:"replay_counter"`
|
||||
ReplayRolloverCounter string `xml:"replay_rollover_counter"`
|
||||
RxUtil string `xml:"rx_util"`
|
||||
TxUtil string `xml:"tx_util"`
|
||||
} `xml:"pci"`
|
||||
PerformanceState string `xml:"performance_state"`
|
||||
PersistenceMode string `xml:"persistence_mode"`
|
||||
PowerReadings struct {
|
||||
PowerState string `xml:"power_state"`
|
||||
PowerManagement string `xml:"power_management"`
|
||||
PowerDraw string `xml:"power_draw"`
|
||||
PowerLimit string `xml:"power_limit"`
|
||||
DefaultPowerLimit string `xml:"default_power_limit"`
|
||||
EnforcedPowerLimit string `xml:"enforced_power_limit"`
|
||||
MinPowerLimit string `xml:"min_power_limit"`
|
||||
MaxPowerLimit string `xml:"max_power_limit"`
|
||||
} `xml:"power_readings"`
|
||||
Processes struct {
|
||||
ProcessInfo []struct {
|
||||
Pid string `xml:"pid"`
|
||||
Type string `xml:"type"`
|
||||
ProcessName string `xml:"process_name"`
|
||||
UsedMemory string `xml:"used_memory"`
|
||||
} `xml:"process_info"`
|
||||
} `xml:"processes"`
|
||||
ProductArchitecture string `xml:"product_architecture"`
|
||||
ProductBrand string `xml:"product_brand"`
|
||||
ProductName string `xml:"product_name"`
|
||||
RemappedRows struct {
|
||||
// Manually added
|
||||
Correctable string `xml:"remapped_row_corr"`
|
||||
Uncorrectable string `xml:"remapped_row_unc"`
|
||||
Pending string `xml:"remapped_row_pending"`
|
||||
Failure string `xml:"remapped_row_failure"`
|
||||
} `xml:"remapped_rows"`
|
||||
RetiredPages struct {
|
||||
DoubleBitRetirement struct {
|
||||
RetiredCount string `xml:"retired_count"`
|
||||
RetiredPagelist string `xml:"retired_pagelist"`
|
||||
} `xml:"double_bit_retirement"`
|
||||
MultipleSingleBitRetirement struct {
|
||||
RetiredCount string `xml:"retired_count"`
|
||||
RetiredPagelist string `xml:"retired_pagelist"`
|
||||
} `xml:"multiple_single_bit_retirement"`
|
||||
PendingBlacklist string `xml:"pending_blacklist"`
|
||||
PendingRetirement string `xml:"pending_retirement"`
|
||||
} `xml:"retired_pages"`
|
||||
Serial string `xml:"serial"`
|
||||
SupportedClocks struct {
|
||||
SupportedMemClock []struct {
|
||||
SupportedGraphicsClock []string `xml:"supported_graphics_clock"`
|
||||
Value string `xml:"value"`
|
||||
} `xml:"supported_mem_clock"`
|
||||
} `xml:"supported_clocks"`
|
||||
SupportedGpuTargetTemp struct {
|
||||
GpuTargetTempMax string `xml:"gpu_target_temp_max"`
|
||||
GpuTargetTempMin string `xml:"gpu_target_temp_min"`
|
||||
} `xml:"supported_gpu_target_temp"`
|
||||
Temperature struct {
|
||||
GpuTargetTemperature string `xml:"gpu_target_temperature"`
|
||||
GpuTemp string `xml:"gpu_temp"`
|
||||
GpuTempMaxGpuThreshold string `xml:"gpu_temp_max_gpu_threshold"`
|
||||
GpuTempMaxMemThreshold string `xml:"gpu_temp_max_mem_threshold"`
|
||||
GpuTempMaxThreshold string `xml:"gpu_temp_max_threshold"`
|
||||
GpuTempSlowThreshold string `xml:"gpu_temp_slow_threshold"`
|
||||
GpuTempTlimit string `xml:"gpu_temp_tlimit"`
|
||||
MemoryTemp string `xml:"memory_temp"`
|
||||
} `xml:"temperature"`
|
||||
Utilization struct {
|
||||
DecoderUtil string `xml:"decoder_util"`
|
||||
EncoderUtil string `xml:"encoder_util"`
|
||||
GpuUtil string `xml:"gpu_util"`
|
||||
JpegUtil string `xml:"jpeg_util"`
|
||||
MemoryUtil string `xml:"memory_util"`
|
||||
OfaUtil string `xml:"ofa_util"`
|
||||
} `xml:"utilization"`
|
||||
UUID string `xml:"uuid"`
|
||||
VbiosVersion string `xml:"vbios_version"`
|
||||
Voltage struct {
|
||||
GraphicsVolt string `xml:"graphics_volt"`
|
||||
} `xml:"voltage"`
|
||||
} `xml:"gpu"`
|
||||
Timestamp string `xml:"timestamp"`
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package xpu
|
||||
|
||||
type DeviceUtilByProc struct {
|
||||
DeviceID int `json:"device_id"`
|
||||
MemSize float64 `json:"mem_size"`
|
||||
ProcessID int `json:"process_id"`
|
||||
ProcessName string `json:"process_name"`
|
||||
SharedMemSize float64 `json:"shared_mem_size"`
|
||||
}
|
||||
|
||||
type DeviceUtilByProcList struct {
|
||||
DeviceUtilByProcList []DeviceUtilByProc `json:"device_util_by_proc_list"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
DeviceFunctionType string `json:"device_function_type"`
|
||||
DeviceID int `json:"device_id"`
|
||||
DeviceName string `json:"device_name"`
|
||||
DeviceType string `json:"device_type"`
|
||||
DrmDevice string `json:"drm_device"`
|
||||
PciBdfAddress string `json:"pci_bdf_address"`
|
||||
PciDeviceID string `json:"pci_device_id"`
|
||||
UUID string `json:"uuid"`
|
||||
VendorName string `json:"vendor_name"`
|
||||
|
||||
MemoryPhysicalSizeByte string `json:"memory_physical_size_byte"`
|
||||
MemoryFreeSizeByte string `json:"memory_free_size_byte"`
|
||||
DriverVersion string `json:"driver_version"`
|
||||
}
|
||||
|
||||
type DeviceInfo struct {
|
||||
DeviceList []Device `json:"device_list"`
|
||||
}
|
||||
|
||||
type DeviceLevelMetric struct {
|
||||
MetricsType string `json:"metrics_type"`
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
type DeviceStats struct {
|
||||
DeviceID int `json:"device_id"`
|
||||
DeviceLevel []DeviceLevelMetric `json:"device_level"`
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user