Compare commits

..
Author SHA1 Message Date
王贺 0be45b6a55 Modify the install script 2025-06-10 17:18:51 +08:00
CityFun c922615183 feat: fix issue with build frontend failed (#8994) 2025-06-10 16:57:45 +08:00
CityFun 8b2990fca4 feat: change 1panel v1 docs url (#8993) 2025-06-10 16:22:04 +08:00
longjuan 37b5cd743a feat: add name attrs to login inputs for password managers to recognize (#8953) 2025-06-10 16:21:59 +08:00
玲珑芯ExquisiteCore 3b1324db08 feat: Move current status to first position in MySQL settings and set… (#8882)
* feat: Move current status to first position in MySQL and Redis settings and set as default tab
2025-06-10 16:21:53 +08:00
Alan f56bbb7dc1 ini filetype support (#8628) 2025-06-10 16:21:43 +08:00
CityFun cff649ae95 fix: Fix issue with update website config (#8610) 2025-06-10 16:21:38 +08:00
Wankko Ree cb87ca4361 fix: the docker image which no tag but used be pruned (#8529) 2025-06-10 16:21:32 +08:00
igophper 165d2b1290 fix(cronjob): prevent unnecessary log updates and clean log formatting (#8481)
Refs #8425
2025-06-10 16:21:25 +08:00
igophper 189555e16c fix(editor): reset edit state after not saving content (#8474)
Refs #8461
2025-06-10 16:21:12 +08:00
ChengPlay 2d8250926f Fix issue with errors when reverse proxy source text includes comments. (#8444)
Refs https://github.com/1Panel-dev/1Panel/issues/8442
2025-06-10 16:20:52 +08:00
ChengPlay 47e343712e feat: change commandPlaceHolder translate (#8391) 2025-06-10 16:20:33 +08:00
ssongliu 11ffb4f2be fix: Fix the mysql root account permission switchover exception (#8378) 2025-06-10 16:20:21 +08:00
zhengkunwang e7670f96de feat: update mcp proxy_pass (#8374) 2025-04-10 16:45:24 +08:00
zhengkunwang 342c04b30a fix: fix issue with mcp bind website (#8356) 2025-04-09 03:55:56 +00:00
zhengkunwang 5ab8377ac1 feat: update allowPort check (#8355) 2025-04-09 02:47:54 +00:00
ssongliu 74eb2c8dd6 fix: Fix the problem that jumping to a directory that does not exist … (#8353) 2025-04-08 14:33:54 +00:00
zhengkunwang f136d97941 change mcp helper (#8352) 2025-04-08 10:59:53 +00:00
ssongliu a5629e3f42 fix: Fix the problem of failed MySQL permission modification (#8351) 2025-04-08 10:49:53 +00:00
zhengkunwang 515d6ca500 fix: fix bug with mcp-server status error (#8349) 2025-04-08 09:21:53 +00:00
zhengkunwang c0ccc0318d feat: add warn text for mcp server (#8346) 2025-04-08 05:55:52 +00:00
zhengkunwang 8196c11010 feat: import mcp server support env (#8344) 2025-04-08 03:11:52 +00:00
ssongliu 170bd22062 fix: Modify database verification rules (#8342) 2025-04-08 02:39:52 +00:00
zhengkunwang 097a33c68f style: update style for mcp server (#8338) 2025-04-07 11:09:51 +00:00
zhengkunwang 3be3bf9926 feat: remove env from mcp config (#8337) 2025-04-07 10:03:50 +00:00
zhengkunwang 038f2b4f54 feat: change mcp list style (#8336) 2025-04-07 09:35:50 +00:00
zhengkunwang f84e284c20 feat: change DNSTypes order (#8335) 2025-04-07 09:01:50 +00:00
ssongliu 48e75b5916 fix: Set bridge as the default network for container creation. (#8332) 2025-04-07 07:47:49 +00:00
zhengkunwang a12ceaee6e feat: add config for mcp server (#8331) 2025-04-07 07:41:51 +00:00
zhengkunwang 9050680115 feat: update mcp form label (#8327) 2025-04-07 02:49:49 +00:00
zhengkunwang 0022a9b508 fix: fix issue with install app no label (#8326) 2025-04-07 02:47:49 +00:00
zhengkunwang 3437d62f31 feat: Website SSL DNS Type support WestCN (#8305) 2025-04-02 10:21:54 +00:00
ssongliu dc880835d0 fix: Modify the container terminal connection mode (#8301) 2025-04-02 10:19:54 +00:00
ssongliu 1663bf8677 fix: Regenerates the interface document (#8298) 2025-04-02 09:07:54 +00:00
ssongliu aed236cbc9 fix: Ignore the scheduled task backup content failed to be uploaded (#8297)
Refs #7869
2025-04-02 09:05:54 +00:00
ssongliu 90be74504e feat: Support the retention of remote backup files when deleting sche… (#8296)
Refs #8236
2025-04-02 09:03:55 +00:00
zhengkunwang bba8aab18c feat: Add MCP management (#8299) 2025-04-02 09:01:54 +00:00
ssongliu 47d135ecca fix: Optimize database password verification and password change (#8294) 2025-04-02 16:03:44 +08:00
ssongliu d96fe49993 fix: Resolve cloudns reference error (#8293) 2025-04-02 03:41:53 +00:00
ssongliu ac396b61ce fix: Modify the panel password verification rule (#8291)
Refs #4290
2025-04-02 03:39:54 +00:00
ssongliu 6798d0c7ee fix: Fixed an update failure when the container network is bridge (#8290)
Refs #8209
2025-04-02 03:37:53 +00:00
亖 1490ed7541 dev:修复注释路由前缀 (#8292) 2025-04-02 10:10:00 +08:00
白铭骢 (Mingcong Bai) 48b3032de6 chore(deps): update modernc.org/sqlite to v1.29.7 (#8260)
With modernc.org/sqlite@v1.29.1, 1Panel fails to build on LoongArch with
the following error:

../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/defs.go:13:35: undefined: sqlite3_index_constraint
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/defs.go:14:35: undefined: sqlite3_index_orderby
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/defs.go:15:35: undefined: sqlite3_index_constraint_usage
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:19:5: undefined: Xsqlite3_threadsafe
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:29:11: undefined: Xsqlite3_config
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:29:32: undefined: SQLITE_CONFIG_MUTEX
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:29:122: undefined: SQLITE_OK
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:30:8: undefined: Xsqlite3_errstr
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:40:17: undefined: Sqlite3_mutex_methods
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:239:46: undefined: SQLITE_OK
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:239:46: too many errors

Upstream introduced LoongArch (loong64) target support in
modernc.org/sqlite@v1.29.7. Take a conservative approach and bump
modernc.org/sqlite to v1.29.7, this will allow for 1Panel to build on
LoongArch (both frontend and backend).

Signed-off-by: Mingcong Bai <jeffbai@aosc.io>
2025-03-27 13:45:10 +08:00
sunsky ed12bec460 feat: add DNS provider ClouDNS (#8207)
* feat: add DNS provider ClouDNS

* feat: ClouDNS account parameter validation
2025-03-25 10:12:14 +08:00
dependabot[bot] 3c4a1ea614 chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 (#8229)
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.1 to 4.5.2.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.1...v4.5.2)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 17:38:30 +08:00
ssongliu 79eb0288b4 fix: Fix the problem that the terminal exit fails (#8189) 2025-03-19 03:43:21 +00:00
蘭 99775e1eb4 fix: Fix some issues (#8178) 2025-03-18 17:16:23 +08:00
zhengkunwang db848b41d2 feat: update buildx and containerd version (#8183) 2025-03-18 17:13:36 +08:00
david082321 b5b11dff86 Update tw.ts (#8161) 2025-03-17 10:58:19 +08:00
zhengkunwang 9cf4bfc606 feat: update golang.org/x/net version to v0.37.0 (#8146) 2025-03-13 18:30:25 +08:00
ThinkStu 221162d44f Update zh.ts (#8136)
correct typos
2025-03-12 22:43:20 +08:00
蘭 617970a1f8 fix: license setting data (#8132) 2025-03-12 16:25:32 +08:00
ssongliu 5610c73d67 fix: Fix the problem that services exit unexpectedly when the contain… (#8127) 2025-03-12 03:19:03 +00:00
ssongliu 51fe02661f fix: Fix the problem of incorrect calculation of container memory res… (#8125)
Refs #8033
2025-03-12 02:13:03 +00:00
lm379andlm379 8f491b2933 feat: allow tencent cloud cos dual stack domain (#8123)
Co-authored-by: lm379 <lm379@qq.com>
2025-03-11 23:58:23 +08:00
王贺 b7e16bd6d5 Update .goreleaser.yaml (#8118) 2025-03-11 13:50:48 +08:00
ssongliu 38ca2ddd01 fix(container): Fix cpu surge when the container terminal executes exit (#8115) 2025-03-10 23:47:50 +08:00
ssongliu 402b41100b fix(container): Fix cpu surge when the container terminal executes exit (#8096)
Refs #7574
2025-03-07 11:06:59 +00:00
ssongliu 766beae4e1 fix(container): Fix the problem that a container cannot create a read… (#8095)
Refs #8074
2025-03-07 07:44:59 +00:00
maninhill 05f72e9dcf chore: Update README.md 2025-03-06 11:40:56 +08:00
igophper 23553c68ae fix(fail2ban): fix the issue of set fail2ban log path err (#8076)
Refs #8075
2025-03-06 09:44:48 +08:00
zhengkunwang 4dd1eb7a1e feat: Modify Swagger route response for unauthenticated access (#8057) 2025-03-04 05:48:57 +00:00
蘭 db307732c6 feat: Adjust license (#8056) 2025-03-03 14:32:56 +00:00
igophperandssongliu 33d4ade1d4 refactor(container): optimize memory calculation and concurrent data handling (#8038)
* refactor(container): optimize memory calculation and concurrent data handling

Refs #8033

* Update container.go

---------

Co-authored-by: ssongliu <73214554+ssongliu@users.noreply.github.com>
2025-03-03 17:38:32 +08:00
igophper 186075ff9a style: optimize code editor file path style (#8039)
Refs #8015
2025-03-03 10:23:24 +08:00
zhengkunwang 34df745d1c style: Change wget page style (#8016) 2025-02-26 10:16:54 +00:00
zhengkunwang ec363ac157 feat: update default app.yaml (#8011) 2025-02-26 15:35:54 +08:00
蘭 193b0f3cd4 fix: Update swagger docs (#8002)
Refs #8001
2025-02-26 10:55:40 +08:00
zhengkunwang 67c1bf2924 feat: Add certificate validation when creating AI Proxy (#7994) 2025-02-25 15:21:16 +08:00
ssongliu b1373ff77e feat: Support gpu of schema v11 (#7988) 2025-02-25 14:16:34 +08:00
dependabot[bot] 55d0e27212 chore(deps): bump github.com/go-jose/go-jose/v4 from 4.0.4 to 4.0.5
Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.0.4 to 4.0.5.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v4.0.4...v4.0.5)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-25 09:34:14 +08:00
zhengkunwang 1c052b7c13 fix: Fix the issue with AI proxy certificate res error (#7983) 2025-02-24 22:24:51 +08:00
蘭 6c8d48c2dc feat: API interface IP whitelist supports IPv6 (#7981)
Refs #7836
2025-02-24 10:05:13 +00:00
ssongliu b61dae638b fix(forward): Fix the problem of duplicate creation of port forwardin… (#7975)
Refs #7969
2025-02-24 09:21:14 +00:00
zhengkunwang 872a93aa07 fix: Fix the issue with the homepage route refresh (#7974) 2025-02-24 15:00:20 +08:00
igophper b1f337020c fix(container/log): enable close-on-click-modal and wrap log update in nextTick (#7960)
Refs #7547
2025-02-24 13:34:51 +08:00
maninhill 56858dd74c chore: Update README.md 2025-02-24 10:54:05 +08:00
Johannes Plunien 358415f737 feat: Support freemyip.com DNS provider (#7958) 2025-02-24 10:21:37 +08:00
david082321 5e73260103 feat: Update zh-Hant translation (#7966) 2025-02-22 23:16:38 +08:00
zhengkunwang e169f46784 fix: Fixed the issue of no echo after binding the SSL to the AI. (#7953) 2025-02-21 09:29:51 +00:00
ssongliu 39416abd47 fix: Fix the issue of inconsistent deletion status for the ollama model (#7950)
Refs #7948
2025-02-21 08:51:51 +00:00
wanghe-fit2cloud 640a0ca815 chore: Optimize some command outputs 2025-02-21 16:45:56 +08:00
ssongliu 8e54cd5162 style: Adjust the width display of the model list (#7946) 2025-02-21 07:54:29 +00:00
ssongliu 0f0099accb fix: Fix the issue of loading errors for editing container port data (#7945)
Refs #7938
2025-02-21 04:01:49 +00:00
146 changed files with 5169 additions and 2495 deletions
+1 -1
View File
@@ -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:
+4 -2
View File
@@ -33,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.
@@ -61,7 +61,9 @@ Please refer to our [documentation](https://docs.1panel.pro/quick_start/) for mo
## 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 tamper protection, website monitoring, GPU monitoring, custom logo and theme color, etc. [Click to view the detailed introduction of the Pro Edition](https://1panel.pro/pricing).
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
+1 -1
View File
@@ -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
+2
View File
@@ -68,4 +68,6 @@ var (
favoriteService = service.NewIFavoriteService()
websiteCAService = service.NewIWebsiteCAService()
mcpServerService = service.NewIMcpServerService()
)
+12 -3
View File
@@ -410,11 +410,20 @@ 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
+8 -8
View 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
+167
View File
@@ -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)
}
+4
View File
@@ -66,6 +66,10 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
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)
}
+8 -8
View File
@@ -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 {
+71 -160
View File
@@ -10,7 +10,6 @@ import (
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/service"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/copier"
@@ -69,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)
@@ -96,193 +94,106 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
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
}
}
if wshandleError(wsConn, err) {
return
}
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)
if wshandleError(wsConn, err) {
return
}
quitChan := make(chan bool, 3)
tty.Start(quitChan)
go slave.Wait(quitChan)
<-quitChan
global.LOG.Info("websocket finished")
dt := time.Now().Add(time.Second)
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
}
func loadRedisInitCmd(c *gin.Context) (string, []string, error) {
name := c.Query("name")
from := c.Query("from")
commands := []string{"redis-cli"}
commands := []string{"exec", "-it"}
database, err := databaseService.Get(name)
if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) {
return
if err != nil {
return "", nil, fmt.Errorf("no such database in db, err: %v", err)
}
if from == "local" {
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"})
if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) {
return
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 = []string{"redis-cli", "-a", database.Password, "--no-auth-warning"}
commands = append(commands, []string{"-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"
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"}...)
}
}
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)
defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
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 name, commands, nil
}
func (b *BaseApi) OllamaWsSsh(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)
return
}
defer wsConn.Close()
if global.CONF.System.IsDemo {
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
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
}
func loadOllamaInitCmd(c *gin.Context) (string, []string, error) {
name := c.Query("name")
if cmd.CheckIllegal(name) {
if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) {
return
}
return "", nil, fmt.Errorf("ollama model %s contains illegal characters", name)
}
container, err := service.LoadContainerName()
if wshandleError(wsConn, errors.WithMessage(err, " load container name for ollama failed")) {
return
}
commands := []string{"ollama", "run", name}
pidMap := loadMapFromDockerTop(container)
fmt.Println("pidMap")
for k, v := range pidMap {
fmt.Println(k, v)
}
itemCmds := append([]string{"exec", "-it", container}, commands...)
slave, err := terminal.NewCommand(itemCmds)
if wshandleError(wsConn, err) {
return
}
defer killBash(container, strings.Join(commands, " "), pidMap)
defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
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
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 (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)
return
}
defer wsConn.Close()
if global.CONF.System.IsDemo {
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
return
}
}
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 {
+6 -5
View File
@@ -36,9 +36,10 @@ type OllamaBindDomainReq struct {
}
type OllamaBindDomainRes struct {
Domain string `json:"domain"`
SSLID uint `json:"sslID"`
AllowIPs []string `json:"allowIPs"`
WebsiteID uint `json:"websiteID"`
ConnUrl string `json:"connUrl"`
Domain string `json:"domain"`
SSLID uint `json:"sslID"`
AcmeAccountID uint `json:"acmeAccountID"`
AllowIPs []string `json:"allowIPs"`
WebsiteID uint `json:"websiteID"`
ConnUrl string `json:"connUrl"`
}
+7 -5
View File
@@ -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 {
+2 -1
View File
@@ -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"`
+10
View File
@@ -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"`
}
+2 -1
View File
@@ -76,7 +76,8 @@ 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 {
+57
View File
@@ -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"`
}
+26
View File
@@ -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"`
}
+1
View File
@@ -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 {
+18
View File
@@ -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"`
}
+7
View File
@@ -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 {
+56
View File
@@ -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
}
+20
View File
@@ -245,6 +245,15 @@ func (u *AIToolService) BindDomain(req dto.OllamaBindDomain) error {
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),
@@ -297,6 +306,8 @@ func (u *AIToolService) GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaB
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)
@@ -318,6 +329,15 @@ func (u *AIToolService) UpdateBindDomain(req dto.OllamaBindDomain) error {
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 {
+3
View File
@@ -839,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)
+5 -2
View File
@@ -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
+5 -2
View File
@@ -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
+32 -17
View File
@@ -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]
+11 -2
View File
@@ -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 {
+8 -1
View File
@@ -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))
}
+2
View File
@@ -46,4 +46,6 @@ var (
phpExtensionsRepo = repo.NewIPHPExtensionsRepo()
favoriteRepo = repo.NewIFavoriteRepo()
mcpServerRepo = repo.NewIMcpServerRepo()
)
+4
View File
@@ -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
}
}
+643
View File
@@ -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)
}
+22 -5
View File
@@ -427,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
@@ -496,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))
@@ -1764,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 {
+1
View File
@@ -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"`
+1
View File
@@ -35,6 +35,7 @@ var WebUrlMap = map[string]struct{}{
"/ai": {},
"/ai/model": {},
"/ai/gpu": {},
"/ai/mcp": {},
"/containers": {},
"/containers/container": {},
+1
View File
@@ -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")
)
+1
View File
@@ -13,6 +13,7 @@ const (
RuntimeStopped = "stopped"
RuntimeUnhealthy = "unhealthy"
RuntimeCreating = "creating"
RuntimeReStarting = "restarting"
RuntimePHP = "php"
RuntimeNode = "node"
+2
View File
@@ -287,6 +287,8 @@ 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.'
-63
View File
@@ -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: "
+42 -39
View File
@@ -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,37 +253,40 @@ 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コードをスキャンしてください。'
+3
View File
@@ -287,6 +287,9 @@ SystemMode: "모드: "
#ai-tool
ErrOpenrestyInstall: '먼저 Openresty를 설치하세요'
ErrSSL: "인증서 내용이 비어 있습니다. 인증서를 확인하세요!"
ErrSsePath: "SSE 경로가 중복되었습니다"
#mobile app
ErrVerifyToken: '토큰 검증 오류가 발생했습니다. 재설정 후 다시 스캔해 주세요.'
+2
View File
@@ -286,6 +286,8 @@ 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.'
+2
View File
@@ -284,6 +284,8 @@ 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.'
+3 -1
View File
@@ -286,7 +286,9 @@ SystemVersion: "версия: "
SystemMode: "режим: "
#ai-tool
"ErrOpenrestyInstall": "Пожалуйста, установите Openresty сначала"
ErrOpenrestyInstall: "Пожалуйста, установите Openresty сначала"
ErrSSL: "Содержимое сертификата пустое, пожалуйста, проверьте сертификат!"
ErrSsePath: "Путь SSE дублируется"
#mobile app
ErrVerifyToken: 'шибка проверки токена, пожалуйста, сбросьте и отсканируйте снова.'
+29 -27
View File
@@ -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 }}"
@@ -243,7 +243,7 @@ ResetMFA: "取消 1Panel 兩步驗證"
ResetHttps: "取消 1Panel https 方式登入"
ResetEntrance: "取消 1Panel 安全入口"
ResetIPs: "取消 1Panel 授權 IP 限制"
ResetDomain: "取消 1Panel 訪問域名綁定"
ResetDomain: "取消 1Panel 瀏覽域名綁定"
RestoreCommands: "轉返 1Panel 服務及資料"
RestoreNoSuchFile: "暫無可轉返文件"
RestoreStep1: "(1/5)開始從 {{ .name }} 目錄轉返 1Panel 服務及資料... "
@@ -256,37 +256,39 @@ 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"
ErrOpenrestyInstall: "請先安裝 Openresty"
ErrSSL: "證書內容為空,請檢查證書!"
ErrSsePath: "SSE 路徑重複"
#mobile app
ErrVerifyToken: '令牌驗證錯誤,請重置後再次掃碼'
+26 -24
View File
@@ -256,37 +256,39 @@ 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: '令牌验证错误,请重置后再次扫码'
+4 -1
View File
@@ -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 {
+15
View File
@@ -84,6 +84,15 @@ 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()
@@ -207,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
+3
View File
@@ -105,6 +105,9 @@ func Init() {
migrations.AddOllamaModel,
migrations.AddAppMenu,
migrations.AddAppPanelName,
migrations.AddLicenseVerify,
migrations.AddMcpServer,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
@@ -448,3 +448,24 @@ var AddAppPanelName = &gormigrate.Migration{
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
},
}
+21 -2
View File
@@ -109,6 +109,15 @@ 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))
@@ -136,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)
})
@@ -174,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) {
+2
View File
@@ -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
}
+20 -9
View File
@@ -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"
)
@@ -102,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))
}
+1
View File
@@ -24,5 +24,6 @@ func commonGroups() []CommonRouter {
&ProcessRouter{},
&WebsiteCARouter{},
&AIToolsRouter{},
&McpServerRouter{},
}
}
+9 -1
View File
@@ -15,7 +15,6 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
baseApi := v1.ApiGroupApp.BaseApi
{
aiToolsRouter.GET("/ollama/exec", baseApi.OllamaWsSsh)
aiToolsRouter.POST("/ollama/close", baseApi.CloseOllamaModel)
aiToolsRouter.POST("/ollama/model", baseApi.CreateOllamaModel)
aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel)
@@ -27,5 +26,14 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter.POST("/domain/bind", baseApi.BindDomain)
aiToolsRouter.POST("/domain/get", baseApi.GetBindDomain)
aiToolsRouter.POST("/domain/update", baseApi.UpdateBindDomain)
aiToolsRouter.POST("/mcp/search", baseApi.PageMcpServers)
aiToolsRouter.POST("/mcp/server", baseApi.CreateMcpServer)
aiToolsRouter.POST("/mcp/server/update", baseApi.UpdateMcpServer)
aiToolsRouter.POST("/mcp/server/del", baseApi.DeleteMcpServer)
aiToolsRouter.POST("/mcp/server/op", baseApi.OperateMcpServer)
aiToolsRouter.POST("/mcp/domain/bind", baseApi.BindMcpDomain)
aiToolsRouter.GET("/mcp/domain/get", baseApi.GetMcpBindDomain)
aiToolsRouter.POST("/mcp/domain/update", baseApi.UpdateMcpBindDomain)
}
}
+1 -1
View File
@@ -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)
-1
View File
@@ -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)
+21
View File
@@ -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)
}
}
+1 -1
View File
@@ -47,9 +47,9 @@ func Start() {
session.Init()
gin.SetMode(gin.DebugMode)
cron.Run()
hook.Init()
InitOthers()
business.Init()
hook.Init()
rootRouter := router.Routers()
+9 -6
View File
@@ -12,7 +12,7 @@ import (
"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_v12"
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu/schema"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
)
@@ -28,7 +28,7 @@ func (n NvidiaSMI) LoadGpuInfo() (*common.GpuInfo, error) {
return nil, fmt.Errorf("calling nvidia-smi failed, err: %v", std)
}
data := []byte(std)
schema := "v11"
version := "v11"
buf := bytes.NewBuffer(data)
decoder := xml.NewDecoder(buf)
@@ -51,15 +51,18 @@ func (n NvidiaSMI) LoadGpuInfo() (*common.GpuInfo, error) {
parts := strings.Split(directive, " ")
s := strings.Trim(parts[len(parts)-1], "\" ")
if strings.HasPrefix(s, "nvsmi_device_") && strings.HasSuffix(s, ".dtd") {
schema = strings.TrimSuffix(strings.TrimPrefix(s, "nvsmi_device_"), ".dtd")
version = strings.TrimSuffix(strings.TrimPrefix(s, "nvsmi_device_"), ".dtd")
} else {
global.LOG.Debugf("Cannot find schema version in %q", directive)
}
break
}
if schema != "v12" {
return &common.GpuInfo{}, nil
if version == "v12" || version == "v11" {
return schema.Parse(data, version)
} else {
global.LOG.Errorf("don't support such schema version %s", version)
}
return schema_v12.Parse(data)
return &common.GpuInfo{}, nil
}
@@ -1,4 +1,4 @@
package schema_v12
package schema
import (
"encoding/xml"
@@ -6,7 +6,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu/common"
)
func Parse(buf []byte) (*common.GpuInfo, error) {
func Parse(buf []byte, version string) (*common.GpuInfo, error) {
var (
s smi
info common.GpuInfo
@@ -36,8 +36,13 @@ func Parse(buf []byte) (*common.GpuInfo, error) {
gpuItem.Temperature = s.Gpu[i].Temperature.GpuTemp
gpuItem.PerformanceState = s.Gpu[i].PerformanceState
gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.PowerDraw
gpuItem.MaxPowerLimit = s.Gpu[i].GpuPowerReadings.MaxPowerLimit
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
@@ -1,4 +1,4 @@
package schema_v12
package schema
type smi struct {
AttachedGpus string `xml:"attached_gpus"`
+13 -2
View File
@@ -6,6 +6,7 @@ import (
"net/http"
"net/url"
"os"
"regexp"
cosSDK "github.com/tencentyun/cos-go-sdk-v5"
)
@@ -18,6 +19,7 @@ type cosClient struct {
func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
region := loadParamFromVars("region", vars)
endpoint := loadParamFromVars("endpoint", vars)
accessKey := loadParamFromVars("accessKey", vars)
secretKey := loadParamFromVars("secretKey", vars)
bucket := loadParamFromVars("bucket", vars)
@@ -26,7 +28,16 @@ func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
scType = "Standard"
}
u, _ := url.Parse(fmt.Sprintf("https://cos.%s.myqcloud.com", region))
// 允许使用对象存储双栈域名
endpointType := "cos"
if len(endpoint) != 0 {
re := regexp.MustCompile(`.*cos-dualstack\..*`)
if re.MatchString(endpoint) {
endpointType = "cos-dualstack"
}
}
u, _ := url.Parse(fmt.Sprintf("https://%s.%s.myqcloud.com", endpointType, region))
b := &cosSDK.BaseURL{BucketURL: u}
client := cosSDK.NewClient(b, &http.Client{
Transport: &cosSDK.AuthorizationTransport{
@@ -36,7 +47,7 @@ func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
})
if len(bucket) != 0 {
u2, _ := url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", bucket, region))
u2, _ := url.Parse(fmt.Sprintf("https://%s.%s.%s.myqcloud.com", bucket, endpointType, region))
b2 := &cosSDK.BaseURL{BucketURL: u2}
clientWithBucket := cosSDK.NewClient(b2, &http.Client{
Transport: &cosSDK.AuthorizationTransport{
+3 -1
View File
@@ -46,7 +46,9 @@ func NewLocation(directive IDirective) *Location {
location.Host = params[1]
}
case "proxy_cache":
location.Cache = true
if params[0] != "off" {
location.Cache = true
}
case "if":
if params[0] == "(" && params[1] == "$uri" && params[2] == "~*" {
dirs := dir.GetBlock().GetDirectives()
-55
View File
@@ -1,11 +1,8 @@
package ssh
import (
"bytes"
"fmt"
"io"
"strings"
"sync"
"time"
gossh "golang.org/x/crypto/ssh"
@@ -82,58 +79,6 @@ func (c *ConnInfo) Close() {
_ = c.Client.Close()
}
type SshConn struct {
StdinPipe io.WriteCloser
ComboOutput *wsBufferWriter
Session *gossh.Session
}
func (c *ConnInfo) NewSshConn(cols, rows int) (*SshConn, error) {
sshSession, err := c.Client.NewSession()
if err != nil {
return nil, err
}
stdinP, err := sshSession.StdinPipe()
if err != nil {
return nil, err
}
comboWriter := new(wsBufferWriter)
sshSession.Stdout = comboWriter
sshSession.Stderr = comboWriter
modes := gossh.TerminalModes{
gossh.ECHO: 1,
gossh.TTY_OP_ISPEED: 14400,
gossh.TTY_OP_OSPEED: 14400,
}
if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil {
return nil, err
}
if err := sshSession.Shell(); err != nil {
return nil, err
}
return &SshConn{StdinPipe: stdinP, ComboOutput: comboWriter, Session: sshSession}, nil
}
func (s *SshConn) Close() {
if s.Session != nil {
s.Session.Close()
}
}
type wsBufferWriter struct {
buffer bytes.Buffer
mu sync.Mutex
}
func (w *wsBufferWriter) Write(p []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
return w.buffer.Write(p)
}
func makePrivateKeySigner(privateKey []byte, passPhrase []byte) (gossh.Signer, error) {
if len(passPhrase) != 0 {
return gossh.ParsePrivateKeyWithPassphrase(privateKey, passPhrase)
+47 -13
View File
@@ -3,11 +3,14 @@ package ssl
import (
"crypto"
"encoding/json"
"github.com/go-acme/lego/v4/providers/dns/rainyun"
"github.com/go-acme/lego/v4/providers/dns/westcn"
"os"
"strings"
"time"
"github.com/go-acme/lego/v4/providers/dns/freemyip"
"github.com/go-acme/lego/v4/providers/dns/rainyun"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/go-acme/lego/v4/acme"
"github.com/go-acme/lego/v4/acme/api"
@@ -18,6 +21,7 @@ import (
"github.com/go-acme/lego/v4/providers/dns/alidns"
"github.com/go-acme/lego/v4/providers/dns/clouddns"
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
"github.com/go-acme/lego/v4/providers/dns/cloudns"
"github.com/go-acme/lego/v4/providers/dns/dnspod"
"github.com/go-acme/lego/v4/providers/dns/godaddy"
"github.com/go-acme/lego/v4/providers/dns/huaweicloud"
@@ -73,6 +77,8 @@ const (
Volcengine DnsType = "Volcengine"
CloudFlare DnsType = "CloudFlare"
CloudDns DnsType = "CloudDns"
ClouDNS DnsType = "ClouDNS"
FreeMyIP DnsType = "FreeMyIP"
NameSilo DnsType = "NameSilo"
NameCheap DnsType = "NameCheap"
NameCom DnsType = "NameCom"
@@ -80,21 +86,26 @@ const (
TencentCloud DnsType = "TencentCloud"
HuaweiCloud DnsType = "HuaweiCloud"
RainYun DnsType = "RainYun"
WestCN DnsType = "WestCN"
)
type DNSParam struct {
ID string `json:"id"`
Token string `json:"token"`
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
Email string `json:"email"`
APIkey string `json:"apiKey"`
APIUser string `json:"apiUser"`
APISecret string `json:"apiSecret"`
SecretID string `json:"secretID"`
Region string `json:"region"`
ClientID string `json:"clientID"`
Password string `json:"password"`
ID string `json:"id"`
Token string `json:"token"`
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
Email string `json:"email"`
APIkey string `json:"apiKey"`
APIUser string `json:"apiUser"`
APISecret string `json:"apiSecret"`
SecretID string `json:"secretID"`
Region string `json:"region"`
ClientID string `json:"clientID"`
Password string `json:"password"`
AuthID string `json:"authID"`
SubAuthID string `json:"subAuthID"`
AuthPassword string `json:"authPassword"`
Username string `json:"username"`
}
var (
@@ -155,6 +166,21 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
clouddnsConfig.PollingInterval = pollingInterval
clouddnsConfig.TTL = ttl
p, err = clouddns.NewDNSProviderConfig(clouddnsConfig)
case ClouDNS:
cloudnsConfig := cloudns.NewDefaultConfig()
cloudnsConfig.AuthID = param.AuthID
cloudnsConfig.SubAuthID = param.SubAuthID
cloudnsConfig.AuthPassword = param.AuthPassword
cloudnsConfig.PropagationTimeout = propagationTimeout
cloudnsConfig.PollingInterval = pollingInterval
cloudnsConfig.TTL = ttl
p, err = cloudns.NewDNSProviderConfig(cloudnsConfig)
case FreeMyIP:
freeMyIpConfig := freemyip.NewDefaultConfig()
freeMyIpConfig.Token = param.Token
freeMyIpConfig.PropagationTimeout = propagationTimeout
freeMyIpConfig.PollingInterval = pollingInterval
p, err = freemyip.NewDNSProviderConfig(freeMyIpConfig)
case NameCheap:
namecheapConfig := namecheap.NewDefaultConfig()
namecheapConfig.APIKey = param.APIkey
@@ -210,6 +236,14 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
rainyunConfig.PollingInterval = pollingInterval
rainyunConfig.TTL = ttl
p, err = rainyun.NewDNSProviderConfig(rainyunConfig)
case WestCN:
westcnConfig := westcn.NewDefaultConfig()
westcnConfig.Username = param.Username
westcnConfig.Password = param.Password
westcnConfig.PropagationTimeout = propagationTimeout
westcnConfig.PollingInterval = pollingInterval
westcnConfig.TTL = ttl
p, err = westcn.NewDNSProviderConfig(westcnConfig)
}
if err != nil {
return err
+8 -2
View File
@@ -25,8 +25,13 @@ type LocalCommand struct {
pty *os.File
}
func NewCommand(commands []string) (*LocalCommand, error) {
cmd := exec.Command("docker", commands...)
func NewCommand(initCmd []string) (*LocalCommand, error) {
cmd := exec.Command("docker", initCmd...)
if term := os.Getenv("TERM"); term != "" {
cmd.Env = append(os.Environ(), "TERM="+term)
} else {
cmd.Env = append(os.Environ(), "TERM=xterm")
}
pty, err := pty.Start(cmd)
if err != nil {
@@ -90,4 +95,5 @@ func (lcmd *LocalCommand) Wait(quitChan chan bool) {
global.LOG.Errorf("ssh session wait failed, err: %v", err)
setQuit(quitChan)
}
setQuit(quitChan)
}
@@ -53,6 +53,11 @@ func (sws *LocalWsSession) handleSlaveEvent(exitCh chan bool) {
}
func (sws *LocalWsSession) masterWrite(data []byte) error {
defer func() {
if r := recover(); r != nil {
global.LOG.Errorf("A panic occurred during write ws message to master, error message: %v", r)
}
}()
sws.writeMutex.Lock()
defer sws.writeMutex.Unlock()
wsData, err := json.Marshal(WsMsg{
@@ -72,6 +77,7 @@ func (sws *LocalWsSession) masterWrite(data []byte) error {
func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) {
defer func() {
if r := recover(); r != nil {
setQuit(exitCh)
global.LOG.Errorf("A panic occurred during receive ws message, error message: %v", r)
}
}()
+3 -3
View File
@@ -6,15 +6,15 @@ command -v wget >/dev/null || {
}
if [ ! -f "1pctl" ]; then
wget https://github.com/1Panel-dev/installer/raw/main/1pctl
wget https://github.com/1Panel-dev/installer/raw/dev/1pctl
fi
if [ ! -f "1panel.service" ]; then
wget https://github.com/1Panel-dev/installer/raw/main/1panel.service
wget https://github.com/1Panel-dev/installer/raw/dev/1panel.service
fi
if [ ! -f "install.sh" ]; then
wget https://github.com/1Panel-dev/installer/raw/main/install.sh
wget https://github.com/1Panel-dev/installer/raw/dev/install.sh
fi
if [ ! -f "GeoIP.mmdb" ]; then
+635 -626
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+382 -376
View File
@@ -1578,6 +1578,8 @@ definitions:
type: object
dto.ForwardRuleOperate:
properties:
forceDelete:
type: boolean
rules:
items:
properties:
@@ -2480,6 +2482,8 @@ definitions:
type: object
dto.OllamaBindDomainRes:
properties:
acmeAccountID:
type: integer
allowIPs:
items:
type: string
@@ -3338,6 +3342,8 @@ definitions:
type: string
lastCleanTime:
type: string
licenseVerify:
type: string
localTime:
type: string
menuTabs:
@@ -8585,7 +8591,7 @@ paths:
in: path
name: id
required: true
type: integer
type: string
responses:
"200":
description: OK
@@ -11233,381 +11239,6 @@ paths:
formatEN: update group [name][type]
formatZH: 更新组 [name][type]
paramKeys: []
/host/conffile/update:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SSHConf'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Update host SSH setting by file
tags:
- SSH
x-panel-log:
BeforeFunctions: []
bodyKeys: []
formatEN: update SSH conf
formatZH: 修改 SSH 配置文件
paramKeys: []
/host/ssh/conf:
get:
responses:
"200":
description: OK
schema:
type: string
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Load host SSH conf
tags:
- SSH
/host/ssh/generate:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.GenerateSSH'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Generate host SSH secret
tags:
- SSH
x-panel-log:
BeforeFunctions: []
bodyKeys: []
formatEN: generate SSH secret
formatZH: '生成 SSH 密钥 '
paramKeys: []
/host/ssh/log:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SearchSSHLog'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.SSHLog'
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Load host SSH logs
tags:
- SSH
/host/ssh/operate:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.Operate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Operate SSH
tags:
- SSH
x-panel-log:
BeforeFunctions: []
bodyKeys:
- operation
formatEN: '[operation] SSH'
formatZH: '[operation] SSH '
paramKeys: []
/host/ssh/search:
post:
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.SSHInfo'
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Load host SSH setting info
tags:
- SSH
/host/ssh/secret:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.GenerateLoad'
responses:
"200":
description: OK
schema:
type: string
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Load host SSH secret
tags:
- SSH
/host/ssh/update:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SSHUpdate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Update host SSH setting
tags:
- SSH
x-panel-log:
BeforeFunctions: []
bodyKeys:
- key
- value
formatEN: update SSH setting [key] => [value]
formatZH: 修改 SSH 配置 [key] => [value]
paramKeys: []
/host/tool:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolReq'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.HostToolRes'
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Get tool status
tags:
- Host tool
/host/tool/config:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolConfig'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.HostToolConfig'
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Get tool config
tags:
- Host tool
x-panel-log:
BeforeFunctions: []
bodyKeys:
- operate
formatEN: '[operate] tool config'
formatZH: '[operate] 主机工具配置文件 '
paramKeys: []
/host/tool/create:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolCreate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Create Host tool Config
tags:
- Host tool
x-panel-log:
BeforeFunctions: []
bodyKeys:
- type
formatEN: create [type] config
formatZH: 创建 [type] 配置
paramKeys: []
/host/tool/log:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolLogReq'
responses:
"200":
description: OK
schema:
type: string
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Get tool logs
tags:
- Host tool
/host/tool/operate:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolReq'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Operate tool
tags:
- Host tool
x-panel-log:
BeforeFunctions: []
bodyKeys:
- operate
- type
formatEN: '[operate] [type]'
formatZH: '[operate] [type] '
paramKeys: []
/host/tool/supervisor/process:
get:
consumes:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.SupervisorProcessConfig'
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Get Supervisor process config
tags:
- Host tool
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.SupervisorProcessConfig'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Create Supervisor process
tags:
- Host tool
x-panel-log:
BeforeFunctions: []
bodyKeys:
- operate
formatEN: '[operate] process'
formatZH: '[operate] 守护进程 '
paramKeys: []
/host/tool/supervisor/process/file:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.SupervisorProcessFileReq'
responses:
"200":
description: OK
schema:
type: string
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Get Supervisor process config
tags:
- Host tool
x-panel-log:
BeforeFunctions: []
bodyKeys:
- operate
formatEN: '[operate] Supervisor Process Config file'
formatZH: '[operate] Supervisor 进程文件 '
paramKeys: []
/hosts:
post:
consumes:
@@ -11874,6 +11505,32 @@ paths:
formatEN: update quick command [name]
formatZH: 更新快捷命令 [name]
paramKeys: []
/hosts/conffile/update:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SSHConf'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Update host SSH setting by file
tags:
- SSH
x-panel-log:
BeforeFunctions: []
bodyKeys: []
formatEN: update SSH conf
formatZH: 修改 SSH 配置文件
paramKeys: []
/hosts/del:
post:
consumes:
@@ -12193,6 +11850,157 @@ paths:
summary: Page host
tags:
- Host
/hosts/ssh/conf:
get:
responses:
"200":
description: OK
schema:
type: string
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Load host SSH conf
tags:
- SSH
/hosts/ssh/generate:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.GenerateSSH'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Generate host SSH secret
tags:
- SSH
x-panel-log:
BeforeFunctions: []
bodyKeys: []
formatEN: generate SSH secret
formatZH: '生成 SSH 密钥 '
paramKeys: []
/hosts/ssh/log:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SearchSSHLog'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.SSHLog'
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Load host SSH logs
tags:
- SSH
/hosts/ssh/operate:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.Operate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Operate SSH
tags:
- SSH
x-panel-log:
BeforeFunctions: []
bodyKeys:
- operation
formatEN: '[operation] SSH'
formatZH: '[operation] SSH '
paramKeys: []
/hosts/ssh/search:
post:
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.SSHInfo'
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Load host SSH setting info
tags:
- SSH
/hosts/ssh/secret:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.GenerateLoad'
responses:
"200":
description: OK
schema:
type: string
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Load host SSH secret
tags:
- SSH
/hosts/ssh/update:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SSHUpdate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Update host SSH setting
tags:
- SSH
x-panel-log:
BeforeFunctions: []
bodyKeys:
- key
- value
formatEN: update SSH setting [key] => [value]
formatZH: 修改 SSH 配置 [key] => [value]
paramKeys: []
/hosts/test/byid/{id}:
post:
consumes:
@@ -12236,6 +12044,204 @@ paths:
summary: Test host conn by info
tags:
- Host
/hosts/tool:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolReq'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.HostToolRes'
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Get tool status
tags:
- Host tool
/hosts/tool/config:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolConfig'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.HostToolConfig'
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Get tool config
tags:
- Host tool
x-panel-log:
BeforeFunctions: []
bodyKeys:
- operate
formatEN: '[operate] tool config'
formatZH: '[operate] 主机工具配置文件 '
paramKeys: []
/hosts/tool/create:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolCreate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Create Host tool Config
tags:
- Host tool
x-panel-log:
BeforeFunctions: []
bodyKeys:
- type
formatEN: create [type] config
formatZH: 创建 [type] 配置
paramKeys: []
/hosts/tool/log:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolLogReq'
responses:
"200":
description: OK
schema:
type: string
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Get tool logs
tags:
- Host tool
/hosts/tool/operate:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.HostToolReq'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Operate tool
tags:
- Host tool
x-panel-log:
BeforeFunctions: []
bodyKeys:
- operate
- type
formatEN: '[operate] [type]'
formatZH: '[operate] [type] '
paramKeys: []
/hosts/tool/supervisor/process:
get:
consumes:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.SupervisorProcessConfig'
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Get Supervisor process config
tags:
- Host tool
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.SupervisorProcessConfig'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Create Supervisor process
tags:
- Host tool
x-panel-log:
BeforeFunctions: []
bodyKeys:
- operate
formatEN: '[operate] process'
formatZH: '[operate] 守护进程 '
paramKeys: []
/hosts/tool/supervisor/process/file:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.SupervisorProcessFileReq'
responses:
"200":
description: OK
schema:
type: string
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Get Supervisor process config
tags:
- Host tool
x-panel-log:
BeforeFunctions: []
bodyKeys:
- operate
formatEN: '[operate] Supervisor Process Config file'
formatZH: '[operate] Supervisor 进程文件 '
paramKeys: []
/hosts/tree:
post:
consumes:
+19
View File
@@ -0,0 +1,19 @@
services:
mcp-server:
image: supercorp/supergateway:latest
container_name: ${CONTAINER_NAME}
restart: unless-stopped
ports:
- "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${PANEL_APP_PORT_HTTP}"
command: [
"--stdio", "${COMMAND}",
"--port", "${PANEL_APP_PORT_HTTP}",
"--baseUrl", "${BASE_URL}",
"--ssePath", "${SSE_PATH}",
"--messagePath", "${SSE_PATH}/messages"
]
networks:
- 1panel-network
networks:
1panel-network:
external: true
+8
View File
@@ -0,0 +1,8 @@
package mcp
import (
_ "embed"
)
//go:embed compose.yml
var DefaultMcpCompose []byte
+3
View File
@@ -37,3 +37,6 @@ var DomainNotFoundHTML []byte
//go:embed stop.html
var StopHTML []byte
//go:embed sse.conf
var SSE []byte
+1
View File
@@ -7,6 +7,7 @@ location ^~ /test {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_http_version 1.1;
add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-cache;
+7
View File
@@ -0,0 +1,7 @@
location ^~ /github {
proxy_pass http://127.0.0.1:8001/github;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Connection '';
chunked_transfer_encoding off;
}
+72
View File
@@ -104,8 +104,80 @@ export namespace AI {
export interface BindDomainRes {
domain: string;
sslID: number;
acmeAccountID: number;
allowIPs: string[];
websiteID?: number;
connUrl: string;
}
export interface Environment {
key: string;
value: string;
}
export interface Volume {
source: string;
target: string;
}
export interface McpServer {
id: number;
name: string;
status: string;
baseUrl: string;
ssePath: string;
command: string;
port: number;
message: string;
createdAt?: string;
containerName: string;
environments: Environment[];
volumes: Volume[];
dir?: string;
hostIP: string;
protocol: string;
url: string;
}
export interface McpServerSearch extends ReqPage {
name: string;
}
export interface McpServerDelete {
id: number;
}
export interface McpServerOperate {
id: number;
operate: string;
}
export interface McpBindDomain {
domain: string;
sslID: number;
ipList: string;
}
export interface McpDomainRes {
domain: string;
sslID: number;
acmeAccountID: number;
allowIPs: string[];
websiteID?: number;
connUrl: string;
}
export interface McpBindDomainUpdate {
websiteID: number;
sslID: number;
ipList: string;
}
export interface ImportMcpServer {
name: string;
command: string;
ssePath: string;
containerName: string;
environments: Environment[];
}
}
+1
View File
@@ -81,6 +81,7 @@ export namespace Cronjob {
export interface CronjobDelete {
ids: Array<number>;
cleanData: boolean;
cleanRemoteData: boolean;
}
export interface UpdateStatus {
id: number;
+1
View File
@@ -62,6 +62,7 @@ export namespace Setting {
apiKey: string;
ipWhiteList: string;
apiKeyValidityTime: number;
licenseVerify: string;
}
export interface SettingUpdate {
key: string;
+32
View File
@@ -39,3 +39,35 @@ export const getBindDomain = (req: AI.BindDomainReq) => {
export const updateBindDomain = (req: AI.BindDomain) => {
return http.post(`/ai/domain/update`, req);
};
export const pageMcpServer = (req: AI.McpServerSearch) => {
return http.post<ResPage<AI.McpServer>>(`/ai/mcp/search`, req);
};
export const createMcpServer = (req: AI.McpServer) => {
return http.post(`/ai/mcp/server`, req);
};
export const updateMcpServer = (req: AI.McpServer) => {
return http.post(`/ai/mcp/server/update`, req);
};
export const deleteMcpServer = (req: AI.McpServerDelete) => {
return http.post(`/ai/mcp/server/del`, req);
};
export const operateMcpServer = (req: AI.McpServerOperate) => {
return http.post(`/ai/mcp/server/op`, req);
};
export const bindMcpDomain = (req: AI.McpBindDomain) => {
return http.post(`/ai/mcp/domain/bind`, req);
};
export const getMcpDomain = () => {
return http.get<AI.McpDomainRes>(`/ai/mcp/domain/get`);
};
export const updateMcpDomain = (req: AI.McpBindDomainUpdate) => {
return http.post(`/ai/mcp/domain/update`, req);
};
+6 -2
View File
@@ -27,8 +27,12 @@ export const searchRecords = (params: Cronjob.SearchRecord) => {
return http.post<ResPage<Cronjob.Record>>(`cronjobs/search/records`, params);
};
export const cleanRecords = (id: number, cleanData: boolean) => {
return http.post(`cronjobs/records/clean`, { cronjobID: id, cleanData: cleanData });
export const cleanRecords = (id: number, cleanData: boolean, cleanRemoteData: boolean) => {
return http.post(`cronjobs/records/clean`, {
cronjobID: id,
cleanData: cleanData,
cleanRemoteData: cleanRemoteData,
});
};
export const getRecordDetail = (params: string) => {
+2 -2
View File
@@ -49,8 +49,8 @@ export const SaveFileContent = (params: File.FileEdit) => {
return http.post<File.File>('files/save', params);
};
export const CheckFile = (path: string) => {
return http.post<boolean>('files/check', { path: path });
export const CheckFile = (path: string, withInit: boolean) => {
return http.post<boolean>('files/check', { path: path, withInit: withInit });
};
export const BatchCheckFiles = (paths: string[]) => {
+1 -1
View File
@@ -98,7 +98,7 @@ export const operateFire = (operation: string) => {
export const operatePortRule = (params: Host.RulePort) => {
return http.post<Host.RulePort>(`/hosts/firewall/port`, params, TimeoutEnum.T_40S);
};
export const operateForwardRule = (params: { rules: Host.RuleForward[] }) => {
export const operateForwardRule = (params: { rules: Host.RuleForward[]; forceDelete: boolean }) => {
return http.post<Host.RulePort>(`/hosts/firewall/forward`, params, TimeoutEnum.T_40S);
};
export const operateIPRule = (params: Host.RuleIP) => {
@@ -59,7 +59,7 @@
<script lang="ts" setup>
import i18n from '@/lang';
import { dateFormatForName } from '@/utils/util';
import { computed, onBeforeUnmount, reactive, ref, shallowRef, watch } from 'vue';
import { computed, nextTick, onBeforeUnmount, reactive, ref, shallowRef, watch } from 'vue';
import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
@@ -145,10 +145,12 @@ const searchLogs = async () => {
);
terminalSocket.value.onmessage = (event) => {
logInfo.value += event.data;
const state = view.value.state;
view.value.dispatch({
selection: { anchor: state.doc.length, head: state.doc.length },
scrollIntoView: true,
nextTick(() => {
const state = view.value.state;
view.value.dispatch({
selection: { anchor: state.doc.length, head: state.doc.length },
scrollIntoView: true,
});
});
};
};
@@ -48,7 +48,7 @@
import { cleanContainerLog } from '@/api/modules/container';
import i18n from '@/lang';
import { dateFormatForName, downloadWithContent } from '@/utils/util';
import { onBeforeUnmount, reactive, ref, shallowRef } from 'vue';
import { nextTick, onBeforeUnmount, reactive, ref, shallowRef } from 'vue';
import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
@@ -113,10 +113,12 @@ const searchLogs = async () => {
);
terminalSocket.value.onmessage = (event) => {
logInfo.value += event.data;
const state = view.value.state;
view.value.dispatch({
selection: { anchor: state.doc.length, head: state.doc.length },
scrollIntoView: true,
nextTick(() => {
const state = view.value.state;
view.value.dispatch({
selection: { anchor: state.doc.length, head: state.doc.length },
scrollIntoView: true,
});
});
};
};
+6 -4
View File
@@ -85,7 +85,9 @@ function onClose(isKeepShow: boolean = false) {
term.value.dispose();
} catch {}
}
terminalElement.value.innerHTML = '';
if (terminalElement.value) {
terminalElement.value.innerHTML = '';
}
}
// terminal 相关代码 start
@@ -202,10 +204,10 @@ const errorRealTerminal = (ex: any) => {
const closeRealTerminal = (ev: CloseEvent) => {
if (heartbeatTimer.value) {
clearInterval(heartbeatTimer.value);
clearInterval(Number(heartbeatTimer.value));
}
term.value.write('The connection has been disconnected.');
term.value.write(ev.reason);
term.value?.write('The connection has been disconnected.');
term.value?.write(ev.reason);
};
const isWsOpen = () => {
+1 -1
View File
@@ -308,7 +308,7 @@ const onSubmit = async () => {
MsgError(i18n.global.t('commons.msg.fileNameErr'));
return;
}
const res = await CheckFile(baseDir.value + file.raw.name);
const res = await CheckFile(baseDir.value + file.raw.name, false);
if (res.data) {
MsgError(i18n.global.t('commons.msg.fileExist'));
return;
+2 -1
View File
@@ -121,7 +121,8 @@ const checkIllegal = (rule: any, value: any, callback: any) => {
value.indexOf('`') !== -1 ||
value.indexOf('(') !== -1 ||
value.indexOf(')') !== -1 ||
value.indexOf("'") !== -1
value.indexOf('>') !== -1 ||
value.indexOf('<') !== -1
) {
callback(new Error(i18n.global.t('commons.rule.illegalInput')));
} else {
+28 -12
View File
@@ -95,6 +95,10 @@ export const Languages = [
label: 'css',
value: ['css'],
},
{
label: 'ini',
value: ['ini'],
},
];
export const Rewrites = [
@@ -169,21 +173,13 @@ export const DNSTypes = [
value: 'HuaweiCloud',
},
{
label: i18n.global.t('website.volcengine'),
value: 'Volcengine',
},
{
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
value: 'DnsPod',
label: 'GoDaddy',
value: 'Godaddy',
},
{
label: 'Cloudflare',
value: 'CloudFlare',
},
{
label: 'CloudDNS',
value: 'CloudDns',
},
{
label: 'NameSilo',
value: 'NameSilo',
@@ -192,18 +188,38 @@ export const DNSTypes = [
label: 'NameCheap',
value: 'NameCheap',
},
{
label: 'CloudDNS',
value: 'CloudDns',
},
{
label: 'ClouDNS',
value: 'ClouDNS',
},
{
label: 'Name.com',
value: 'NameCom',
},
{
label: 'GoDaddy',
value: 'Godaddy',
label: 'FreeMyIP',
value: 'FreeMyIP',
},
{
label: i18n.global.t('website.rainyun'),
value: 'RainYun',
},
{
label: i18n.global.t('website.westCN'),
value: 'WestCN',
},
{
label: i18n.global.t('website.volcengine'),
value: 'Volcengine',
},
{
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
value: 'DnsPod',
},
];
export const Fields = [
+32 -4
View File
@@ -123,8 +123,6 @@ const message = {
},
msg: {
noneData: 'No data available',
disConn:
'Please click the disconnect button directly to terminate the terminal connection, avoiding the use of exit commands like {0}.',
delete: `This operation delete can't be undone. Do you want to continue?`,
clean: `This operation clean can't be undone. Do you want to continue?`,
deleteTitle: 'Delete',
@@ -193,6 +191,7 @@ const message = {
rePassword: 'Confirm password is inconsistent with the password.',
requiredInput: 'This field is required.',
requiredSelect: 'Select an item in the list',
illegalChar: 'Injection of characters & ; $ \' ` ( ) " > < | is currently not supported',
illegalInput: `This field mustn't contains illegal characters.`,
commonName:
'This field must start with non-special characters and must consist of English, Chinese, numbers, ".", "-", and "_" characters with a length of 1-128.',
@@ -429,6 +428,8 @@ const message = {
permission: 'Permissions',
permissionForIP: 'IP',
permissionAll: 'All of them(%)',
localhostHelper:
'Configuring database permissions as "localhost" for container deployment will prevent external access to the container. Please choose carefully!',
databaseConnInfo: 'Connection info',
rootPassword: 'Root password',
serviceName: 'Service Name',
@@ -440,6 +441,7 @@ const message = {
remoteConnHelper:
'Remote connection to MySQL as user root may have security risks. Therefore, perform this operation with caution.',
changePassword: 'Password',
changeConnHelper: 'This operation will modify the current database {0}. Do you want to continue?',
changePasswordHelper:
'The database has been associated with an application. Changing the password will change the database password of the application at the same time. The change takes effect after the application restarts.',
@@ -1011,6 +1013,7 @@ const message = {
errHandle: 'Cronjob execution failure',
noRecord: 'Trigger the Cron Job, and you will see the records here.',
cleanData: 'Clean data',
cleanRemoteData: 'Delete remote data',
cleanDataHelper: 'Delete the backup file generated during this task.',
noLogs: 'No task output yet...',
errPath: 'Backup path [{0}] error, cannot download!',
@@ -1382,6 +1385,7 @@ const message = {
existFileHelper: 'The uploaded file contains a file with the same name, do you want to overwrite it?',
existFileSize: 'File size (new -> old)',
existFileDirHelper: 'The selected file/folder has a duplicate name. Please proceed with caution!',
noSuchFile: 'The file or directory was not found. Please check and try again.',
},
ssh: {
setting: 'Setting',
@@ -2258,6 +2262,7 @@ const message = {
"When the reverse proxy backend is HTTPS, you might need to set the origin SNI. Please refer to the CDN service provider's documentation for details.",
huaweicloud: 'Huawei Cloud',
rainyun: 'Rain Yun',
westCN: 'West CN',
},
php: {
short_open_tag: 'Short tag support',
@@ -2539,8 +2544,8 @@ const message = {
env: 'Environments',
noenv: 'None',
net: 'Network connections',
laddr: 'Source address/port',
raddr: 'Destination address/port',
laddr: 'Local address/port',
raddr: 'Remote address/port',
stopProcess: 'End',
viewDetails: 'Details',
stopProcessWarn: 'Are you sure you want to end this process (PID:{0})?',
@@ -2599,6 +2604,29 @@ const message = {
proxyHelper6: 'To disable proxy configuration, you can delete it from the website list.',
whiteListHelper: 'Restrict access to only IPs in the whitelist',
},
mcp: {
server: 'MCP Server',
create: 'Add MCP Server',
edit: 'Edit MCP Server',
commandHelper: 'For example: npx -y {0}',
baseUrl: 'External Access Path',
baseUrlHelper: 'For example: http://192.168.1.2:8000',
ssePath: 'SSE Path',
ssePathHelper: 'For example: /sse, note not to duplicate with other servers',
environment: 'Environment Variables',
envKey: 'Variable Name',
envValue: 'Variable Value',
externalUrl: 'External Connection Address',
operatorHelper: 'Will perform {1} operation on {0}, continue?',
domain: 'Default Access Address',
domainHelper: 'For example: 192.168.1.1 or example.com',
bindDomain: 'Bind Website',
commandPlaceHolder: 'Currently only supports npx and binary startup commands',
importMcpJson: 'Import MCP Server Configuration',
importMcpJsonError: 'mcpServers structure is incorrect',
bindDomainHelper:
'After binding the website, it will modify the access address of all installed MCP Servers and close external access to the ports',
},
};
export default {
+32 -3
View File
@@ -74,7 +74,7 @@ const message = {
createNewFile: '新しいファイルを作成します',
helpDoc: '文書をヘルプします',
unbind: 'バインド',
cover: '上書き',
cover: 'に覆いを',
skip: 'スキップ',
},
search: {
@@ -122,8 +122,6 @@ const message = {
},
msg: {
noneData: '利用可能なデータはありません',
disConn:
'端末接続を切断するには、{0} のような終了コマンドを使用せずに、直接切断ボタンをクリックしてください',
delete: `この操作削除は元に戻すことはできません。続けたいですか?`,
clean: `この操作は取り消すことはできません。続けたいですか?`,
deleteTitle: '消去',
@@ -192,6 +190,7 @@ const message = {
rePassword: 'パスワードがパスワードと矛盾することを確認してください。',
requiredInput: 'この項目は必須です。',
requiredSelect: 'リスト内のアイテムを選択します',
illegalChar: '現在、文字 & ; $ \' ` ( ) " > < | の注入はサポートされていません',
illegalInput: `このフィールドには違法なキャラクターが含まれてはなりません。`,
commonName:
'このフィールドは、特別なキャラクターではなく、英語、中国語、数字で構成されている必要があります。「。」、「」、および「_」文字が1〜128の文字で構成されている必要があります。',
@@ -429,6 +428,8 @@ const message = {
permission: '権限',
permissionForIP: 'ip',
permissionAll: 'それらすべて(%)',
localhostHelper:
'コンテナ展開でデータベース権限を"localhost"に設定すると、コンテナ外部からのアクセスができなくなります。慎重に選択してください!',
databaseConnInfo: '接続情報',
rootPassword: 'ルートパスワード',
serviceName: 'サービス名',
@@ -440,6 +441,7 @@ const message = {
remoteConnHelper:
'ユーザールートとしてのMySQLへのリモート接続には、セキュリティリスクがある場合があります。したがって、この操作を慎重に実行します。',
changePassword: 'パスワード',
changeConnHelper: 'この操作は現在のデータベース {0} を変更します。続行しますか?',
changePasswordHelper:
'データベースはアプリケーションに関連付けられています。パスワードを変更すると、アプリケーションのデータベースパスワードが同時に変更されます。アプリケーションが再起動した後、変更は有効になります。',
@@ -996,6 +998,7 @@ const message = {
errHandle: 'cronjob実行障害',
noRecord: 'Cronジョブをトリガーすると、ここにレコードが表示されます。',
cleanData: 'クリーンデータ',
cleanRemoteData: 'リモートデータを削除',
cleanDataHelper: 'このタスク中に生成されたバックアップファイルを削除します。',
noLogs: 'タスク出力はまだありません...',
errPath: 'バックアップパス[{0}]エラー、ダウンロードできません!',
@@ -1360,6 +1363,7 @@ const message = {
existFileHelper: 'アップロードしたファイルに同じ名前のファイルが含まれています。上書きしますか?',
existFileSize: 'ファイルサイズ(新しい -> 古い)',
existFileDirHelper: '選択したファイル/フォルダーには同じ名前のものが既に存在します。慎重に操作してください!',
noSuchFile: 'ファイルまたはディレクトリが見つかりませんでした。確認して再試行してください。',
},
ssh: {
setting: '設定',
@@ -2230,6 +2234,8 @@ const message = {
sniHelper:
'逆プロキシバックエンドがHTTPSの場合、Origin SNIを設定する必要がある場合があります。詳細については、CDNサービスプロバイダーのドキュメントを参照してください。',
huaweicloud: 'huaweiCloud',
rainyun: 'Rain Yun',
westCN: 'West CN',
},
php: {
short_open_tag: '短いタグサポート',
@@ -2568,6 +2574,29 @@ const message = {
proxyHelper6: 'プロキシ設定を無効にするには、ウェブサイトリストから削除できます。',
whiteListHelper: 'ホワイトリスト内のIPのみアクセスを許可する',
},
mcp: {
server: 'MCP サーバー',
create: 'サーバーを追加',
edit: 'サーバーを編集',
commandHelper: '例: npx -y {0}',
baseUrl: '外部アクセスパス',
baseUrlHelper: '例: http://192.168.1.2:8000',
ssePath: 'SSE パス',
ssePathHelper: '例: /sse, 他のサーバーと重複しないように注意してください',
environment: '環境変数',
envKey: '変数名',
envValue: '変数値',
externalUrl: '外部接続アドレス',
operatorHelper: '{0} に {1} 操作を実行します、続行しますか?',
domain: 'デフォルトアクセスアドレス',
domainHelper: '例: 192.168.1.1 または example.com',
bindDomain: 'ウェブサイトをバインド',
commandPlaceHolder: '現在、npx およびバイナリスタートアップコマンドのみをサポートしています',
importMcpJson: 'MCP サーバー設定をインポート',
importMcpJsonError: 'mcpServers 構造が正しくありません',
bindDomainHelper:
'ウェブサイトをバインドした後、インストールされたすべての MCP サーバーのアクセスアドレスを変更し、ポートへの外部アクセスを閉じます',
},
};
export default {
...fit2cloudJaLocale,
+30 -2
View File
@@ -123,8 +123,6 @@ const message = {
},
msg: {
noneData: '데이터가 없습니다',
disConn:
'종료 명령어인 {0} 등을 사용하지 않고 직접 연결 끊기 버튼을 클릭하여 터미널 연결을 종료해 주십시오.',
delete: `이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?`,
clean: `이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?`,
deleteTitle: '삭제',
@@ -192,6 +190,7 @@ const message = {
rePassword: '확인 비밀번호가 비밀번호와 일치하지 않습니다.',
requiredInput: '이 필드는 필수 항목입니다.',
requiredSelect: '목록에서 항목을 선택하세요',
illegalChar: '현재 & ; $ \' ` ( ) " > < | 문자 주입은 지원되지 않습니다',
illegalInput: '이 필드에는 유효하지 않은 문자가 포함될 수 없습니다.',
commonName:
'이 필드는 특수 문자로 시작할 수 없으며, 영어, 한자, 숫자, ".", "-", "_" 문자로 구성되어야 하며 길이는 1-128자여야 합니다.',
@@ -431,6 +430,8 @@ const message = {
permission: '권한',
permissionForIP: 'IP',
permissionAll: '모두(%)',
localhostHelper:
'컨테이너 배포 시 데이터베이스 권한을 "localhost"로 설정하면 컨테이너 외부에서 접근할 수 없게 됩니다. 신중하게 선택하세요!',
databaseConnInfo: '연결 정보',
rootPassword: '루트 비밀번호',
serviceName: '서비스 이름',
@@ -442,6 +443,7 @@ const message = {
remoteConnHelper:
'MySQL 의 root 사용자로 원격 접속은 보안 위험을 초래할 수 있습니다. 따라서 이 작업은 신중히 수행해야 합니다.',
changePassword: '비밀번호',
changeConnHelper: '이 작업은 현재 데이터베이스 {0}을(를) 수정합니다. 계속하시겠습니까?',
changePasswordHelper:
'데이터베이스가 애플리케이션과 연결되어 있습니다. 비밀번호를 변경하면 애플리케이션의 데이터베이스 비밀번호도 변경됩니다. 변경 사항은 애플리케이션이 재시작된 후에 적용됩니다.',
confChange: '설정',
@@ -990,6 +992,7 @@ const message = {
errHandle: '크론 작업 실행 실패',
noRecord: '크론 작업을 트리거하고 나면 여기에 레코드가 표시됩니다.',
cleanData: '데이터 정리',
cleanRemoteData: '원격 데이터 삭제',
cleanDataHelper: '이 작업에서 생성된 백업 파일을 삭제합니다.',
noLogs: '작업 출력이 아직 없습니다...',
errPath: '백업 경로 [{0}] 오류, 다운로드할 수 없습니다!',
@@ -1346,6 +1349,7 @@ const message = {
existFileHelper: '업로드한 파일에 동일한 이름의 파일이 포함되어 있습니다. 덮어쓰시겠습니까?',
existFileSize: '파일 크기 (새로운 -> 오래된)',
existFileDirHelper: '선택한 파일/폴더에 동일한 이름이 이미 존재합니다. 신중하게 작업하세요!',
noSuchFile: '파일 또는 디렉터리를 찾을 수 없습니다. 확인 후 다시 시도하세요.',
},
ssh: {
setting: '설정',
@@ -2192,6 +2196,7 @@ const message = {
'역방향 프록시 백엔드가 HTTPS 인 경우 원본 SNI 를 설정해야 할 수 있습니다. 자세한 내용은 CDN 서비스 제공자의 문서를 참조하세요.',
huaweicloud: '화웨이 클라우드',
rainyun: '레인윤',
westCN: 'West CN',
},
php: {
short_open_tag: '짧은 태그 지원',
@@ -2528,6 +2533,29 @@ const message = {
proxyHelper6: '프록시 구성을 비활성화하려면 웹사이트 목록에서 삭제할 수 있습니다.',
whiteListHelper: '화이트리스트에 있는 IP만 접근 허용',
},
mcp: {
server: 'MCP サーバー',
create: 'サーバーを追加',
edit: 'サーバーを編集',
commandHelper: '例: npx -y {0}',
baseUrl: '外部アクセスパス',
baseUrlHelper: '例: http://192.168.1.2:8000',
ssePath: 'SSE パス',
ssePathHelper: '例: /sse, 他のサーバーと重複しないように注意してください',
environment: '環境変数',
envKey: '変数名',
envValue: '変数値',
externalUrl: '外部接続アドレス',
operatorHelper: '{0} に {1} 操作を実行します、続行しますか?',
domain: 'デフォルトアクセスアドレス',
domainHelper: '例: 192.168.1.1 または example.com',
bindDomain: 'ウェブサイトをバインド',
commandPlaceHolder: '현재 npx 및 바이너리 시작 명령만 지원합니다',
importMcpJson: 'MCP サーバー設定をインポート',
importMcpJsonError: 'mcpServers 構造が正しくありません',
bindDomainHelper:
'웹사이트를 바인딩한 후, 설치된 모든 MCP 서버의 접근 주소를 수정하고 포트의 외부 접근을 닫습니다',
},
};
export default {
+30 -2
View File
@@ -123,8 +123,6 @@ const message = {
},
msg: {
noneData: 'Tiada data tersedia',
disConn:
'Sila klik butang putus sambungan secara langsung untuk menamatkan sambungan terminal, mengelakkan penggunaan arahan keluar seperti {0}.',
delete: 'Operasi ini tidak boleh diundur. Adakah anda mahu meneruskan?',
clean: 'Operasi ini tidak boleh diundur. Adakah anda mahu meneruskan?',
deleteTitle: 'Padam',
@@ -195,6 +193,7 @@ const message = {
rePassword: 'Pengesahan kata laluan tidak sepadan dengan kata laluan.',
requiredInput: 'Ruangan ini wajib diisi.',
requiredSelect: 'Pilih satu item dalam senarai',
illegalChar: 'Suntikan aksara & ; $ \' ` ( ) " > < | tidak disokong buat masa ini',
illegalInput: 'Ruangan ini tidak boleh mengandungi aksara tidak sah.',
commonName:
'Ruangan ini mesti bermula dengan aksara bukan khas dan mesti terdiri daripada aksara rumi, Cina, nombor, ".", "-", dan "_" dengan panjang 1-128 aksara.',
@@ -437,6 +436,8 @@ const message = {
permission: 'Kebenaran',
permissionForIP: 'IP',
permissionAll: 'Kesemuanya(%)',
localhostHelper:
'Mengkonfigurasi kebenaran pangkalan data sebagai "localhost" untuk penyebaran kontena akan menghalang akses luar ke kontena. Sila pilih dengan teliti!',
databaseConnInfo: 'Maklumat sambungan',
rootPassword: 'Kata laluan root',
serviceName: 'Nama Perkhidmatan',
@@ -448,6 +449,7 @@ const message = {
remoteConnHelper:
'Sambungan jauh ke MySQL sebagai pengguna root mungkin mempunyai risiko keselamatan. Oleh itu, lakukan operasi ini dengan berhati-hati.',
changePassword: 'Kata laluan',
changeConnHelper: 'Operasi ini akan mengubah pangkalan data semasa {0}. Adakah anda ingin meneruskan?',
changePasswordHelper:
'Pangkalan data telah dikaitkan dengan aplikasi. Menukar kata laluan akan menukar kata laluan pangkalan data aplikasi pada masa yang sama. Perubahan ini akan berkuat kuasa selepas aplikasi dimulakan semula.',
@@ -1025,6 +1027,7 @@ const message = {
errHandle: 'Kegagalan pelaksanaan tugas cron',
noRecord: 'Picu Tugas Cron, dan anda akan melihat rekod di sini.',
cleanData: 'Bersihkan data',
cleanRemoteData: 'Padam data jarak jauh',
cleanDataHelper: 'Padam fail sandaran yang dijana semasa tugas ini.',
noLogs: 'Tiada keluaran tugas lagi...',
errPath: 'Laluan sandaran [{0}] salah, tidak boleh dimuat turun!',
@@ -1403,6 +1406,7 @@ const message = {
existFileHelper: 'Fail yang dimuat naik mengandungi fail dengan nama yang sama. Adakah anda mahu menimpanya?',
existFileSize: 'Saiz fail (baru -> lama)',
existFileDirHelper: 'Fail/folder yang dipilih mempunyai nama yang sama. Sila berhati-hati!',
noSuchFile: 'Fail atau direktori tidak ditemui. Sila periksa dan cuba lagi.',
},
ssh: {
setting: 'tetapan',
@@ -2281,6 +2285,7 @@ const message = {
'Apabila backend proksi terbalik adalah HTTPS, anda mungkin perlu menetapkan sumber SNI. Sila rujuk dokumentasi penyedia perkhidmatan CDN untuk butiran.',
huaweicloud: 'Huawei Cloud',
rainyun: 'Rain Yun',
westCN: 'West CN',
},
php: {
short_open_tag: 'Sokongan tag pendek',
@@ -2627,6 +2632,29 @@ const message = {
proxyHelper6: 'Untuk melumpuhkan konfigurasi proksi, anda boleh memadamnya dari senarai laman web.',
whiteListHelper: 'Hadkan akses kepada hanya IP dalam senarai putih',
},
mcp: {
server: 'Pelayan MCP',
create: 'Tambah Pelayan',
edit: 'Edit Pelayan',
commandHelper: 'Contoh: npx -y {0}',
baseUrl: 'Laluan Akses Luar',
baseUrlHelper: 'Contoh: http://192.168.1.2:8000',
ssePath: 'Laluan SSE',
ssePathHelper: 'Contoh: /sse, berhati-hati jangan bertindan dengan pelayan lain',
environment: 'Pemboleh Ubah Persekitaran',
envKey: 'Nama Pemboleh Ubah',
envValue: 'Nilai Pemboleh Ubah',
externalUrl: 'Alamat Sambungan Luar',
operatorHelper: 'Akan melakukan operasi {1} pada {0}, teruskan?',
domain: 'Alamat Akses Lalai',
domainHelper: 'Contoh: 192.168.1.1 atau example.com',
bindDomain: 'Sematkan Laman Web',
commandPlaceHolder: 'Kini hanya menyokong perintah pelancaran npx dan binari',
importMcpJson: 'Import Konfigurasi Pelayan MCP',
importMcpJsonError: 'Struktur mcpServers tidak betul',
bindDomainHelper:
'Setelah mengikat laman web, ia akan mengubah alamat akses semua Pelayan MCP yang dipasang dan menutup akses luaran ke pelabuhan',
},
};
export default {
+31 -2
View File
@@ -123,8 +123,6 @@ const message = {
},
msg: {
noneData: 'Nenhum dado disponível',
disConn:
'Por favor, clique diretamente no botão de desconexão para encerrar a conexão do terminal, evitando o uso de comandos de saída como {0}.',
delete: 'Esta operação de exclusão não pode ser desfeita. Deseja continuar?',
clean: 'Esta operação de limpeza não pode ser desfeita. Deseja continuar?',
deleteTitle: 'Excluir',
@@ -194,6 +192,7 @@ const message = {
rePassword: 'A confirmação da senha não corresponde à senha.',
requiredInput: 'Este campo é obrigatório.',
requiredSelect: 'Selecione um item na lista',
illegalChar: 'Atualmente não há suporte para injeção dos caracteres & ; $ \' ` ( ) " > < |',
illegalInput: 'Este campo não deve conter caracteres ilegais.',
commonName:
'Este campo deve começar com caracteres não especiais e consistir em letras, números, ".", "-", e "_" com comprimento de 1-128.',
@@ -435,6 +434,8 @@ const message = {
permission: 'Permissões',
permissionForIP: 'IP',
permissionAll: 'Todos (% de)',
localhostHelper:
'Configurar permissões de banco de dados como "localhost" para implantação em contêiner impedirá o acesso externo ao contêiner. Por favor, escolha com cuidado!',
databaseConnInfo: 'Informações de conexão',
rootPassword: 'Senha root',
serviceName: 'Nome do serviço',
@@ -446,6 +447,7 @@ const message = {
remoteConnHelper:
'Conectar-se ao MySQL como usuário root pode representar riscos de segurança. Realize esta operação com cautela.',
changePassword: 'Senha',
changeConnHelper: 'Esta operação modificará o banco de dados atual {0}. Deseja continuar?',
changePasswordHelper:
'O banco de dados está associado a um aplicativo. Alterar a senha alterará a senha do banco de dados do aplicativo ao mesmo tempo. A mudança surtirá efeito após a reinicialização do aplicativo.',
@@ -1016,6 +1018,7 @@ const message = {
errHandle: 'Falha na execução do Cronjob',
noRecord: 'Acione a tarefa Cron e você verá os registros aqui.',
cleanData: 'Limpar dados',
cleanRemoteData: 'Excluir dados remotos',
cleanDataHelper: 'Excluir o arquivo de backup gerado durante esta tarefa.',
noLogs: 'Ainda não há saída de tarefa...',
errPath: 'Caminho de backup [{0}] com erro, não é possível fazer o download!',
@@ -1390,6 +1393,7 @@ const message = {
existFileHelper: 'O arquivo enviado contém um arquivo com o mesmo nome. Deseja substituí-lo?',
existFileSize: 'Tamanho do arquivo (novo -> antigo)',
existFileDirHelper: 'O arquivo/pasta selecionado tem um nome duplicado. Por favor, prossiga com cautela!',
noSuchFile: 'O arquivo ou diretório não foi encontrado. Por favor, verifique e tente novamente.',
},
ssh: {
setting: 'configuração',
@@ -2277,6 +2281,8 @@ const message = {
sniHelper:
'Quando o proxy reverso de backend for HTTPS, você pode precisar configurar o SNI de origem. Consulte a documentação do provedor de serviços CDN para mais detalhes.',
huaweicloud: 'Huawei Cloud',
rainyun: 'Rain Yun',
westCN: 'West CN',
},
php: {
short_open_tag: 'Suporte para short tags',
@@ -2624,6 +2630,29 @@ const message = {
proxyHelper6: 'Para desativar a configuração de proxy, você pode excluí-la da lista de sites.',
whiteListHelper: 'Restringir o acesso apenas aos IPs na lista branca',
},
mcp: {
server: 'Servidor MCP',
create: 'Adicionar Servidor',
edit: 'Editar Servidor',
commandHelper: 'Por exemplo: npx -y {0}',
baseUrl: 'Caminho de Acesso Externo',
baseUrlHelper: 'Por exemplo: http://192.168.1.2:8000',
ssePath: 'Caminho SSE',
ssePathHelper: 'Por exemplo: /sse, tome cuidado para não duplicar com outros servidores',
environment: 'Variáveis de Ambiente',
envKey: 'Nome da Variável',
envValue: 'Valor da Variável',
externalUrl: 'Endereço de Conexão Externo',
operatorHelper: 'Será realizada a operação {1} no {0}, continuar?',
domain: 'Endereço de Acesso Padrão',
domainHelper: 'Por exemplo: 192.168.1.1 ou example.com',
bindDomain: 'Vincular Site',
commandPlaceHolder: 'Atualmente, apenas comandos de inicialização npx e binários são suportados',
importMcpJson: 'Importar Configuração do Servidor MCP',
importMcpJsonError: 'A estrutura mcpServers está incorreta',
bindDomainHelper:
'Após vincular o site, ele modificará o endereço de acesso de todos os servidores MCP instalados e fechará o acesso externo às portas',
},
};
export default {
+31 -4
View File
@@ -113,8 +113,7 @@ const message = {
refreshRate: 'Частота обновления',
refreshRateUnit: 'Без обновления | {n} секунда/раз | {n} секунд/раз',
selectColumn: 'Выбрать столбец',
cover: 'Заменить',
skip: 'Пропустить',
serialNumber: 'Серийный номер',
},
loadingText: {
Upgrading: 'Обновление системы, пожалуйста, подождите...',
@@ -124,8 +123,6 @@ const message = {
},
msg: {
noneData: 'Нет данных',
disConn:
'Пожалуйста, нажмите кнопку отключения, чтобы разорвать соединение с терминалом, избегая использования команд выхода, таких как {0}.',
delete: 'Эта операция удаления не может быть отменена. Хотите продолжить?',
clean: 'Эта операция очистки не может быть отменена. Хотите продолжить?',
deleteTitle: 'Удалить',
@@ -194,6 +191,7 @@ const message = {
rePassword: 'Подтверждение пароля не совпадает с паролем.',
requiredInput: 'Это поле обязательно для заполнения.',
requiredSelect: 'Выберите элемент из списка',
illegalChar: 'В настоящее время не поддерживается вставка символов & ; $ \' ` ( ) " > < |',
illegalInput: 'Это поле не должно содержать недопустимых символов.',
commonName:
'Это поле должно начинаться с неспециальных символов и должно состоять из английских букв, китайских иероглифов, цифр, ".", "-" и "_" длиной 1-128.',
@@ -432,6 +430,8 @@ const message = {
permission: 'Разрешения',
permissionForIP: 'IP',
permissionAll: 'Все (%)',
localhostHelper:
'Настройка разрешений базы данных как "localhost" для развертывания контейнера предотвратит внешний доступ к контейнеру. Пожалуйста, выбирайте внимательно!',
databaseConnInfo: 'Информация о подключении',
rootPassword: 'Пароль root',
serviceName: 'Имя сервиса',
@@ -443,6 +443,7 @@ const message = {
remoteConnHelper:
'Удаленное подключение к MySQL как пользователь root может иметь риски безопасности. Поэтому выполняйте эту операцию с осторожностью.',
changePassword: 'Пароль',
changeConnHelper: 'Эта операция изменит текущую базу данных {0}. Продолжить?',
changePasswordHelper:
'База данных была связана с приложением. Изменение пароля изменит пароль базы данных приложения одновременно. Изменение вступит в силу после перезапуска приложения.',
@@ -1020,6 +1021,7 @@ const message = {
errHandle: 'Сбой выполнения задачи Cron',
noRecord: 'Запустите задачу Cron, и вы увидите записи здесь.',
cleanData: 'Очистить данные',
cleanRemoteData: 'Удалить удалённые данные',
cleanDataHelper: 'Удалить файл резервной копии, созданный во время этой задачи.',
noLogs: 'Пока нет вывода задачи...',
errPath: 'Ошибка пути резервной копии [{0}], невозможно скачать!',
@@ -1393,6 +1395,7 @@ const message = {
existFileHelper: 'Загруженный файл содержит файл с таким же именем. Заменить его?',
existFileSize: 'Размер файла (новый -> старый)',
existFileDirHelper: 'Выбранный файл/папка имеет дублирующееся имя. Пожалуйста, действуйте осторожно!',
noSuchFile: 'Файл или каталог не найдены. Пожалуйста, проверьте и повторите попытку.',
},
ssh: {
setting: 'настройка',
@@ -2279,6 +2282,7 @@ const message = {
'Когда бэкенд обратного прокси использует HTTPS, может потребоваться установить origin SNI. Подробности см. в документации провайдера CDN.',
huaweicloud: 'Huawei Cloud',
rainyun: 'Rain Yun',
westCN: 'West CN',
},
php: {
short_open_tag: 'Поддержка коротких тегов',
@@ -2622,6 +2626,29 @@ const message = {
proxyHelper6: 'Чтобы отключить настройку прокси, вы можете удалить её из списка сайтов.',
whiteListHelper: 'Ограничить доступ только для IP-адресов из белого списка',
},
mcp: {
server: 'Сервер MCP',
create: 'Добавить сервер',
edit: 'Редактировать сервер',
commandHelper: 'Например: npx -y {0}',
baseUrl: 'Внешний путь доступа',
baseUrlHelper: 'Например: http://192.168.1.2:8000',
ssePath: 'Путь SSE',
ssePathHelper: 'Например: /sse, будьте осторожны, чтобы не дублировать с другими серверами',
environment: 'Переменные среды',
envKey: 'Имя переменной',
envValue: 'Значение переменной',
externalUrl: 'Внешний адрес подключения',
operatorHelper: 'Будет выполнена операция {1} на {0}, продолжить?',
domain: 'Адрес доступа по умолчанию',
domainHelper: 'Например: 192.168.1.1 или example.com',
bindDomain: 'Привязать сайт',
commandPlaceHolder: 'В настоящее время поддерживаются только команды запуска npx и двоичных файлов',
importMcpJson: 'Импортировать конфигурацию сервера MCP',
importMcpJsonError: 'Структура mcpServers некорректна',
bindDomainHelper:
'После привязки веб-сайта он изменит адрес доступа для всех установленных серверов MCP и закроет внешний доступ к портам',
},
};
export default {
+51 -24
View File
@@ -14,7 +14,7 @@ const message = {
fit2cloud: '飛致雲',
lingxia: '凌霞',
button: {
run: '運行',
run: '執行',
create: '建立',
add: '新增',
save: '儲存',
@@ -123,7 +123,6 @@ const message = {
},
msg: {
noneData: '暫無資料',
disConn: '請直接點擊斷開按鈕斷開終端連接,避免使用 {0} 等退出命令。',
delete: '刪除 操作不可復原,是否繼續?',
clean: '清空 操作不可復原,是否繼續?',
deleteTitle: '刪除',
@@ -193,6 +192,7 @@ const message = {
rePassword: '密碼不一致,請檢查後重新輸入',
requiredInput: '請填寫必填項',
requiredSelect: '請選擇必選項',
illegalChar: '暫不支援注入字元 & ; $ \' ` ( ) " > < |',
illegalInput: '輸入框中存在不合法字元',
commonName: '支援非特殊字元開頭,英文、中文、數字、.-和_,長度1-128',
userName: '支援英文、中文、數字和_,長度3-30',
@@ -375,7 +375,7 @@ const message = {
core: '物理核心',
logicCore: '邏輯核心',
loadAverage: '最近 {0} 分鐘平均負載',
loadAverage: '最近 1 分鐘平均負載 | 最近 {n} 分鐘平均負載',
load: '負載',
mount: '掛載點',
fileSystem: '檔案系統',
@@ -419,6 +419,7 @@ const message = {
permission: '權限',
permissionForIP: '指定 IP',
permissionAll: '所有人(%)',
localhostHelper: '將容器部署的資料庫權限配置為"localhost"會導致容器外部無法存取,請謹慎選擇!',
databaseConnInfo: '連接資訊',
rootPassword: 'root 密碼',
serviceName: '服務名稱',
@@ -429,6 +430,7 @@ const message = {
remoteHelper: '多個 ip 以逗號分隔,例:172.16.10.111,172.16.10.112',
remoteConnHelper: 'root 帳號遠端連接 MySQL 有安全風險,開啟需謹慎!',
changePassword: '改密',
changeConnHelper: '此操作將修改當前資料庫 {0},是否繼續?',
changePasswordHelper: '目前資料庫已經關聯應用,修改密碼將同步修改應用中資料庫密碼,修改後重啟生效。',
portHelper: '該埠為容器對外暴露埠,修改需要單獨儲存並且重啟容器!',
@@ -579,18 +581,18 @@ const message = {
ai_tools: {
model: {
model: '模型',
create: '添加模型',
create: '新增模型',
create_helper: '拉取 "{0}"',
ollama_doc: '您可以訪問 Ollama 官方網站,搜索並查找更多模型。',
container_conn_helper: '容器間訪問或連接使用此地址',
ollama_doc: '您可以瀏覽 Ollama 官方網站,搜尋並查找更多模型。',
container_conn_helper: '容器間瀏覽或連接使用此地址',
ollama_sync: '同步 Ollama 模型發現下列模型不存在,是否刪除?',
from_remote: '該模型並非通過 1Panel 下載,無相關拉取日誌。',
from_remote: '該模型並非透過 1Panel 下載,無相關拉取日誌。',
no_logs: '該模型的拉取日誌已被刪除,無法查看相關日誌。',
},
gpu: {
gpu: 'GPU 监控',
gpu: 'GPU 監控',
base: '基礎資訊',
gpuHelper: '目前系統未檢測到 NVIDIA-SMI或者XPU-SMI 指令,請檢查後重試!',
gpuHelper: '目前系統未檢測到 NVIDIA-SMI 或者 XPU-SMI 指令,請檢查後重試!',
driverVersion: '驅動版本',
cudaVersion: 'CUDA 版本',
process: '行程資訊',
@@ -599,10 +601,10 @@ const message = {
typeC: '計算',
typeCG: '計算+圖形',
processName: '行程名稱',
processMemoryUsage: '顯存使用',
processMemoryUsage: '記憶體使用',
temperatureHelper: 'GPU 溫度過高會導致 GPU 頻率下降',
performanceStateHelper: '從 P0 (最大性能) 到 P12 (最小性能)',
busID: '總線地址',
busID: '匯流排地址',
persistenceMode: '持續模式',
enabled: '開啟',
disabled: '關閉',
@@ -610,15 +612,15 @@ const message = {
displayActive: '顯卡初始化',
displayActiveT: '是',
displayActiveF: '否',
ecc: '是否開啟錯誤檢查和紀正技術',
ecc: '是否開啟錯誤檢查和糾正技術',
computeMode: '計算模式',
default: '預設',
exclusiveProcess: '行程排他',
exclusiveThread: '線程排他',
exclusiveThread: '執行緒排他',
prohibited: '禁止',
defaultHelper: '預設: 行程可以並發執行',
exclusiveProcessHelper: '行程排他: 只有一個 CUDA 上下文可以使用 GPU, 但可以由多個線程共享',
exclusiveThreadHelper: '線程排他: 只有一個線程在 CUDA 上下文中可以使用 GPU',
exclusiveProcessHelper: '行程排他: 只有一個 CUDA 上下文可以使用 GPU, 但可以由多個執行緒共享',
exclusiveThreadHelper: '執行緒排他: 只有一個執行緒在 CUDA 上下文中可以使用 GPU',
prohibitedHelper: '禁止: 不允許行程同時執行',
migModeHelper: '用於建立 MIG 實例,在用戶層實現 GPU 的物理隔離。',
migModeNA: '不支援',
@@ -963,6 +965,7 @@ const message = {
errHandle: '任務執行失敗',
noRecord: '目前計劃任務暫未產生記錄',
cleanData: '刪除備份檔案',
cleanRemoteData: '刪除遠端備份檔案',
cleanDataHelper: '刪除該任務執行過程中產生的備份檔案',
noLogs: '暫無任務輸出...',
errPath: '備份路徑 [{0}] 錯誤,無法下載!',
@@ -1316,6 +1319,7 @@ const message = {
existFileHelper: '上傳的檔案存在同名檔案,是否覆蓋?',
existFileSize: '文件大小(新->舊)',
existFileDirHelper: '選擇的檔案/資料夾存在同名,請謹慎操作!',
noSuchFile: '找不到該檔案或目錄,請檢查後重試。',
},
ssh: {
setting: '設定',
@@ -1414,7 +1418,7 @@ const message = {
proxyDockerHelper: '將代理伺服器配寘同步至 Docker,支援離線伺服器拉取鏡像等操作',
apiInterface: 'API 介面',
apiInterfaceClose: '關閉後將不能使用 API 介面進行瀏覽,APP功能將被限制,是否繼續?',
apiInterfaceHelper: '支持面板 API 接口訪問功能(面板 APP 需要開啟該功能)',
apiInterfaceHelper: '支持面板 API 介面瀏覽功能(面板 APP 需要開啟該功能)',
apiInterfaceAlert1: '請不要在生產環境開啟,這可能新增伺服器安全風險',
apiInterfaceAlert2: '請不要使用協力廠商應用呼叫面板 API,以防止潛在的安全威脅',
apiInterfaceAlert3: 'API 介面檔案',
@@ -1692,7 +1696,7 @@ const message = {
setting: '升級專業版可以自訂面板 Logo、歡迎簡介等資訊。',
monitor: '升級專業版可以查看網站的即時狀態、訪客趨勢、訪客來源、請求日誌等資訊。 ',
alert: '升級專業版可透過簡訊接收告警資訊,並查看告警日誌,全面掌控各類關鍵事件,確保系統執行無憂。',
app: '升級專業版可以通過手機 APP 管理伺服器並查看監控等功能。',
app: '升級專業版可以透過手機 APP 管理伺服器並查看監控等功能。',
},
clean: {
scan: '開始掃描',
@@ -2112,7 +2116,8 @@ const message = {
sni: '回源 SNI',
sniHelper: '反代後端為 https 的時候可能需要設定回源 SNI,具體需要看 CDN 服務商文件',
huaweicloud: '華為雲',
rainyun: '瑞雲',
rainyun: '雨雲',
westCN: '西部數據',
},
php: {
short_open_tag: '短標籤支援',
@@ -2376,8 +2381,8 @@ const message = {
env: '環境變數',
noenv: '無',
net: '網路連線',
laddr: '源地址/埠',
raddr: '目標地址/埠',
laddr: '本地地址/埠',
raddr: '远程地址/埠',
stopProcess: '結束',
viewDetails: '查看詳情',
stopProcessWarn: '是否確定結束此行程 (PID:{0})?',
@@ -2427,12 +2432,34 @@ const message = {
aitool: {
proxy: 'AI 代理增強',
proxyHelper1: '綁定域名並啟用 HTTPS,提高傳輸安全性',
proxyHelper2: '限制 IP 訪問,防止在公共網絡上暴露',
proxyHelper3: '啟用流式傳輸',
proxyHelper2: '限制 IP 瀏覽,防止在網路上暴露',
proxyHelper3: '啟用即時串流',
proxyHelper4: '創建後,您可以在網站列表中查看並管理',
proxyHelper5: '啟用後,您可以在應用商店 - 已安裝 - Ollama - 參數中取消端口外部訪問以提高安全性。',
proxyHelper5: '啟用後,您可以在應用商店 - 已安裝 - Ollama - 參數中取消埠外部瀏覽以提高安全性',
proxyHelper6: '如需關閉代理配置,可以在網站列表中刪除',
whiteListHelper: '限制僅白名單中的 IP 可訪問',
whiteListHelper: '限制僅白名單中的 IP 可瀏覽',
},
mcp: {
server: 'MCP Server',
create: '创建 MCP Server',
edit: '編輯 MCP Server',
commandHelper: '例如:npx -y {0}',
baseUrl: '外部訪問路徑',
baseUrlHelper: '例如:http://192.168.1.2:8000',
ssePath: 'SSE 路徑',
ssePathHelper: '例如:/sse,注意不要與其他 Server 重複',
environment: '環境變數',
envKey: '變數名',
envValue: '變數值',
externalUrl: '外部連接地址',
operatorHelper: '將對 {0} 進行 {1} 操作,是否繼續?',
domain: '默認訪問地址',
domainHelper: '例如:192.168.1.1 或者 example.com',
bindDomain: '綁定網站',
commandPlaceHolder: '當前僅支持 npx 和 二進制啟動的命令',
importMcpJson: '導入 MCP Server配置',
importMcpJsonError: 'mcpServers 結構不正確',
bindDomainHelper: '綁定網站之後會修改所有已安裝 MCP Server 的訪問地址,並關閉端口的外部訪問',
},
};
export default {
+31 -4
View File
@@ -121,7 +121,6 @@ const message = {
Rollbacking: '快照回滚中,请稍候...',
},
msg: {
disConn: '请直接点击断开按钮断开终端连接,避免使用 {0} 等退出命令',
noneData: '暂无数据',
delete: '删除 操作不可回滚,是否继续?',
clean: '清空 操作不可回滚,是否继续?',
@@ -192,6 +191,7 @@ const message = {
rePassword: '密码不一致,请检查后重新输入',
requiredInput: '请填写必填项',
requiredSelect: '请选择必选项',
illegalChar: '暂不支持注入字符 & ; $ \' ` ( ) " > < |',
illegalInput: '输入框中存在不合法字符',
commonName: '支持非特殊字符开头,英文、中文、数字、.-和_,长度1-128',
userName: '支持英文、中文、数字和_,长度3-30',
@@ -420,6 +420,7 @@ const message = {
permission: '权限',
permissionForIP: '指定 IP',
permissionAll: '所有人(%)',
localhostHelper: '将容器部署的数据库权限配置为 localhost 会导致容器外部无法访问,请谨慎选择!',
databaseConnInfo: '连接信息',
rootPassword: 'root 密码',
serviceName: '服务名称',
@@ -430,6 +431,7 @@ const message = {
remoteHelper: '多个 ip 以逗号分隔,例:172.16.10.111,172.16.10.112',
remoteConnHelper: 'root 帐号远程连接 MySQL 有安全风险,开启需谨慎!',
changePassword: '改密',
changeConnHelper: '此操作将修改当前数据库 {0},是否继续?',
changePasswordHelper: '当前数据库已经关联应用,修改密码将同步修改应用中数据库密码,修改后重启生效。',
portHelper: '该端口为容器对外暴露端口,修改需要单独保存并且重启容器!',
@@ -564,7 +566,7 @@ const message = {
restartNowHelper: '修改配置后需要重启生效。',
persistence: '持久化',
rdbHelper1: '秒內,插入',
rdbHelper1: '秒内,插入',
rdbHelper2: '条数据',
rdbHelper3: '符合任意一个条件将会触发RDB持久化',
rdbInfo: '请确认规则列表中值在 1-100000 之间',
@@ -964,6 +966,7 @@ const message = {
errHandle: '任务执行失败',
noRecord: '当前计划任务暂未产生记录',
cleanData: '删除备份文件',
cleanRemoteData: '删除远程备份文件',
cleanDataHelper: '删除该任务执行过程中产生的备份文件',
noLogs: '暂无任务输出...',
errPath: '备份路径 [{0}] 错误,无法下载!',
@@ -1318,6 +1321,7 @@ const message = {
existFileHelper: '上传的文件存在同名文件,是否覆盖?',
existFileSize: '文件大小 (新 -> 旧)',
existFileDirHelper: '选择的文件/文件夹存在同名,请谨慎操作!',
noSuchFile: '未能找到该文件或目录,请检查后重试',
},
ssh: {
setting: '配置',
@@ -2114,6 +2118,7 @@ const message = {
sniHelper: '反代后端为 https 的时候可能需要设置回源 SNI,具体需要看 CDN 服务商文档',
huaweicloud: '华为云',
rainyun: '雨云',
westCN: '西部数码',
},
php: {
short_open_tag: '短标签支持',
@@ -2378,8 +2383,8 @@ const message = {
env: '环境变量',
noenv: '无',
net: '网络连接',
laddr: '源地址/端口',
raddr: '目标地址/端口',
laddr: '本地地址/端口',
raddr: '远程地址/端口',
stopProcess: '结束',
viewDetails: '查看详情',
stopProcessWarn: '是否确定结束此进程 (PID:{0})?',
@@ -2436,6 +2441,28 @@ const message = {
proxyHelper6: '如需关闭代理配置,可以在网站列表中删除',
whiteListHelper: '限制仅白名单中的 IP 可访问',
},
mcp: {
server: 'MCP Server',
create: '创建 MCP Server',
edit: '编辑 MCP Server',
commandHelper: '例如:npx -y {0}',
baseUrl: '外部访问路径',
baseUrlHelper: '例如:http://192.168.1.1:8000',
ssePath: 'SSE 路径',
ssePathHelper: '例如:/sse,注意不要与其他 Server 重复',
environment: '环境变量',
envKey: '变量名',
envValue: '变量值',
externalUrl: '外部连接地址',
operatorHelper: '将对 {0} 进行 {1} 操作,是否继续?',
domain: '默认访问地址',
domainHelper: '例如:192.168.1.1 或者 example.com',
bindDomain: '绑定网站',
commandPlaceHolder: '当前仅支持 npx 和 二进制启动的命令',
importMcpJson: '导入 MCP Server 配置',
importMcpJsonError: 'mcpServers 结构不正确',
bindDomainHelper: '绑定网站之后会修改所有已安装 MCP Server 的访问地址,并关闭端口的外部访问',
},
};
export default {
...fit2cloudZhLocale,
+9
View File
@@ -19,6 +19,15 @@ const databaseRouter = {
requiresAuth: true,
},
},
{
path: '/ai/mcp',
name: 'MCPServer',
component: () => import('@/views/ai/mcp/server/index.vue'),
meta: {
title: 'MCP',
requiresAuth: true,
},
},
{
path: '/ai/gpu',
name: 'GPU',
+1
View File
@@ -37,6 +37,7 @@ export interface GlobalState {
isIntl: boolean;
isTrial: boolean;
productProExpires: number;
licenseVerify: string;
errStatus: string;
}
+5 -1
View File
@@ -40,6 +40,7 @@ const GlobalStore = defineStore({
isIntl: false,
isTrial: false,
productProExpires: 0,
licenseVerify: '',
errStatus: '',
}),
@@ -48,7 +49,7 @@ const GlobalStore = defineStore({
state.themeConfig.theme === 'dark' ||
(state.themeConfig.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches),
isDarkGoldTheme: (state) => state.themeConfig.primary === '#F0BE96' && state.isProductPro,
docsUrl: (state) => (state.isIntl ? 'https://docs.1panel.pro' : 'https://1panel.cn/docs'),
docsUrl: (state) => (state.isIntl ? 'https://docs.1panel.pro' : 'https://1panel.cn/docs/v1'),
},
actions: {
setOpenMenuTabs(openMenuTabs: boolean) {
@@ -70,6 +71,9 @@ const GlobalStore = defineStore({
this.csrfToken = token;
},
updateLanguage(language: any) {
if (language === 'pt-BR') {
language = 'pt-br';
}
this.language = language;
localStorage.setItem('lang', language);
},

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