mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac1a474fae | ||
|
|
00ce5a7b36 | ||
|
|
c17f055e06 | ||
|
|
f22df4b29c | ||
|
|
b9bab90a14 | ||
|
|
ce14a104cc | ||
|
|
2eaabccf94 | ||
|
|
e119239d3d | ||
|
|
14a57af4e9 | ||
|
|
2701af9054 | ||
|
|
4954067736 | ||
|
|
b962144e7d | ||
|
|
8f1be42b6b | ||
|
|
c6969f231b | ||
|
|
3571fb8d86 | ||
|
|
d21288788e | ||
|
|
f65e4b06ab | ||
|
|
d26bc9a3a4 | ||
|
|
7088a903fe | ||
|
|
864ebeed82 | ||
|
|
c1b2b92708 | ||
|
|
2189d9229a | ||
|
|
d0183f460e | ||
|
|
d27d6db1ea | ||
|
|
d2bb3813d9 | ||
|
|
cac73e6d45 | ||
|
|
4712ceaf2c | ||
|
|
65243c68c4 | ||
|
|
256e79ca81 | ||
|
|
4861eb69cb | ||
|
|
fb8cf15537 | ||
|
|
c4a6791271 | ||
|
|
f58e147636 | ||
|
|
387e9fbeed | ||
|
|
4eb627bc79 | ||
|
|
850c86229c | ||
|
|
f984917a66 | ||
|
|
8588217fbf | ||
|
|
a2307c5f64 | ||
|
|
5923290de8 | ||
|
|
1c994fba4a | ||
|
|
415ab96aab | ||
|
|
19bb823b05 | ||
|
|
3a5371652e | ||
|
|
a267b4148a | ||
|
|
36a01eb60d | ||
|
|
65f6fdd045 | ||
|
|
75b60b32e4 | ||
|
|
6cb65e2290 | ||
|
|
0bad1b471f | ||
|
|
3814525edd | ||
|
|
8162dd1856 | ||
|
|
e833787020 | ||
|
|
673ffac516 | ||
|
|
e864610015 | ||
|
|
78402e1b7d | ||
|
|
782bc1e67c | ||
|
|
86e4ed6f64 | ||
|
|
ee8bac39af | ||
|
|
fe742b9f41 | ||
|
|
9a5bd9bcba | ||
|
|
b9c8e39560 | ||
|
|
6b20ff0b13 | ||
|
|
89bd32b6d4 | ||
|
|
005f240fb7 | ||
|
|
75da53e374 | ||
|
|
2485b0aa5e | ||
|
|
ed51a5e1fa | ||
|
|
56870504ac | ||
|
|
7aefb47cc3 | ||
|
|
aba41c0aea | ||
|
|
9300bf4141 | ||
|
|
b7ec17b3e3 | ||
|
|
2fcfe56a30 | ||
|
|
63b2d4e4d5 | ||
|
|
4cd77d8ee1 | ||
|
|
53a7347bea | ||
|
|
a02c25ebcc | ||
|
|
605c8cc6db | ||
|
|
033cc7c2d1 | ||
|
|
7c1ddb5b4c | ||
|
|
eb0f5264d7 | ||
|
|
5ad12c6fe4 | ||
|
|
61dacce5e0 | ||
|
|
e3f0381a26 | ||
|
|
6bc9dd96af | ||
|
|
e23f338b31 | ||
|
|
6e08b50e3c | ||
|
|
536712cd55 | ||
|
|
191ff0cda4 | ||
|
|
671f781564 | ||
|
|
30dc36b95d | ||
|
|
fac4aec680 | ||
|
|
8eac9a1808 | ||
|
|
ce74d96617 | ||
|
|
a15e77d605 | ||
|
|
bad022f524 | ||
|
|
50a54d0613 | ||
|
|
a47e41a8b7 | ||
|
|
f938443e55 | ||
|
|
b90abd2b28 | ||
|
|
da5682a600 | ||
|
|
81b72d9b7d | ||
|
|
6e13143286 | ||
|
|
70fc628c81 | ||
|
|
9858881ce6 | ||
|
|
eb6a8c7646 | ||
|
|
a71aea8aec | ||
|
|
918c441f88 | ||
|
|
960b4b0345 | ||
|
|
3aa4bfaa82 | ||
|
|
b3bdf9ef7e | ||
|
|
2948b8ffe8 | ||
|
|
e99c6c08a5 | ||
|
|
6fb389b2ed | ||
|
|
c09833cbde | ||
|
|
fa2ad69154 | ||
|
|
51d84455a3 | ||
|
|
d88d98d8a8 | ||
|
|
5aec466c8e | ||
|
|
0ee93774d5 | ||
|
|
7be7368bb9 | ||
|
|
eab0bb4a94 | ||
|
|
9f74f2077a | ||
|
|
a6e2efa6c9 | ||
|
|
205ef3009a | ||
|
|
d7edbd1e95 | ||
|
|
b361f464c5 | ||
|
|
fb377d2e99 | ||
|
|
deddd392ba | ||
|
|
3ab10848c8 | ||
|
|
433f1a940f | ||
|
|
1f12c09eb5 | ||
|
|
31e6d523f9 | ||
|
|
3c0bd051bf | ||
|
|
3c2d92dc5f | ||
|
|
262bd14bc8 | ||
|
|
f15ff46e34 | ||
|
|
fc1ec4e1b0 | ||
|
|
53f75826d8 | ||
|
|
ddfb816ef1 | ||
|
|
18428d108e | ||
|
|
3506c5dd3b | ||
|
|
2dffd06b1b | ||
|
|
12f2484d12 | ||
|
|
2dea44acf6 | ||
|
|
205f76c65d | ||
|
|
86af4fbd4d | ||
|
|
7915230121 | ||
|
|
1b27db7daa | ||
|
|
7370dcaa55 | ||
|
|
6a378b6863 | ||
|
|
6f6747a584 | ||
|
|
825221b2bb | ||
|
|
1e9d4b592e | ||
|
|
4a51db4764 | ||
|
|
afea71c81c | ||
|
|
9c8ca2ab3c | ||
|
|
a04875f64b | ||
|
|
7ec0bdb3f7 | ||
|
|
d2dbb6486e |
@@ -124,7 +124,7 @@ func (b *BaseApi) PageAgents(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, list, err := agentService.Page(req, helper.IsDemoRequest(c))
|
||||
total, list, err := agentService.Page(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
@@ -447,7 +447,7 @@ func (b *BaseApi) PageAgentAccounts(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, list, err := agentService.PageAccounts(req, helper.IsDemoRequest(c))
|
||||
total, list, err := agentService.PageAccounts(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
|
||||
@@ -2,11 +2,14 @@ package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -268,7 +271,7 @@ func (b *BaseApi) PageAlertConfig(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, configs, err := alertService.PageAlertConfig(req, helper.IsDemoRequest(c))
|
||||
total, configs, err := alertService.PageAlertConfig(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -294,6 +297,34 @@ func (b *BaseApi) UpdateAlertConfig(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := alertService.UpdateAlertConfig(req, loadAuditUser(c)); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, repo.ErrAlertConfigRevisionConflict):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusConflict, "ALERT_CONFIG_REVISION_CONFLICT", "ErrInvalidParams", err)
|
||||
case errors.Is(err, repo.ErrAlertConfigRevisionRequired):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusConflict, "ALERT_CONFIG_REVISION_REQUIRED", "ErrInvalidParams", err)
|
||||
default:
|
||||
helper.InternalServer(c, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Update alert config status
|
||||
// @Accept json
|
||||
// @Param request body dto.AlertConfigStatusUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/config/status [post]
|
||||
// @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警配置状态 [id][status]","formatEN":"update alert config status [id][status]"}
|
||||
func (b *BaseApi) UpdateAlertConfigStatus(c *gin.Context) {
|
||||
var req dto.AlertConfigStatusUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := alertService.UpdateAlertConfigStatus(req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -346,6 +377,15 @@ func (b *BaseApi) TestAlertConfig(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if req.Type == constant.Custom {
|
||||
result, err := alertService.TestCustomAlertConfig(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
return
|
||||
}
|
||||
flag, err := alertService.TestAlertConfig(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
|
||||
@@ -120,7 +120,7 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) {
|
||||
}
|
||||
version := c.Param("version")
|
||||
appType := c.Param("type")
|
||||
appDetailDTO, err := appService.GetAppDetail(appID, version, appType, helper.IsDemoRequest(c))
|
||||
appDetailDTO, err := appService.GetAppDetail(appID, version, appType)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// @Security Timestamp
|
||||
// @Router /apps/ignored/detail [get]
|
||||
func (b *BaseApi) ListAppIgnored(c *gin.Context) {
|
||||
res, err := appIgnoreUpgradeService.List(helper.IsDemoRequest(c))
|
||||
res, err := appIgnoreUpgradeService.List()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -21,7 +21,6 @@ func (b *BaseApi) SearchAppInstalled(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
req.ReadOnly = helper.IsDemoRequest(c)
|
||||
if req.All {
|
||||
list, err := appInstallService.SearchForWebsite(req)
|
||||
if err != nil {
|
||||
@@ -74,7 +73,6 @@ func (b *BaseApi) CheckAppInstalled(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
req.ReadOnly = helper.IsDemoRequest(c)
|
||||
checkData, err := appInstallService.CheckExist(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
@@ -117,7 +115,7 @@ func (b *BaseApi) LoadConnInfo(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
conn, err := appInstallService.LoadConnInfo(req, helper.IsDemoRequest(c))
|
||||
conn, err := appInstallService.LoadConnInfo(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -139,7 +137,7 @@ func (b *BaseApi) DeleteCheck(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
checkData, err := appInstallService.DeleteCheck(appInstallId, helper.IsDemoRequest(c))
|
||||
checkData, err := appInstallService.DeleteCheck(appInstallId)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -279,7 +277,7 @@ func (b *BaseApi) GetParams(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
content, err := appInstallService.GetParams(appInstallId, helper.IsDemoRequest(c))
|
||||
content, err := appInstallService.GetParams(appInstallId)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -343,7 +341,7 @@ func (b *BaseApi) GetAppInstallInfo(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
info, err := appInstallService.GetAppInstallInfo(appInstallId, helper.IsDemoRequest(c))
|
||||
info, err := appInstallService.GetAppInstallInfo(appInstallId)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -209,7 +209,7 @@ func (b *BaseApi) SearchBackup(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := backupService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
total, list, err := backupService.SearchWithPage(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -106,7 +106,7 @@ func (b *BaseApi) SearchClam(c *gin.Context) {
|
||||
// @Security Timestamp
|
||||
// @Router /toolbox/clam/base [post]
|
||||
func (b *BaseApi) LoadClamBaseInfo(c *gin.Context) {
|
||||
info, err := clamService.LoadBaseInfo(helper.IsDemoRequest(c))
|
||||
info, err := clamService.LoadBaseInfo()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -65,7 +65,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := composeTemplateService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
total, list, err := composeTemplateService.SearchWithPage(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -85,7 +85,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) {
|
||||
// @Security Timestamp
|
||||
// @Router /containers/template [get]
|
||||
func (b *BaseApi) ListComposeTemplate(c *gin.Context) {
|
||||
list, err := composeTemplateService.List(helper.IsDemoRequest(c))
|
||||
list, err := composeTemplateService.List()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -273,7 +273,7 @@ func (b *BaseApi) SearchCompose(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := containerService.PageCompose(req, helper.IsDemoRequest(c))
|
||||
total, list, err := containerService.PageCompose(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -387,7 +387,7 @@ func (b *BaseApi) ContainerInfo(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := containerService.ContainerInfo(req, helper.IsDemoRequest(c))
|
||||
data, err := containerService.ContainerInfo(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -439,7 +439,7 @@ func (b *BaseApi) ContainerItemStats(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := containerService.ContainerItemStats(req)
|
||||
data, err := containerService.ContainerItemStats(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -144,7 +144,7 @@ func (b *BaseApi) SearchCronjob(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := cronjobService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
total, list, err := cronjobService.SearchWithPage(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -78,7 +78,7 @@ func (b *BaseApi) SearchDatabase(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := databaseService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
total, list, err := databaseService.SearchWithPage(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -147,7 +147,7 @@ func (b *BaseApi) GetDatabase(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
data, err := databaseService.Get(name, helper.IsDemoRequest(c))
|
||||
data, err := databaseService.Get(name)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -54,7 +54,7 @@ func (b *BaseApi) SearchMongodb(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := mongodbService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
total, list, err := mongodbService.SearchWithPage(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -53,7 +53,7 @@ func (b *BaseApi) ListMysqlUsers(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := mysqlService.ListUsers(req, helper.IsDemoRequest(c))
|
||||
data, err := mysqlService.ListUsers(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -151,7 +151,7 @@ func (b *BaseApi) SearchPostgresql(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := postgresqlService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
total, list, err := postgresqlService.SearchWithPage(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -42,8 +42,9 @@ var (
|
||||
fileShareService = service.NewIFileShareService()
|
||||
sshService = service.NewISSHService()
|
||||
firewallService = service.NewIFirewallService()
|
||||
firewallSettingService = service.NewIFirewallSettingService()
|
||||
forwardingService = service.NewIForwardingService()
|
||||
iptablesService = service.NewIIptablesService()
|
||||
dockerPortGuardService = service.NewIDockerPortGuardService()
|
||||
monitorService = service.NewIMonitorService()
|
||||
systemService = service.NewISystemService()
|
||||
runtimeDiagnosticsService = service.NewIRuntimeDiagnosticsService()
|
||||
|
||||
@@ -148,7 +148,7 @@ func (b *BaseApi) FileAISearch(c *gin.Context) {
|
||||
if strings.TrimSpace(req.ResponseLanguage) == "" {
|
||||
req.ResponseLanguage = strings.TrimSpace(c.GetHeader("Accept-Language"))
|
||||
}
|
||||
res, err := fileService.AISearch(req, helper.IsDemoRequest(c))
|
||||
res, err := fileService.AISearch(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -684,10 +684,35 @@ func (b *BaseApi) StopWget(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
files.CancelDownload(req.Key)
|
||||
if err := files.CancelDownload(req.Key); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Remove finished download progress records without deleting files
|
||||
// @Accept json
|
||||
// @Param request body request.FileProcessRemoveReq true "request"
|
||||
// @Success 200 {object} response.FileProcessKeys
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/wget/process/remove [post]
|
||||
// @x-panel-log {"bodyKeys":["keys"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"移除已结束下载记录 [keys]","formatEN":"Remove finished download records [keys]"}
|
||||
func (b *BaseApi) RemoveWgetRecords(c *gin.Context) {
|
||||
var req request.FileProcessRemoveReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
keys, err := files.RemoveDownloadRecords(req.Keys)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, response.FileProcessKeys{Keys: keys})
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Move file
|
||||
// @Accept json
|
||||
@@ -1633,7 +1658,7 @@ func (b *BaseApi) SearchFileShare(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, list, err := fileShareService.Page(req, helper.IsDemoRequest(c))
|
||||
total, list, err := fileShareService.Page(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -1657,7 +1682,7 @@ func (b *BaseApi) GetFileShareDetail(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
info, err := fileShareService.GetByPath(req.Path, helper.IsDemoRequest(c))
|
||||
info, err := fileShareService.GetByPath(req.Path)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -1676,7 +1701,7 @@ func (b *BaseApi) GetPublicFileShareInfo(c *gin.Context) {
|
||||
helper.BadRequest(c, errors.New("code is required"))
|
||||
return
|
||||
}
|
||||
info, err := fileShareService.GetPublicByCode(code, helper.IsDemoRequest(c))
|
||||
info, err := fileShareService.GetPublicByCode(code)
|
||||
if err != nil {
|
||||
if be, ok := err.(buserr.BusinessError); ok {
|
||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||
@@ -1729,7 +1754,7 @@ func (b *BaseApi) GetFileShareQRCode(c *gin.Context) {
|
||||
helper.BadRequest(c, errors.New("code is required"))
|
||||
return
|
||||
}
|
||||
if _, err := fileShareService.GetByCode(code, helper.IsDemoRequest(c)); err != nil {
|
||||
if _, err := fileShareService.GetByCode(code); err != nil {
|
||||
if be, ok := err.(buserr.BusinessError); ok {
|
||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||
return
|
||||
@@ -1811,7 +1836,7 @@ func (b *BaseApi) CheckFileShare(c *gin.Context) {
|
||||
helper.BadRequest(c, errors.New("code is required"))
|
||||
return
|
||||
}
|
||||
if err := fileShareService.Check(code, password, helper.IsDemoRequest(c)); err != nil {
|
||||
if err := fileShareService.Check(code, password); err != nil {
|
||||
if be, ok := err.(buserr.BusinessError); ok {
|
||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||
return
|
||||
@@ -1836,7 +1861,7 @@ func (b *BaseApi) DownloadFileShare(c *gin.Context) {
|
||||
helper.BadRequest(c, errors.New("code is required"))
|
||||
return
|
||||
}
|
||||
filePath, displayName, err := fileShareService.PrepareDownload(code, password, helper.IsDemoRequest(c))
|
||||
filePath, displayName, err := fileShareService.PrepareDownload(code, password)
|
||||
if err != nil {
|
||||
if be, ok := err.(buserr.BusinessError); ok {
|
||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||
|
||||
+566
-253
@@ -1,34 +1,53 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"net/http"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (b *BaseApi) UpdatePanelFirewallPort(c *gin.Context) {
|
||||
if !global.IsMaster {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
var request struct {
|
||||
OldPort uint `json:"oldPort" validate:"required,min=1,max=65535"`
|
||||
NewPort uint `json:"newPort" validate:"required,min=1,max=65535"`
|
||||
}
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := firewallService.UpdatePanelPort(c.Request.Context(), request.OldPort, request.NewPort); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Load firewall base info
|
||||
// @Accept json
|
||||
// @Param request body dto.OperationWithName true "request"
|
||||
// @Success 200 {object} dto.FirewallBaseInfo
|
||||
// @Success 200 {object} dto.FirewallSubsystemStatus
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/base [post]
|
||||
func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
|
||||
var req dto.OperationWithName
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
var request dto.OperationWithName
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
data dto.FirewallBaseInfo
|
||||
err error
|
||||
)
|
||||
if req.Name == "forward" {
|
||||
data, err = forwardingService.LoadBaseInfo()
|
||||
} else {
|
||||
data, err = firewallService.LoadBaseInfo(req.Name)
|
||||
}
|
||||
data, err := firewallService.LoadBaseInfo(request.Name)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -37,331 +56,625 @@ func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Page firewall rules
|
||||
// @Accept json
|
||||
// @Param request body dto.RuleSearch true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/search [post]
|
||||
func (b *BaseApi) SearchFirewallRule(c *gin.Context) {
|
||||
var req dto.RuleSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
total int64
|
||||
list interface{}
|
||||
err error
|
||||
)
|
||||
if req.Type == "forward" {
|
||||
total, list, err = forwardingService.SearchWithPage(dto.ForwardRuleSearch{
|
||||
PageInfo: req.PageInfo,
|
||||
Info: req.Info,
|
||||
Status: req.Status,
|
||||
Strategy: req.Strategy,
|
||||
})
|
||||
} else {
|
||||
total, list, err = firewallService.SearchWithPage(req)
|
||||
}
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Operate firewall
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallOperation true "request"
|
||||
// @Success 200
|
||||
// @Param request body dto.FirewallLifecycleOperation true "request"
|
||||
// @Success 200 {object} dto.FirewallLifecycleOperationResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] 防火墙","formatEN":"[operation] firewall"}
|
||||
func (b *BaseApi) OperateFirewall(c *gin.Context) {
|
||||
var req dto.FirewallOperation
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
var request dto.FirewallLifecycleOperation
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.OperateFirewall(req); err != nil {
|
||||
result, err := firewallService.QueueFirewallOperation(request)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Create group
|
||||
// @Summary Load forwarding base info
|
||||
// @Accept json
|
||||
// @Param request body dto.PortRuleOperate true "request"
|
||||
// @Success 200
|
||||
// @Success 200 {object} dto.FirewallSubsystemStatus
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/port [post]
|
||||
// @x-panel-log {"bodyKeys":["port","strategy"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加端口规则 [strategy] [port]","formatEN":"create port rules [strategy][port]"}
|
||||
func (b *BaseApi) OperatePortRule(c *gin.Context) {
|
||||
var req dto.PortRuleOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.OperatePortRule(req, true); err != nil {
|
||||
// @Router /hosts/firewall/forward/base [post]
|
||||
func (b *BaseApi) LoadForwardingBaseInfo(c *gin.Context) {
|
||||
data, err := forwardingService.LoadBaseInfo(c.Request.Context())
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// OperateForwardRule
|
||||
// @Tags Firewall
|
||||
// @Summary Operate forward rule
|
||||
// @Accept json
|
||||
// @Param request body dto.ForwardRuleOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/forward [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新端口转发规则","formatEN":"update port forward rules"}
|
||||
func (b *BaseApi) OperateForwardRule(c *gin.Context) {
|
||||
var req dto.ForwardRuleOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := forwardingService.Operate(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Operate Ip rule
|
||||
// @Summary Page forwarding rules
|
||||
// @Accept json
|
||||
// @Param request body dto.AddrRuleOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/ip [post]
|
||||
// @x-panel-log {"bodyKeys":["strategy","address"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 ip 规则 [strategy] [address]","formatEN":"create address rules [strategy][address]"}
|
||||
func (b *BaseApi) OperateIPRule(c *gin.Context) {
|
||||
var req dto.AddrRuleOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.OperateAddressRule(req, true); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Batch operate rule
|
||||
// @Accept json
|
||||
// @Param request body dto.BatchRuleOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/batch [post]
|
||||
func (b *BaseApi) BatchOperateRule(c *gin.Context) {
|
||||
var req dto.BatchRuleOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.BatchOperateRule(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Update rule description
|
||||
// @Accept json
|
||||
// @Param request body dto.UpdateFirewallDescription true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/update/description [post]
|
||||
func (b *BaseApi) UpdateFirewallDescription(c *gin.Context) {
|
||||
var req dto.UpdateFirewallDescription
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.UpdateDescription(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Update port rule
|
||||
// @Accept json
|
||||
// @Param request body dto.PortRuleUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/update/port [post]
|
||||
func (b *BaseApi) UpdatePortRule(c *gin.Context) {
|
||||
var req dto.PortRuleUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.UpdatePortRule(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Update Ip rule
|
||||
// @Accept json
|
||||
// @Param request body dto.AddrRuleUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/update/addr [post]
|
||||
func (b *BaseApi) UpdateAddrRule(c *gin.Context) {
|
||||
var req dto.AddrRuleUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.UpdateAddrRule(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary search iptables filter rules
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchPageWithType true "request"
|
||||
// @Param request body dto.ForwardRuleSearch true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/filter/rule/search [post]
|
||||
func (b *BaseApi) SearchFilterRules(c *gin.Context) {
|
||||
var req dto.SearchPageWithType
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
// @Router /hosts/firewall/forward/search [post]
|
||||
func (b *BaseApi) SearchForwardingRules(c *gin.Context) {
|
||||
var request dto.ForwardRuleSearch
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := iptablesService.Search(req)
|
||||
total, items, err := forwardingService.SearchRules(c.Request.Context(), request)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
helper.SuccessWithData(c, dto.PageResult{Items: items, Total: total})
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Operate iptables filter rule
|
||||
// @Summary Operate forwarding rules
|
||||
// @Accept json
|
||||
// @Param request body dto.IptablesRuleOp true "request"
|
||||
// @Success 200
|
||||
// @Param request body dto.ForwardRuleOperate true "request"
|
||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/filter/rule/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operation","chain"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] filter规则到 [chain]","formatEN":"[operation] filter rule to [chain]"}
|
||||
func (b *BaseApi) OperateFilterRule(c *gin.Context) {
|
||||
var req dto.IptablesRuleOp
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
// @Router /hosts/firewall/forward/operate [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新端口转发规则","formatEN":"update port forward rules"}
|
||||
func (b *BaseApi) OperateForwardingRules(c *gin.Context) {
|
||||
var request dto.ForwardRuleOperate
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := iptablesService.OperateRule(req, true); err != nil {
|
||||
|
||||
result, err := forwardingService.OperateRules(request)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Batch operate iptables filter rules
|
||||
// @Summary Enable forwarding
|
||||
// @Accept json
|
||||
// @Param request body dto.IptablesBatchOperate true "request"
|
||||
// @Success 200
|
||||
// @Param request body dto.FirewallInitializationTask true "request"
|
||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/filter/rule/batch [post]
|
||||
func (b *BaseApi) BatchOperateFilterRule(c *gin.Context) {
|
||||
var req dto.IptablesBatchOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
// @Router /hosts/firewall/forward/enable [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"初始化并启用端口转发","formatEN":"initialize and enable port forwarding"}
|
||||
func (b *BaseApi) EnableForwarding(c *gin.Context) {
|
||||
var request dto.FirewallInitializationTask
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := iptablesService.BatchOperate(req); err != nil {
|
||||
result, err := forwardingService.QueueInitialization(request)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Apply/Unload/Init iptables filter
|
||||
// @Summary Apply/Unload/Init firewall filter chain
|
||||
// @Accept json
|
||||
// @Param request body dto.IptablesOp true "request"
|
||||
// @Success 200
|
||||
// @Param request body dto.FilterChainOperation true "request"
|
||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/filter/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] iptables filter 防火墙","formatEN":"[operate] iptables filter firewall"}
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 防火墙过滤链","formatEN":"[operate] firewall filter chain"}
|
||||
func (b *BaseApi) OperateFilterChain(c *gin.Context) {
|
||||
var req dto.IptablesOp
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
var request dto.FilterChainOperation
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
if req.Operate == "init-forward" {
|
||||
err = forwardingService.Enable()
|
||||
} else {
|
||||
err = iptablesService.Operate(req)
|
||||
if request.Operate == "init-base" {
|
||||
result, err := firewallService.QueueFilterChainInitialization(request)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
if err := firewallService.OperateFilterChain(request); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, dto.FilterChainOperationResponse{})
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary List unified firewall v2 rules
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleInventory true "request"
|
||||
// @Success 200 {object} dto.FirewallRuleInventoryResponse
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/search [post]
|
||||
func (b *BaseApi) SearchFirewallRules(c *gin.Context) {
|
||||
var request dto.FirewallRuleInventory
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
inventory, err := firewallService.Inventory(c.Request.Context(), request)
|
||||
if err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, inventory)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Reset firewall rules
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleReset true "request"
|
||||
// @Success 200 {object} dto.FirewallRuleResetResponse
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/reset [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"重置防火墙规则","formatEN":"reset firewall rules"}
|
||||
func (b *BaseApi) ResetFirewallRules(c *gin.Context) {
|
||||
var request dto.FirewallRuleReset
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := firewallService.Reset(c.Request.Context(), request)
|
||||
if err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Load one provider-native firewall object definition
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallNativeDetail true "request"
|
||||
// @Success 200 {string} string
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/native/detail [post]
|
||||
func (b *BaseApi) LoadFirewallNativeDetail(c *gin.Context) {
|
||||
var request dto.FirewallNativeDetail
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
info, err := firewallService.LoadFirewallNativeDetail(c.Request.Context(), request)
|
||||
if err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Queue firewall rule creation
|
||||
// @Description Creation and import return a taskID immediately; validation and execution results are written to the task log.
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleCreate true "request"
|
||||
// @Success 200 {object} dto.FirewallRuleCreateResponse
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Failure 409 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加防火墙规则","formatEN":"create firewall rules"}
|
||||
func (b *BaseApi) CreateFirewallRules(c *gin.Context) {
|
||||
var request dto.FirewallRuleCreate
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := firewallService.Create(c.Request.Context(), request)
|
||||
if err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Queue firewall rule deletion
|
||||
// @Description Deletes non-whitelist rules by scope and instance key. Returns a taskID immediately; results are written to the task log.
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleDelete true "request"
|
||||
// @Success 200 {object} dto.FirewallRuleDeleteResponse
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/delete [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除防火墙规则","formatEN":"delete firewall rules"}
|
||||
func (b *BaseApi) DeleteFirewallRules(c *gin.Context) {
|
||||
var request dto.FirewallRuleDelete
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := firewallService.Delete(c.Request.Context(), request)
|
||||
if err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Update a firewall rule
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleUpdate true "request"
|
||||
// @Success 200
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/update [post]
|
||||
// @x-panel-log {"bodyKeys":["instanceKey"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新防火墙规则 [instanceKey]","formatEN":"update firewall rule [instanceKey]"}
|
||||
func (b *BaseApi) UpdateFirewallRule(c *gin.Context) {
|
||||
var request dto.FirewallRuleUpdate
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := firewallService.Update(c.Request.Context(), request); err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary load chain status with name
|
||||
// @Summary Reorder a firewall rule
|
||||
// @Accept json
|
||||
// @Param request body dto.OperationWithName true "request"
|
||||
// @Param request body dto.FirewallRuleReorder true "request"
|
||||
// @Success 200
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/reorder [post]
|
||||
// @x-panel-log {"bodyKeys":["instanceKey"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"调整防火墙规则顺序 [instanceKey]","formatEN":"reorder firewall rule [instanceKey]"}
|
||||
func (b *BaseApi) ReorderFirewallRule(c *gin.Context) {
|
||||
var request dto.FirewallRuleReorder
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := firewallService.Reorder(c.Request.Context(), request); err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
func handleFirewallRuleError(c *gin.Context, err error) {
|
||||
var businessErr buserr.BusinessError
|
||||
isBusinessError := errors.As(err, &businessErr)
|
||||
switch {
|
||||
case errors.Is(err, filter.ErrProtectedRule):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_LOCKOUT_RISK", "ErrInvalidParams", err)
|
||||
case errors.Is(err, filter.ErrRuleStale):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_STALE", "ErrInvalidParams", err)
|
||||
case errors.Is(err, filter.ErrUnsupportedScope), errors.Is(err, filter.ErrInvalidScope),
|
||||
errors.Is(err, filter.ErrProviderUnavailable), errors.Is(err, filter.ErrAdapterUnavailable):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_SCOPE_UNSUPPORTED", "ErrInvalidParams", err)
|
||||
case errors.Is(err, filter.ErrInvalidRule), errors.Is(err, filter.ErrRuleOperation):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_RULE_UNSUPPORTED", "ErrInvalidParams", err)
|
||||
case isBusinessError && businessErr.Msg == "ErrRecordExist":
|
||||
c.JSON(http.StatusOK, dto.Response{Code: http.StatusConflict, ErrorCode: "FW_RULE_DUPLICATE", Message: err.Error()})
|
||||
c.Abort()
|
||||
case isBusinessError && businessErr.Msg == "ErrFirewallRuleConflict":
|
||||
c.JSON(http.StatusOK, dto.Response{Code: http.StatusConflict, ErrorCode: "FW_RULE_CONFLICT", Message: err.Error()})
|
||||
c.Abort()
|
||||
case isBusinessError && businessErr.Msg == "ErrInvalidParams":
|
||||
c.JSON(http.StatusOK, dto.Response{Code: http.StatusBadRequest, ErrorCode: "FW_RULE_UNSUPPORTED", Message: err.Error()})
|
||||
c.Abort()
|
||||
default:
|
||||
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_APPLY_FAILED", "ErrInternalServer", err)
|
||||
}
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Load firewall settings
|
||||
// @Success 200 {object} dto.FirewallSettings
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/settings [get]
|
||||
func (b *BaseApi) LoadFirewallSettings(c *gin.Context) {
|
||||
data, err := firewallSettingService.Load(c.Request.Context())
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Create firewall port whitelist rules
|
||||
// @Description Saves whitelist configuration and applies missing allowances; existing rules are not removed.
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallPortWhitelistCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/filter/chain/status [post]
|
||||
func (b *BaseApi) LoadChainStatus(c *gin.Context) {
|
||||
var req dto.OperationWithName
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
// @Router /hosts/firewall/settings/whitelist [post]
|
||||
// @x-panel-log {"bodyKeys":["rule"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建防火墙端口白名单","formatEN":"create firewall port whitelist"}
|
||||
func (b *BaseApi) CreateFirewallPortWhitelist(c *gin.Context) {
|
||||
var request dto.FirewallPortWhitelistCreate
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := firewallSettingService.CreatePortWhitelist(c.Request.Context(), request); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, iptablesService.LoadChainStatus(req))
|
||||
// @Tags Firewall
|
||||
// @Summary Update firewall port whitelist rules
|
||||
// @Description Saves whitelist configuration and applies missing allowances; existing rules are not removed.
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallPortWhitelistUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/settings/whitelist/update [post]
|
||||
// @x-panel-log {"bodyKeys":["oldRule","rule"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"编辑防火墙端口白名单","formatEN":"update firewall port whitelist"}
|
||||
func (b *BaseApi) UpdateFirewallPortWhitelist(c *gin.Context) {
|
||||
var request dto.FirewallPortWhitelistUpdate
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := firewallSettingService.UpdatePortWhitelist(c.Request.Context(), request); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Delete firewall port whitelist rules
|
||||
// @Description Removes whitelist configuration; existing firewall rules are not removed.
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallPortWhitelistDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/settings/whitelist/delete [post]
|
||||
// @x-panel-log {"bodyKeys":["rules"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除防火墙端口白名单","formatEN":"delete firewall port whitelist"}
|
||||
func (b *BaseApi) DeleteFirewallPortWhitelist(c *gin.Context) {
|
||||
var request dto.FirewallPortWhitelistDelete
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := firewallSettingService.DeletePortWhitelist(c.Request.Context(), request); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Operate firewall backend
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallBackendOperation true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/settings/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["subsystem","backend","operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"防火墙子系统 [subsystem] 后端 [operation] [backend]","formatEN":"[operation] firewall [subsystem] backend [backend]"}
|
||||
func (b *BaseApi) OperateFirewallBackend(c *gin.Context) {
|
||||
var request dto.FirewallBackendOperation
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := firewallSettingService.Operate(c.Request.Context(), request); err != nil {
|
||||
var businessErr buserr.BusinessError
|
||||
if errors.As(err, &businessErr) && businessErr.Msg == "ErrFirewallBackendCleanupRequired" {
|
||||
c.JSON(http.StatusOK, dto.Response{Code: http.StatusConflict, ErrorCode: "FW_BACKEND_CLEANUP_REQUIRED", Message: err.Error()})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary List Docker port guard status and policies
|
||||
// @Success 200 {object} dto.DockerPortGuardList
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/docker/ports [get]
|
||||
func (b *BaseApi) ListDockerPortGuard(c *gin.Context) {
|
||||
data, err := dockerPortGuardService.LoadOverview(c.Request.Context())
|
||||
if err != nil {
|
||||
handleDockerPortGuardError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary List Docker published ports
|
||||
// @Success 200 {array} dto.DockerPortGuardContainer
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/docker/endpoints [get]
|
||||
func (b *BaseApi) ListDockerPublishedPorts(c *gin.Context) {
|
||||
data, err := dockerPortGuardService.LoadPublishedPorts(c.Request.Context())
|
||||
if err != nil {
|
||||
handleDockerPortGuardError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Operate Docker port guard
|
||||
// @Accept json
|
||||
// @Param request body dto.DockerPortGuardOperation true "request"
|
||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/docker/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] Docker 端口防护","formatEN":"[operation] Docker port guard"}
|
||||
func (b *BaseApi) OperateDockerPortGuard(c *gin.Context) {
|
||||
var request dto.DockerPortGuardOperation
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
if request.Operation == "initialize" {
|
||||
result, err := dockerPortGuardService.QueueInitialization(request)
|
||||
if err != nil {
|
||||
handleDockerPortGuardError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
return
|
||||
}
|
||||
if err := dockerPortGuardService.Operate(c.Request.Context(), request); err != nil {
|
||||
handleDockerPortGuardError(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Delete Docker port guard policies
|
||||
// @Accept json
|
||||
// @Param request body dto.DockerPortGuardPolicyBatchDelete true "request"
|
||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/docker/policies/delete/batch [post]
|
||||
// @x-panel-log {"bodyKeys":["uuids"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除 Docker 端口防护策略 [uuids]","formatEN":"delete Docker port guard policies [uuids]"}
|
||||
func (b *BaseApi) DeleteDockerPortGuardPolicies(c *gin.Context) {
|
||||
var request dto.DockerPortGuardPolicyBatchDelete
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := dockerPortGuardService.DeletePolicies(request)
|
||||
if err != nil {
|
||||
handleDockerPortGuardError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Batch upsert Docker port guard policies
|
||||
// @Accept json
|
||||
// @Param request body dto.DockerPortGuardPolicyBatch true "request"
|
||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/docker/policies/batch [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"批量更新 Docker 端口防护策略","formatEN":"batch update Docker port guard policies"}
|
||||
func (b *BaseApi) UpsertDockerPortGuardPolicies(c *gin.Context) {
|
||||
var request dto.DockerPortGuardPolicyBatch
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := dockerPortGuardService.UpsertPolicies(request)
|
||||
if err != nil {
|
||||
handleDockerPortGuardError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
func handleDockerPortGuardError(c *gin.Context, err error) {
|
||||
var businessErr buserr.BusinessError
|
||||
if errors.As(err, &businessErr) {
|
||||
code, errorCode := http.StatusInternalServerError, ""
|
||||
switch businessErr.Msg {
|
||||
case "ErrDockerIptablesChainUnavailable":
|
||||
code, errorCode = http.StatusServiceUnavailable, "FW_DOCKER_IPTABLES_CHAIN_UNAVAILABLE"
|
||||
case "ErrDockerNftablesChainUnavailable":
|
||||
code, errorCode = http.StatusServiceUnavailable, "FW_DOCKER_NFTABLES_CHAIN_UNAVAILABLE"
|
||||
case "ErrInvalidParams":
|
||||
code, errorCode = http.StatusBadRequest, "FW_DOCKER_GUARD_INVALID"
|
||||
case "ErrDockerFailed":
|
||||
code, errorCode = http.StatusServiceUnavailable, "FW_DOCKER_UNAVAILABLE"
|
||||
}
|
||||
if errorCode != "" {
|
||||
c.JSON(http.StatusOK, dto.Response{Code: code, ErrorCode: errorCode, Message: err.Error()})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
if errors.Is(err, docker.ErrUnavailable) {
|
||||
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_UNAVAILABLE", "ErrDockerFailed", err)
|
||||
return
|
||||
}
|
||||
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_DOCKER_GUARD_FAILED", "ErrInternalServer", err)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary List firewall rule backups
|
||||
// @Param subsystem query string false "Firewall subsystem" Enums(system,forwarding,docker) default(system)
|
||||
// @Success 200 {object} dto.FirewallRuleBackups
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/backups [get]
|
||||
func (b *BaseApi) ListFirewallRuleBackups(c *gin.Context) {
|
||||
result, err := firewallService.ListRuleBackups(c.Request.Context(), c.DefaultQuery("subsystem", "system"))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Initialize, repair or bind one firewall address family
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallFamilyOperation true "request"
|
||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/family/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["subsystem","family","operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] [subsystem] [family] 防火墙链","formatEN":"[operation] [subsystem] [family] firewall chains"}
|
||||
func (b *BaseApi) OperateFirewallFamily(c *gin.Context) {
|
||||
var request dto.FirewallFamilyOperation
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := firewallSettingService.OperateFamily(request)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Update firewall IPv6 support
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallIPv6Operation true "request"
|
||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/settings/ipv6 [post]
|
||||
// @x-panel-log {"bodyKeys":["status"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"设置防火墙 IPv6 支持为 [status]","formatEN":"Set firewall IPv6 support to [status]"}
|
||||
func (b *BaseApi) OperateFirewallIPv6(c *gin.Context) {
|
||||
var request dto.FirewallIPv6Operation
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := firewallSettingService.OperateIPv6(request)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func (b *BaseApi) SearchFtp(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := ftpService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
total, list, err := ftpService.SearchWithPage(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
+8
-16
@@ -3,9 +3,8 @@ package v2
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -17,27 +16,20 @@ import (
|
||||
// @Security Timestamp
|
||||
// @Router /ai/gpu/load [get]
|
||||
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
|
||||
ok, client := gpu.New()
|
||||
ok, client := accelerator.New()
|
||||
if ok {
|
||||
info, err := client.LoadGpuInfo()
|
||||
snapshot, err := client.Collect(c.Request.Context())
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
return
|
||||
}
|
||||
xpuOK, xpuClient := xpu.New()
|
||||
if xpuOK {
|
||||
info, err := xpuClient.LoadGpuInfo()
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
if warning := snapshot.Warning(); warning != nil {
|
||||
global.LOG.Warnf("load realtime accelerator data partially failed, err: %v", warning)
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
helper.SuccessWithData(c, &snapshot.Info)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, &common.GpuInfo{})
|
||||
helper.SuccessWithData(c, &accelerator.Info{})
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
|
||||
@@ -30,6 +30,16 @@ func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) {
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func ErrorWithBusinessCode(ctx *gin.Context, code int, businessCode, msgKey string, err error) {
|
||||
res := dto.Response{
|
||||
Code: code,
|
||||
ErrorCode: businessCode,
|
||||
Message: i18n.GetMsgWithDetail(msgKey, err.Error()),
|
||||
}
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func ErrorWithDetailAndData(ctx *gin.Context, code int, msgKey string, err error, data interface{}) {
|
||||
res := dto.Response{
|
||||
Code: code,
|
||||
@@ -48,10 +58,6 @@ func BadRequest(ctx *gin.Context, err error) {
|
||||
ErrorWithDetail(ctx, http.StatusBadRequest, "ErrInvalidParams", err)
|
||||
}
|
||||
|
||||
func IsDemoRequest(ctx *gin.Context) bool {
|
||||
return global.CONF.Base.IsDemo || ctx.GetHeader(constant.DemoModeHeader) == strconv.FormatBool(true)
|
||||
}
|
||||
|
||||
func SuccessWithData(ctx *gin.Context, data interface{}) {
|
||||
if data == nil {
|
||||
data = gin.H{}
|
||||
|
||||
@@ -100,7 +100,7 @@ func (b *BaseApi) SearchHost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := hostService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
total, list, err := hostService.SearchWithPage(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ func (b *BaseApi) SearchRepo(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := imageRepoService.Page(req, helper.IsDemoRequest(c))
|
||||
total, list, err := imageRepoService.Page(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -19,7 +19,7 @@ func (b *BaseApi) PageMcpServers(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
list := mcpServerService.Page(req, helper.IsDemoRequest(c))
|
||||
list := mcpServerService.Page(req)
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
|
||||
+10
-23
@@ -1,13 +1,9 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
// @Tags Monitor
|
||||
@@ -32,14 +28,19 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags Monitor
|
||||
// @Summary Clean monitor data
|
||||
// @Summary Clean host or GPU monitor data
|
||||
// @Param request body dto.MonitorClean true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/clean [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空监控数据","formatEN":"clean monitor datas"}
|
||||
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空 [type] 监控数据","formatEN":"clean [type] monitoring data"}
|
||||
func (b *BaseApi) CleanMonitor(c *gin.Context) {
|
||||
if err := monitorService.CleanData(); err != nil {
|
||||
var req dto.MonitorClean
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := monitorService.CleanData(req.Type); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -91,14 +92,7 @@ func (b *BaseApi) UpdateMonitorSetting(c *gin.Context) {
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/netoptions [get]
|
||||
func (b *BaseApi) GetNetworkOptions(c *gin.Context) {
|
||||
netStat, _ := net.IOCounters(true)
|
||||
var options []string
|
||||
options = append(options, "all")
|
||||
for _, net := range netStat {
|
||||
options = append(options, net.Name)
|
||||
}
|
||||
sort.Strings(options)
|
||||
helper.SuccessWithData(c, options)
|
||||
helper.SuccessWithData(c, monitorService.LoadNetworkOptions())
|
||||
}
|
||||
|
||||
// @Tags Monitor
|
||||
@@ -108,12 +102,5 @@ func (b *BaseApi) GetNetworkOptions(c *gin.Context) {
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/iooptions [get]
|
||||
func (b *BaseApi) GetIOOptions(c *gin.Context) {
|
||||
diskStat, _ := disk.IOCounters()
|
||||
var options []string
|
||||
options = append(options, "all")
|
||||
for _, net := range diskStat {
|
||||
options = append(options, net.Name)
|
||||
}
|
||||
sort.Strings(options)
|
||||
helper.SuccessWithData(c, options)
|
||||
helper.SuccessWithData(c, monitorService.LoadIOOptions())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (b *BaseApi) LoadVLLMMonitor(c *gin.Context) {
|
||||
var req dto.MonitorVLLMSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := monitorService.LoadVLLMMonitorData(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
func (b *BaseApi) LoadVLLMCurrent(c *gin.Context) {
|
||||
var req dto.MonitorVLLMCurrent
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := monitorService.LoadVLLMCurrent(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
func (b *BaseApi) CleanVLLMMonitor(c *gin.Context) {
|
||||
var req dto.MonitorVLLMClean
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := monitorService.CleanVLLMMonitor(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
@@ -20,7 +20,6 @@ func (b *BaseApi) SearchRuntimes(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
req.ReadOnly = helper.IsDemoRequest(c)
|
||||
total, items, err := runtimeService.Page(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
@@ -133,7 +132,7 @@ func (b *BaseApi) GetRuntime(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
res, err := runtimeService.Get(id, helper.IsDemoRequest(c))
|
||||
res, err := runtimeService.Get(id)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -170,7 +169,7 @@ func (b *BaseApi) GetNodePackageRunScript(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"操作运行环境 [id]","formatEN":"Operate runtime [id]"}
|
||||
// @x-panel-log {"bodyKeys":["ID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ID","isList":false,"db":"runtimes","output_column":"name","output_value":"name"}],"formatZH":"操作运行环境 [name]","formatEN":"Operate runtime [name]"}
|
||||
func (b *BaseApi) OperateRuntime(c *gin.Context) {
|
||||
var req request.RuntimeOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -530,7 +529,7 @@ func (b *BaseApi) GetPHPContainerConfig(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
data, err := runtimeService.GetPHPContainerConfig(id, helper.IsDemoRequest(c))
|
||||
data, err := runtimeService.GetPHPContainerConfig(id)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -191,7 +191,7 @@ func (b *BaseApi) LoadBaseDir(c *gin.Context) {
|
||||
// @Security Timestamp
|
||||
// @Router /settings/ssh/conn [get]
|
||||
func (b *BaseApi) LoadLocalConn(c *gin.Context) {
|
||||
helper.SuccessWithData(c, settingService.GetLocalConn(helper.IsDemoRequest(c)))
|
||||
helper.SuccessWithData(c, settingService.GetLocalConn())
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
|
||||
@@ -83,6 +83,7 @@ func (b *BaseApi) CreateRootCert(c *gin.Context) {
|
||||
}
|
||||
if err := loadCertAfterDecrypt(&req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
if err := sshService.CreateRootCert(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
@@ -107,6 +108,7 @@ func (b *BaseApi) EditRootCert(c *gin.Context) {
|
||||
}
|
||||
if err := loadCertAfterDecrypt(&req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
if err := sshService.EditRootCert(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
@@ -144,7 +146,7 @@ func (b *BaseApi) SearchRootCert(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, data, err := sshService.SearchRootCerts(req, helper.IsDemoRequest(c))
|
||||
total, data, err := sshService.SearchRootCerts(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -19,7 +19,7 @@ func (b *BaseApi) PageTensorRTLLMs(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
list := tensorrtLLMService.Page(req, helper.IsDemoRequest(c))
|
||||
list := tensorrtLLMService.Page(req)
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
|
||||
+154
-32
@@ -1,11 +1,14 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
@@ -19,29 +22,35 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// @Tags Terminal
|
||||
// @Summary Ws local terminal
|
||||
// @Param command query string false "command"
|
||||
// @Param session query string false "session id to reattach"
|
||||
// @Param terminalPersistent query boolean false "allow recovery after an unexpected disconnect"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/terminal/local [get]
|
||||
func (b *BaseApi) WsLocalTerminal(c *gin.Context) {
|
||||
b.runSSHSession(c, loadLocalConn, c.DefaultQuery("command", ""))
|
||||
b.runSSHSession(c, "local", loadLocalConn, c.DefaultQuery("command", ""))
|
||||
}
|
||||
|
||||
// @Tags Terminal
|
||||
// @Summary Ws host SSH
|
||||
// @Param id query integer false "id"
|
||||
// @Param command query string false "command"
|
||||
// @Param session query string false "session id to reattach"
|
||||
// @Param title query string false "session title shown in the session list"
|
||||
// @Param terminalPersistent query boolean false "allow recovery after an unexpected disconnect"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/terminal/ssh [get]
|
||||
func (b *BaseApi) WsHostSSH(c *gin.Context) {
|
||||
b.runSSHSession(c, func() (*ssh.SSHClient, error) {
|
||||
b.runSSHSession(c, "ssh", func() (*ssh.SSHClient, error) {
|
||||
hostID, _ := strconv.Atoi(c.DefaultQuery("id", "0"))
|
||||
if hostID <= 0 {
|
||||
return nil, errors.New("missing host id")
|
||||
@@ -65,26 +74,33 @@ func (b *BaseApi) WsContainerTerminal(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
slave, err := loadContainerTerminalCommand(c)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
if wshandleError(wsConn, err) {
|
||||
identity, ok := loadTerminalIdentity(c)
|
||||
if !ok {
|
||||
_ = wshandleError(wsConn, errors.New("missing terminal identity"))
|
||||
return
|
||||
}
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
tty.Start(quitChan)
|
||||
go slave.Wait(quitChan)
|
||||
opts := terminal.SessionOptions{
|
||||
Identity: identity,
|
||||
Kind: "container",
|
||||
Target: containerTerminalTarget(c),
|
||||
Cols: cols,
|
||||
Rows: rows,
|
||||
}
|
||||
if err := terminal.ServeCommand(wsConn, strings.TrimSpace(c.Query("session")), opts, func() (*terminal.LocalCommand, error) {
|
||||
return loadContainerTerminalCommand(c)
|
||||
}); err != nil {
|
||||
_ = wshandleError(wsConn, err)
|
||||
}
|
||||
}
|
||||
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
closeTerminalConn(wsConn)
|
||||
func containerTerminalTarget(c *gin.Context) string {
|
||||
query := c.Request.URL.Query()
|
||||
for _, key := range []string{"cols", "rows", "session", "terminalRevalidate"} {
|
||||
query.Del(key)
|
||||
}
|
||||
sum := sha256.Sum256([]byte(query.Encode()))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
|
||||
@@ -98,7 +114,7 @@ func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
|
||||
return nil, 0, 0, false
|
||||
}
|
||||
|
||||
if helper.IsDemoRequest(c) {
|
||||
if global.CONF.Base.IsDemo {
|
||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||
return nil, 0, 0, false
|
||||
}
|
||||
@@ -115,32 +131,138 @@ func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
|
||||
return wsConn, cols, rows, true
|
||||
}
|
||||
|
||||
func (b *BaseApi) runSSHSession(c *gin.Context, connect func() (*ssh.SSHClient, error), command string) {
|
||||
func (b *BaseApi) runSSHSession(c *gin.Context, kind string, connect func() (*ssh.SSHClient, error), command string) {
|
||||
wsConn, cols, rows, ok := prepareTerminalSession(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
client, clientErr := connect()
|
||||
if wshandleError(wsConn, errors.WithMessage(clientErr, "failed to set up the connection. Please check the host information")) {
|
||||
identity, ok := loadTerminalIdentity(c)
|
||||
if !ok {
|
||||
_ = wshandleError(wsConn, errors.New("missing terminal identity"))
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, command)
|
||||
if wshandleError(wsConn, err) {
|
||||
hostID := 0
|
||||
if kind == "ssh" {
|
||||
hostID, _ = strconv.Atoi(c.DefaultQuery("id", "0"))
|
||||
}
|
||||
opts := terminal.SessionOptions{
|
||||
Identity: identity,
|
||||
Kind: kind,
|
||||
Title: sanitizeTerminalTitle(c.Query("title")),
|
||||
Persistent: c.Query("terminalPersistent") == "true",
|
||||
HostID: uint(max(hostID, 0)),
|
||||
Cols: cols,
|
||||
Rows: rows,
|
||||
InitCmd: command,
|
||||
}
|
||||
err := terminal.Serve(wsConn, strings.TrimSpace(c.Query("session")), opts, func() (*gossh.Client, error) {
|
||||
client, err := connect()
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to set up the connection. Please check the host information")
|
||||
}
|
||||
return client.Client, nil
|
||||
})
|
||||
if err != nil {
|
||||
_ = wshandleError(wsConn, err)
|
||||
}
|
||||
}
|
||||
|
||||
// @Tags Terminal
|
||||
// @Summary List the caller's live terminal sessions
|
||||
// @Success 200 {array} terminal.Info
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/terminal/sessions/search [post]
|
||||
func (b *BaseApi) SearchTerminalSessions(c *gin.Context) {
|
||||
identity, ok := loadTerminalIdentity(c)
|
||||
if !ok {
|
||||
helper.BadRequest(c, errors.New("missing terminal identity"))
|
||||
return
|
||||
}
|
||||
defer sws.Close()
|
||||
helper.SuccessWithData(c, terminal.List(identity))
|
||||
}
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
sws.Start(quitChan)
|
||||
go sws.Wait(quitChan)
|
||||
// @Tags Terminal
|
||||
// @Summary Close a terminal session
|
||||
// @Accept json
|
||||
// @Param request body dto.TerminalSessionClose true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/terminal/sessions/close [post]
|
||||
func (b *BaseApi) CloseTerminalSession(c *gin.Context) {
|
||||
var req dto.TerminalSessionClose
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
identity, ok := loadTerminalIdentity(c)
|
||||
if !ok {
|
||||
helper.BadRequest(c, errors.New("missing terminal identity"))
|
||||
return
|
||||
}
|
||||
if err := terminal.CloseSession(req.ID, identity); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
<-quitChan
|
||||
// @Tags Terminal
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/terminal/sessions/closeAll [post]
|
||||
func (b *BaseApi) CloseAllTerminalSessions(c *gin.Context) {
|
||||
identity, ok := loadTerminalIdentity(c)
|
||||
if !ok {
|
||||
helper.BadRequest(c, errors.New("missing terminal identity"))
|
||||
return
|
||||
}
|
||||
terminal.Revoke("auth_session", identity.UserID, identity.AuthSessionID)
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
closeTerminalConn(wsConn)
|
||||
func (b *BaseApi) RevokeTerminalSessions(c *gin.Context) {
|
||||
var req dto.TerminalSessionRevoke
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if (req.Scope == "auth_session" && (req.UserID == "" || req.AuthSessionID == "")) ||
|
||||
(req.Scope == "user" && req.UserID == "") {
|
||||
helper.BadRequest(c, errors.New("missing terminal revocation identity"))
|
||||
return
|
||||
}
|
||||
terminal.Revoke(req.Scope, req.UserID, req.AuthSessionID)
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
func loadTerminalIdentity(c *gin.Context) (terminal.Identity, bool) {
|
||||
identity := terminal.Identity{
|
||||
UserID: strings.TrimSpace(c.GetHeader(terminal.HeaderUserID)),
|
||||
AuthSessionID: strings.TrimSpace(c.GetHeader(terminal.HeaderAuthSessionID)),
|
||||
}
|
||||
if value := c.GetHeader(terminal.HeaderAuthLeaseUntil); value != "" {
|
||||
millis, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil || millis <= 0 {
|
||||
return terminal.Identity{}, false
|
||||
}
|
||||
identity.AuthLeaseUntil = time.UnixMilli(millis)
|
||||
if maximum := time.Now().Add(90 * time.Second); identity.AuthLeaseUntil.After(maximum) {
|
||||
identity.AuthLeaseUntil = maximum
|
||||
}
|
||||
}
|
||||
return identity, identity.Valid()
|
||||
}
|
||||
|
||||
// sanitizeTerminalTitle keeps the title a short single line.
|
||||
func sanitizeTerminalTitle(title string) string {
|
||||
title = strings.Join(strings.Fields(title), " ")
|
||||
if r := []rune(title); len(r) > 64 {
|
||||
title = string(r[:64])
|
||||
}
|
||||
return title
|
||||
}
|
||||
|
||||
func closeTerminalConn(wsConn *websocket.Conn) {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (b *BaseApi) TerminalCapabilities(c *gin.Context) {
|
||||
helper.SuccessWithData(c, gin.H{"apiKeyLeaseVersion": 1})
|
||||
}
|
||||
@@ -255,7 +255,7 @@ func (b *BaseApi) GetHTTPSConfig(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
res, err := websiteService.GetWebsiteHTTPS(id, helper.IsDemoRequest(c))
|
||||
res, err := websiteService.GetWebsiteHTTPS(id)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -1080,7 +1080,7 @@ func (b *BaseApi) GetWebsiteResource(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
res, err := websiteService.GetWebsiteResource(id, helper.IsDemoRequest(c))
|
||||
res, err := websiteService.GetWebsiteResource(id)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ func (b *BaseApi) PageWebsiteAcmeAccount(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, accounts, err := websiteAcmeAccountService.Page(req, helper.IsDemoRequest(c))
|
||||
total, accounts, err := websiteAcmeAccountService.Page(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -24,7 +24,7 @@ func (b *BaseApi) PageWebsiteCA(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, cas, err := websiteCAService.Page(req, helper.IsDemoRequest(c))
|
||||
total, cas, err := websiteCAService.Page(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -70,7 +70,7 @@ func (b *BaseApi) GetWebsiteCA(c *gin.Context) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res, err := websiteCAService.GetCA(id, helper.IsDemoRequest(c))
|
||||
res, err := websiteCAService.GetCA(id)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ func (b *BaseApi) PageWebsiteDnsAccount(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, accounts, err := websiteDnsAccountService.Page(req, helper.IsDemoRequest(c))
|
||||
total, accounts, err := websiteDnsAccountService.Page(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -28,7 +28,7 @@ func (b *BaseApi) PageWebsiteSSL(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, accounts, err := websiteSSLService.Page(req, helper.IsDemoRequest(c))
|
||||
total, accounts, err := websiteSSLService.Page(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -159,7 +159,7 @@ func (b *BaseApi) GetWebsiteSSLByWebsiteId(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
websiteSSL, err := websiteSSLService.GetWebsiteSSL(websiteId, helper.IsDemoRequest(c))
|
||||
websiteSSL, err := websiteSSLService.GetWebsiteSSL(websiteId)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -181,7 +181,7 @@ func (b *BaseApi) GetWebsiteSSLById(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
websiteSSL, err := websiteSSLService.GetSSL(id, helper.IsDemoRequest(c))
|
||||
websiteSSL, err := websiteSSLService.GetSSL(id)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
+16
-7
@@ -162,16 +162,25 @@ type AgentWebsiteBindReq struct {
|
||||
}
|
||||
|
||||
type AgentModelConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
Model string `json:"model" validate:"required"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
Model string `json:"model" validate:"required"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
Metadata []AgentModelMetadata `json:"metadata" validate:"dive"`
|
||||
}
|
||||
|
||||
type AgentModelConfig struct {
|
||||
AccountID uint `json:"accountId"`
|
||||
Model string `json:"model"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
AccountID uint `json:"accountId"`
|
||||
Model string `json:"model"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
Metadata []AgentModelMetadata `json:"metadata"`
|
||||
}
|
||||
|
||||
type AgentModelMetadata struct {
|
||||
Model string `json:"model" validate:"required"`
|
||||
InputMode string `json:"inputMode" validate:"required,oneof=auto text image"`
|
||||
ContextWindow int `json:"contextWindow" validate:"min=0"`
|
||||
MaxTokens int `json:"maxTokens" validate:"min=0"`
|
||||
}
|
||||
|
||||
type AgentHermesChatSessionItem struct {
|
||||
|
||||
+31
-16
@@ -21,6 +21,7 @@ type AlertBase struct {
|
||||
}
|
||||
|
||||
type PushAlert struct {
|
||||
Result string `json:"result,omitempty"`
|
||||
TaskName string `json:"taskName"`
|
||||
AlertType string `json:"alertType"`
|
||||
EntryID uint `json:"entryID"`
|
||||
@@ -53,6 +54,7 @@ type AlertDTO struct {
|
||||
Method string `json:"method"`
|
||||
Title string `json:"title"`
|
||||
Project string `json:"project"`
|
||||
TaskName string `json:"taskName,omitempty"`
|
||||
Status string `json:"status"`
|
||||
SendCount uint `json:"sendCount"`
|
||||
AdvancedParams string `json:"advancedParams"`
|
||||
@@ -151,16 +153,25 @@ type AlertLog struct {
|
||||
}
|
||||
|
||||
type AlertDetail struct {
|
||||
LicenseId string `json:"licenseId"`
|
||||
Type string `json:"type"`
|
||||
SubType string `json:"subType"`
|
||||
Title string `json:"title"`
|
||||
Method string `json:"method"`
|
||||
LicenseCode string `json:"licenseCode"`
|
||||
DeviceId string `json:"deviceId"`
|
||||
Project string `json:"project"`
|
||||
Params []Param `json:"params"`
|
||||
Phone string `json:"phone"`
|
||||
LicenseId string `json:"licenseId"`
|
||||
Type string `json:"type"`
|
||||
SubType string `json:"subType"`
|
||||
Title string `json:"title"`
|
||||
Method string `json:"method"`
|
||||
LicenseCode string `json:"licenseCode"`
|
||||
DeviceId string `json:"deviceId"`
|
||||
Project string `json:"project"`
|
||||
Params []Param `json:"params"`
|
||||
Phone string `json:"phone"`
|
||||
Task *AlertTaskMetadata `json:"task,omitempty"`
|
||||
}
|
||||
|
||||
type AlertTaskMetadata struct {
|
||||
AlertID uint `json:"alertId"`
|
||||
Type string `json:"type"`
|
||||
Quota string `json:"quota"`
|
||||
QuotaType string `json:"quotaType"`
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
type AlertRule struct {
|
||||
@@ -295,15 +306,19 @@ type OfflineQueryRequest struct {
|
||||
}
|
||||
|
||||
type AlertConfigUpdate struct {
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Config string `json:"config"`
|
||||
DisplayName string `json:"displayName"`
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Config string `json:"config"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Revision *time.Time `json:"revision"`
|
||||
}
|
||||
|
||||
type AlertConfigTest struct {
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Config string `json:"config"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Sender string `json:"sender"`
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package dto
|
||||
|
||||
const AlertCustomWebhookSchemaVersion = 1
|
||||
|
||||
type AlertConfigStatusUpdate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Status string `json:"status" validate:"required,oneof=Enable Disable"`
|
||||
}
|
||||
|
||||
type AlertCustomWebhookSecretMutation struct {
|
||||
Action string `json:"action,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type AlertCustomWebhookURL struct {
|
||||
AlertCustomWebhookSecretMutation
|
||||
Configured bool `json:"configured"`
|
||||
Masked string `json:"masked,omitempty"`
|
||||
}
|
||||
|
||||
type AlertCustomWebhookBody struct {
|
||||
Type string `json:"type"`
|
||||
Template string `json:"template,omitempty"`
|
||||
Fields []AlertCustomWebhookFormField `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
type AlertCustomWebhookFormField struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type AlertCustomWebhookHeader struct {
|
||||
UID string `json:"uid"`
|
||||
Key string `json:"key"`
|
||||
Secret bool `json:"secret"`
|
||||
Action string `json:"action,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Configured bool `json:"configured,omitempty"`
|
||||
Masked string `json:"masked,omitempty"`
|
||||
}
|
||||
|
||||
type AlertCustomWebhookConfig struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
State string `json:"state,omitempty"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Preset string `json:"preset"`
|
||||
Method string `json:"method"`
|
||||
URL AlertCustomWebhookURL `json:"url"`
|
||||
Body AlertCustomWebhookBody `json:"body"`
|
||||
Headers []AlertCustomWebhookHeader `json:"headers"`
|
||||
}
|
||||
|
||||
type AlertCustomWebhookSecretConfig struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
URL string `json:"url"`
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
}
|
||||
|
||||
type AlertCustomWebhookResolvedConfig struct {
|
||||
SchemaVersion int
|
||||
DisplayName string
|
||||
Preset string
|
||||
Method string
|
||||
URL string
|
||||
Body AlertCustomWebhookBody
|
||||
Headers []AlertCustomWebhookResolvedHeader
|
||||
}
|
||||
|
||||
type AlertCustomWebhookResolvedHeader struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
type AlertConfigTestResult struct {
|
||||
Success bool `json:"success"`
|
||||
StatusCode int `json:"statusCode,omitempty"`
|
||||
Duration int64 `json:"duration,omitempty"` // milliseconds
|
||||
Message string `json:"message,omitempty"`
|
||||
Response string `json:"response,omitempty"`
|
||||
}
|
||||
@@ -6,9 +6,10 @@ type PageResult struct {
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
Code int `json:"code"`
|
||||
ErrorCode string `json:"errorCode,omitempty"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
|
||||
@@ -51,9 +51,10 @@ type CronjobOperate struct {
|
||||
Secret string `json:"secret"`
|
||||
Args string `json:"args"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
|
||||
|
||||
CleanLogConfig
|
||||
}
|
||||
@@ -126,7 +127,8 @@ type CronjobInfo struct {
|
||||
Secret string `json:"secret"`
|
||||
Args string `json:"args"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTriggerMode string `json:"alertTriggerMode"`
|
||||
}
|
||||
|
||||
type CronjobImport struct {
|
||||
@@ -169,9 +171,10 @@ type CronjobTrans struct {
|
||||
SourceAccounts []string `json:"sourceAccounts"`
|
||||
DownloadAccount string `json:"downloadAccount"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
|
||||
}
|
||||
type TransHelper struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -121,6 +121,7 @@ type DashboardCurrent struct {
|
||||
NetBytesRecv uint64 `json:"netBytesRecv"`
|
||||
|
||||
GPUData []GPUInfo `json:"gpuData"`
|
||||
NPUData []NPUInfo `json:"npuData"`
|
||||
XPUData []XPUInfo `json:"xpuData"`
|
||||
|
||||
TopCPUItems []Process `json:"topCPUItems"`
|
||||
@@ -158,7 +159,10 @@ type DiskInfo struct {
|
||||
type GPUInfo struct {
|
||||
Type string `json:"type"`
|
||||
Index uint `json:"index"`
|
||||
NPUIndex uint `json:"npuIndex"`
|
||||
ChipIndex uint `json:"chipIndex"`
|
||||
ProductName string `json:"productName"`
|
||||
BusID string `json:"busID"`
|
||||
GPUUtil string `json:"gpuUtil"`
|
||||
Temperature string `json:"temperature"`
|
||||
PerformanceState string `json:"performanceState"`
|
||||
@@ -171,6 +175,27 @@ type GPUInfo struct {
|
||||
FanSpeed string `json:"fanSpeed"`
|
||||
}
|
||||
|
||||
type NPUInfo struct {
|
||||
Type string `json:"type"`
|
||||
Index uint `json:"index"`
|
||||
NPUIndex uint `json:"npuIndex"`
|
||||
ChipIndex uint `json:"chipIndex"`
|
||||
ProductName string `json:"productName"`
|
||||
BusID string `json:"busID"`
|
||||
Health string `json:"health"`
|
||||
Temperature string `json:"temperature"`
|
||||
PowerDraw string `json:"powerDraw"`
|
||||
AICore string `json:"aiCore"`
|
||||
MemUsed string `json:"memUsed"`
|
||||
MemTotal string `json:"memTotal"`
|
||||
MemoryUsed string `json:"memoryUsed"`
|
||||
MemoryTotal string `json:"memoryTotal"`
|
||||
HBMUsed string `json:"hbmUsed"`
|
||||
HBMTotal string `json:"hbmTotal"`
|
||||
HugepagesUsed string `json:"hugepagesUsed"`
|
||||
HugepagesTotal string `json:"hugepagesTotal"`
|
||||
}
|
||||
|
||||
type AppLauncher struct {
|
||||
Key string `json:"key"`
|
||||
Type string `json:"type"`
|
||||
@@ -203,11 +228,13 @@ type LauncherOption struct {
|
||||
}
|
||||
|
||||
type XPUInfo struct {
|
||||
DeviceID int `json:"deviceID"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
Memory string `json:"memory"`
|
||||
Temperature string `json:"temperature"`
|
||||
MemoryUsed string `json:"memoryUsed"`
|
||||
Power string `json:"power"`
|
||||
MemoryUtil string `json:"memoryUtil"`
|
||||
DeviceID int `json:"deviceID"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
PciBdfAddress string `json:"pciBdfAddress"`
|
||||
Memory string `json:"memory"`
|
||||
Temperature string `json:"temperature"`
|
||||
GPUUtil string `json:"gpuUtil"`
|
||||
MemoryUsed string `json:"memoryUsed"`
|
||||
Power string `json:"power"`
|
||||
MemoryUtil string `json:"memoryUtil"`
|
||||
}
|
||||
|
||||
+326
-73
@@ -1,100 +1,353 @@
|
||||
package dto
|
||||
|
||||
type FirewallBaseInfo struct {
|
||||
Name string `json:"name"`
|
||||
IsExist bool `json:"isExist"`
|
||||
IsActive bool `json:"isActive"`
|
||||
IsInit bool `json:"isInit"`
|
||||
IsBind bool `json:"isBind"`
|
||||
Version string `json:"version"`
|
||||
PingStatus string `json:"pingStatus"`
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
dockerfirewall "github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
|
||||
)
|
||||
|
||||
type FirewallSubsystemStatus struct {
|
||||
IPv6Enabled bool `json:"ipv6Enabled"`
|
||||
Name string `json:"name"`
|
||||
Backend string `json:"backend"`
|
||||
ConflictBackend string `json:"conflictBackend,omitempty"`
|
||||
IsExist bool `json:"isExist"`
|
||||
IsActive bool `json:"isActive"`
|
||||
IsInit bool `json:"isInit"`
|
||||
IsBind bool `json:"isBind"`
|
||||
Version string `json:"version"`
|
||||
PingStatus string `json:"pingStatus"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
LifecycleTaskID string `json:"lifecycleTaskID,omitempty"`
|
||||
IPv4 FirewallBackendFamilyStatus `json:"ipv4"`
|
||||
IPv6 FirewallBackendFamilyStatus `json:"ipv6"`
|
||||
}
|
||||
|
||||
type RuleSearch struct {
|
||||
PageInfo
|
||||
Info string `json:"info"`
|
||||
Status string `json:"status"`
|
||||
Strategy string `json:"strategy"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
}
|
||||
|
||||
type FirewallOperation struct {
|
||||
type FirewallLifecycleOperation struct {
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop restart disableBanPing enableBanPing"`
|
||||
WithDockerRestart bool `json:"withDockerRestart"`
|
||||
}
|
||||
|
||||
type PortRuleOperate struct {
|
||||
ID uint `json:"id"`
|
||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||
Chain string `json:"chain"`
|
||||
Address string `json:"address"`
|
||||
Port string `json:"port" validate:"required"`
|
||||
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
|
||||
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
|
||||
|
||||
Description string `json:"description"`
|
||||
type FirewallLifecycleOperationResponse struct {
|
||||
TaskID string `json:"taskID,omitempty"`
|
||||
Queued bool `json:"queued"`
|
||||
}
|
||||
|
||||
type UpdateFirewallDescription struct {
|
||||
Type string `json:"type"`
|
||||
Chain string `json:"chain"`
|
||||
SrcIP string `json:"srcIP"`
|
||||
DstIP string `json:"dstIP"`
|
||||
SrcPort string `json:"srcPort"`
|
||||
DstPort string `json:"dstPort"`
|
||||
Protocol string `json:"protocol"`
|
||||
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
|
||||
|
||||
Description string `json:"description"`
|
||||
type FirewallBackendOption struct {
|
||||
Name string `json:"name"`
|
||||
Installed bool `json:"installed"`
|
||||
Active bool `json:"active"`
|
||||
Initialized bool `json:"initialized"`
|
||||
Bound bool `json:"bound"`
|
||||
Supported bool `json:"supported"`
|
||||
SupportReason string `json:"supportReason,omitempty"`
|
||||
Implementation string `json:"implementation,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
IPv4 FirewallBackendFamilyStatus `json:"ipv4"`
|
||||
IPv6 FirewallBackendFamilyStatus `json:"ipv6"`
|
||||
}
|
||||
|
||||
type AddrRuleOperate struct {
|
||||
ID uint `json:"id"`
|
||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||
Address string `json:"address" validate:"required"`
|
||||
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
|
||||
|
||||
Description string `json:"description"`
|
||||
type FirewallBackendFamilyStatus struct {
|
||||
Partial bool `json:"partial"`
|
||||
Available bool `json:"available"`
|
||||
Initialized bool `json:"initialized"`
|
||||
Bound bool `json:"bound"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
RAInterfaces []string `json:"raInterfaces,omitempty"`
|
||||
}
|
||||
|
||||
type PortRuleUpdate struct {
|
||||
OldRule PortRuleOperate `json:"oldRule"`
|
||||
NewRule PortRuleOperate `json:"newRule"`
|
||||
type FirewallBackendGroup struct {
|
||||
Selected string `json:"selected"`
|
||||
Current string `json:"current,omitempty"`
|
||||
Options []FirewallBackendOption `json:"options"`
|
||||
}
|
||||
|
||||
type AddrRuleUpdate struct {
|
||||
OldRule AddrRuleOperate `json:"oldRule"`
|
||||
NewRule AddrRuleOperate `json:"newRule"`
|
||||
type FirewallSettings struct {
|
||||
IPv6Enabled bool `json:"ipv6Enabled"`
|
||||
System FirewallBackendGroup `json:"system"`
|
||||
Forwarding FirewallBackendGroup `json:"forwarding"`
|
||||
Docker FirewallBackendGroup `json:"docker"`
|
||||
PingStatus string `json:"pingStatus"`
|
||||
PortWhitelist []filter.PortWhitelist `json:"portWhiteList"`
|
||||
PanelPort string `json:"panelPort"`
|
||||
SSHPort string `json:"sshPort"`
|
||||
}
|
||||
|
||||
type BatchRuleOperate struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
Rules []PortRuleOperate `json:"rules"`
|
||||
type FirewallPortWhitelistCreate struct {
|
||||
Rule filter.PortWhitelist `json:"rule" validate:"required"`
|
||||
}
|
||||
|
||||
type IptablesOp struct {
|
||||
Name string `json:"name" validate:"required,oneof=1PANEL_INPUT 1PANEL_OUTPUT 1PANEL_BASIC 1PANEL_FORWARD"`
|
||||
Operate string `json:"operate" validate:"required,oneof=init-base init-forward init-advance bind-base unbind-base bind unbind"`
|
||||
type FirewallPortWhitelistUpdate struct {
|
||||
OldRule filter.PortWhitelist `json:"oldRule" validate:"required"`
|
||||
Rule filter.PortWhitelist `json:"rule" validate:"required"`
|
||||
}
|
||||
|
||||
type IptablesRuleOp struct {
|
||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||
ID uint `json:"id"`
|
||||
Chain string `json:"chain" validate:"required,oneof=1PANEL_BASIC 1PANEL_BASIC_BEFORE 1PANEL_INPUT 1PANEL_OUTPUT"`
|
||||
Protocol string `json:"protocol"`
|
||||
SrcIP string `json:"srcIP"`
|
||||
SrcPort uint `json:"srcPort"`
|
||||
DstIP string `json:"dstIP"`
|
||||
DstPort uint `json:"dstPort"`
|
||||
Strategy string `json:"strategy" validate:"required,oneof=accept drop reject"`
|
||||
Description string `json:"description"`
|
||||
type FirewallPortWhitelistDelete struct {
|
||||
Rule *filter.PortWhitelist `json:"rule" validate:"required"`
|
||||
}
|
||||
|
||||
type IptablesBatchOperate struct {
|
||||
Rules []IptablesRuleOp `json:"rules"`
|
||||
type FirewallBackendOperation struct {
|
||||
Subsystem string `json:"subsystem" validate:"required,oneof=system forwarding docker"`
|
||||
Backend string `json:"backend" validate:"required,oneof=firewalld ufw iptables nftables"`
|
||||
Operation string `json:"operation" validate:"required,oneof=select initialize cleanup"`
|
||||
}
|
||||
|
||||
type IptablesChainStatus struct {
|
||||
IsBind bool `json:"isBind"`
|
||||
DefaultStrategy string `json:"defaultStrategy"`
|
||||
type FirewallIPv6Operation struct {
|
||||
Status string `json:"status" validate:"required,oneof=Enable Disable"`
|
||||
}
|
||||
|
||||
type FirewallFamilyOperation struct {
|
||||
Subsystem string `json:"subsystem" validate:"required,oneof=system forwarding docker"`
|
||||
Backend string `json:"backend" validate:"required,oneof=iptables nftables"`
|
||||
Family string `json:"family" validate:"required,oneof=ipv4 ipv6"`
|
||||
Operation string `json:"operation" validate:"required,oneof=initialize repair bind"`
|
||||
}
|
||||
|
||||
type FilterChainOperation struct {
|
||||
Name string `json:"name" validate:"required,eq=1PANEL_BASIC"`
|
||||
Operate string `json:"operate" validate:"required,oneof=init-base bind-base unbind-base"`
|
||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
||||
}
|
||||
|
||||
type FilterChainOperationResponse struct {
|
||||
TaskID string `json:"taskID"`
|
||||
Queued bool `json:"queued"`
|
||||
}
|
||||
|
||||
type FirewallInitializationTask struct {
|
||||
BackupFile string `json:"backupFile,omitempty" validate:"omitempty,max=255"`
|
||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
||||
}
|
||||
|
||||
type FirewallSystemPort = firewall.SystemPort
|
||||
|
||||
type FirewallRuleInventoryResponse struct {
|
||||
IPv4Range filter.PositionRange `json:"ipv4Range"`
|
||||
IPv6Range filter.PositionRange `json:"ipv6Range"`
|
||||
Total int64 `json:"total"`
|
||||
AllTotal int64 `json:"allTotal"`
|
||||
Items []filter.InventoryItem `json:"items"`
|
||||
Notices []filter.ScopeNotice `json:"notices,omitempty"`
|
||||
}
|
||||
|
||||
type FirewallRuleBackup struct {
|
||||
Name string `json:"name"`
|
||||
Provider filter.Provider `json:"provider"`
|
||||
RuleCount int `json:"ruleCount"`
|
||||
ModifiedAt int64 `json:"modifiedAt"`
|
||||
}
|
||||
|
||||
type FirewallRuleBackups struct {
|
||||
Directory string `json:"directory"`
|
||||
Files []FirewallRuleBackup `json:"files"`
|
||||
}
|
||||
|
||||
type FirewallRuleResetResponse struct {
|
||||
BackupPath string `json:"backupPath"`
|
||||
Removed int `json:"removed"`
|
||||
Disabled bool `json:"disabled"`
|
||||
}
|
||||
|
||||
type FirewallRuleReset struct {
|
||||
Subsystem string `json:"subsystem,omitempty" validate:"omitempty,oneof=system forwarding docker"`
|
||||
Backup *bool `json:"backup,omitempty" default:"true"`
|
||||
Provider filter.Provider `json:"provider,omitempty" validate:"omitempty,oneof=firewalld ufw iptables nftables"`
|
||||
WithDockerRestart bool `json:"withDockerRestart"`
|
||||
}
|
||||
|
||||
type FirewallRuleInventory struct {
|
||||
PageInfo
|
||||
Scope filter.Scope `json:"scope,omitempty"`
|
||||
Scopes []filter.Scope `json:"scopes,omitempty" validate:"max=16"`
|
||||
All bool `json:"all,omitempty"`
|
||||
Info string `json:"info"`
|
||||
Families []filter.Family `json:"families,omitempty" validate:"omitempty,dive,oneof=ipv4 ipv6"`
|
||||
Actions []string `json:"actions,omitempty" validate:"omitempty,dive,oneof=accept deny"`
|
||||
ExcludeChains []string `json:"excludeChains,omitempty" validate:"omitempty,dive,oneof=1PANEL_BASIC_BEFORE 1PANEL_BASIC 1PANEL_BASIC_AFTER"`
|
||||
}
|
||||
|
||||
type FirewallNativeDetail struct {
|
||||
Provider filter.Provider `json:"provider" validate:"required,oneof=firewalld ufw"`
|
||||
NativeKind filter.NativeKind `json:"nativeKind" validate:"required,oneof=zone_service ufw_application"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Permanent bool `json:"permanent"`
|
||||
}
|
||||
|
||||
type DockerPortGuardBase struct {
|
||||
IPv6Enabled bool `json:"ipv6Enabled"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
IsExist bool `json:"isExist"`
|
||||
Initialized bool `json:"initialized"`
|
||||
Bound bool `json:"bound"`
|
||||
IPv4 DockerPortGuardFamilyStatus `json:"ipv4"`
|
||||
IPv6 DockerPortGuardFamilyStatus `json:"ipv6"`
|
||||
Backend string `json:"backend"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type DockerPortGuardFamilyStatus struct {
|
||||
Partial bool `json:"partial"`
|
||||
State string `json:"state"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Initialized bool `json:"initialized"`
|
||||
Bound bool `json:"bound"`
|
||||
Effective bool `json:"effective"`
|
||||
}
|
||||
|
||||
type DockerPortGuardEndpoint struct {
|
||||
Family string `json:"family"`
|
||||
HostIP string `json:"hostIP"`
|
||||
HostPort uint16 `json:"hostPort"`
|
||||
Protocol string `json:"protocol"`
|
||||
ContainerID string `json:"containerID"`
|
||||
ContainerName string `json:"containerName"`
|
||||
ContainerState string `json:"containerState,omitempty"`
|
||||
ContainerPort uint16 `json:"containerPort"`
|
||||
Compose string `json:"compose,omitempty"`
|
||||
Application string `json:"application,omitempty"`
|
||||
PolicyUUID string `json:"policyUUID,omitempty"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
Sources []string `json:"sources"`
|
||||
Effective bool `json:"effective"`
|
||||
TrafficPath string `json:"trafficPath"`
|
||||
ManagementTarget string `json:"managementTarget"`
|
||||
ManagementReason string `json:"managementReason,omitempty"`
|
||||
}
|
||||
|
||||
type DockerPortGuardPortGroup struct {
|
||||
Key string `json:"key"`
|
||||
Label string `json:"label"`
|
||||
Endpoint DockerPortGuardEndpoint `json:"endpoint"`
|
||||
Endpoints []DockerPortGuardEndpoint `json:"endpoints"`
|
||||
}
|
||||
|
||||
type DockerPortGuardContainer struct {
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
Compose string `json:"compose,omitempty"`
|
||||
Application string `json:"application,omitempty"`
|
||||
Endpoints []DockerPortGuardEndpoint `json:"endpoints"`
|
||||
PortGroups []DockerPortGuardPortGroup `json:"portGroups"`
|
||||
}
|
||||
|
||||
type DockerPortGuardList struct {
|
||||
Base DockerPortGuardBase `json:"base"`
|
||||
Containers []DockerPortGuardContainer `json:"containers"`
|
||||
OrphanPolicies []DockerPortGuardEndpoint `json:"orphanPolicies"`
|
||||
}
|
||||
|
||||
type DockerPortGuardEndpointIdentity struct {
|
||||
Family string `json:"family" validate:"required,oneof=ipv4 ipv6"`
|
||||
HostIP string `json:"hostIP" validate:"required,max=45"`
|
||||
HostPort uint16 `json:"hostPort" validate:"required,min=1"`
|
||||
Protocol string `json:"protocol" validate:"required,oneof=tcp udp"`
|
||||
}
|
||||
|
||||
type DockerPortGuardPolicyBatch struct {
|
||||
Policies []DockerPortGuardPolicy `json:"policies" validate:"required,min=1,dive"`
|
||||
Import bool `json:"import"`
|
||||
}
|
||||
|
||||
type DockerPortGuardPolicyBatchDelete struct {
|
||||
UUIDs []string `json:"uuids" validate:"required,min=1,dive,required,max=64"`
|
||||
}
|
||||
|
||||
type DockerPortGuardPolicy struct {
|
||||
DockerPortGuardEndpointIdentity
|
||||
Mode string `json:"mode" validate:"required,oneof=deny_sources allow_sources deny_all accept_sources accept_all"`
|
||||
Sources []string `json:"sources" validate:"dive,required,max=64"`
|
||||
}
|
||||
|
||||
type DockerPortGuardOperation struct {
|
||||
BackupFile string `json:"backupFile,omitempty" validate:"omitempty,max=255"`
|
||||
Operation string `json:"operation" validate:"required,oneof=initialize bind unbind"`
|
||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
||||
}
|
||||
|
||||
type FirewallRuleCreateItem struct {
|
||||
Raw string `json:"raw,omitempty"`
|
||||
ParseStatus filter.ParseStatus `json:"parseStatus,omitempty"`
|
||||
Rule filter.FirewallRule `json:"rule" validate:"required"`
|
||||
SourceKind string `json:"sourceKind" validate:"omitempty,oneof=user imported"`
|
||||
}
|
||||
|
||||
type FirewallRuleCreate struct {
|
||||
BackupFile string `json:"backupFile,omitempty" validate:"omitempty,max=255"`
|
||||
Initialize bool `json:"initialize"`
|
||||
Items []FirewallRuleCreateItem `json:"items" validate:"dive"`
|
||||
}
|
||||
|
||||
type FirewallRuleCreateResponse struct {
|
||||
TaskID string `json:"taskID,omitempty"`
|
||||
Queued bool `json:"queued,omitempty"`
|
||||
Succeeded int `json:"succeeded"`
|
||||
Failed int `json:"failed"`
|
||||
Skipped int `json:"skipped"`
|
||||
Errors []FirewallRuleCreateFailure `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
type FirewallRuleCreateFailure struct {
|
||||
Index int `json:"index"`
|
||||
Status string `json:"status"`
|
||||
Rule filter.FirewallRule `json:"rule"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type FirewallRuleDelete struct {
|
||||
Targets []FirewallRuleDeleteItem `json:"targets" validate:"required,min=1,dive"`
|
||||
}
|
||||
|
||||
type FirewallRuleDeleteItem struct {
|
||||
FirewallRuleDeleteTarget
|
||||
Observed filter.ObservedRule `json:"observed" validate:"required"`
|
||||
}
|
||||
|
||||
type FirewallRuleDeleteTarget struct {
|
||||
Scope filter.Scope `json:"scope" validate:"required"`
|
||||
InstanceKey string `json:"instanceKey" validate:"required,max=128"`
|
||||
}
|
||||
|
||||
type FirewallRuleDeleteResponse struct {
|
||||
TaskID string `json:"taskID,omitempty"`
|
||||
Queued bool `json:"queued,omitempty"`
|
||||
Succeeded int `json:"succeeded"`
|
||||
Failed int `json:"failed"`
|
||||
Errors []FirewallRuleDeleteFailure `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
type FirewallRuleDeleteFailure struct {
|
||||
Index int `json:"index"`
|
||||
InstanceKey string `json:"instanceKey"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type FirewallRuleUpdate struct {
|
||||
FirewallRuleDeleteTarget
|
||||
Rule *filter.FirewallRule `json:"rule,omitempty" validate:"required_without_all=Description OrderIndex Priority,excluded_with=Description OrderIndex Priority"`
|
||||
Description *string `json:"description,omitempty" validate:"excluded_with=Rule"`
|
||||
OrderIndex *int64 `json:"orderIndex,omitempty" validate:"excluded_with=Rule Priority"`
|
||||
Priority *int `json:"priority,omitempty" validate:"excluded_with=Rule OrderIndex"`
|
||||
}
|
||||
|
||||
type FirewallRuleReorder struct {
|
||||
FirewallRuleDeleteTarget
|
||||
TargetPosition *int64 `json:"targetPosition"`
|
||||
Priority *int `json:"priority"`
|
||||
}
|
||||
|
||||
type FirewallRuleExportItem struct {
|
||||
filter.FirewallRule
|
||||
Raw string `json:"raw,omitempty"`
|
||||
ParseStatus filter.ParseStatus `json:"parseStatus,omitempty"`
|
||||
}
|
||||
|
||||
type FirewallSubsystemBackup struct {
|
||||
Families []string `json:"families,omitempty"`
|
||||
Subsystem string `json:"subsystem"`
|
||||
Provider filter.Provider `json:"provider"`
|
||||
Forwarding []forwarding.Rule `json:"forwarding"`
|
||||
Docker *dockerfirewall.PolicyInventory `json:"docker,omitempty"`
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@ package dto
|
||||
|
||||
type ForwardRuleSearch struct {
|
||||
PageInfo
|
||||
All bool `json:"all,omitempty"`
|
||||
Info string `json:"info"`
|
||||
Status string `json:"status"`
|
||||
Strategy string `json:"strategy"`
|
||||
}
|
||||
|
||||
// ForwardRule preserves the existing firewall search response shape while
|
||||
// keeping forwarding data separate from the filter client model.
|
||||
type ForwardRule struct {
|
||||
ID uint `json:"id"`
|
||||
Chain string `json:"chain"`
|
||||
@@ -28,13 +27,14 @@ type ForwardRule struct {
|
||||
}
|
||||
|
||||
type ForwardRuleOperate struct {
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
Rules []ForwardRuleOperation `json:"rules"`
|
||||
Import bool `json:"import"`
|
||||
Rules []ForwardRuleOperation `json:"rules" validate:"required,min=1,dive"`
|
||||
}
|
||||
|
||||
type ForwardRuleOperation struct {
|
||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||
Num string `json:"num"`
|
||||
Family string `json:"family" validate:"omitempty,oneof=ipv4 ipv6"`
|
||||
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
|
||||
Interface string `json:"interface"`
|
||||
Port string `json:"port" validate:"required"`
|
||||
|
||||
+88
-18
@@ -1,6 +1,10 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
)
|
||||
|
||||
type MonitorSearch struct {
|
||||
Param string `json:"param" validate:"required,oneof=all cpu memory load io network"`
|
||||
@@ -26,25 +30,35 @@ type Process struct {
|
||||
}
|
||||
|
||||
type MonitorSetting struct {
|
||||
MonitorStatus string `json:"monitorStatus"`
|
||||
MonitorStoreDays string `json:"monitorStoreDays"`
|
||||
MonitorInterval string `json:"monitorInterval"`
|
||||
DefaultNetwork string `json:"defaultNetwork"`
|
||||
DefaultIO string `json:"defaultIO"`
|
||||
GPUMonitorStatus string `json:"gpuMonitorStatus"`
|
||||
GPUMonitorStoreDays string `json:"gpuMonitorStoreDays"`
|
||||
GPUMonitorInterval string `json:"gpuMonitorInterval"`
|
||||
VLLMMonitorStatus string `json:"vllmMonitorStatus"`
|
||||
VLLMMonitorStoreDays string `json:"vllmMonitorStoreDays"`
|
||||
VLLMMonitorInterval string `json:"vllmMonitorInterval"`
|
||||
MonitorStatus string `json:"monitorStatus"`
|
||||
MonitorStoreDays string `json:"monitorStoreDays"`
|
||||
MonitorInterval string `json:"monitorInterval"`
|
||||
DefaultNetwork string `json:"defaultNetwork"`
|
||||
DefaultIO string `json:"defaultIO"`
|
||||
}
|
||||
|
||||
type MonitorSettingUpdate struct {
|
||||
Key string `json:"key" validate:"required,oneof=MonitorStatus MonitorStoreDays MonitorInterval DefaultNetwork DefaultIO"`
|
||||
Key string `json:"key" validate:"required,oneof=MonitorStatus MonitorStoreDays MonitorInterval GPUMonitorStatus GPUMonitorStoreDays GPUMonitorInterval VLLMMonitorStatus VLLMMonitorStoreDays VLLMMonitorInterval DefaultNetwork DefaultIO"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type MonitorGPUOptions struct {
|
||||
Supported bool `json:"supported"`
|
||||
GPUType string `json:"gpuType"`
|
||||
ChartHide []GPUChartHide `json:"chartHide"`
|
||||
Options []string `json:"options"`
|
||||
}
|
||||
type GPUChartHide struct {
|
||||
DeviceID string `json:"deviceID"`
|
||||
Legacy bool `json:"legacy"`
|
||||
ProductName string `json:"productName"`
|
||||
Type string `json:"type"`
|
||||
Process bool `json:"process"`
|
||||
GPU bool `json:"gpu"`
|
||||
Memory bool `json:"memory"`
|
||||
@@ -54,23 +68,54 @@ type GPUChartHide struct {
|
||||
Speed bool `json:"speed"`
|
||||
}
|
||||
type MonitorGPUSearch struct {
|
||||
Aggregation string `json:"aggregation" validate:"omitempty,oneof=avg max"`
|
||||
DeviceID string `json:"deviceID"`
|
||||
Legacy bool `json:"legacy"`
|
||||
ProductName string `json:"productName"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
}
|
||||
type MonitorGPUData struct {
|
||||
Date []time.Time `json:"date"`
|
||||
GPUValue []float64 `json:"gpuValue"`
|
||||
TemperatureValue []float64 `json:"temperatureValue"`
|
||||
PowerTotal []float64 `json:"powerTotal"`
|
||||
PowerUsed []float64 `json:"powerUsed"`
|
||||
PowerPercent []float64 `json:"powerPercent"`
|
||||
MemoryTotal []float64 `json:"memoryTotal"`
|
||||
MemoryUsed []float64 `json:"memoryUsed"`
|
||||
MemoryPercent []float64 `json:"memoryPercent"`
|
||||
SpeedValue []int `json:"speedValue"`
|
||||
MemoryActivity []*float64 `json:"memoryActivity"`
|
||||
EncoderUtil []*float64 `json:"encoderUtil"`
|
||||
DecoderUtil []*float64 `json:"decoderUtil"`
|
||||
JPEGUtil []*float64 `json:"jpegUtil"`
|
||||
OFAUtil []*float64 `json:"ofaUtil"`
|
||||
MediaUtil []*float64 `json:"mediaUtil"`
|
||||
ComputeUtil []*float64 `json:"computeUtil"`
|
||||
CopyUtil []*float64 `json:"copyUtil"`
|
||||
HotspotTemperature []*float64 `json:"hotspotTemperature"`
|
||||
FanRPM []*float64 `json:"fanRPM"`
|
||||
AICPUUtil []*float64 `json:"aiCPUUtil"`
|
||||
CtrlCPUUtil []*float64 `json:"ctrlCPUUtil"`
|
||||
DDRUsed []*float64 `json:"ddrUsed"`
|
||||
DDRTotal []*float64 `json:"ddrTotal"`
|
||||
HBMUsed []*float64 `json:"hbmUsed"`
|
||||
HBMTotal []*float64 `json:"hbmTotal"`
|
||||
DDRBandwidth []*float64 `json:"ddrBandwidth"`
|
||||
HBMBandwidth []*float64 `json:"hbmBandwidth"`
|
||||
MemoryBandwidth []*float64 `json:"memoryBandwidth"`
|
||||
MediaFrequency []*float64 `json:"mediaFrequency"`
|
||||
HugepagesUsed []*float64 `json:"hugepagesUsed"`
|
||||
HugepagesTotal []*float64 `json:"hugepagesTotal"`
|
||||
|
||||
ProcessCount []int `json:"processCount"`
|
||||
BucketSeconds int64 `json:"bucketSeconds"`
|
||||
SampleCount int64 `json:"sampleCount"`
|
||||
MemoryTemperatureValue []*float64 `json:"memoryTemperatureValue"`
|
||||
FrequencyValue []*float64 `json:"frequencyValue"`
|
||||
MemoryFrequencyValue []*float64 `json:"memoryFrequencyValue"`
|
||||
Date []time.Time `json:"date"`
|
||||
GPUValue []*float64 `json:"gpuValue"`
|
||||
TemperatureValue []*float64 `json:"temperatureValue"`
|
||||
PowerTotal []*float64 `json:"powerTotal"`
|
||||
PowerUsed []*float64 `json:"powerUsed"`
|
||||
PowerPercent []*float64 `json:"powerPercent"`
|
||||
MemoryTotal []*float64 `json:"memoryTotal"`
|
||||
MemoryUsed []*float64 `json:"memoryUsed"`
|
||||
MemoryPercent []*float64 `json:"memoryPercent"`
|
||||
SpeedValue []*float64 `json:"speedValue"`
|
||||
|
||||
ProcessCount []*float64 `json:"processCount"`
|
||||
GPUProcesses [][]GPUProcess `json:"gpuProcesses"`
|
||||
}
|
||||
|
||||
@@ -80,3 +125,28 @@ type GPUProcess struct {
|
||||
ProcessName string `json:"processName"`
|
||||
UsedMemory string `json:"usedMemory"`
|
||||
}
|
||||
|
||||
type MonitorVLLMSearch struct {
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
StartTime time.Time `json:"startTime" validate:"required"`
|
||||
EndTime time.Time `json:"endTime" validate:"required"`
|
||||
Aggregation string `json:"aggregation" validate:"omitempty,oneof=avg max"`
|
||||
}
|
||||
|
||||
type MonitorVLLMData struct {
|
||||
SampleCount int64 `json:"sampleCount"`
|
||||
BucketSeconds int64 `json:"bucketSeconds"`
|
||||
Points []model.MonitorVLLM `json:"points"`
|
||||
}
|
||||
|
||||
type MonitorVLLMCurrent struct {
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
}
|
||||
|
||||
type MonitorVLLMClean struct {
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
}
|
||||
|
||||
type MonitorClean struct {
|
||||
Type string `json:"type" validate:"required,oneof=host gpu"`
|
||||
}
|
||||
|
||||
@@ -51,13 +51,19 @@ const (
|
||||
CACHE NginxKey = "cache"
|
||||
HttpPer NginxKey = "http-per"
|
||||
ProxyCache NginxKey = "proxy-cache"
|
||||
Brotli NginxKey = "brotli"
|
||||
)
|
||||
|
||||
// BrotliKeys are served from the panel-managed http.d file rather than
|
||||
// nginx.conf, because the module is optional: its directives must disappear
|
||||
// together with the module, otherwise nginx refuses to start.
|
||||
var BrotliKeys = []string{"brotli", "brotli_comp_level", "brotli_min_length", "brotli_types"}
|
||||
|
||||
var ScopeKeyMap = map[NginxKey][]string{
|
||||
Index: {"index"},
|
||||
LimitConn: {"limit_conn", "limit_rate", "limit_conn_zone"},
|
||||
SSL: {"ssl_certificate", "ssl_certificate_key"},
|
||||
HttpPer: {"server_names_hash_bucket_size", "client_header_buffer_size", "client_max_body_size", "keepalive_timeout", "gzip", "gzip_min_length", "gzip_comp_level"},
|
||||
HttpPer: {"server_names_hash_bucket_size", "client_header_buffer_size", "client_max_body_size", "keepalive_timeout", "gzip", "gzip_min_length", "gzip_comp_level", "gzip_types", "gzip_vary", "gzip_proxied"},
|
||||
}
|
||||
|
||||
var StaticFileKeyMap = map[NginxKey]struct {
|
||||
|
||||
@@ -50,6 +50,10 @@ type AppContainerConfig struct {
|
||||
Type string `json:"type"`
|
||||
SpecifyIP string `json:"specifyIP"`
|
||||
RestartPolicy string `json:"restartPolicy" validate:"omitempty,oneof=always unless-stopped no on-failure"`
|
||||
|
||||
KeepServiceName bool `json:"-"`
|
||||
SkipComposeCommonConfig bool `json:"-"`
|
||||
UseLifecycleScripts bool `json:"-"`
|
||||
}
|
||||
|
||||
type AppInstalledSearch struct {
|
||||
@@ -62,13 +66,11 @@ type AppInstalledSearch struct {
|
||||
All bool `json:"all"`
|
||||
Sync bool `json:"sync"`
|
||||
CheckUpdate bool `json:"checkUpdate"`
|
||||
ReadOnly bool `json:"-"`
|
||||
}
|
||||
|
||||
type AppInstalledInfo struct {
|
||||
Key string `json:"key" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
ReadOnly bool `json:"-"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type AppBackupSearch struct {
|
||||
@@ -94,6 +96,8 @@ type AppInstalledOperate struct {
|
||||
TaskID string `json:"taskID"`
|
||||
DeleteImage bool `json:"deleteImage"`
|
||||
Favorite bool `json:"favorite"`
|
||||
|
||||
UseLifecycleScripts bool `json:"-"`
|
||||
}
|
||||
|
||||
type AppInstallUpgrade struct {
|
||||
@@ -113,11 +117,14 @@ type AppInstallDelete struct {
|
||||
DeleteDB bool `json:"deleteDB"`
|
||||
DeleteImage bool `json:"deleteImage"`
|
||||
TaskID string `json:"taskID"`
|
||||
|
||||
UseLifecycleScripts bool `json:"-"`
|
||||
}
|
||||
|
||||
type AppInstalledUpdate struct {
|
||||
InstallId uint `json:"installId" validate:"required"`
|
||||
Params map[string]interface{} `json:"params" validate:"required"`
|
||||
TaskID string `json:"-"`
|
||||
AppContainerConfig
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +122,7 @@ type FileWget struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
IgnoreCertificate bool `json:"ignoreCertificate"`
|
||||
UseProxy bool `json:"useProxy"`
|
||||
UseServerFilename bool `json:"useServerFilename"`
|
||||
}
|
||||
|
||||
type FileMove struct {
|
||||
@@ -158,6 +159,10 @@ type FileProcessReq struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type FileProcessRemoveReq struct {
|
||||
Keys []string `json:"keys" validate:"required,min=1,max=1000"`
|
||||
}
|
||||
|
||||
type FileRoleUpdate struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
User string `json:"user" validate:"required"`
|
||||
|
||||
@@ -6,10 +6,9 @@ import (
|
||||
|
||||
type RuntimeSearch struct {
|
||||
dto.PageInfo
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
ReadOnly bool `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type RuntimeCreate struct {
|
||||
@@ -67,14 +66,15 @@ type RuntimeDelete struct {
|
||||
}
|
||||
|
||||
type RuntimeUpdate struct {
|
||||
Name string `json:"name"`
|
||||
ID uint `json:"id"`
|
||||
Image string `json:"image"`
|
||||
Version string `json:"version"`
|
||||
Rebuild bool `json:"rebuild"`
|
||||
Source string `json:"source"`
|
||||
CodeDir string `json:"codeDir"`
|
||||
Remark string `json:"remark"`
|
||||
AppDetailID uint `json:"appDetailId"`
|
||||
Name string `json:"name"`
|
||||
ID uint `json:"id"`
|
||||
Image string `json:"image"`
|
||||
Version string `json:"version"`
|
||||
Rebuild bool `json:"rebuild"`
|
||||
Source string `json:"source"`
|
||||
CodeDir string `json:"codeDir"`
|
||||
Remark string `json:"remark"`
|
||||
|
||||
Params map[string]interface{} `json:"params"`
|
||||
NodeConfig
|
||||
|
||||
@@ -17,6 +17,17 @@ type NginxParam struct {
|
||||
Params []string `json:"params"`
|
||||
}
|
||||
|
||||
// NginxBrotliRes carries the brotli settings together with where they live.
|
||||
// ManagedExternally is true when the user defined brotli by hand, in which
|
||||
// case the panel only reports the values and must not write its own copy.
|
||||
// ManagedUnavailable is true when the panel could not wire the managed
|
||||
// configuration into nginx.conf at all, so the reported values are inert.
|
||||
type NginxBrotliRes struct {
|
||||
Params []NginxParam `json:"params"`
|
||||
ManagedExternally bool `json:"managedExternally"`
|
||||
ManagedUnavailable bool `json:"managedUnavailable"`
|
||||
}
|
||||
|
||||
type NginxAuthRes struct {
|
||||
Enable bool `json:"enable"`
|
||||
Items []dto.NginxAuth `json:"items"`
|
||||
|
||||
@@ -35,7 +35,7 @@ type SettingUpdate struct {
|
||||
}
|
||||
|
||||
type AgentSettingUpdate struct {
|
||||
Key string `json:"key" validate:"required,oneof=SystemIP DockerSockPath FileRecycleBin FirewallPortWhiteList"`
|
||||
Key string `json:"key" validate:"required,oneof=SystemIP DockerSockPath FileRecycleBin"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ type RootCertOperate struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Mode string `json:"mode"`
|
||||
EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"`
|
||||
EncryptionMode string `json:"encryptionMode"`
|
||||
PassPhrase string `json:"passPhrase"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package dto
|
||||
|
||||
type TerminalSessionClose struct {
|
||||
ID string `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
type TerminalSessionRevoke struct {
|
||||
Scope string `json:"scope" validate:"required,oneof=auth_session user all"`
|
||||
UserID string `json:"userId"`
|
||||
AuthSessionID string `json:"authSessionId"`
|
||||
}
|
||||
+27
-10
@@ -1,5 +1,12 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Alert struct {
|
||||
BaseModel
|
||||
|
||||
@@ -18,10 +25,11 @@ type Alert struct {
|
||||
|
||||
type AlertTask struct {
|
||||
BaseModel
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Quota string `gorm:"type:varchar(64)" json:"quota"`
|
||||
QuotaType string `gorm:"type:varchar(64)" json:"quotaType"`
|
||||
Method string `gorm:"type:varchar(128);not null;default:'sms'" json:"method"`
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Quota string `gorm:"type:varchar(64)" json:"quota"`
|
||||
QuotaType string `gorm:"type:varchar(64)" json:"quotaType"`
|
||||
Method string `gorm:"type:varchar(128);not null;default:'sms'" json:"method"`
|
||||
DeliveryLogID *uint `gorm:"uniqueIndex" json:"-"`
|
||||
}
|
||||
|
||||
type AlertLog struct {
|
||||
@@ -41,12 +49,21 @@ type AlertLog struct {
|
||||
|
||||
type AlertConfig struct {
|
||||
BaseModel
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Title string `gorm:"type:varchar(64);not null" json:"title"`
|
||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||
Config string `gorm:"type:varchar(256);not null" json:"config"`
|
||||
CreateUser string `gorm:"type:varchar(256)" json:"createUser"`
|
||||
UpdateUser string `gorm:"type:varchar(256)" json:"updateUser"`
|
||||
UID string `gorm:"type:varchar(64);not null;uniqueIndex" json:"uid"`
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Title string `gorm:"type:varchar(64);not null" json:"title"`
|
||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||
Config string `gorm:"type:text;not null" json:"config"`
|
||||
SecretConfig string `gorm:"type:text;not null;default:''" json:"-"`
|
||||
CreateUser string `gorm:"type:varchar(256)" json:"createUser"`
|
||||
UpdateUser string `gorm:"type:varchar(256)" json:"updateUser"`
|
||||
}
|
||||
|
||||
func (a *AlertConfig) BeforeCreate(_ *gorm.DB) error {
|
||||
if strings.TrimSpace(a.UID) == "" {
|
||||
a.UID = uuid.NewString()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type LoginLog struct {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package model
|
||||
|
||||
type Firewall struct {
|
||||
BaseModel
|
||||
|
||||
Type string `json:"type"`
|
||||
Port string `json:"port"` // Deprecated
|
||||
Address string `json:"address"` // Deprecated
|
||||
|
||||
Chain string `json:"chain"`
|
||||
Protocol string `json:"protocol"`
|
||||
SrcIP string `json:"srcIP"`
|
||||
SrcPort string `json:"srcPort"`
|
||||
DstIP string `json:"dstIP"`
|
||||
DstPort string `json:"dstPort"`
|
||||
Strategy string `gorm:"not null" json:"strategy"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type MonitorBase struct {
|
||||
BaseModel
|
||||
Cpu float64 `json:"cpu"`
|
||||
@@ -33,14 +35,78 @@ type MonitorNetwork struct {
|
||||
}
|
||||
|
||||
type MonitorGPU struct {
|
||||
MemoryUtil *float64 `json:"memoryUtil"`
|
||||
MemoryActivity *float64 `json:"memoryActivity"`
|
||||
EncoderUtil *float64 `json:"encoderUtil"`
|
||||
DecoderUtil *float64 `json:"decoderUtil"`
|
||||
JPEGUtil *float64 `json:"jpegUtil"`
|
||||
OFAUtil *float64 `json:"ofaUtil"`
|
||||
MediaUtil *float64 `json:"mediaUtil"`
|
||||
ComputeUtil *float64 `json:"computeUtil"`
|
||||
CopyUtil *float64 `json:"copyUtil"`
|
||||
HotspotTemperature *float64 `json:"hotspotTemperature"`
|
||||
FanRPM *float64 `json:"fanRPM"`
|
||||
AICPUUtil *float64 `json:"aiCPUUtil"`
|
||||
CtrlCPUUtil *float64 `json:"ctrlCPUUtil"`
|
||||
DDRUsed *float64 `json:"ddrUsed"`
|
||||
DDRTotal *float64 `json:"ddrTotal"`
|
||||
HBMUsed *float64 `json:"hbmUsed"`
|
||||
HBMTotal *float64 `json:"hbmTotal"`
|
||||
DDRBandwidth *float64 `json:"ddrBandwidth"`
|
||||
HBMBandwidth *float64 `json:"hbmBandwidth"`
|
||||
MemoryBandwidth *float64 `json:"memoryBandwidth"`
|
||||
MediaFrequency *float64 `json:"mediaFrequency"`
|
||||
HugepagesUsed *float64 `json:"hugepagesUsed"`
|
||||
HugepagesTotal *float64 `json:"hugepagesTotal"`
|
||||
|
||||
MemoryTemperature *float64 `json:"memoryTemperature"`
|
||||
DeviceID string `json:"deviceID"`
|
||||
DeviceType string `json:"deviceType"`
|
||||
ProcessStatus string `json:"processStatus"`
|
||||
Frequency *float64 `json:"frequency"`
|
||||
MemoryFrequency *float64 `json:"memoryFrequency"`
|
||||
IntervalSeconds int `json:"intervalSeconds"`
|
||||
BaseModel
|
||||
ProductName string `json:"productName"`
|
||||
GPUUtil float64 `json:"gpuUtil"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
PowerDraw float64 `json:"powerDraw"`
|
||||
MaxPowerLimit float64 `json:"maxPowerLimit"`
|
||||
MemUsed float64 `json:"memUsed"`
|
||||
MemTotal float64 `json:"memTotal"`
|
||||
FanSpeed int `json:"fanSpeed"`
|
||||
Processes string `json:"processes"`
|
||||
ProductName string `json:"productName"`
|
||||
GPUUtil *float64 `json:"gpuUtil"`
|
||||
Temperature *float64 `json:"temperature"`
|
||||
PowerDraw *float64 `json:"powerDraw"`
|
||||
MaxPowerLimit *float64 `json:"maxPowerLimit"`
|
||||
MemUsed *float64 `json:"memUsed"`
|
||||
MemTotal *float64 `json:"memTotal"`
|
||||
FanSpeed *float64 `json:"fanSpeed"`
|
||||
Processes string `json:"processes"`
|
||||
}
|
||||
|
||||
type MonitorVLLM struct {
|
||||
ID uint `json:"-" gorm:"primarykey;autoIncrement"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
AppInstallID uint `json:"appInstallID"`
|
||||
Status string `json:"status"`
|
||||
RawMetrics string `json:"-"`
|
||||
HistogramDeltas string `json:"-"`
|
||||
|
||||
Running *float64 `json:"running"`
|
||||
Waiting *float64 `json:"waiting"`
|
||||
CacheUsage *float64 `json:"cacheUsage"`
|
||||
PromptThroughput *float64 `json:"promptThroughput"`
|
||||
GenerationThroughput *float64 `json:"generationThroughput"`
|
||||
RequestThroughput *float64 `json:"requestThroughput"`
|
||||
TimeToFirstToken *float64 `json:"timeToFirstToken"`
|
||||
TimePerOutputToken *float64 `json:"timePerOutputToken"`
|
||||
RequestLatency *float64 `json:"requestLatency"`
|
||||
PrefillTime *float64 `json:"prefillTime"`
|
||||
DecodeTime *float64 `json:"decodeTime"`
|
||||
TimeToFirstTokenP50 *float64 `json:"timeToFirstTokenP50"`
|
||||
TimeToFirstTokenP90 *float64 `json:"timeToFirstTokenP90"`
|
||||
TimeToFirstTokenP95 *float64 `json:"timeToFirstTokenP95"`
|
||||
TimeToFirstTokenP99 *float64 `json:"timeToFirstTokenP99"`
|
||||
TimePerOutputTokenP50 *float64 `json:"timePerOutputTokenP50"`
|
||||
TimePerOutputTokenP90 *float64 `json:"timePerOutputTokenP90"`
|
||||
TimePerOutputTokenP95 *float64 `json:"timePerOutputTokenP95"`
|
||||
TimePerOutputTokenP99 *float64 `json:"timePerOutputTokenP99"`
|
||||
RequestLatencyP50 *float64 `json:"requestLatencyP50"`
|
||||
RequestLatencyP90 *float64 `json:"requestLatencyP90"`
|
||||
RequestLatencyP95 *float64 `json:"requestLatencyP95"`
|
||||
RequestLatencyP99 *float64 `json:"requestLatencyP99"`
|
||||
}
|
||||
|
||||
@@ -40,12 +40,21 @@ type Meta struct {
|
||||
var catalog = map[string]Meta{
|
||||
"custom": {
|
||||
Key: "custom", DisplayName: "Custom", Sort: 10, DefaultAPIType: "openai-completions", EnvKey: "CUSTOM_API_KEY",
|
||||
APIConfigs: editableAPIConfigs(true, "openai-completions", "openai-responses", "anthropic-messages", "openai-images", "openai-embeddings"),
|
||||
APIConfigs: editableAPIConfigs(true, "openai-completions", "openai-responses", "anthropic-messages", "openai-images", "dashscope-images", "openai-embeddings"),
|
||||
},
|
||||
"ollama": {
|
||||
Key: "ollama", DisplayName: "Ollama", Sort: 15, DefaultAPIType: "openai-responses",
|
||||
APIConfigs: editableAPIConfigs(false, "openai-responses", "openai-completions", "openai-embeddings"),
|
||||
},
|
||||
// llmman (https://github.com/llmmanorg/llmman): local runner with Ollama/OpenAI-compatible routes on 127.0.0.1:17434.
|
||||
"llmman": {
|
||||
Key: "llmman", DisplayName: "llmman", Sort: 16, DefaultAPIType: "openai-responses",
|
||||
APIConfigs: []APIConfig{
|
||||
{APIType: "openai-responses", BaseURL: "http://127.0.0.1:17434/v1", EditableBaseURL: true},
|
||||
{APIType: "openai-completions", BaseURL: "http://127.0.0.1:17434/v1", EditableBaseURL: true},
|
||||
{APIType: "openai-embeddings", BaseURL: "http://127.0.0.1:17434/v1", EditableBaseURL: true},
|
||||
},
|
||||
},
|
||||
"vllm": {
|
||||
Key: "vllm", DisplayName: "vLLM", Sort: 20, DefaultAPIType: "openai-completions", EnvKey: "VLLM_API_KEY",
|
||||
APIConfigs: editableAPIConfigs(false, "openai-completions", "openai-responses", "anthropic-messages", "openai-images", "openai-embeddings"),
|
||||
@@ -453,6 +462,7 @@ var legacyModelPrefixes = map[string][]string{
|
||||
"custom": {"custom"},
|
||||
"vllm": {"custom"},
|
||||
"ollama": {"ollama"},
|
||||
"llmman": {"llmman"},
|
||||
"deepseek": {"deepseek"},
|
||||
"bailian-coding-plan": {"bailian-coding-plan"},
|
||||
"ark-coding-plan": {"ark-coding-plan"},
|
||||
|
||||
@@ -43,8 +43,8 @@ func BuildOpenClawProviderPatch(provider, modelName, apiType, authMode, baseURL,
|
||||
providerKey = "moonshot"
|
||||
resolvedAPIType = "openai-completions"
|
||||
usesBearer = false
|
||||
case "ollama":
|
||||
apiKey = "ollama"
|
||||
case "ollama", "llmman":
|
||||
apiKey = provider
|
||||
usesBearer = false
|
||||
case "openai", "openrouter", "anthropic":
|
||||
preserveQualifiedModel = strings.Contains(modelName, "/")
|
||||
|
||||
@@ -27,11 +27,14 @@ type verifyErrorResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
const defaultVerifyTimeout = 30 * time.Second
|
||||
const (
|
||||
defaultVerifyTimeout = 30 * time.Second
|
||||
defaultVerifyMaxTokens = 16
|
||||
)
|
||||
|
||||
func SkipVerification(provider string) bool {
|
||||
switch provider {
|
||||
case "vllm", "ollama", "kimi-coding":
|
||||
case "vllm", "ollama", "llmman", "kimi-coding":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -116,20 +119,20 @@ func BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model stri
|
||||
}
|
||||
headers["anthropic-version"] = "2023-06-01"
|
||||
request.Body = mustJSON(map[string]interface{}{
|
||||
"model": model, "max_tokens": 1, "stream": false,
|
||||
"model": model, "max_tokens": defaultVerifyMaxTokens, "stream": false,
|
||||
"messages": []map[string]interface{}{{"role": "user", "content": []map[string]string{{"type": "text", "text": "test"}}}},
|
||||
})
|
||||
case "openai-responses":
|
||||
request.URL = baseURL + "/responses"
|
||||
headers["Authorization"] = "Bearer " + apiKey
|
||||
request.Body = mustJSON(map[string]interface{}{"model": model, "input": "test", "max_output_tokens": 1, "stream": false})
|
||||
request.Body = mustJSON(map[string]interface{}{"model": model, "input": "test", "max_output_tokens": defaultVerifyMaxTokens, "stream": false})
|
||||
default:
|
||||
request.URL = baseURL + "/chat/completions"
|
||||
if provider != "ollama" || strings.TrimSpace(apiKey) != "" {
|
||||
if (provider != "ollama" && provider != "llmman") || strings.TrimSpace(apiKey) != "" {
|
||||
headers["Authorization"] = "Bearer " + apiKey
|
||||
}
|
||||
request.Body = mustJSON(map[string]interface{}{
|
||||
"model": model, "messages": []map[string]string{{"role": "user", "content": "test"}}, "max_tokens": 1, "stream": false,
|
||||
"model": model, "messages": []map[string]string{{"role": "user", "content": "test"}}, "max_tokens": defaultVerifyMaxTokens, "stream": false,
|
||||
})
|
||||
}
|
||||
return request
|
||||
|
||||
+217
-9
@@ -1,20 +1,30 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/genproto/googleapis/type/date"
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
"time"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type AlertRepo struct{}
|
||||
|
||||
var (
|
||||
ErrAlertConfigRevisionConflict = errors.New("alert config revision conflict")
|
||||
ErrAlertConfigRevisionRequired = errors.New("alert config revision is required")
|
||||
)
|
||||
|
||||
type IAlertRepo interface {
|
||||
WithByType(alertType string) DBOption
|
||||
WithByStatusIn(status []string) DBOption
|
||||
@@ -24,6 +34,7 @@ type IAlertRepo interface {
|
||||
WithByCreateAt(date *date.Date) DBOption
|
||||
WithByLicenseId(licenseId string) DBOption
|
||||
WithByRecordId(recordId uint) DBOption
|
||||
WithByDeliveryLogID(logID uint) DBOption
|
||||
WithByAlertMethodContainsConfigID(id uint) DBOption
|
||||
WithByMethodConfigIDs(ids []uint) DBOption
|
||||
|
||||
@@ -45,6 +56,8 @@ type IAlertRepo interface {
|
||||
CleanAlertLogs() error
|
||||
|
||||
CreateAlertTask(alertTaskBase *model.AlertTask) error
|
||||
CreatePendingAlertTask(logID, alertID uint, alertTask *model.AlertTask) (bool, error)
|
||||
FinalizePendingAlertTask(logID uint, succeeded bool, message string, fallback *model.AlertTask) (bool, error)
|
||||
DeleteAlertTask(opts ...DBOption) error
|
||||
GetAlertTask(opts ...DBOption) (model.AlertTask, error)
|
||||
LoadTaskCount(alertType string, project string, method string) (uint, uint, error)
|
||||
@@ -55,6 +68,7 @@ type IAlertRepo interface {
|
||||
GetConfigById(id uint) (model.AlertConfig, error)
|
||||
AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error)
|
||||
UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error
|
||||
UpdateAlertConfigWithRevision(maps map[string]interface{}, revision *time.Time, opts ...DBOption) error
|
||||
CreateAlertConfig(config *model.AlertConfig) error
|
||||
DeleteAlertConfig(opts ...DBOption) error
|
||||
|
||||
@@ -223,13 +237,78 @@ func (a *AlertRepo) DeleteLog(opts ...DBOption) error {
|
||||
}
|
||||
|
||||
func (a *AlertRepo) CleanAlertLogs() error {
|
||||
return global.AlertDB.Where("1 = 1").Delete(&model.AlertLog{}).Error
|
||||
return global.AlertDB.Where("status <> ?", constant.AlertPushing).Delete(&model.AlertLog{}).Error
|
||||
}
|
||||
|
||||
func (a *AlertRepo) CreateAlertTask(alertTaskBase *model.AlertTask) error {
|
||||
return global.AlertDB.Model(&model.AlertTask{}).Create(&alertTaskBase).Error
|
||||
}
|
||||
|
||||
func (a *AlertRepo) CreatePendingAlertTask(logID, alertID uint, alertTask *model.AlertTask) (bool, error) {
|
||||
if alertTask == nil {
|
||||
return false, fmt.Errorf("pending alert task is required")
|
||||
}
|
||||
created := false
|
||||
err := global.AlertDB.Transaction(func(tx *gorm.DB) error {
|
||||
var log model.AlertLog
|
||||
if err := tx.Where("id = ? AND status = ?", logID, constant.AlertPushing).First(&log).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if log.AlertId != alertID || log.Type != alertTask.Type || log.Method != alertTask.Method {
|
||||
return fmt.Errorf("pending alert task does not match delivery log %d", logID)
|
||||
}
|
||||
alertTask.DeliveryLogID = &logID
|
||||
result := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "delivery_log_id"}},
|
||||
DoNothing: true,
|
||||
}).Create(alertTask)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
created = result.RowsAffected > 0
|
||||
return nil
|
||||
})
|
||||
return created, err
|
||||
}
|
||||
|
||||
func (a *AlertRepo) FinalizePendingAlertTask(logID uint, succeeded bool, message string, fallback *model.AlertTask) (bool, error) {
|
||||
finalized := false
|
||||
err := global.AlertDB.Transaction(func(tx *gorm.DB) error {
|
||||
status := constant.AlertError
|
||||
if succeeded {
|
||||
status = constant.AlertSuccess
|
||||
message = ""
|
||||
}
|
||||
result := tx.Model(&model.AlertLog{}).
|
||||
Where("id = ? AND status = ?", logID, constant.AlertPushing).
|
||||
Updates(map[string]interface{}{"status": status, "message": message})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return nil
|
||||
}
|
||||
finalized = true
|
||||
if !succeeded {
|
||||
return tx.Where("delivery_log_id = ?", logID).Delete(&model.AlertTask{}).Error
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := tx.Model(&model.AlertTask{}).Where("delivery_log_id = ?", logID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
if fallback == nil {
|
||||
return fmt.Errorf("pending alert task metadata is unavailable for delivery log %d", logID)
|
||||
}
|
||||
fallback.DeliveryLogID = &logID
|
||||
return tx.Create(fallback).Error
|
||||
})
|
||||
return finalized, err
|
||||
}
|
||||
|
||||
func (a *AlertRepo) DeleteAlertTask(opts ...DBOption) error {
|
||||
db, _ := getAlertDB(opts...)
|
||||
return db.Delete(&model.AlertTask{}).Error
|
||||
@@ -310,7 +389,23 @@ func (a *AlertRepo) UpdateAlertConfig(maps map[string]interface{}, opts ...DBOpt
|
||||
return db.Model(&model.AlertConfig{}).Updates(maps).Error
|
||||
}
|
||||
|
||||
func (a *AlertRepo) UpdateAlertConfigWithRevision(maps map[string]interface{}, revision *time.Time, opts ...DBOption) error {
|
||||
if revision == nil {
|
||||
return a.UpdateAlertConfig(maps, opts...)
|
||||
}
|
||||
db, _ := getAlertDB(opts...)
|
||||
result := db.Model(&model.AlertConfig{}).Where("updated_at = ?", *revision).Updates(maps)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return ErrAlertConfigRevisionConflict
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AlertRepo) CreateAlertConfig(config *model.AlertConfig) error {
|
||||
ensureAlertConfigUID(config)
|
||||
return global.AlertDB.Model(&model.AlertConfig{}).Create(config).Error
|
||||
}
|
||||
|
||||
@@ -338,6 +433,12 @@ func (a *AlertRepo) WithByTypeNotIn(types []string) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AlertRepo) WithByDeliveryLogID(logID uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("delivery_log_id = ?", logID)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AlertRepo) PageAlertConfig(page, size int, opts ...DBOption) (int64, []model.AlertConfig, error) {
|
||||
var configs []model.AlertConfig
|
||||
db := global.AlertDB.Model(&model.AlertConfig{})
|
||||
@@ -378,26 +479,44 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
oldConfigMap := make(map[string]uint)
|
||||
oldConfigMap := make(map[string]model.AlertConfig)
|
||||
oldConfigByUID := make(map[string]model.AlertConfig)
|
||||
oldConfigByType := make(map[string][]model.AlertConfig)
|
||||
oldConfigByKey := make(map[string][]model.AlertConfig)
|
||||
consumedConfigIDs := make(map[uint]struct{})
|
||||
for _, item := range oldConfigs {
|
||||
if strings.TrimSpace(item.UID) != "" {
|
||||
oldConfigByUID[item.UID] = item
|
||||
}
|
||||
if singletonTypes[item.Type] {
|
||||
oldConfigMap[item.Type] = item.ID
|
||||
oldConfigMap[item.Type] = item
|
||||
continue
|
||||
}
|
||||
oldConfigByType[item.Type] = append(oldConfigByType[item.Type], item)
|
||||
oldConfigByKey[alertConfigSyncKey(item)] = append(oldConfigByKey[alertConfigSyncKey(item)], item)
|
||||
}
|
||||
for _, item := range data {
|
||||
if uid := strings.TrimSpace(item.UID); uid != "" {
|
||||
if matched, ok := oldConfigByUID[uid]; ok && matched.Type != item.Type {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("alert config UID %q belongs to type %q, not %q", uid, matched.Type, item.Type)
|
||||
}
|
||||
}
|
||||
if singletonTypes[item.Type] {
|
||||
if val, ok := oldConfigMap[item.Type]; ok {
|
||||
item.ID = val
|
||||
if matched, ok := oldConfigMap[item.Type]; ok {
|
||||
if err := inheritAlertConfigSyncState(&item, matched); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
delete(oldConfigMap, item.Type)
|
||||
consumedConfigIDs[item.ID] = struct{}{}
|
||||
} else {
|
||||
item.ID = 0
|
||||
ensureAlertConfigUID(&item)
|
||||
if err := validateAlertConfigSyncSecret(&item); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
if item.ID == 0 {
|
||||
if err := tx.Create(&item).Error; err != nil {
|
||||
@@ -411,9 +530,31 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.TrimSpace(item.UID) != "" {
|
||||
if matched, ok := oldConfigByUID[item.UID]; ok {
|
||||
delete(oldConfigByUID, item.UID)
|
||||
if err := inheritAlertConfigSyncState(&item, matched); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
consumedConfigIDs[item.ID] = struct{}{}
|
||||
if err := tx.Save(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
deleteAlertConfigByID(oldConfigByType, matched.ID)
|
||||
deleteAlertConfigByID(oldConfigByKey, matched.ID)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
key := alertConfigSyncKey(item)
|
||||
if matched, ok := popAlertConfigByKey(oldConfigByKey, key); ok {
|
||||
item.ID = matched.ID
|
||||
delete(oldConfigByUID, matched.UID)
|
||||
if err := inheritAlertConfigSyncState(&item, matched); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
consumedConfigIDs[item.ID] = struct{}{}
|
||||
if err := tx.Save(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
@@ -424,7 +565,12 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
||||
}
|
||||
|
||||
if matched, ok := popUnusedAlertConfigByType(oldConfigByType, usedConfigIDs, item.Type); ok {
|
||||
item.ID = matched.ID
|
||||
delete(oldConfigByUID, matched.UID)
|
||||
deleteAlertConfigByID(oldConfigByKey, matched.ID)
|
||||
if err := inheritAlertConfigSyncState(&item, matched); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
consumedConfigIDs[item.ID] = struct{}{}
|
||||
if err := tx.Save(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
@@ -434,6 +580,11 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
||||
}
|
||||
|
||||
item.ID = 0
|
||||
ensureAlertConfigUID(&item)
|
||||
if err := validateAlertConfigSyncSecret(&item); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
@@ -458,6 +609,63 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureAlertConfigUID(config *model.AlertConfig) {
|
||||
if config != nil && strings.TrimSpace(config.UID) == "" {
|
||||
config.UID = uuid.NewString()
|
||||
}
|
||||
}
|
||||
|
||||
func inheritAlertConfigSyncState(incoming *model.AlertConfig, existing model.AlertConfig) error {
|
||||
if incoming.Type != existing.Type {
|
||||
return fmt.Errorf("alert config UID %q belongs to type %q, not %q", incoming.UID, existing.Type, incoming.Type)
|
||||
}
|
||||
preserveExistingCustom := incoming.Type == constant.Custom &&
|
||||
existing.Status == constant.AlertDisable &&
|
||||
incoming.Title == existing.Title &&
|
||||
incoming.Status == existing.Status &&
|
||||
incoming.Config == existing.Config &&
|
||||
(incoming.SecretConfig == "" || incoming.SecretConfig == existing.SecretConfig)
|
||||
incoming.ID = existing.ID
|
||||
if strings.TrimSpace(incoming.UID) == "" {
|
||||
incoming.UID = existing.UID
|
||||
}
|
||||
if incoming.Type == constant.Custom && incoming.SecretConfig == "" {
|
||||
incoming.SecretConfig = existing.SecretConfig
|
||||
}
|
||||
if preserveExistingCustom {
|
||||
return nil
|
||||
}
|
||||
return validateAlertConfigSyncSecret(incoming)
|
||||
}
|
||||
|
||||
func validateAlertConfigSyncSecret(incoming *model.AlertConfig) error {
|
||||
if incoming.Type != constant.Custom {
|
||||
incoming.SecretConfig = ""
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(incoming.SecretConfig) == "" {
|
||||
return fmt.Errorf("custom webhook sync secret is missing")
|
||||
}
|
||||
var version struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(incoming.Config), &version); err != nil || version.SchemaVersion != 1 {
|
||||
return fmt.Errorf("custom webhook sync config must use schemaVersion 1")
|
||||
}
|
||||
secret := incoming.SecretConfig
|
||||
for _, prefix := range []string{"core:v1:", "agent:v1:"} {
|
||||
if !strings.HasPrefix(secret, prefix) {
|
||||
continue
|
||||
}
|
||||
ciphertext, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(secret, prefix))
|
||||
if err != nil || len(ciphertext) < 32 || len(ciphertext)%16 != 0 {
|
||||
return fmt.Errorf("custom webhook sync secret envelope is invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("custom webhook sync secret must use a versioned envelope")
|
||||
}
|
||||
|
||||
func loadUsedAlertConfigIDs(tx *gorm.DB) (map[uint]struct{}, error) {
|
||||
var alerts []model.Alert
|
||||
if err := tx.Select("method").Find(&alerts).Error; err != nil {
|
||||
|
||||
@@ -22,11 +22,6 @@ type IHostRepo interface {
|
||||
WithByPort(port uint) DBOption
|
||||
WithByUser(user string) DBOption
|
||||
|
||||
GetFirewallRecord(opts ...DBOption) (model.Firewall, error)
|
||||
ListFirewallRecord(opts ...DBOption) ([]model.Firewall, error)
|
||||
SaveFirewallRecord(firewall *model.Firewall) error
|
||||
DeleteFirewallRecordByID(id uint) error
|
||||
|
||||
SyncCert(data []model.RootCert) error
|
||||
GetCert(opts ...DBOption) (model.RootCert, error)
|
||||
PageCert(limit, offset int, opts ...DBOption) (int64, []model.RootCert, error)
|
||||
@@ -34,8 +29,6 @@ type IHostRepo interface {
|
||||
SaveCert(cert *model.RootCert) error
|
||||
UpdateCert(id uint, vars map[string]interface{}) error
|
||||
DeleteCert(opts ...DBOption) error
|
||||
|
||||
WithByChain(chain string) DBOption
|
||||
}
|
||||
|
||||
func NewIHostRepo() IHostRepo {
|
||||
@@ -116,65 +109,6 @@ func (h *HostRepo) Delete(opts ...DBOption) error {
|
||||
return db.Delete(&model.Host{}).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) GetFirewallRecord(opts ...DBOption) (model.Firewall, error) {
|
||||
var firewall model.Firewall
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&firewall).Error
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
func (h *HostRepo) ListFirewallRecord(opts ...DBOption) ([]model.Firewall, error) {
|
||||
var firewalls []model.Firewall
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
if err := global.DB.Find(&firewalls).Error; err != nil {
|
||||
return firewalls, nil
|
||||
}
|
||||
return firewalls, nil
|
||||
}
|
||||
|
||||
func (h *HostRepo) SaveFirewallRecord(firewall *model.Firewall) error {
|
||||
if firewall.ID != 0 {
|
||||
return global.DB.Save(firewall).Error
|
||||
}
|
||||
var data model.Firewall
|
||||
switch firewall.Type {
|
||||
case "port":
|
||||
_ = global.DB.Where("type = ? AND dst_port = ? AND protocol = ? AND src_ip = ? AND strategy = ?", "port",
|
||||
firewall.DstPort,
|
||||
firewall.Protocol,
|
||||
firewall.SrcIP,
|
||||
firewall.Strategy,
|
||||
).First(&data).Error
|
||||
case "ip":
|
||||
_ = global.DB.Where("type = ? AND src_ip = ? AND strategy = ?", "address", firewall.SrcIP, firewall.Strategy).First(&data)
|
||||
default:
|
||||
_ = global.DB.Where("type = ? AND chain = ? AND src_port = ? AND dst_port = ? AND protocol = ? AND src_ip = ? AND dst_ip = ? AND strategy = ?",
|
||||
firewall.Type,
|
||||
firewall.Chain,
|
||||
firewall.SrcPort,
|
||||
firewall.DstPort,
|
||||
firewall.Protocol,
|
||||
firewall.SrcIP,
|
||||
firewall.DstIP,
|
||||
firewall.Strategy,
|
||||
).First(&data).Error
|
||||
}
|
||||
if data.ID != 0 {
|
||||
firewall.ID = data.ID
|
||||
}
|
||||
return global.DB.Save(firewall).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) DeleteFirewallRecordByID(id uint) error {
|
||||
return global.DB.Where("id = ?", id).Delete(&model.Firewall{}).Error
|
||||
}
|
||||
|
||||
func (u *HostRepo) GetCert(opts ...DBOption) (model.RootCert, error) {
|
||||
var cert model.RootCert
|
||||
db := global.DB
|
||||
@@ -253,9 +187,3 @@ func (u *HostRepo) SyncCert(data []model.RootCert) error {
|
||||
tx.Commit()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *HostRepo) WithByChain(chain string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("chain = ?", chain)
|
||||
}
|
||||
}
|
||||
|
||||
+103
-13
@@ -1,6 +1,8 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
@@ -10,11 +12,25 @@ import (
|
||||
|
||||
type MonitorRepo struct{}
|
||||
|
||||
type GPUHistoryPoint struct {
|
||||
model.MonitorGPU
|
||||
Bucket int64
|
||||
PowerPercent *float64
|
||||
MemoryPercent *float64
|
||||
ProcessCount *float64
|
||||
}
|
||||
|
||||
type IMonitorRepo interface {
|
||||
CleanHost() error
|
||||
CleanGPU() error
|
||||
GetBase(opts ...DBOption) ([]model.MonitorBase, error)
|
||||
GetGPU(opts ...DBOption) ([]model.MonitorGPU, error)
|
||||
CountGPU(opts ...DBOption) (int64, error)
|
||||
GetGPUHistory(start time.Time, bucketSeconds int64, aggregation string, opts ...DBOption) ([]GPUHistoryPoint, error)
|
||||
GetGPUDevices() ([]model.MonitorGPU, error)
|
||||
GetIO(opts ...DBOption) ([]model.MonitorIO, error)
|
||||
GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error)
|
||||
GetIONames() ([]string, error)
|
||||
GetNetworkNames() ([]string, error)
|
||||
|
||||
CreateMonitorBase(model model.MonitorBase) error
|
||||
BatchCreateMonitorGPU(list []model.MonitorGPU) error
|
||||
@@ -25,13 +41,26 @@ type IMonitorRepo interface {
|
||||
DelMonitorIO(timeForDelete time.Time) error
|
||||
DelMonitorNet(timeForDelete time.Time) error
|
||||
|
||||
WithByProductName(name string) DBOption
|
||||
WithByGPUDevice(deviceID, name string, legacy bool) DBOption
|
||||
}
|
||||
|
||||
func NewIMonitorRepo() IMonitorRepo {
|
||||
return &MonitorRepo{}
|
||||
}
|
||||
|
||||
func (s *MonitorRepo) CleanHost() error {
|
||||
for _, item := range []interface{}{&model.MonitorBase{}, &model.MonitorIO{}, &model.MonitorNetwork{}} {
|
||||
if err := global.MonitorDB.Where("1 = 1").Delete(item).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MonitorRepo) CleanGPU() error {
|
||||
return global.GPUMonitorDB.Where("1 = 1").Delete(&model.MonitorGPU{}).Error
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) GetBase(opts ...DBOption) ([]model.MonitorBase, error) {
|
||||
var data []model.MonitorBase
|
||||
db := global.MonitorDB
|
||||
@@ -59,14 +88,16 @@ func (u *MonitorRepo) GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, erro
|
||||
err := db.Find(&data).Error
|
||||
return data, err
|
||||
}
|
||||
func (u *MonitorRepo) GetGPU(opts ...DBOption) ([]model.MonitorGPU, error) {
|
||||
var data []model.MonitorGPU
|
||||
db := global.GPUMonitorDB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&data).Error
|
||||
return data, err
|
||||
func (u *MonitorRepo) GetIONames() ([]string, error) {
|
||||
var names []string
|
||||
err := global.MonitorDB.Model(&model.MonitorIO{}).Distinct().Pluck("name", &names).Error
|
||||
return names, err
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) GetNetworkNames() ([]string, error) {
|
||||
var names []string
|
||||
err := global.MonitorDB.Model(&model.MonitorNetwork{}).Distinct().Pluck("name", &names).Error
|
||||
return names, err
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) CreateMonitorBase(model model.MonitorBase) error {
|
||||
@@ -97,8 +128,67 @@ func (s *MonitorRepo) DelMonitorGPU(timeForDelete time.Time) error {
|
||||
return global.GPUMonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorGPU{}).Error
|
||||
}
|
||||
|
||||
func (s *MonitorRepo) WithByProductName(name string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("product_name = ?", name)
|
||||
func (u *MonitorRepo) GetGPUDevices() ([]model.MonitorGPU, error) {
|
||||
var data []model.MonitorGPU
|
||||
err := global.GPUMonitorDB.Model(&model.MonitorGPU{}).Select("device_id, product_name, device_type").Group("device_id, product_name, device_type").Order("product_name, device_id").Find(&data).Error
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) WithByGPUDevice(deviceID, name string, legacy bool) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
if deviceID != "" {
|
||||
return db.Where("device_id = ?", deviceID)
|
||||
}
|
||||
db = db.Where("product_name = ?", name)
|
||||
if legacy {
|
||||
db = db.Where("device_id IS NULL OR device_id = ''")
|
||||
}
|
||||
return db
|
||||
}
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) CountGPU(opts ...DBOption) (int64, error) {
|
||||
db := global.GPUMonitorDB.Model(&model.MonitorGPU{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
var count int64
|
||||
err := db.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) GetGPUHistory(start time.Time, bucketSeconds int64, aggregation string, opts ...DBOption) ([]GPUHistoryPoint, error) {
|
||||
db := global.GPUMonitorDB.Model(&model.MonitorGPU{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
expressions := []string{
|
||||
"CASE WHEN max_power_limit > 0 THEN 100.0 * power_draw / max_power_limit END",
|
||||
"CASE WHEN mem_total > 0 AND mem_used IS NOT NULL THEN 100.0 * mem_used / mem_total ELSE memory_util END",
|
||||
"CASE WHEN (process_status = 'ok' OR process_status IS NULL OR process_status = '') AND json_valid(processes) THEN CASE WHEN json_type(processes) = 'array' THEN json_array_length(processes) END END",
|
||||
}
|
||||
aliases := []string{"power_percent", "memory_percent", "process_count"}
|
||||
columns := []string{"*"}
|
||||
if bucketSeconds > 0 {
|
||||
operation := "AVG"
|
||||
if aggregation == "max" {
|
||||
operation = "MAX"
|
||||
}
|
||||
columns = []string{fmt.Sprintf("(CAST(strftime('%%s', created_at) AS INTEGER) - %d) / %d AS bucket", start.Unix(), bucketSeconds)}
|
||||
for _, column := range []string{"memory_activity", "encoder_util", "decoder_util", "jpeg_util", "ofa_util", "media_util", "compute_util", "copy_util", "hotspot_temperature", "fan_rpm", "ai_cpu_util", "ctrl_cpu_util", "ddr_used", "ddr_total", "hbm_used", "hbm_total", "ddr_bandwidth", "hbm_bandwidth", "memory_bandwidth", "media_frequency", "hugepages_used", "hugepages_total", "gpu_util", "temperature", "memory_temperature", "power_draw", "max_power_limit", "mem_used", "mem_total", "frequency", "memory_frequency", "fan_speed"} {
|
||||
columns = append(columns, operation+"("+column+") AS "+column)
|
||||
}
|
||||
for i := range expressions {
|
||||
expressions[i] = operation + "(" + expressions[i] + ")"
|
||||
}
|
||||
db = db.Group("bucket").Order("bucket ASC")
|
||||
} else {
|
||||
db = db.Order("created_at ASC, id ASC")
|
||||
}
|
||||
for i, expression := range expressions {
|
||||
columns = append(columns, expression+" AS "+aliases[i])
|
||||
}
|
||||
var data []GPUHistoryPoint
|
||||
err := db.Select(strings.Join(columns, ", ")).Scan(&data).Error
|
||||
return data, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type VLLMMonitorRepo struct{}
|
||||
|
||||
type VLLMHistoryPoint struct {
|
||||
model.MonitorVLLM
|
||||
Bucket int64
|
||||
HistogramSamples string
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) Create(point *model.MonitorVLLM) error {
|
||||
return global.VLLMMonitorDB.Create(point).Error
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) Latest(id uint) (model.MonitorVLLM, error) {
|
||||
var point model.MonitorVLLM
|
||||
db := global.VLLMMonitorDB.Where("app_install_id = ?", id)
|
||||
err := db.Order("created_at DESC, id DESC").First(&point).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return point, nil
|
||||
}
|
||||
return point, err
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) CleanTarget(id uint) error {
|
||||
return global.VLLMMonitorDB.Where("app_install_id = ?", id).Delete(&model.MonitorVLLM{}).Error
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) DeleteBefore(before time.Time) error {
|
||||
return global.VLLMMonitorDB.Where("created_at < ?", before).Delete(&model.MonitorVLLM{}).Error
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) Count(id uint, start, end time.Time) (int64, error) {
|
||||
var count int64
|
||||
db := global.VLLMMonitorDB.Model(&model.MonitorVLLM{}).Where("app_install_id = ? AND created_at >= ? AND created_at <= ?", id, start, end)
|
||||
err := db.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) History(id uint, start, end time.Time, seconds int64, aggregation string) ([]VLLMHistoryPoint, error) {
|
||||
db := global.VLLMMonitorDB.Model(&model.MonitorVLLM{}).Where("app_install_id = ? AND created_at >= ? AND created_at <= ?", id, start, end)
|
||||
metrics := []string{"running", "waiting", "cache_usage", "prompt_throughput", "generation_throughput", "request_throughput", "time_to_first_token", "time_per_output_token", "request_latency", "prefill_time", "decode_time", "time_to_first_token_p50", "time_to_first_token_p90", "time_to_first_token_p95", "time_to_first_token_p99", "time_per_output_token_p50", "time_per_output_token_p90", "time_per_output_token_p95", "time_per_output_token_p99", "request_latency_p50", "request_latency_p90", "request_latency_p95", "request_latency_p99"}
|
||||
var columns []string
|
||||
if seconds > 0 {
|
||||
operation := "AVG"
|
||||
if aggregation == "max" {
|
||||
operation = "MAX"
|
||||
}
|
||||
columns = []string{fmt.Sprintf("(CAST(strftime('%%s', created_at) AS INTEGER) - %d) / %d AS bucket", start.Unix(), seconds)}
|
||||
for _, column := range metrics {
|
||||
if aggregation != "max" && (strings.HasPrefix(column, "time_to_first_token_p") || strings.HasPrefix(column, "time_per_output_token_p") || strings.HasPrefix(column, "request_latency_p")) {
|
||||
continue
|
||||
}
|
||||
columns = append(columns, operation+"("+column+") AS "+column)
|
||||
}
|
||||
if aggregation != "max" {
|
||||
columns = append(columns, "json_group_array(json(NULLIF(histogram_deltas, ''))) AS histogram_samples")
|
||||
}
|
||||
db = db.Group("bucket").Order("bucket ASC")
|
||||
} else {
|
||||
columns = append([]string{"id", "created_at", "app_install_id", "status"}, metrics...)
|
||||
db = db.Order("created_at ASC, id ASC")
|
||||
}
|
||||
var points []VLLMHistoryPoint
|
||||
err := db.Select(strings.Join(columns, ", ")).Scan(&points).Error
|
||||
return points, err
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type SettingRepo struct{}
|
||||
@@ -19,12 +21,16 @@ type ISettingRepo interface {
|
||||
WithByKey(key string) DBOption
|
||||
|
||||
UpdateOrCreate(key, value string) error
|
||||
UpdateValues(map[string]string) error
|
||||
|
||||
GetDescription(opts ...DBOption) (model.CommonDescription, error)
|
||||
GetDescriptionList(opts ...DBOption) ([]model.CommonDescription, error)
|
||||
CreateDescription(data *model.CommonDescription) error
|
||||
SaveDescriptions(context.Context, []model.CommonDescription) error
|
||||
UpdateDescription(id string, val map[string]interface{}) error
|
||||
DelDescription(id string) error
|
||||
DeleteDescriptions(context.Context, string, []string, bool) (int64, error)
|
||||
WithDescriptionIDs(ids []string) DBOption
|
||||
WithByDescriptionID(id string) DBOption
|
||||
}
|
||||
|
||||
@@ -90,6 +96,25 @@ func (s *SettingRepo) UpdateOrCreate(key, value string) error {
|
||||
return global.DB.Model(&setting).UpdateColumn("value", value).Error
|
||||
}
|
||||
|
||||
func (s *SettingRepo) UpdateValues(values map[string]string) error {
|
||||
return global.DB.Transaction(func(tx *gorm.DB) error {
|
||||
for key, value := range values {
|
||||
var setting model.Setting
|
||||
err := tx.Where("key = ?", key).First(&setting).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if err := tx.Create(&model.Setting{Key: key, Value: value}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
} else if err := tx.Model(&setting).UpdateColumn("value", value).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SettingRepo) GetDescriptionList(opts ...DBOption) ([]model.CommonDescription, error) {
|
||||
var lists []model.CommonDescription
|
||||
db := global.DB.Model(&model.CommonDescription{})
|
||||
@@ -111,14 +136,42 @@ func (s *SettingRepo) GetDescription(opts ...DBOption) (model.CommonDescription,
|
||||
func (s *SettingRepo) CreateDescription(data *model.CommonDescription) error {
|
||||
return global.DB.Create(data).Error
|
||||
}
|
||||
|
||||
func (s *SettingRepo) SaveDescriptions(ctx context.Context, descriptions []model.CommonDescription) error {
|
||||
return global.DB.WithContext(ctx).Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"description"}),
|
||||
}).CreateInBatches(&descriptions, 100).Error
|
||||
}
|
||||
|
||||
func (s *SettingRepo) UpdateDescription(id string, val map[string]interface{}) error {
|
||||
return global.DB.Model(&model.CommonDescription{}).Where("id = ?", id).Updates(val).Error
|
||||
}
|
||||
func (s *SettingRepo) DelDescription(id string) error {
|
||||
return global.DB.Where("id = ?", id).Delete(&model.CommonDescription{}).Error
|
||||
}
|
||||
|
||||
func (s *SettingRepo) DeleteDescriptions(ctx context.Context, kind string, ids []string, emptyOnly bool) (int64, error) {
|
||||
var deleted int64
|
||||
for start := 0; start < len(ids); start += 500 {
|
||||
query := global.DB.WithContext(ctx).Where("type = ? AND id IN ?", kind, ids[start:min(start+500, len(ids))])
|
||||
if emptyOnly {
|
||||
query = query.Where("description = ? AND is_pinned = ?", "", false)
|
||||
}
|
||||
result := query.Delete(&model.CommonDescription{})
|
||||
deleted += result.RowsAffected
|
||||
if result.Error != nil {
|
||||
return deleted, result.Error
|
||||
}
|
||||
}
|
||||
return deleted, nil
|
||||
}
|
||||
func (s *SettingRepo) WithByDescriptionID(id string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("id = ?", id)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SettingRepo) WithDescriptionIDs(ids []string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB { return db.Where("id IN ?", ids) }
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
@@ -110,7 +111,9 @@ func (t TaskRepo) Update(ctx context.Context, task *model.Task) error {
|
||||
}
|
||||
|
||||
func (t TaskRepo) UpdateRunningTaskToFailed() error {
|
||||
return getTaskDb(t.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Updates(map[string]interface{}{"status": constant.StatusFailed, "error_msg": "1Panel restart causes failure"}).Error
|
||||
return getTaskDb(t.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Updates(map[string]interface{}{
|
||||
"status": constant.StatusFailed, "error_msg": constant.InterruptedMsg, "end_at": time.Now(),
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (t TaskRepo) CountExecutingTask() (int64, error) {
|
||||
|
||||
@@ -38,7 +38,7 @@ type IAgentService interface {
|
||||
BatchUpgrade(req dto.AgentBatchUpgradeReq) ([]dto.AgentBatchUpgradeResult, error)
|
||||
BatchInstallSkill(req dto.AgentBatchSkillInstallReq) ([]dto.AgentBatchSkillInstallResult, error)
|
||||
BatchOperate(req dto.AgentBatchOperateReq) ([]dto.AgentBatchOperateResult, error)
|
||||
Page(req dto.SearchWithPage, readOnly bool) (int64, []dto.AgentItem, error)
|
||||
Page(req dto.SearchWithPage) (int64, []dto.AgentItem, error)
|
||||
DeleteCheck(req dto.AgentIDReq) ([]dto.AppResource, error)
|
||||
Delete(req dto.AgentDeleteReq) error
|
||||
ResetToken(req dto.AgentTokenResetReq) error
|
||||
@@ -76,7 +76,7 @@ type IAgentService interface {
|
||||
CreateAccount(req dto.AgentAccountCreateReq) error
|
||||
UpdateAccount(req dto.AgentAccountUpdateReq) error
|
||||
SyncAgentsByAccount(account *model.AgentAccount) error
|
||||
PageAccounts(req dto.AgentAccountSearch, readOnly ...bool) (int64, []dto.AgentAccountInfo, error)
|
||||
PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error)
|
||||
CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error)
|
||||
GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error)
|
||||
DiscoverAccountModels(req dto.AgentAccountModelDiscoverReq) ([]dto.AgentAccountModel, error)
|
||||
@@ -745,7 +745,7 @@ func setAgentWebUIParams(params map[string]interface{}, agentType, appVersion st
|
||||
params["PANEL_APP_PORT_HTTP"] = webUIPort
|
||||
}
|
||||
|
||||
func (a AgentService) Page(req dto.SearchWithPage, readOnly bool) (int64, []dto.AgentItem, error) {
|
||||
func (a AgentService) Page(req dto.SearchWithPage) (int64, []dto.AgentItem, error) {
|
||||
var opts []repo.DBOption
|
||||
if strings.TrimSpace(req.Info) != "" {
|
||||
opts = append(opts, repo.WithByLikeName(req.Info))
|
||||
@@ -760,19 +760,11 @@ func (a AgentService) Page(req dto.SearchWithPage, readOnly bool) (int64, []dto.
|
||||
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(item.AppInstallID))
|
||||
appInstalls = append(appInstalls, appInstall)
|
||||
}
|
||||
readOnlyMode := isDemoReadOnly(readOnly)
|
||||
if !readOnlyMode {
|
||||
syncAgentAppInstalls(appInstalls)
|
||||
}
|
||||
syncAgentAppInstalls(appInstalls)
|
||||
for index, item := range list {
|
||||
appInstall := appInstalls[index]
|
||||
envMap := readInstallEnv(appInstall.Env)
|
||||
agentItem := buildAgentItem(&item, &appInstall, envMap)
|
||||
if readOnlyMode {
|
||||
agentItem.Token = ""
|
||||
agentItem.APIKey = ""
|
||||
agentItem.DashboardPassword = ""
|
||||
}
|
||||
agentItem.Upgradable = checkAgentUpgradable(appInstall)
|
||||
items = append(items, agentItem)
|
||||
}
|
||||
@@ -944,6 +936,7 @@ func (a AgentService) GetModelConfig(req dto.AgentIDReq) (*dto.AgentModelConfig,
|
||||
AccountID: agent.AccountID,
|
||||
Model: model,
|
||||
Fallbacks: extractOpenclawFallbackModelIDs(conf, account, models, model),
|
||||
Metadata: extractOpenclawModelMetadata(conf, account, models),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -975,7 +968,7 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
||||
if agent.AgentType != constant.AppOpenclaw {
|
||||
return fmt.Errorf("%s does not support", agent.AgentType)
|
||||
}
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, req.Fallbacks); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, req.Fallbacks, req.Metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1136,7 +1129,7 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AgentService) PageAccounts(req dto.AgentAccountSearch, readOnly ...bool) (int64, []dto.AgentAccountInfo, error) {
|
||||
func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error) {
|
||||
var opts []repo.DBOption
|
||||
if strings.TrimSpace(req.Provider) != "" {
|
||||
opts = append(opts, repo.WithByProvider(req.Provider))
|
||||
@@ -1157,7 +1150,7 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch, readOnly ...bool)
|
||||
items := make([]dto.AgentAccountInfo, 0, len(list))
|
||||
for _, item := range list {
|
||||
apiKey := ""
|
||||
if item.RememberAPIKey && !isDemoReadOnly(readOnly...) {
|
||||
if item.RememberAPIKey {
|
||||
apiKey = item.APIKey
|
||||
}
|
||||
items = append(items, dto.AgentAccountInfo{
|
||||
@@ -1692,7 +1685,7 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
|
||||
return err
|
||||
}
|
||||
fallbacks := extractOpenclawFallbackModelIDs(conf, account, accountModels, selectedAccountModel.ID)
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, fallbacks); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, fallbacks, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
case constant.AppHermesAgent:
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -1320,6 +1321,10 @@ func appendPluginAllow(conf map[string]interface{}, pluginID string) {
|
||||
}
|
||||
|
||||
func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID string) error {
|
||||
help, err := cmd.RunDockerExecWithStdout(time.Minute, containerName, "openclaw", "plugins", "install", "--help")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workdir := path.Join(openclawPluginPackageTmpDir, pluginID)
|
||||
defer func() {
|
||||
_ = mgr.Run("docker", "exec", containerName, "rm", "-rf", workdir)
|
||||
@@ -1341,7 +1346,19 @@ func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID
|
||||
if pkgPath == "" {
|
||||
return fmt.Errorf("openclaw plugin package not found")
|
||||
}
|
||||
return mgr.Run("docker", "exec", containerName, "openclaw", "plugins", "install", pkgPath, "--dangerously-force-unsafe-install")
|
||||
args := []string{"exec", containerName, "openclaw", "plugins", "install", pkgPath}
|
||||
// Newer CLIs require source confirmation; older releases do not support --force.
|
||||
options := strings.Fields(help)
|
||||
if slices.Contains(options, "--force") {
|
||||
args = append(args, "--force")
|
||||
} else if slices.Contains(options, "--dangerously-force-unsafe-install") {
|
||||
args = append(args, "--dangerously-force-unsafe-install")
|
||||
}
|
||||
// Source confirmation does not grant the selected channel plugin's capabilities.
|
||||
if slices.Contains(options, "--accept-capabilities") {
|
||||
args = append(args, "--accept-capabilities")
|
||||
}
|
||||
return mgr.Run("docker", args...)
|
||||
}
|
||||
|
||||
func uninstallOpenclawPlugin(mgr *cmd.CommandHelper, containerName, pluginID string) error {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -737,9 +738,11 @@ type modelProvider struct {
|
||||
}
|
||||
|
||||
type modelEntry struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Input []string `json:"input,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Input []string `json:"input,omitempty"`
|
||||
ContextWindow int `json:"contextWindow,omitempty"`
|
||||
MaxTokens int `json:"maxTokens,omitempty"`
|
||||
}
|
||||
|
||||
func requiresOpenclawProviderModels(provider string) bool {
|
||||
@@ -767,7 +770,7 @@ type browserConfig struct {
|
||||
DefaultProfile string `json:"defaultProfile"`
|
||||
}
|
||||
|
||||
func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, token string, allowedOrigins []string, fallbacks []string) error {
|
||||
func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, token string, allowedOrigins []string, fallbacks []string, metadata []dto.AgentModelMetadata) error {
|
||||
if strings.TrimSpace(confDir) == "" {
|
||||
return fmt.Errorf("config dir is required")
|
||||
}
|
||||
@@ -852,6 +855,7 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
}
|
||||
conf = initial
|
||||
} else {
|
||||
preserveOpenclawModelMetadata(conf, cfg.Models)
|
||||
if err := applyOpenclawModelsConfig(conf, cfg.Models); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -906,6 +910,9 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
if allowedOrigins != nil {
|
||||
setSecurityConfig(conf, dto.AgentSecurityConfig{AllowedOrigins: allowedOrigins})
|
||||
}
|
||||
if err := applyOpenclawModelMetadata(conf, account, metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -920,6 +927,144 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
return writeAgentEnvMap(path.Join(confDir, ".env"), envMap, order)
|
||||
}
|
||||
|
||||
func readOpenclawModelsConfig(conf map[string]interface{}) *modelsConfig {
|
||||
raw, ok := conf["models"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
payload, err := json.Marshal(raw)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var models modelsConfig
|
||||
if err := json.Unmarshal(payload, &models); err != nil {
|
||||
return nil
|
||||
}
|
||||
return &models
|
||||
}
|
||||
|
||||
func preserveOpenclawModelMetadata(conf map[string]interface{}, next *modelsConfig) {
|
||||
current := readOpenclawModelsConfig(conf)
|
||||
if current == nil || next == nil {
|
||||
return
|
||||
}
|
||||
for providerID, nextProvider := range next.Providers {
|
||||
currentProvider, ok := current.Providers[providerID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
byID := make(map[string]modelEntry, len(currentProvider.Models))
|
||||
for _, entry := range currentProvider.Models {
|
||||
byID[entry.ID] = entry
|
||||
}
|
||||
for index := range nextProvider.Models {
|
||||
currentEntry, ok := byID[nextProvider.Models[index].ID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
nextProvider.Models[index].Input = currentEntry.Input
|
||||
nextProvider.Models[index].ContextWindow = currentEntry.ContextWindow
|
||||
nextProvider.Models[index].MaxTokens = currentEntry.MaxTokens
|
||||
}
|
||||
next.Providers[providerID] = nextProvider
|
||||
}
|
||||
}
|
||||
|
||||
func extractOpenclawModelMetadata(conf map[string]interface{}, account *model.AgentAccount, accountModels []dto.AgentAccountModel) []dto.AgentModelMetadata {
|
||||
result := make([]dto.AgentModelMetadata, 0, len(accountModels))
|
||||
configured := readOpenclawModelsConfig(conf)
|
||||
for _, item := range accountModels {
|
||||
_, inferred, providerID, _, err := buildOpenclawAccountModelConfig(account, item)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
metadata := dto.AgentModelMetadata{Model: item.ID, InputMode: "auto"}
|
||||
if configured != nil {
|
||||
for _, entry := range configured.Providers[providerID].Models {
|
||||
if entry.ID != inferred.ID {
|
||||
continue
|
||||
}
|
||||
metadata.ContextWindow = entry.ContextWindow
|
||||
metadata.MaxTokens = entry.MaxTokens
|
||||
if len(entry.Input) > 0 && !slices.Equal(entry.Input, inferred.Input) {
|
||||
if slices.Contains(entry.Input, "image") {
|
||||
metadata.InputMode = "image"
|
||||
} else {
|
||||
metadata.InputMode = "text"
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
result = append(result, metadata)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func applyOpenclawModelMetadata(conf map[string]interface{}, account *model.AgentAccount, requested []dto.AgentModelMetadata) error {
|
||||
if len(requested) == 0 {
|
||||
return nil
|
||||
}
|
||||
configured := readOpenclawModelsConfig(conf)
|
||||
if configured == nil {
|
||||
return fmt.Errorf("model metadata is not supported for provider %s", account.Provider)
|
||||
}
|
||||
accountModels, err := loadAgentAccountModels(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
available := make(map[string]dto.AgentAccountModel, len(accountModels))
|
||||
for _, item := range accountModels {
|
||||
available[item.ID] = item
|
||||
}
|
||||
seen := make(map[string]struct{}, len(requested))
|
||||
for _, metadata := range requested {
|
||||
item, ok := available[metadata.Model]
|
||||
if !ok {
|
||||
return buserr.New("ErrAgentModelNotInAccount")
|
||||
}
|
||||
if _, ok := seen[metadata.Model]; ok {
|
||||
return fmt.Errorf("duplicate model metadata: %s", metadata.Model)
|
||||
}
|
||||
seen[metadata.Model] = struct{}{}
|
||||
_, inferred, providerID, _, err := buildOpenclawAccountModelConfig(account, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
provider := configured.Providers[providerID]
|
||||
found := false
|
||||
for index := range provider.Models {
|
||||
if provider.Models[index].ID != inferred.ID {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
provider.Models[index].ContextWindow = metadata.ContextWindow
|
||||
provider.Models[index].MaxTokens = metadata.MaxTokens
|
||||
switch metadata.InputMode {
|
||||
case "auto":
|
||||
provider.Models[index].Input = inferred.Input
|
||||
case "text":
|
||||
provider.Models[index].Input = []string{"text"}
|
||||
case "image":
|
||||
provider.Models[index].Input = []string{"text", "image"}
|
||||
default:
|
||||
return fmt.Errorf("unsupported model input mode: %s", metadata.InputMode)
|
||||
}
|
||||
break
|
||||
}
|
||||
if !found {
|
||||
return buserr.New("ErrAgentModelNotInAccount")
|
||||
}
|
||||
configured.Providers[providerID] = provider
|
||||
}
|
||||
modelsMap, err := structToMap(configured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conf["models"] = modelsMap
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveOpenclawFallbackModels(account *model.AgentAccount, primaryModel string, fallbackIDs []string) ([]string, error) {
|
||||
accountModels, err := loadAgentAccountModels(account)
|
||||
if err != nil {
|
||||
@@ -1041,7 +1186,7 @@ func prepareOpenclawInstallFiles(appInstall *model.AppInstall, account *model.Ag
|
||||
return fmt.Errorf("app install is required")
|
||||
}
|
||||
confDir := path.Join(appInstall.GetPath(), "data", "conf")
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, token, allowedOrigins, nil); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, token, allowedOrigins, nil, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
dataDir := path.Join(appInstall.GetPath(), "data")
|
||||
@@ -1338,7 +1483,7 @@ func normalizeAgentAccountModel(account *model.AgentAccount, model dto.AgentAcco
|
||||
|
||||
func requiresInitialAgentAccountModels(provider string) bool {
|
||||
switch provider {
|
||||
case "custom", "vllm", "ollama":
|
||||
case "custom", "vllm", "ollama", "llmman":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
+429
-162
@@ -4,11 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
@@ -17,11 +14,13 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
alertconfig "github.com/1Panel-dev/1Panel/agent/utils/alert_config"
|
||||
alertwebhook "github.com/1Panel-dev/1Panel/agent/utils/alert_webhook"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/email"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack/providers"
|
||||
)
|
||||
|
||||
type AlertService struct{}
|
||||
@@ -34,6 +33,28 @@ var communityAlertMethodTypeNames = map[string]string{
|
||||
constant.SMS: "SMS",
|
||||
}
|
||||
|
||||
var legacyAlertMethodTypeMap = map[string]string{
|
||||
"mail": constant.Email,
|
||||
constant.Email: constant.Email,
|
||||
constant.SMS: constant.SMS,
|
||||
constant.Bark: constant.Bark,
|
||||
constant.WeChat: constant.WeCom,
|
||||
constant.WeCom: constant.WeCom,
|
||||
constant.DingTalk: constant.DingTalk,
|
||||
constant.FeiShu: constant.FeiShu,
|
||||
constant.Custom: constant.Custom,
|
||||
}
|
||||
|
||||
var supportedAlertMethodTypes = map[string]struct{}{
|
||||
constant.Email: {},
|
||||
constant.SMS: {},
|
||||
constant.Bark: {},
|
||||
constant.WeCom: {},
|
||||
constant.DingTalk: {},
|
||||
constant.FeiShu: {},
|
||||
constant.Custom: {},
|
||||
}
|
||||
|
||||
type IAlertService interface {
|
||||
PageAlert(req dto.AlertSearch) (int64, []dto.AlertDTO, error)
|
||||
GetAlerts() ([]dto.AlertDTO, error)
|
||||
@@ -51,10 +72,12 @@ type IAlertService interface {
|
||||
GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error)
|
||||
|
||||
GetAlertConfig(req dto.AlertConfigQuery) ([]model.AlertConfig, error)
|
||||
PageAlertConfig(req dto.AlertConfigPageReq, readOnly ...bool) (int64, []model.AlertConfig, error)
|
||||
PageAlertConfig(req dto.AlertConfigPageReq) (int64, []model.AlertConfig, error)
|
||||
UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error
|
||||
UpdateAlertConfigStatus(req dto.AlertConfigStatusUpdate, operator string) error
|
||||
DeleteAlertConfig(id uint) error
|
||||
TestAlertConfig(req dto.AlertConfigTest) (bool, error)
|
||||
TestCustomAlertConfig(req dto.AlertConfigTest) (dto.AlertConfigTestResult, error)
|
||||
}
|
||||
|
||||
func NewIAlertService() IAlertService {
|
||||
@@ -82,7 +105,38 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
cronjobProjects := make(map[string]uint)
|
||||
var cronjobIDs []uint
|
||||
for _, item := range alerts {
|
||||
if alertUtil.GetCronJobType(item.Type) != "cronJob" {
|
||||
continue
|
||||
}
|
||||
if _, exists := cronjobProjects[item.Project]; exists {
|
||||
continue
|
||||
}
|
||||
id, parseErr := strconv.ParseUint(item.Project, 10, strconv.IntSize)
|
||||
if parseErr != nil || id == 0 {
|
||||
continue
|
||||
}
|
||||
cronjobProjects[item.Project] = uint(id)
|
||||
cronjobIDs = append(cronjobIDs, uint(id))
|
||||
}
|
||||
cronjobsByID := make(map[uint]model.Cronjob)
|
||||
if len(cronjobIDs) > 0 {
|
||||
cronjobs, err := cronjobRepo.List(repo.WithByIDs(cronjobIDs))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
for _, cronjob := range cronjobs {
|
||||
cronjobsByID[cronjob.ID] = cronjob
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range alerts {
|
||||
var taskName string
|
||||
if cronjob, exists := cronjobsByID[cronjobProjects[item.Project]]; exists && cronjob.Type == item.Type {
|
||||
taskName = cronjob.Name
|
||||
}
|
||||
|
||||
result = append(result, dto.AlertDTO{
|
||||
ID: item.ID,
|
||||
@@ -92,6 +146,7 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
||||
Method: item.Method,
|
||||
Title: item.Title,
|
||||
Project: item.Project,
|
||||
TaskName: taskName,
|
||||
Status: item.Status,
|
||||
SendCount: item.SendCount,
|
||||
AdvancedParams: item.AdvancedParams,
|
||||
@@ -163,6 +218,16 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
advanced, err := prepareCronJobAlertParams(create.Type, "", create.AdvancedParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
create.AdvancedParams = advanced
|
||||
if create.Status != constant.AlertDisable {
|
||||
if err := a.validateCronJobAlertChannels(create.Type, advanced, create.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
alertInfo.Status = constant.AlertEnable
|
||||
if err := copier.Copy(&alertInfo, &create); err != nil {
|
||||
return buserr.WithErr("ErrStructTransform", err)
|
||||
@@ -180,9 +245,28 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlert(req dto.AlertUpdate, operator string) error {
|
||||
if err := a.validateCommunityAlertMethod(req.Method); err != nil {
|
||||
if alertUtil.GetCronJobType(req.Type) == "cronJob" {
|
||||
previous, err := alertRepo.Get(repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.AdvancedParams, err = prepareCronJobAlertParams(req.Type, previous.AdvancedParams, req.AdvancedParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
methodTypes, err := a.validateAlertMethodReferences(req.Method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Status != constant.AlertDisable {
|
||||
if err := a.validateCronJobAlertChannels(req.Type, req.AdvancedParams, req.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["id"] = req.ID
|
||||
@@ -240,7 +324,19 @@ func (a AlertService) UpdateStatus(id uint, status string) error {
|
||||
if alertInfo.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
err := alertRepo.Update(map[string]interface{}{"status": status}, repo.WithByID(alertInfo.ID))
|
||||
methodTypes, err := a.validateAlertMethodReferences(alertInfo.Method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status == constant.AlertEnable {
|
||||
if err := a.validateCronJobAlertChannels(alertInfo.Type, alertInfo.AdvancedParams, alertInfo.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = alertRepo.Update(map[string]interface{}{"status": status}, repo.WithByID(alertInfo.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -257,122 +353,14 @@ func (a AlertService) UpdateStatus(id uint, status string) error {
|
||||
}
|
||||
|
||||
func (a AlertService) GetDisks() ([]dto.DiskDTO, error) {
|
||||
var disks []dto.DiskDTO
|
||||
excludes := map[string]struct{}{
|
||||
"/mnt/cdrom": {}, "/boot": {}, "/boot/efi": {}, "/dev": {}, "/dev/shm": {},
|
||||
"/run/lock": {}, "/run": {}, "/run/shm": {}, "/run/user": {},
|
||||
infos := loadDiskInfo(true)
|
||||
disks := make([]dto.DiskDTO, 0, len(infos))
|
||||
for _, item := range infos {
|
||||
disks = append(disks, dto.DiskDTO(item))
|
||||
}
|
||||
stdout, err := executeDiskCommand()
|
||||
if err != nil {
|
||||
return disks, nil
|
||||
}
|
||||
|
||||
lines := strings.Split(stdout, "\n")
|
||||
var mounts []dto.AlertDiskInfo
|
||||
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 7 {
|
||||
continue
|
||||
}
|
||||
mountPoint := strings.Join(fields[6:], " ")
|
||||
if shouldExclude(fields, mountPoint, excludes) {
|
||||
continue
|
||||
}
|
||||
mounts = append(mounts, dto.AlertDiskInfo{Type: fields[1], Device: fields[0], Mount: mountPoint})
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
)
|
||||
wg.Add(len(mounts))
|
||||
for i := 0; i < len(mounts); i++ {
|
||||
go func(timeoutCh <-chan time.Time, mount dto.AlertDiskInfo) {
|
||||
defer wg.Done()
|
||||
|
||||
var itemData dto.DiskDTO
|
||||
itemData.Path = mount.Mount
|
||||
itemData.Type = mount.Type
|
||||
itemData.Device = mount.Device
|
||||
select {
|
||||
case <-timeoutCh:
|
||||
mu.Lock()
|
||||
disks = append(disks, itemData)
|
||||
mu.Unlock()
|
||||
global.LOG.Errorf("load disk info from %s failed, err: timeout", mount.Mount)
|
||||
default:
|
||||
state, err := disk.Usage(mount.Mount)
|
||||
if err != nil {
|
||||
mu.Lock()
|
||||
disks = append(disks, itemData)
|
||||
mu.Unlock()
|
||||
global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, err)
|
||||
return
|
||||
}
|
||||
itemData.Total = state.Total
|
||||
itemData.Free = state.Free
|
||||
itemData.Used = state.Used
|
||||
itemData.UsedPercent = state.UsedPercent
|
||||
itemData.InodesTotal = state.InodesTotal
|
||||
itemData.InodesUsed = state.InodesUsed
|
||||
itemData.InodesFree = state.InodesFree
|
||||
itemData.InodesUsedPercent = state.InodesUsedPercent
|
||||
mu.Lock()
|
||||
disks = append(disks, itemData)
|
||||
mu.Unlock()
|
||||
}
|
||||
}(time.After(5*time.Second), mounts[i])
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
sort.Slice(disks, func(i, j int) bool {
|
||||
return disks[i].Path < disks[j].Path
|
||||
})
|
||||
return disks, nil
|
||||
}
|
||||
|
||||
func executeDiskCommand() (string, error) {
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second))
|
||||
stdout, err := cmdMgr.RunWithStdout("df", "-hT", "-P")
|
||||
if err != nil {
|
||||
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
|
||||
stdout, err = cmdMgr2.RunWithStdout("df", "-lhT", "-P")
|
||||
}
|
||||
if err != nil {
|
||||
return stdout, err
|
||||
}
|
||||
var lines []string
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
if !strings.Contains(line, "/") || strings.Contains(line, "tmpfs") || strings.Contains(line, "snap/core") || strings.Contains(line, "udev") {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
if len(lines) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return strings.Join(lines, "\n"), nil
|
||||
}
|
||||
|
||||
func shouldExclude(fields []string, mountPoint string, excludes map[string]struct{}) bool {
|
||||
if strings.HasPrefix(mountPoint, "/snap") || len(strings.Split(mountPoint, "/")) > 10 {
|
||||
return true
|
||||
}
|
||||
if strings.TrimSpace(fields[1]) == "tmpfs" {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(fields[2], "K") {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(mountPoint, "docker") {
|
||||
return true
|
||||
}
|
||||
_, excluded := excludes[mountPoint]
|
||||
return excluded
|
||||
}
|
||||
|
||||
func (a AlertService) PageAlertLogs(search dto.AlertLogSearch) (int64, []dto.AlertLogDTO, error) {
|
||||
var (
|
||||
opts []repo.DBOption
|
||||
@@ -412,6 +400,7 @@ func (a AlertService) parseAlertLog(item model.AlertLog) (dto.AlertLogDTO, error
|
||||
if err := unmarshalAlertInfo(item.AlertDetail, &alertDetail); err != nil {
|
||||
return dto.AlertLogDTO{}, err
|
||||
}
|
||||
alertDetail.Task = nil
|
||||
if err := unmarshalAlertInfo(item.AlertRule, &alertRule); err != nil {
|
||||
return dto.AlertLogDTO{}, err
|
||||
}
|
||||
@@ -494,10 +483,16 @@ func (a AlertService) GetAlertConfig(req dto.AlertConfigQuery) ([]model.AlertCon
|
||||
}
|
||||
opts = append(opts, repo.WithByStatus(constant.AlertEnable))
|
||||
configs, err := alertRepo.AlertConfigList(opts...)
|
||||
return configs, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := exposeCustomAlertConfigSecrets(configs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq, readOnly ...bool) (int64, []model.AlertConfig, error) {
|
||||
func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq) (int64, []model.AlertConfig, error) {
|
||||
opts := []repo.DBOption{
|
||||
alertRepo.WithByTypeNotIn([]string{"common"}),
|
||||
repo.WithOrderDesc("created_at"),
|
||||
@@ -506,30 +501,48 @@ func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq, readOnly ...bo
|
||||
opts = append(opts, alertRepo.WithByTypeNotIn(req.ExcludeTypes))
|
||||
}
|
||||
total, configs, err := alertRepo.PageAlertConfig(req.Page, req.PageSize, opts...)
|
||||
if err != nil || !isDemoReadOnly(readOnly...) {
|
||||
return total, configs, err
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
for i := range configs {
|
||||
var value interface{}
|
||||
if err := json.Unmarshal([]byte(configs[i].Config), &value); err != nil {
|
||||
configs[i].Config = ""
|
||||
continue
|
||||
}
|
||||
redactSensitiveData(value)
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
configs[i].Config = ""
|
||||
continue
|
||||
}
|
||||
configs[i].Config = string(data)
|
||||
if err := exposeCustomAlertConfigSecrets(configs); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return total, configs, nil
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error {
|
||||
if req.Type == constant.Custom {
|
||||
if req.ID != 0 && req.Revision == nil {
|
||||
return repo.ErrAlertConfigRevisionRequired
|
||||
}
|
||||
return a.updateCustomAlertConfig(req, operator)
|
||||
}
|
||||
usesMutation, err := alertconfig.UsesMutation(req.Type, req.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.ID != 0 && usesMutation && req.Revision == nil {
|
||||
return repo.ErrAlertConfigRevisionRequired
|
||||
}
|
||||
var existing *model.AlertConfig
|
||||
if req.ID != 0 {
|
||||
stored, err := alertRepo.GetConfigById(req.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stored.Type != req.Type {
|
||||
return fmt.Errorf("alert config %d has type %s, not %s", req.ID, stored.Type, req.Type)
|
||||
}
|
||||
existing = &stored
|
||||
}
|
||||
if err := a.validateCommunityAlertConfigType(req.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
prepared, err := alertconfig.Prepare(req.Type, req.Config, req.Status, existing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Config = prepared
|
||||
if err := a.checkAlertConfigDisplayNameUnique(req); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -544,7 +557,7 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator stri
|
||||
upMap["status"] = req.Status
|
||||
upMap["config"] = req.Config
|
||||
upMap["update_user"] = operator
|
||||
if err := alertRepo.UpdateAlertConfig(upMap, repo.WithByID(req.ID)); err != nil {
|
||||
if err := alertRepo.UpdateAlertConfigWithRevision(upMap, req.Revision, repo.WithByID(req.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -562,6 +575,99 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) updateCustomAlertConfig(req dto.AlertConfigUpdate, operator string) error {
|
||||
if err := validateAlertConfigStatus(req.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var existing *model.AlertConfig
|
||||
if req.ID != 0 {
|
||||
config, err := alertRepo.GetConfigById(req.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if config.Type != constant.Custom {
|
||||
return fmt.Errorf("alert config %d is not a custom webhook", req.ID)
|
||||
}
|
||||
existing = &config
|
||||
}
|
||||
prepared, err := alertwebhook.Prepare(req.Config, req.Status, existing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
validatedReq := req
|
||||
validatedReq.Config = prepared.Config
|
||||
if err := a.checkAlertConfigDisplayNameUnique(validatedReq); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if existing != nil {
|
||||
return alertRepo.UpdateAlertConfigWithRevision(map[string]interface{}{
|
||||
"type": constant.Custom,
|
||||
"title": req.Title,
|
||||
"status": req.Status,
|
||||
"config": prepared.Config,
|
||||
"secret_config": prepared.SecretConfig,
|
||||
"update_user": operator,
|
||||
}, req.Revision, repo.WithByID(req.ID))
|
||||
}
|
||||
|
||||
return alertRepo.CreateAlertConfig(&model.AlertConfig{
|
||||
Type: constant.Custom,
|
||||
Title: req.Title,
|
||||
Status: req.Status,
|
||||
Config: prepared.Config,
|
||||
SecretConfig: prepared.SecretConfig,
|
||||
CreateUser: operator,
|
||||
UpdateUser: operator,
|
||||
})
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlertConfigStatus(req dto.AlertConfigStatusUpdate, operator string) error {
|
||||
if err := validateAlertConfigStatus(req.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
config, err := alertRepo.GetConfigById(req.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Status == constant.AlertEnable {
|
||||
if err := a.validateCommunityAlertConfigType(config.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
if config.Type == constant.Custom {
|
||||
if _, err := alertwebhook.Resolve(config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return alertRepo.UpdateAlertConfig(map[string]interface{}{
|
||||
"status": req.Status,
|
||||
"update_user": operator,
|
||||
}, repo.WithByID(req.ID))
|
||||
}
|
||||
|
||||
func validateAlertConfigStatus(status string) error {
|
||||
if status != constant.AlertEnable && status != constant.AlertDisable {
|
||||
return fmt.Errorf("alert config status must be Enable or Disable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func exposeCustomAlertConfigSecrets(configs []model.AlertConfig) error {
|
||||
for index := range configs {
|
||||
if configs[index].Type != constant.Custom {
|
||||
continue
|
||||
}
|
||||
view, err := alertwebhook.PlainView(configs[index])
|
||||
if err != nil {
|
||||
return fmt.Errorf("build editable custom alert config %d: %w", configs[index].ID, err)
|
||||
}
|
||||
configs[index].Config = view
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) checkAlertConfigSMSPhoneUnique(req dto.AlertConfigUpdate) error {
|
||||
if req.Type != constant.SMSConfig {
|
||||
return nil
|
||||
@@ -586,6 +692,9 @@ func (a AlertService) checkAlertConfigSMSPhoneUnique(req dto.AlertConfigUpdate)
|
||||
}
|
||||
|
||||
func (a AlertService) checkAlertConfigDisplayNameUnique(req dto.AlertConfigUpdate) error {
|
||||
if req.Type != constant.Custom && (global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn") {
|
||||
return nil
|
||||
}
|
||||
displayName := alertConfigDisplayName(req.Type, req.Config)
|
||||
if displayName == "" {
|
||||
return nil
|
||||
@@ -609,37 +718,67 @@ func (a AlertService) checkAlertConfigDisplayNameUnique(req dto.AlertConfigUpdat
|
||||
}
|
||||
|
||||
func (a AlertService) validateCommunityAlertMethod(method string) error {
|
||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(method) == "" {
|
||||
return nil
|
||||
methodTypes, err := a.validateAlertMethodReferences(method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.validateAlertMethodEntitlement(methodTypes)
|
||||
}
|
||||
|
||||
func (a AlertService) validateAlertMethodReferences(method string) ([]string, error) {
|
||||
if strings.TrimSpace(method) == "" {
|
||||
return nil, buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
}
|
||||
methodTypes := make([]string, 0)
|
||||
for _, item := range strings.Split(method, ",") {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
configType := ""
|
||||
if configID, err := strconv.ParseUint(item, 10, 64); err == nil {
|
||||
config, err := alertRepo.GetConfigById(uint(configID))
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := communityAlertMethodTypeNames[config.Type]; ok {
|
||||
return buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
configType = config.Type
|
||||
} else {
|
||||
var ok bool
|
||||
configType, ok = legacyAlertMethodTypeMap[item]
|
||||
if !ok {
|
||||
return nil, buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
}
|
||||
}
|
||||
if _, ok := supportedAlertMethodTypes[configType]; !ok {
|
||||
return nil, buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
}
|
||||
methodTypes = append(methodTypes, configType)
|
||||
}
|
||||
if len(methodTypes) == 0 {
|
||||
return nil, buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
}
|
||||
return methodTypes, nil
|
||||
}
|
||||
|
||||
func (a AlertService) validateAlertMethodEntitlement(methodTypes []string) error {
|
||||
for _, configType := range methodTypes {
|
||||
if configType == constant.Custom {
|
||||
continue
|
||||
}
|
||||
if _, ok := communityAlertMethodTypeNames[item]; ok {
|
||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
||||
continue
|
||||
}
|
||||
if _, ok := communityAlertMethodTypeNames[configType]; ok {
|
||||
return buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) validateCommunityAlertConfigType(configType string) error {
|
||||
if configType == constant.Custom {
|
||||
return nil
|
||||
}
|
||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
||||
return nil
|
||||
}
|
||||
@@ -651,7 +790,7 @@ func (a AlertService) validateCommunityAlertConfigType(configType string) error
|
||||
|
||||
func alertConfigDisplayName(configType, configData string) string {
|
||||
switch configType {
|
||||
case constant.Email, constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Bark, constant.SMS:
|
||||
case constant.Email, constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Bark, constant.SMS, constant.Custom:
|
||||
var cfg struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
}
|
||||
@@ -690,20 +829,24 @@ func (a AlertService) DeleteAlertConfig(id uint) error {
|
||||
}
|
||||
|
||||
func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
|
||||
username := req.UserName
|
||||
if username == "" {
|
||||
username = req.Sender
|
||||
emailConfig, err := resolveEmailTestConfig(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
encodedDisplayName := mime.BEncoding.Encode("UTF-8", req.DisplayName)
|
||||
username := emailConfig.UserName
|
||||
if username == "" {
|
||||
username = emailConfig.Sender
|
||||
}
|
||||
encodedDisplayName := mime.BEncoding.Encode("UTF-8", emailConfig.DisplayName)
|
||||
cfg := email.SMTPConfig{
|
||||
Host: req.Host,
|
||||
Port: req.Port,
|
||||
Sender: req.Sender,
|
||||
Host: emailConfig.Host,
|
||||
Port: emailConfig.Port,
|
||||
Sender: emailConfig.Sender,
|
||||
Username: username,
|
||||
Password: req.Password,
|
||||
From: fmt.Sprintf(`"%s" <%s>`, encodedDisplayName, req.Sender),
|
||||
Encryption: req.Encryption,
|
||||
Recipient: req.Recipient,
|
||||
Password: emailConfig.Password,
|
||||
From: fmt.Sprintf(`"%s" <%s>`, encodedDisplayName, emailConfig.Sender),
|
||||
Encryption: emailConfig.Encryption,
|
||||
Recipient: emailConfig.Recipient,
|
||||
}
|
||||
|
||||
msg := email.EmailMessage{
|
||||
@@ -718,9 +861,94 @@ func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func resolveEmailTestConfig(req dto.AlertConfigTest) (dto.AlertEmailConfig, error) {
|
||||
emailConfig := dto.AlertEmailConfig{
|
||||
Host: req.Host,
|
||||
Port: req.Port,
|
||||
Sender: req.Sender,
|
||||
UserName: req.UserName,
|
||||
Password: req.Password,
|
||||
DisplayName: req.DisplayName,
|
||||
Encryption: req.Encryption,
|
||||
Recipient: req.Recipient,
|
||||
}
|
||||
if strings.TrimSpace(req.Config) != "" {
|
||||
configType := req.Type
|
||||
if configType == "" {
|
||||
configType = constant.EmailConfig
|
||||
}
|
||||
if configType != constant.EmailConfig {
|
||||
return dto.AlertEmailConfig{}, fmt.Errorf("alert config test type must be email")
|
||||
}
|
||||
var existing *model.AlertConfig
|
||||
if req.ID != 0 {
|
||||
stored, err := alertRepo.GetConfigById(req.ID)
|
||||
if err != nil {
|
||||
return dto.AlertEmailConfig{}, err
|
||||
}
|
||||
existing = &stored
|
||||
}
|
||||
prepared, err := alertconfig.Prepare(configType, req.Config, constant.AlertEnable, existing)
|
||||
if err != nil {
|
||||
return dto.AlertEmailConfig{}, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(prepared), &emailConfig); err != nil {
|
||||
return dto.AlertEmailConfig{}, fmt.Errorf("decode email alert config: %w", err)
|
||||
}
|
||||
}
|
||||
return emailConfig, nil
|
||||
}
|
||||
|
||||
func (a AlertService) TestCustomAlertConfig(req dto.AlertConfigTest) (dto.AlertConfigTestResult, error) {
|
||||
if req.Type != constant.Custom {
|
||||
return dto.AlertConfigTestResult{}, fmt.Errorf("alert config test type must be custom")
|
||||
}
|
||||
var existing *model.AlertConfig
|
||||
if req.ID != 0 {
|
||||
config, err := alertRepo.GetConfigById(req.ID)
|
||||
if err != nil {
|
||||
return dto.AlertConfigTestResult{}, err
|
||||
}
|
||||
if config.Type != constant.Custom {
|
||||
return dto.AlertConfigTestResult{}, fmt.Errorf("alert config %d is not a custom webhook", req.ID)
|
||||
}
|
||||
existing = &config
|
||||
}
|
||||
prepared, err := alertwebhook.Prepare(req.Config, constant.AlertEnable, existing)
|
||||
if err != nil {
|
||||
return dto.AlertConfigTestResult{}, err
|
||||
}
|
||||
resolved, err := alertwebhook.Resolve(model.AlertConfig{
|
||||
Type: constant.Custom,
|
||||
Config: prepared.Config,
|
||||
SecretConfig: prepared.SecretConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return dto.AlertConfigTestResult{}, err
|
||||
}
|
||||
tester, ok := xpack.AlertProvider.(providers.CustomWebhookTester)
|
||||
if !ok {
|
||||
return dto.AlertConfigTestResult{
|
||||
Success: false,
|
||||
Message: providers.ErrCustomWebhookUnsupported.Error(),
|
||||
}, nil
|
||||
}
|
||||
return tester.TestCustomWebhook(resolved)
|
||||
}
|
||||
|
||||
func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator string) error {
|
||||
if err := a.validateCommunityAlertMethod(updateAlert.Method); err != nil {
|
||||
return err
|
||||
var methodTypes []string
|
||||
if updateAlert.SendCount != 0 || strings.TrimSpace(updateAlert.Method) != "" {
|
||||
var err error
|
||||
methodTypes, err = a.validateAlertMethodReferences(updateAlert.Method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if updateAlert.SendCount != 0 {
|
||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
upMap := make(map[string]interface{})
|
||||
var newStatus string
|
||||
@@ -739,6 +967,23 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
||||
alertRepo.WithByType(updateAlert.Type),
|
||||
alertRepo.WithByProject(updateAlert.Project),
|
||||
)
|
||||
advanced, err := prepareCronJobAlertParams(updateAlert.Type, alertInfo.AdvancedParams, updateAlert.AdvancedParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateAlert.AdvancedParams = advanced
|
||||
if alertUtil.GetCronJobType(updateAlert.Type) == "cronJob" {
|
||||
upMap["advanced_params"] = advanced
|
||||
}
|
||||
if newStatus == constant.AlertEnable {
|
||||
method := updateAlert.Method
|
||||
if method == "" {
|
||||
method = alertInfo.Method
|
||||
}
|
||||
if err := a.validateCronJobAlertChannels(updateAlert.Type, advanced, method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if alertInfo.ID > 0 {
|
||||
shouldUpdate := false
|
||||
@@ -752,6 +997,9 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
||||
if val, ok := upMap["method"]; ok && val != "" && val != alertInfo.Method {
|
||||
shouldUpdate = true
|
||||
}
|
||||
if val, ok := upMap["advanced_params"]; ok && val != alertInfo.AdvancedParams {
|
||||
shouldUpdate = true
|
||||
}
|
||||
|
||||
if shouldUpdate {
|
||||
if err := alertRepo.Update(
|
||||
@@ -773,3 +1021,22 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareCronJobAlertParams(alertType, previous, incoming string) (string, error) {
|
||||
if alertUtil.GetCronJobType(alertType) != "cronJob" {
|
||||
return incoming, nil
|
||||
}
|
||||
return alertUtil.MergeCronJobAlertParams(previous, incoming)
|
||||
}
|
||||
|
||||
func (a AlertService) validateCronJobAlertChannels(alertType, advanced, method string) error {
|
||||
if alertUtil.GetCronJobType(alertType) != "cronJob" {
|
||||
return nil
|
||||
}
|
||||
mode, err := alertUtil.CronJobAlertTriggerMode(advanced)
|
||||
if err != nil || mode != alertUtil.CronJobAlertSuccess {
|
||||
return err
|
||||
}
|
||||
_, err = a.validateAlertMethodReferences(method)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
@@ -29,9 +30,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ResourceAlertInterval = 30
|
||||
CheckIntervalSec = 3
|
||||
LoadCheckIntervalMin = 5
|
||||
ResourceAlertInterval = 30
|
||||
CheckIntervalSec = 3
|
||||
LoadCheckIntervalMin = 5
|
||||
sshIPLoginWindow = 30 * time.Minute
|
||||
sslAutoRenewAlertSkipDays = 31
|
||||
)
|
||||
|
||||
type AlertTaskHelper struct {
|
||||
@@ -512,10 +515,32 @@ func loadPanelLogin(alert dto.AlertDTO) {
|
||||
}
|
||||
|
||||
func loadSSHLogin(alert dto.AlertDTO) {
|
||||
count, isAlert, err := alertUtil.CountRecentFailedSSHLog(alert.Cycle, alert.Count)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to count recent failed ssh login logs: %v", err)
|
||||
now := time.Now()
|
||||
failedWindow := time.Duration(alert.Cycle) * time.Minute
|
||||
loadWindow := failedWindow
|
||||
if loadWindow < sshIPLoginWindow {
|
||||
loadWindow = sshIPLoginWindow
|
||||
}
|
||||
location, err := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to load timezone for ssh login logs: %v", err)
|
||||
location = time.Local
|
||||
}
|
||||
histories, err := loadSSHAlertHistories(defaultSSHLogDir, now.Add(-loadWindow), now, location)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to load ssh login logs: %v", err)
|
||||
}
|
||||
interfaceAddrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
global.LOG.Warnf("Failed to load local IP addresses for ssh login alert: %v", err)
|
||||
}
|
||||
count, records := summarizeSSHLoginHistories(
|
||||
histories,
|
||||
now,
|
||||
failedWindow,
|
||||
sshSuccessLoginWhitelist(alert.AdvancedParams, interfaceAddrs),
|
||||
)
|
||||
isAlert := count >= int(alert.Count)
|
||||
if isAlert {
|
||||
params := []dto.Param{
|
||||
{
|
||||
@@ -531,12 +556,6 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
||||
}
|
||||
sendAlerts(alert, "sshLogin", strconv.Itoa(count), "sshLogin", params)
|
||||
}
|
||||
whitelist := strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n")
|
||||
records, err := alertUtil.FindRecentSuccessLoginNotInWhitelist(30, whitelist)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to check recent failed ip ssh login logs: %v", err)
|
||||
}
|
||||
records = filterSSHLoginEntriesNotInWhitelist(records, whitelist)
|
||||
if len(records) > 0 {
|
||||
quota := strings.Join(records, "\n")
|
||||
params := []dto.Param{
|
||||
@@ -555,6 +574,19 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
||||
}
|
||||
}
|
||||
|
||||
func sshSuccessLoginWhitelist(configured string, interfaceAddrs []net.Addr) []string {
|
||||
whitelist := strings.Split(strings.TrimSpace(configured), "\n")
|
||||
whitelist = append(whitelist, "127.0.0.0/8", "::1")
|
||||
for _, addr := range interfaceAddrs {
|
||||
ipNet, ok := addr.(*net.IPNet)
|
||||
if !ok || ipNet.IP == nil || ipNet.IP.IsUnspecified() {
|
||||
continue
|
||||
}
|
||||
whitelist = append(whitelist, ipNet.IP.String())
|
||||
}
|
||||
return whitelist
|
||||
}
|
||||
|
||||
func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string) []model.LoginLog {
|
||||
filtered := make([]model.LoginLog, 0, len(records))
|
||||
for _, record := range records {
|
||||
@@ -565,20 +597,6 @@ func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string)
|
||||
return filtered
|
||||
}
|
||||
|
||||
func filterSSHLoginEntriesNotInWhitelist(records []string, whitelist []string) []string {
|
||||
filtered := make([]string, 0, len(records))
|
||||
for _, record := range records {
|
||||
ip := record
|
||||
if idx := strings.Index(record, "-"); idx >= 0 {
|
||||
ip = record[:idx]
|
||||
}
|
||||
if !isIPInWhitelist(ip, whitelist) {
|
||||
filtered = append(filtered, record)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func isIPInWhitelist(ip string, whitelist []string) bool {
|
||||
targetIP := net.ParseIP(strings.TrimSpace(ip))
|
||||
if targetIP == nil {
|
||||
@@ -695,9 +713,10 @@ func sendAlertsByConfigId(alert dto.AlertDTO, alertType, quota, quotaType string
|
||||
|
||||
func sendAlertsByLegacyMethod(alert dto.AlertDTO, alertType, quota, quotaType string, params []dto.Param, method string) {
|
||||
typeMap := map[string]string{
|
||||
"mail": constant.Email,
|
||||
constant.Bark: constant.Bark,
|
||||
constant.SMS: constant.SMS,
|
||||
"mail": constant.Email,
|
||||
constant.Bark: constant.Bark,
|
||||
constant.SMS: constant.SMS,
|
||||
constant.Custom: constant.Custom,
|
||||
}
|
||||
configType, ok := typeMap[method]
|
||||
if !ok {
|
||||
@@ -785,7 +804,7 @@ func doSendAlert(alert dto.AlertDTO, alertType, quota, quotaType string, params
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Custom:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, methodStr)
|
||||
if !isValid {
|
||||
return
|
||||
@@ -798,12 +817,31 @@ func doSendAlert(alert dto.AlertDTO, alertType, quota, quotaType string, params
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
alertErr := xpack.AlertProvider.CreateWebhookAlertLog(alertType, alert, create, quotaType, params, config, transport, agentInfo)
|
||||
queued := false
|
||||
var alertErr error
|
||||
if config.Type == constant.Custom {
|
||||
task := dto.AlertTaskMetadata{
|
||||
AlertID: alert.ID,
|
||||
Type: alertType,
|
||||
Quota: quota,
|
||||
QuotaType: quotaType,
|
||||
Method: methodStr,
|
||||
}
|
||||
result, deliveryErr := xpack.DeliverCustomWebhookAlertLog(alertType, alert, create, quotaType, params, config, transport, agentInfo, task)
|
||||
queued, alertErr = result.Queued, deliveryErr
|
||||
if alertErr == nil && result.Queued {
|
||||
_, alertErr = alertUtil.RecordQueuedAlertTask(result.LogID, task)
|
||||
}
|
||||
} else {
|
||||
alertErr = xpack.AlertProvider.CreateWebhookAlertLog(alertType, alert, create, quotaType, params, config, transport, agentInfo)
|
||||
}
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert webhook %s push faild, err: %v", alertType, methodStr, alertErr)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
if !queued {
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -844,7 +882,7 @@ func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]s
|
||||
daysDiff := int(math.Ceil(
|
||||
ssl.ExpireDate.Sub(currentDate).Hours() / 24,
|
||||
))
|
||||
if daysDiff > 0 && int(cycle) >= daysDiff {
|
||||
if daysDiff > 0 && int(cycle) >= daysDiff && !shouldSuppressSSLExpiryAlert(ssl, daysDiff) {
|
||||
daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], ssl.PrimaryDomain)
|
||||
projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate)
|
||||
}
|
||||
@@ -852,6 +890,10 @@ func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]s
|
||||
return daysDiffMap, projectMap
|
||||
}
|
||||
|
||||
func shouldSuppressSSLExpiryAlert(ssl model.WebsiteSSL, remainingDays int) bool {
|
||||
return ssl.AutoRenew && remainingDays < sslAutoRenewAlertSkipDays
|
||||
}
|
||||
|
||||
func calculateWebsiteExpiryDays(websites []model.Website, cycle uint) (map[int][]string, map[uint][]time.Time) {
|
||||
currentDate := time.Now()
|
||||
daysDiffMap := make(map[int][]string)
|
||||
@@ -1024,50 +1066,37 @@ func processAllDisks(alert dto.AlertDTO) error {
|
||||
global.LOG.Errorf("error getting disk list, err: %v", err)
|
||||
return err
|
||||
}
|
||||
var errMsgs []string
|
||||
for _, item := range diskList {
|
||||
err := checkAndCreateDiskAlert(alert, item.Path)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("disk path %s process failed: %v", item.Path, err)
|
||||
errMsgs = append(errMsgs, errMsg)
|
||||
global.LOG.Errorf("%s", errMsg)
|
||||
if item.Total == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(errMsgs) > 0 {
|
||||
return fmt.Errorf("batch process disks failed, error count: %d, details: %s", len(errMsgs), strings.Join(errMsgs, "; "))
|
||||
checkAndCreateDiskAlert(alert, item.Path, &disk.UsageStat{Used: item.Used, UsedPercent: item.UsedPercent})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func processSingleDisk(alert dto.AlertDTO) error {
|
||||
err := checkAndCreateDiskAlert(alert, alert.Project)
|
||||
usageStat, err := loadDiskUsageWithTimeout(alert.Project, true)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s", err.Error())
|
||||
global.LOG.Errorf("error getting disk usage for %s, err: %v", alert.Project, err)
|
||||
return err
|
||||
}
|
||||
checkAndCreateDiskAlert(alert, alert.Project, usageStat)
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) error {
|
||||
usageStat, err := psutil.DISK.GetUsage(path, false)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("error getting disk usage for %s, err: %v", path, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func checkAndCreateDiskAlert(alert dto.AlertDTO, path string, usageStat *disk.UsageStat) {
|
||||
usedTotal, usedStr := calculateUsedTotal(alert.Cycle, usageStat)
|
||||
commonTotal := float64(alert.Count)
|
||||
if alert.Cycle == 1 {
|
||||
commonTotal *= 1024 * 1024 * 1024
|
||||
}
|
||||
if usedTotal < commonTotal {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
params := createAlertDiskParams(path, usedStr)
|
||||
sender := NewAlertSender(alert, alert.Project)
|
||||
sender.ResourceSend(path, params)
|
||||
return nil
|
||||
}
|
||||
|
||||
func calculateUsedTotal(cycle uint, usageStat *disk.UsageStat) (float64, string) {
|
||||
@@ -1097,3 +1126,55 @@ func calculateMinutesDifference(newDate time.Time) int {
|
||||
minutesDifference := int(now.Sub(newDate).Minutes())
|
||||
return minutesDifference
|
||||
}
|
||||
|
||||
func loadSSHAlertHistories(
|
||||
baseDir string,
|
||||
startTime, endTime time.Time,
|
||||
location *time.Location,
|
||||
) ([]dto.SSHHistory, error) {
|
||||
fileList, err := listSSHLogFiles(baseDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
histories []dto.SSHHistory
|
||||
loadErr error
|
||||
)
|
||||
for _, file := range fileList {
|
||||
items, err := loadSSHHistoriesFromFile(file.Name, "", "", startTime, endTime, file.Year, location)
|
||||
if err != nil {
|
||||
loadErr = errors.Join(loadErr, fmt.Errorf("load SSH log file %s: %w", file.Name, err))
|
||||
continue
|
||||
}
|
||||
histories = append(histories, items...)
|
||||
}
|
||||
return histories, loadErr
|
||||
}
|
||||
|
||||
func summarizeSSHLoginHistories(
|
||||
histories []dto.SSHHistory,
|
||||
now time.Time,
|
||||
failedWindow time.Duration,
|
||||
whitelist []string,
|
||||
) (int, []string) {
|
||||
failedStartTime := now.Add(-failedWindow)
|
||||
successStartTime := now.Add(-sshIPLoginWindow)
|
||||
failedCount := 0
|
||||
var abnormalLogins []string
|
||||
|
||||
for _, item := range histories {
|
||||
switch item.Status {
|
||||
case constant.StatusFailed:
|
||||
if isSSHLogWithinTimeRange(item.Date, failedStartTime, now) {
|
||||
failedCount++
|
||||
}
|
||||
case constant.StatusSuccess:
|
||||
if !isSSHLogWithinTimeRange(item.Date, successStartTime, now) || isIPInWhitelist(item.Address, whitelist) {
|
||||
continue
|
||||
}
|
||||
abnormalLogins = append(abnormalLogins, fmt.Sprintf("%s-%s", item.Address, item.Date.Format(constant.DateTimeLayout)))
|
||||
}
|
||||
}
|
||||
return failedCount, abnormalLogins
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func (s *AlertSender) sendByConfig(config model.AlertConfig, quota string, param
|
||||
} else {
|
||||
s.sendBarkWithConfig(config, quota, params)
|
||||
}
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Custom:
|
||||
if isResource {
|
||||
s.sendResourceWebhookWithConfig(config, quota, params)
|
||||
} else {
|
||||
@@ -86,7 +86,7 @@ func (s *AlertSender) sendByConfig(config model.AlertConfig, quota string, param
|
||||
|
||||
func (s *AlertSender) sendByLegacyMethod(method string, quota string, params []dto.Param, isResource bool) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
typeMap := map[string]string{"mail": constant.Email, constant.Bark: constant.Bark, constant.SMS: constant.SMS}
|
||||
typeMap := map[string]string{"mail": constant.Email, constant.Bark: constant.Bark, constant.SMS: constant.SMS, constant.Custom: constant.Custom}
|
||||
configType := method
|
||||
if mapped, ok := typeMap[method]; ok {
|
||||
configType = mapped
|
||||
@@ -308,12 +308,31 @@ func (s *AlertSender) sendWebhookWithConfig(config model.AlertConfig, quota stri
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo)
|
||||
queued := false
|
||||
var err error
|
||||
if config.Type == constant.Custom {
|
||||
task := dto.AlertTaskMetadata{
|
||||
AlertID: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
Quota: quota,
|
||||
QuotaType: s.quotaType,
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
result, deliveryErr := xpack.DeliverCustomWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo, task)
|
||||
queued, err = result.Queued, deliveryErr
|
||||
if err == nil && result.Queued {
|
||||
_, err = alertUtil.RecordQueuedAlertTask(result.LogID, task)
|
||||
}
|
||||
} else {
|
||||
err = xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo)
|
||||
}
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
if !queued {
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceWebhookWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
@@ -334,11 +353,31 @@ func (s *AlertSender) sendResourceWebhookWithConfig(config model.AlertConfig, qu
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
if err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo); err != nil {
|
||||
queued := false
|
||||
var err error
|
||||
if config.Type == constant.Custom {
|
||||
task := dto.AlertTaskMetadata{
|
||||
AlertID: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
Quota: quota,
|
||||
QuotaType: s.quotaType,
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
result, deliveryErr := xpack.DeliverCustomWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo, task)
|
||||
queued, err = result.Queued, deliveryErr
|
||||
if err == nil && result.Queued {
|
||||
_, err = alertUtil.RecordQueuedAlertTask(result.LogID, task)
|
||||
}
|
||||
} else {
|
||||
err = xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo)
|
||||
}
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
if !queued {
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method string) {
|
||||
|
||||
+61
-54
@@ -48,7 +48,7 @@ type IAppService interface {
|
||||
PageApp(ctx *gin.Context, req request.AppSearch) (*response.AppRes, error)
|
||||
GetAppTags(ctx *gin.Context) ([]response.TagDTO, error)
|
||||
GetApp(ctx *gin.Context, key string) (*response.AppDTO, error)
|
||||
GetAppDetail(appId uint, version, appType string, readOnly ...bool) (response.AppDetailDTO, error)
|
||||
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
|
||||
Install(req request.AppInstallCreate, executeScript bool) (*model.AppInstall, error)
|
||||
SyncAppListFromRemote(taskID string) error
|
||||
GetAppUpdate() (*response.AppUpdateRes, error)
|
||||
@@ -223,6 +223,9 @@ func (a AppService) GetAppDetailByKey(appKey, version string) (response.AppDetai
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
if err = checkVllmVersionAccess(app.Key, version); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
appDetail, err := appDetailRepo.GetFirst(appDetailRepo.WithAppId(app.ID), appDetailRepo.WithVersion(version))
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
@@ -231,7 +234,7 @@ func (a AppService) GetAppDetailByKey(appKey, version string) (response.AppDetai
|
||||
return appDetailDTO, nil
|
||||
}
|
||||
|
||||
func (a AppService) GetAppDetail(appID uint, version, appType string, readOnly ...bool) (response.AppDetailDTO, error) {
|
||||
func (a AppService) GetAppDetail(appID uint, version, appType string) (response.AppDetailDTO, error) {
|
||||
var (
|
||||
appDetailDTO response.AppDetailDTO
|
||||
opts []repo.DBOption
|
||||
@@ -241,58 +244,56 @@ func (a AppService) GetAppDetail(appID uint, version, appType string, readOnly .
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
app, err := appRepo.GetFirst(repo.WithByID(detail.AppId))
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
if err = checkVllmVersionAccess(app.Key, detail.Version); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
appDetailDTO.AppDetail = detail
|
||||
appDetailDTO.Enable = true
|
||||
readOnlyMode := isDemoReadOnly(readOnly...)
|
||||
|
||||
if appType == "runtime" {
|
||||
app, err := appRepo.GetFirst(repo.WithByID(appID))
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
|
||||
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
|
||||
versionExists := fileOp.Stat(versionPath)
|
||||
if (!versionExists || detail.Update) && !readOnlyMode {
|
||||
if !fileOp.Stat(versionPath) || detail.Update {
|
||||
if err = downloadApp(app, detail, nil, nil); err != nil && !fileOp.Stat(versionPath) {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
versionExists = fileOp.Stat(versionPath)
|
||||
}
|
||||
if versionExists {
|
||||
switch app.Type {
|
||||
case constant.RuntimePHP:
|
||||
paramsPath := filepath.Join(versionPath, "data.yml")
|
||||
if !fileOp.Stat(paramsPath) {
|
||||
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", paramsPath, nil)
|
||||
}
|
||||
param, err := fileOp.GetContent(paramsPath)
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
paramMap := make(map[string]interface{})
|
||||
if err = yaml.Unmarshal(param, ¶mMap); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
appDetailDTO.Params = paramMap["additionalProperties"]
|
||||
composePath := filepath.Join(versionPath, "docker-compose.yml")
|
||||
if !fileOp.Stat(composePath) {
|
||||
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", composePath, nil)
|
||||
}
|
||||
compose, err := fileOp.GetContent(composePath)
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
composeMap := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal(compose, &composeMap); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
if service, ok := composeMap["services"]; ok {
|
||||
servicesMap := service.(map[string]interface{})
|
||||
for k := range servicesMap {
|
||||
appDetailDTO.Image = k
|
||||
}
|
||||
switch app.Type {
|
||||
case constant.RuntimePHP:
|
||||
paramsPath := filepath.Join(versionPath, "data.yml")
|
||||
if !fileOp.Stat(paramsPath) {
|
||||
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", paramsPath, nil)
|
||||
}
|
||||
param, err := fileOp.GetContent(paramsPath)
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
paramMap := make(map[string]interface{})
|
||||
if err = yaml.Unmarshal(param, ¶mMap); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
appDetailDTO.Params = paramMap["additionalProperties"]
|
||||
composePath := filepath.Join(versionPath, "docker-compose.yml")
|
||||
if !fileOp.Stat(composePath) {
|
||||
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", composePath, nil)
|
||||
}
|
||||
compose, err := fileOp.GetContent(composePath)
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
composeMap := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal(compose, &composeMap); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
if service, ok := composeMap["services"]; ok {
|
||||
servicesMap := service.(map[string]interface{})
|
||||
for k := range servicesMap {
|
||||
appDetailDTO.Image = k
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -304,7 +305,7 @@ func (a AppService) GetAppDetail(appID uint, version, appType string, readOnly .
|
||||
appDetailDTO.Params = paramMap
|
||||
}
|
||||
|
||||
if appDetailDTO.DockerCompose == "" && !readOnlyMode {
|
||||
if appDetailDTO.DockerCompose == "" {
|
||||
filename := filepath.Base(appDetailDTO.DownloadUrl)
|
||||
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(appDetailDTO.DownloadUrl, filename), "docker-compose.yml")
|
||||
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||
@@ -324,10 +325,6 @@ func (a AppService) GetAppDetail(appID uint, version, appType string, readOnly .
|
||||
|
||||
appDetailDTO.HostMode = isHostModel(appDetailDTO.DockerCompose)
|
||||
|
||||
app, err := appRepo.GetFirst(repo.WithByID(detail.AppId))
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
if err := checkLimit(app); err != nil {
|
||||
appDetailDTO.Enable = false
|
||||
}
|
||||
@@ -379,6 +376,9 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = checkVllmVersionAccess(app.Key, appDetail.Version); err != nil {
|
||||
return
|
||||
}
|
||||
if DatabaseKeys[app.Key] > 0 {
|
||||
if existDatabases, _ := databaseRepo.GetList(repo.WithByName(req.Name)); len(existDatabases) > 0 {
|
||||
err = buserr.New("ErrRemoteExist")
|
||||
@@ -488,15 +488,17 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
index++
|
||||
}
|
||||
newServiceName := strings.ToLower(appInstall.Name)
|
||||
if app.Limit == 0 && newServiceName != serviceName && len(servicesMap) == 1 {
|
||||
if app.Limit == 0 && newServiceName != serviceName && len(servicesMap) == 1 && !req.KeepServiceName {
|
||||
servicesMap[newServiceName] = servicesMap[serviceName]
|
||||
delete(servicesMap, serviceName)
|
||||
serviceName = newServiceName
|
||||
}
|
||||
appInstall.ServiceName = serviceName
|
||||
|
||||
if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
||||
return
|
||||
if !req.SkipComposeCommonConfig {
|
||||
if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
var (
|
||||
composeByte []byte
|
||||
@@ -548,6 +550,11 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
|
||||
installTask, err := task.NewTaskWithOps(appInstall.Name, task.TaskInstall, task.TaskScopeApp, req.TaskID, appInstall.ID)
|
||||
if err != nil {
|
||||
appInstall.Status = constant.StatusInstallErr
|
||||
appInstall.Message = err.Error()
|
||||
if saveErr := appInstallRepo.Save(context.Background(), appInstall); saveErr != nil {
|
||||
err = fmt.Errorf("%w; save failed install status: %v", err, saveErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -555,7 +562,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
return
|
||||
}
|
||||
|
||||
installApp := func(t *task.Task) error {
|
||||
installApp := func(t *task.Task) (err error) {
|
||||
if err = copyData(t, app, appDetail, appInstall, req); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -564,7 +571,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
return err
|
||||
}
|
||||
}
|
||||
if executeScript {
|
||||
if executeScript || req.UseLifecycleScripts {
|
||||
if err = runScript(t, appInstall, "init"); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -577,7 +584,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = upApp(t, appInstall, req.PullImage); err != nil {
|
||||
if err = upApp(t, appInstall, req.PullImage, req.UseLifecycleScripts); err != nil {
|
||||
return err
|
||||
}
|
||||
updateToolApp(appInstall)
|
||||
|
||||
@@ -13,7 +13,7 @@ type AppIgnoreUpgradeService struct {
|
||||
}
|
||||
|
||||
type IAppIgnoreUpgradeService interface {
|
||||
List(readOnly ...bool) ([]response.AppIgnoreUpgradeDTO, error)
|
||||
List() ([]response.AppIgnoreUpgradeDTO, error)
|
||||
CreateAppIgnore(req request.AppIgnoreUpgradeReq) error
|
||||
Delete(req request.ReqWithID) error
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func NewIAppIgnoreUpgradeService() IAppIgnoreUpgradeService {
|
||||
return AppIgnoreUpgradeService{}
|
||||
}
|
||||
|
||||
func (a AppIgnoreUpgradeService) List(readOnly ...bool) ([]response.AppIgnoreUpgradeDTO, error) {
|
||||
func (a AppIgnoreUpgradeService) List() ([]response.AppIgnoreUpgradeDTO, error) {
|
||||
var res []response.AppIgnoreUpgradeDTO
|
||||
ignores, err := appIgnoreUpgradeRepo.List()
|
||||
if err != nil {
|
||||
@@ -37,18 +37,14 @@ func (a AppIgnoreUpgradeService) List(readOnly ...bool) ([]response.AppIgnoreUpg
|
||||
}
|
||||
app, err := appRepo.GetFirst(repo.WithByID(ignore.AppID))
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||
}
|
||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||
continue
|
||||
}
|
||||
dto.Name = app.Name
|
||||
if ignore.Scope == "version" {
|
||||
appDetail, err := appDetailRepo.GetFirst(repo.WithByID(ignore.AppDetailID))
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||
}
|
||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||
continue
|
||||
}
|
||||
dto.Version = appDetail.Version
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -13,12 +14,14 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
@@ -42,21 +45,21 @@ type IAppInstallService interface {
|
||||
Page(req request.AppInstalledSearch) (int64, []response.AppInstallDTO, error)
|
||||
CheckExist(req request.AppInstalledInfo) (*response.AppInstalledCheck, error)
|
||||
LoadPort(req dto.OperationWithNameAndType) (int64, error)
|
||||
LoadConnInfo(req dto.OperationWithNameAndType, readOnly ...bool) (response.DatabaseConn, error)
|
||||
LoadConnInfo(req dto.OperationWithNameAndType) (response.DatabaseConn, error)
|
||||
SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstallDTO, error)
|
||||
Operate(req request.AppInstalledOperate) error
|
||||
Update(req request.AppInstalledUpdate) error
|
||||
SyncAll(systemInit bool) error
|
||||
GetServices(key string) ([]response.AppService, error)
|
||||
GetUpdateVersions(req request.AppUpdateVersion) ([]dto.AppVersion, error)
|
||||
GetParams(id uint, readOnly ...bool) (*response.AppConfig, error)
|
||||
GetParams(id uint) (*response.AppConfig, error)
|
||||
ChangeAppPort(req request.PortUpdate) error
|
||||
GetDefaultConfigByKey(key, name string) (string, error)
|
||||
DeleteCheck(installId uint, readOnly ...bool) ([]dto.AppResource, error)
|
||||
DeleteCheck(installId uint) ([]dto.AppResource, error)
|
||||
|
||||
UpdateAppConfig(req request.AppConfigUpdate) error
|
||||
GetInstallList() ([]dto.AppInstallInfo, error)
|
||||
GetAppInstallInfo(appInstallID uint, readOnly ...bool) (*response.AppInstallInfo, error)
|
||||
GetAppInstallInfo(appInstallID uint) (*response.AppInstallInfo, error)
|
||||
UpdateSort(req request.AppInstallSort) error
|
||||
}
|
||||
|
||||
@@ -123,7 +126,7 @@ func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []respo
|
||||
}
|
||||
}
|
||||
|
||||
installDTOs, _ := handleInstalled(installs, req.Update, req.Sync && !req.ReadOnly, req.CheckUpdate, req.ReadOnly)
|
||||
installDTOs, _ := handleInstalled(installs, req.Update, req.Sync, req.CheckUpdate)
|
||||
if req.Update {
|
||||
total = int64(len(installDTOs))
|
||||
}
|
||||
@@ -151,10 +154,8 @@ func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.
|
||||
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
|
||||
return res, nil
|
||||
}
|
||||
if !req.ReadOnly {
|
||||
if err = syncAppInstallStatus(&appInstall, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = syncAppInstallStatus(&appInstall, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.ContainerName = appInstall.ContainerName
|
||||
@@ -184,7 +185,7 @@ func (a *AppInstallService) LoadPort(req dto.OperationWithNameAndType) (int64, e
|
||||
return app.Port, nil
|
||||
}
|
||||
|
||||
func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType, readOnly ...bool) (response.DatabaseConn, error) {
|
||||
func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType) (response.DatabaseConn, error) {
|
||||
var data response.DatabaseConn
|
||||
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
|
||||
if err != nil {
|
||||
@@ -193,9 +194,6 @@ func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType, readO
|
||||
data.Status = app.Status
|
||||
data.Username = app.UserName
|
||||
data.Password = app.Password
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
data.Password = ""
|
||||
}
|
||||
data.ServiceName = app.ServiceName
|
||||
data.Port = app.Port
|
||||
data.ContainerName = app.ContainerName
|
||||
@@ -245,7 +243,7 @@ func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]
|
||||
}
|
||||
}
|
||||
|
||||
return handleInstalled(installs, false, !req.ReadOnly, false, req.ReadOnly)
|
||||
return handleInstalled(installs, false, true, false)
|
||||
}
|
||||
|
||||
func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
@@ -257,6 +255,9 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
return buserr.New("ErrInstallDirNotFound")
|
||||
}
|
||||
dockerComposePath := install.GetComposePath()
|
||||
if req.UseLifecycleScripts && (req.Operate == constant.Start || req.Operate == constant.Stop || req.Operate == constant.Restart) {
|
||||
return operateAppWithLifecycleScripts(install, req, nil)
|
||||
}
|
||||
switch req.Operate {
|
||||
case constant.Rebuild:
|
||||
return rebuildApp(install)
|
||||
@@ -280,12 +281,13 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
return syncAppInstallStatus(&install, false)
|
||||
case constant.Delete:
|
||||
deleteReq := request.AppInstallDelete{
|
||||
Install: install,
|
||||
DeleteBackup: req.DeleteBackup,
|
||||
ForceDelete: req.ForceDelete,
|
||||
DeleteDB: req.DeleteDB,
|
||||
DeleteImage: req.DeleteImage,
|
||||
TaskID: req.TaskID,
|
||||
Install: install,
|
||||
DeleteBackup: req.DeleteBackup,
|
||||
ForceDelete: req.ForceDelete,
|
||||
DeleteDB: req.DeleteDB,
|
||||
DeleteImage: req.DeleteImage,
|
||||
TaskID: req.TaskID,
|
||||
UseLifecycleScripts: req.UseLifecycleScripts,
|
||||
}
|
||||
if err = deleteAppInstall(deleteReq); err != nil && !req.ForceDelete {
|
||||
return err
|
||||
@@ -317,6 +319,75 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
}
|
||||
}
|
||||
|
||||
func operateAppWithLifecycleScripts(install model.AppInstall, req request.AppInstalledOperate, onFailure func(error)) error {
|
||||
taskType := task.TaskUpdate
|
||||
switch req.Operate {
|
||||
case constant.Start:
|
||||
install.Status = constant.StatusStarting
|
||||
case constant.Restart:
|
||||
taskType = task.TaskRestart
|
||||
install.Status = constant.StatusRestarting
|
||||
case constant.Stop:
|
||||
install.Status = constant.StatusWaiting
|
||||
default:
|
||||
return errors.New("lifecycle script operation not supported")
|
||||
}
|
||||
install.Message = ""
|
||||
if err := appInstallRepo.Save(context.Background(), &install); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
operationTask, err := task.NewTaskWithOps(install.Name, taskType, task.TaskScopeApp, req.TaskID, install.ID)
|
||||
if err != nil {
|
||||
install.Status = constant.StatusUpErr
|
||||
install.Message = err.Error()
|
||||
if saveErr := appInstallRepo.Save(context.Background(), &install); saveErr != nil {
|
||||
return fmt.Errorf("%w; save failed operation status: %v", err, saveErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
operation := string(req.Operate)
|
||||
operationTask.AddSubTaskWithOps(
|
||||
task.GetTaskName(install.Name, taskType, task.TaskScopeApp),
|
||||
func(t *task.Task) error {
|
||||
if err := runScript(t, &install, operation); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Operate == constant.Stop {
|
||||
install.Status = constant.StatusStopped
|
||||
install.Message = ""
|
||||
return appInstallRepo.Save(context.Background(), &install)
|
||||
}
|
||||
containerNames, err := getContainerNames(install)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(containerNames) == 0 {
|
||||
return buserr.WithName("ErrContainerNotFound", install.Name)
|
||||
}
|
||||
install.ContainerName = strings.Join(containerNames, ",")
|
||||
install.Status = constant.StatusRunning
|
||||
install.Message = ""
|
||||
return appInstallRepo.Save(context.Background(), &install)
|
||||
},
|
||||
nil,
|
||||
0,
|
||||
time.Hour,
|
||||
)
|
||||
go func() {
|
||||
if taskErr := operationTask.Execute(); taskErr != nil {
|
||||
if onFailure != nil {
|
||||
onFailure(taskErr)
|
||||
return
|
||||
}
|
||||
install.Status = constant.StatusUpErr
|
||||
install.Message = taskErr.Error()
|
||||
_ = appInstallRepo.Save(context.Background(), &install)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AppInstallService) UpdateAppConfig(req request.AppConfigUpdate) error {
|
||||
installed, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
|
||||
if err != nil {
|
||||
@@ -379,8 +450,10 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
||||
return err
|
||||
if !req.SkipComposeCommonConfig {
|
||||
if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
composeByte, err := yaml.Marshal(composeMap)
|
||||
if err != nil {
|
||||
@@ -413,7 +486,7 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backupEnvMaps := oldEnvMaps
|
||||
backupEnvMaps := maps.Clone(oldEnvMaps)
|
||||
handleMap(req.Params, oldEnvMaps)
|
||||
paramByte, err := json.Marshal(oldEnvMaps)
|
||||
if err != nil {
|
||||
@@ -425,13 +498,32 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(installed.DockerCompose), constant.DirPerm)
|
||||
if err := rebuildApp(installed); err != nil {
|
||||
restoreConfig := func(operationErr error) {
|
||||
_ = env.Write(backupEnvMaps, envPath)
|
||||
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(backupDockerCompose), constant.DirPerm)
|
||||
failed := oldInstalled
|
||||
failed.Status = constant.StatusUpErr
|
||||
failed.Message = operationErr.Error()
|
||||
_ = appInstallRepo.Save(context.Background(), &failed)
|
||||
}
|
||||
if req.UseLifecycleScripts {
|
||||
err = operateAppWithLifecycleScripts(installed, request.AppInstalledOperate{
|
||||
InstallId: installed.ID,
|
||||
Operate: constant.Restart,
|
||||
TaskID: req.TaskID,
|
||||
UseLifecycleScripts: true,
|
||||
}, restoreConfig)
|
||||
} else {
|
||||
err = rebuildApp(installed)
|
||||
}
|
||||
if err != nil {
|
||||
restoreConfig(err)
|
||||
return err
|
||||
}
|
||||
installed.Status = constant.StatusRunning
|
||||
_ = appInstallRepo.Save(context.Background(), &installed)
|
||||
if !req.UseLifecycleScripts {
|
||||
installed.Status = constant.StatusRunning
|
||||
_ = appInstallRepo.Save(context.Background(), &installed)
|
||||
}
|
||||
|
||||
proxyChanged := hasAppInstallProxyPassChanged(&oldInstalled, &installed)
|
||||
currentProxy, currentProxyErr := getAppInstallProxyPass(&installed)
|
||||
@@ -480,11 +572,15 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
|
||||
return err
|
||||
}
|
||||
for _, i := range allList {
|
||||
if i.Status == constant.StatusInstalling || i.Status == constant.StatusUpgrading || i.Status == constant.StatusRebuilding || i.Status == constant.StatusUninstalling {
|
||||
if appInstallOperationInterruptedOnRestart(i.Status) {
|
||||
if systemInit {
|
||||
i.Status = constant.StatusError
|
||||
i.Message = "1Panel restart causes the task to terminate"
|
||||
_ = appInstallRepo.Save(context.Background(), &i)
|
||||
i.Status = appInstallOperationFailureStatus(i.Status)
|
||||
i.Message = constant.InterruptedMsg
|
||||
if err := appInstallRepo.Save(context.Background(), &i); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := syncAppInstallStatus(&i, false); err != nil {
|
||||
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -588,6 +684,9 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
|
||||
return versions, err
|
||||
}
|
||||
for _, detail := range details {
|
||||
if !canAccessVllmVersion(app.Key, detail.Version) {
|
||||
continue
|
||||
}
|
||||
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(detail.ID), appIgnoreUpgradeRepo.WithScope("version"))
|
||||
if len(ignores) > 0 {
|
||||
continue
|
||||
@@ -669,7 +768,7 @@ func (a *AppInstallService) ChangeAppPort(req request.PortUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AppInstallService) DeleteCheck(installID uint, readOnly ...bool) ([]dto.AppResource, error) {
|
||||
func (a *AppInstallService) DeleteCheck(installID uint) ([]dto.AppResource, error) {
|
||||
var res []dto.AppResource
|
||||
appInstall, err := appInstallRepo.GetFirst(repo.WithByID(installID))
|
||||
if err != nil {
|
||||
@@ -690,7 +789,7 @@ func (a *AppInstallService) DeleteCheck(installID uint, readOnly ...bool) ([]dto
|
||||
Type: "app",
|
||||
Name: linkInstall.Name,
|
||||
})
|
||||
} else if !isDemoReadOnly(readOnly...) {
|
||||
} else {
|
||||
_ = appInstallResourceRepo.DeleteBy(context.Background(), appInstallResourceRepo.WithAppInstallId(resource.AppInstallId))
|
||||
}
|
||||
}
|
||||
@@ -728,7 +827,7 @@ func (a *AppInstallService) GetDefaultConfigByKey(key, name string) (string, err
|
||||
return string(contentByte), nil
|
||||
}
|
||||
|
||||
func (a *AppInstallService) GetParams(id uint, readOnly ...bool) (*response.AppConfig, error) {
|
||||
func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
var (
|
||||
params []response.AppParam
|
||||
appForm dto.AppForm
|
||||
@@ -823,14 +922,8 @@ func (a *AppInstallService) GetParams(id uint, readOnly ...bool) (*response.AppC
|
||||
}
|
||||
}
|
||||
|
||||
readOnlyMode := isDemoReadOnly(readOnly...)
|
||||
if readOnlyMode {
|
||||
redactSensitiveAppParams(params)
|
||||
}
|
||||
config := getAppCommonConfig(envs)
|
||||
if !readOnlyMode {
|
||||
config.DockerCompose = install.DockerCompose
|
||||
}
|
||||
config.DockerCompose = install.DockerCompose
|
||||
res.Params = params
|
||||
if config.ContainerName == "" {
|
||||
config.ContainerName = install.ContainerName
|
||||
@@ -840,17 +933,41 @@ func (a *AppInstallService) GetParams(id uint, readOnly ...bool) (*response.AppC
|
||||
res.RestartPolicy = getRestartPolicy(install.DockerCompose)
|
||||
res.WebUI = install.WebUI
|
||||
res.Type = install.App.Type
|
||||
if !readOnlyMode {
|
||||
if rawCompose, err := getUpgradeCompose(install, detail); err == nil {
|
||||
res.RawCompose = rawCompose
|
||||
}
|
||||
if rawCompose, err := getUpgradeCompose(install, detail); err == nil {
|
||||
res.RawCompose = rawCompose
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func syncAppInstallStatus(appInstall *model.AppInstall, force bool) error {
|
||||
if appInstall.Status == constant.StatusInstalling || appInstall.Status == constant.StatusRebuilding || appInstall.Status == constant.StatusUpgrading || appInstall.Status == constant.StatusUninstalling {
|
||||
operation := ""
|
||||
switch appInstall.Status {
|
||||
case constant.StatusInstalling:
|
||||
operation = task.TaskInstall
|
||||
case constant.StatusUpgrading:
|
||||
operation = task.TaskUpgrade
|
||||
case constant.StatusRebuilding:
|
||||
operation = task.TaskBuild
|
||||
case constant.StatusUninstalling:
|
||||
operation = task.TaskUninstall
|
||||
case constant.StatusStarting, constant.StatusWaiting:
|
||||
operation = task.TaskUpdate
|
||||
case constant.StatusRestarting:
|
||||
operation = task.TaskRestart
|
||||
}
|
||||
if operation != "" {
|
||||
if err := reconcileAppInstallTaskFailure(appInstall, operation, global.DB, global.TaskDB); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
switch appInstall.Status {
|
||||
case constant.StatusInstalling, constant.StatusRebuilding, constant.StatusUpgrading, constant.StatusUninstalling,
|
||||
constant.StatusStarting, constant.StatusRestarting, constant.StatusWaiting:
|
||||
return nil
|
||||
case constant.StatusInstallErr, constant.StatusUpgradeErr, constant.StatusUpErr:
|
||||
if !force {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
cli, err := docker.NewClient()
|
||||
if err != nil {
|
||||
@@ -971,25 +1088,20 @@ func updateInstallInfoInDB(appKey, appName, param string, value interface{}) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AppInstallService) GetAppInstallInfo(installID uint, readOnly ...bool) (*response.AppInstallInfo, error) {
|
||||
func (a *AppInstallService) GetAppInstallInfo(installID uint) (*response.AppInstallInfo, error) {
|
||||
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(installID))
|
||||
if appInstall.ID == 0 {
|
||||
return &response.AppInstallInfo{
|
||||
Status: constant.StatusDeleted,
|
||||
}, nil
|
||||
}
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
_ = syncAppInstallStatus(&appInstall, false)
|
||||
}
|
||||
_ = syncAppInstallStatus(&appInstall, false)
|
||||
appInstall, _ = appInstallRepo.GetFirst(repo.WithByID(installID))
|
||||
var envMap map[string]interface{}
|
||||
err := json.Unmarshal([]byte(appInstall.Env), &envMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
redactSensitiveValues(envMap)
|
||||
}
|
||||
res := &response.AppInstallInfo{
|
||||
ID: appInstall.ID,
|
||||
Name: appInstall.Name,
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func appInstallOperationFailureStatus(status string) string {
|
||||
switch status {
|
||||
case constant.StatusInstalling:
|
||||
return constant.StatusInstallErr
|
||||
case constant.StatusUpgrading:
|
||||
return constant.StatusUpgradeErr
|
||||
case constant.StatusUninstalling:
|
||||
return constant.StatusError
|
||||
default:
|
||||
return constant.StatusUpErr
|
||||
}
|
||||
}
|
||||
|
||||
func appInstallOperationPending(status string) bool {
|
||||
switch status {
|
||||
case constant.StatusInstalling, constant.StatusRebuilding, constant.StatusUpgrading, constant.StatusUninstalling,
|
||||
constant.StatusStarting, constant.StatusRestarting, constant.StatusWaiting:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func appInstallOperationInterruptedOnRestart(status string) bool {
|
||||
// ReStarting can also describe a container's Docker restart policy.
|
||||
return appInstallOperationPending(status) && status != constant.StatusRestarting
|
||||
}
|
||||
|
||||
func reconcileAppInstallTaskFailure(install *model.AppInstall, operation string, appDB, taskDB *gorm.DB) error {
|
||||
if !appInstallOperationPending(install.Status) {
|
||||
return nil
|
||||
}
|
||||
var operationTask model.Task
|
||||
err := taskDB.Where("type = ? AND resource_id = ? AND operate = ?", "App", install.ID, operation).
|
||||
Order("created_at DESC").First(&operationTask).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if operationTask.Status != constant.StatusFailed && operationTask.Status != constant.StatusCanceled {
|
||||
return nil
|
||||
}
|
||||
// A previous operation must not turn a newly queued retry into a failure.
|
||||
if operationTask.CreatedAt.Before(install.UpdatedAt) &&
|
||||
(operationTask.EndAt.IsZero() || operationTask.EndAt.Before(install.UpdatedAt)) {
|
||||
return nil
|
||||
}
|
||||
message := operationTask.ErrorMsg
|
||||
if message == "" {
|
||||
message = "the application operation task failed"
|
||||
}
|
||||
status := appInstallOperationFailureStatus(install.Status)
|
||||
result := appDB.Model(&model.AppInstall{}).
|
||||
Where("id = ? AND status = ? AND updated_at = ?", install.ID, install.Status, install.UpdatedAt).
|
||||
Updates(map[string]interface{}{"status": status, "message": message})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected > 0 {
|
||||
install.Status = status
|
||||
install.Message = message
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
func operationStatusTestDB(t *testing.T) (*gorm.DB, *gorm.DB, model.AppInstall) {
|
||||
t.Helper()
|
||||
open := func(name string, schema interface{}) *gorm.DB {
|
||||
db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), name)), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.AutoMigrate(schema); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
sqlDB, _ := db.DB()
|
||||
if sqlDB != nil {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
})
|
||||
return db
|
||||
}
|
||||
appDB := open("apps.db", &model.AppInstall{})
|
||||
taskDB := open("tasks.db", &model.Task{})
|
||||
install := model.AppInstall{
|
||||
Name: "gb10-ds-vision", AppId: 1, AppDetailId: 1,
|
||||
Version: "nvidia-gb10-dspark-0.1.1", ContainerName: "vllm-dspark-1", ServiceName: "vllm-dspark",
|
||||
Status: constant.StatusStarting, Env: `{"MODEL_DIR":"/models/vision"}`,
|
||||
}
|
||||
if err := appDB.Omit(clause.Associations).Create(&install).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return appDB, taskDB, install
|
||||
}
|
||||
|
||||
func TestAppInstallFailedTaskRecoversPendingState(t *testing.T) {
|
||||
for _, state := range []string{constant.StatusStarting, constant.StatusWaiting, constant.StatusRestarting, constant.StatusInstalling} {
|
||||
t.Run(state, func(t *testing.T) {
|
||||
appDB, taskDB, install := operationStatusTestDB(t)
|
||||
install.Status = state
|
||||
if err := appDB.Omit(clause.Associations).Save(&install).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
task := model.Task{ID: "failed", Type: "App", Operate: "TaskUpdate", ResourceID: install.ID,
|
||||
Status: constant.StatusFailed, ErrorMsg: "model startup script failed", CreatedAt: install.UpdatedAt.Add(time.Second), EndAt: install.UpdatedAt.Add(2 * time.Second)}
|
||||
if err := taskDB.Create(&task).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := reconcileAppInstallTaskFailure(&install, task.Operate, appDB, taskDB); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := constant.StatusUpErr
|
||||
if state == constant.StatusInstalling {
|
||||
want = constant.StatusInstallErr
|
||||
}
|
||||
var stored model.AppInstall
|
||||
if err := appDB.First(&stored, install.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if install.Status != want || stored.Status != want || stored.Message != task.ErrorMsg || stored.Env != install.Env {
|
||||
t.Fatalf("recovered state = %s/%s, message %q, env %q", install.Status, stored.Status, stored.Message, stored.Env)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppInstallPendingStateKeepsActiveAndUnrelatedTasks(t *testing.T) {
|
||||
for _, taskStatus := range []string{constant.StatusExecuting, constant.StatusSuccess, "missing", "previous-failure", "other-resource"} {
|
||||
t.Run(taskStatus, func(t *testing.T) {
|
||||
appDB, taskDB, install := operationStatusTestDB(t)
|
||||
task := model.Task{ID: "task", Type: "App", Operate: "TaskUpdate", ResourceID: install.ID,
|
||||
Status: taskStatus, CreatedAt: install.UpdatedAt.Add(time.Second), EndAt: install.UpdatedAt.Add(2 * time.Second)}
|
||||
if taskStatus == "previous-failure" {
|
||||
task.Status = constant.StatusFailed
|
||||
task.CreatedAt = install.UpdatedAt.Add(-2 * time.Hour)
|
||||
task.EndAt = install.UpdatedAt.Add(-time.Hour)
|
||||
}
|
||||
if taskStatus == "other-resource" {
|
||||
task.Status = constant.StatusFailed
|
||||
task.ResourceID++
|
||||
}
|
||||
if taskStatus != "missing" {
|
||||
if err := taskDB.Create(&task).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := reconcileAppInstallTaskFailure(&install, task.Operate, appDB, taskDB); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if install.Status != constant.StatusStarting {
|
||||
t.Fatalf("unrelated or active task changed pending state to %s", install.Status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppInstallRecoveryDoesNotOverwriteConcurrentRetry(t *testing.T) {
|
||||
appDB, taskDB, install := operationStatusTestDB(t)
|
||||
task := model.Task{ID: "failed", Type: "App", Operate: "TaskUpdate", ResourceID: install.ID,
|
||||
Status: constant.StatusCanceled, ErrorMsg: "interrupted", CreatedAt: install.UpdatedAt.Add(time.Second), EndAt: install.UpdatedAt.Add(2 * time.Second)}
|
||||
if err := taskDB.Create(&task).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := appDB.Callback().Update().Before("gorm:update").Register("new-operation", func(db *gorm.DB) {
|
||||
if err := db.Exec("UPDATE app_installs SET updated_at = ?, message = ? WHERE id = ?", install.UpdatedAt.Add(time.Hour), "new attempt", install.ID).Error; err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := reconcileAppInstallTaskFailure(&install, task.Operate, appDB, taskDB); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var stored model.AppInstall
|
||||
if err := appDB.First(&stored, install.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stored.Status != constant.StatusStarting || stored.Message != "new attempt" {
|
||||
t.Fatalf("recovery overwrote a newer operation: %s, %s", stored.Status, stored.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppInstallRestartRecoveryCoversLifecycleStates(t *testing.T) {
|
||||
for _, state := range []string{constant.StatusStarting, constant.StatusWaiting, constant.StatusInstalling} {
|
||||
if !appInstallOperationInterruptedOnRestart(state) {
|
||||
t.Errorf("restart recovery omitted %s", state)
|
||||
}
|
||||
}
|
||||
for _, state := range []string{constant.StatusRunning, constant.StatusStopped, constant.StatusRestarting} {
|
||||
if appInstallOperationInterruptedOnRestart(state) {
|
||||
t.Errorf("restart recovery changes a Docker runtime state: %s", state)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,14 +40,13 @@ const (
|
||||
appUpgradeDown
|
||||
appUpgradeMutated
|
||||
appUpgradeStarted
|
||||
appUpgradeReady
|
||||
appUpgradeCommitted
|
||||
)
|
||||
|
||||
const composeServiceLabel = "com.docker.compose.service"
|
||||
|
||||
var appUpgradeLocks sync.Map
|
||||
|
||||
const composeServiceLabel = "com.docker.compose.service"
|
||||
|
||||
type appUpgradeSnapshot interface {
|
||||
Restore() error
|
||||
Cleanup()
|
||||
@@ -107,6 +106,9 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = checkVllmVersionAccess(install.App.Key, detail.Version); err != nil {
|
||||
return err
|
||||
}
|
||||
if install.App.Key == vllmAppKeyForUpgrade && !isVllmUpgradeVersionAllowed(install.Version, detail.Version, loadVllmImageFromEnv(install.Env)) {
|
||||
return errors.New("vLLM can only upgrade within the same image type")
|
||||
}
|
||||
@@ -436,15 +438,14 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
|
||||
t.LogSuccess(logStr)
|
||||
u.phase = appUpgradeStarted
|
||||
|
||||
t.LogStart(i18n.GetMsgByKey("UpgradeWaitReady"))
|
||||
containerNames, err := waitAppContainersReady(context.Background(), u.candidate)
|
||||
if err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("UpgradeWaitReady"), err)
|
||||
return err
|
||||
containerNames, discoverErr := discoverUpgradeContainerNames(u.candidate, u.envContent)
|
||||
if discoverErr != nil {
|
||||
t.Logf("WARNING: discover upgraded application containers failed: %v", discoverErr)
|
||||
} else if len(containerNames) > 0 {
|
||||
u.candidate.ContainerName = strings.Join(containerNames, ",")
|
||||
} else {
|
||||
t.Log("WARNING: no containers found for the upgraded application")
|
||||
}
|
||||
t.LogSuccess(i18n.GetMsgByKey("UpgradeWaitReady"))
|
||||
u.phase = appUpgradeReady
|
||||
u.candidate.ContainerName = strings.Join(containerNames, ",")
|
||||
u.candidate.Status = constant.StatusRunning
|
||||
u.candidate.Message = ""
|
||||
|
||||
@@ -459,9 +460,21 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
// Upgrades deliberately keep the user's nginx.conf, so corrected gzip
|
||||
// defaults shipped with a new version would never reach existing
|
||||
// installations. Rewrite only an untouched factory configuration, and
|
||||
// never fail the upgrade over it.
|
||||
if gzipErr := upgradeStockNginxGzipConfig(u.candidate); gzipErr != nil {
|
||||
t.Logf("WARNING: update stock gzip configuration failed, keeping the current one: %v", gzipErr)
|
||||
}
|
||||
} else if err = appInstallRepo.Save(context.Background(), &u.candidate); err != nil {
|
||||
return err
|
||||
}
|
||||
if discoverErr == nil && len(containerNames) > 0 {
|
||||
if syncErr := syncAppInstallStatus(&u.candidate, true); syncErr != nil {
|
||||
t.Logf("WARNING: sync upgraded application status failed: %v", syncErr)
|
||||
}
|
||||
}
|
||||
u.phase = appUpgradeCommitted
|
||||
u.deleteOldImages(t)
|
||||
return nil
|
||||
@@ -581,9 +594,6 @@ func (u *appUpgradeContext) rollback(t *task.Task) (rollbackErr error) {
|
||||
}
|
||||
|
||||
func (u *appUpgradeContext) finishRollback() error {
|
||||
if _, err := waitAppContainersReady(context.Background(), u.original); err != nil {
|
||||
return err
|
||||
}
|
||||
restored := u.original
|
||||
if err := appInstallRepo.Save(context.Background(), &restored); err != nil {
|
||||
return err
|
||||
@@ -715,6 +725,20 @@ func renderUpgradeEnv(install *model.AppInstall, original []byte) ([]byte, error
|
||||
return nil, err
|
||||
}
|
||||
handleMap(envs, params)
|
||||
if install.App.Key == "openlist" {
|
||||
// The upgrade script updates this too late for the pre-pull phase.
|
||||
image := "openlistteam/openlist:v" + strings.TrimPrefix(install.Version, "v")
|
||||
if preInstalled := params["PRE_INSTALLED"]; preInstalled != "" {
|
||||
image += "-" + preInstalled
|
||||
}
|
||||
params["OPENLIST_IMAGE"] = image
|
||||
envs["OPENLIST_IMAGE"] = image
|
||||
content, err := json.Marshal(envs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
install.Env = string(content)
|
||||
}
|
||||
if install.App.Key == constant.AppOpenresty {
|
||||
for _, key := range []string{"CONTAINER_PACKAGE_URL", "RESTY_ADD_PACKAGE_BUILDDEPS", "RESTY_CONFIG_OPTIONS_MORE"} {
|
||||
if value, ok := originalEnv[key]; ok {
|
||||
@@ -857,28 +881,7 @@ func (s *upgradeFileSnapshot) Cleanup() {
|
||||
}
|
||||
}
|
||||
|
||||
type appContainerReadinessClient interface {
|
||||
ContainerList(context.Context, container.ListOptions) ([]container.Summary, error)
|
||||
ContainerInspect(context.Context, string) (container.InspectResponse, error)
|
||||
}
|
||||
|
||||
func waitAppContainersReady(ctx context.Context, install model.AppInstall) ([]string, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
return waitAppContainersReadyWithClient(ctx, client, install)
|
||||
}
|
||||
|
||||
func waitAppContainersReadyWithClient(ctx context.Context, client appContainerReadinessClient, install model.AppInstall) ([]string, error) {
|
||||
envContent, err := os.ReadFile(install.GetEnvPath())
|
||||
if err != nil {
|
||||
envContent, err = renderUpgradeEnv(&install, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
func discoverUpgradeContainerNames(install model.AppInstall, envContent []byte) ([]string, error) {
|
||||
project, err := docker.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), envContent, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -892,36 +895,24 @@ func waitAppContainersReadyWithClient(ctx context.Context, client appContainerRe
|
||||
if len(expectedServices) == 0 {
|
||||
return strings.Split(install.ContainerName, ","), nil
|
||||
}
|
||||
options := container.ListOptions{
|
||||
All: true,
|
||||
Filters: filters.NewArgs(
|
||||
filters.Arg("label", composeWorkdirLabel+"="+install.GetPath()),
|
||||
),
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
containers, err := client.ContainerList(ctx, options)
|
||||
defer client.Close()
|
||||
containers, err := client.ContainerList(context.Background(), container.ListOptions{
|
||||
All: true,
|
||||
Filters: filters.NewArgs(filters.Arg("label", composeWorkdirLabel+"="+install.GetPath())),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
foundServices := make(map[string]bool, len(expectedServices))
|
||||
containerNames := make([]string, 0, len(containers))
|
||||
for _, item := range containers {
|
||||
serviceName := item.Labels[composeServiceLabel]
|
||||
if _, ok := expectedServices[serviceName]; !ok {
|
||||
continue
|
||||
}
|
||||
if err = waitContainerReady(ctx, client, item.ID); err != nil {
|
||||
return nil, fmt.Errorf("container %s is not ready: %w", serviceName, err)
|
||||
}
|
||||
foundServices[serviceName] = true
|
||||
if len(item.Names) > 0 {
|
||||
if _, ok := expectedServices[item.Labels[composeServiceLabel]]; ok && len(item.Names) > 0 {
|
||||
containerNames = append(containerNames, strings.TrimPrefix(item.Names[0], "/"))
|
||||
}
|
||||
}
|
||||
for serviceName := range expectedServices {
|
||||
if !foundServices[serviceName] {
|
||||
return nil, fmt.Errorf("container for service %s was not created", serviceName)
|
||||
}
|
||||
}
|
||||
sort.Strings(containerNames)
|
||||
return containerNames, nil
|
||||
}
|
||||
|
||||
@@ -53,62 +53,6 @@ var (
|
||||
Delete DatabaseOp = "delete"
|
||||
)
|
||||
|
||||
func isDemoReadOnly(readOnly ...bool) bool {
|
||||
return global.CONF.Base.IsDemo || len(readOnly) > 0 && readOnly[0]
|
||||
}
|
||||
|
||||
func normalizeConfigKey(key string) string {
|
||||
return strings.ToLower(strings.NewReplacer("_", "", "-", "", ".", "", " ", "").Replace(key))
|
||||
}
|
||||
|
||||
func isSensitiveConfigKey(key string) bool {
|
||||
normalized := normalizeConfigKey(key)
|
||||
for _, marker := range []string{"password", "passwd", "passphrase", "secret", "token", "credential", "privatekey", "authorization"} {
|
||||
if strings.Contains(normalized, marker) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return normalized == "key" || strings.HasSuffix(normalized, "key")
|
||||
}
|
||||
|
||||
func redactSensitiveValues(values map[string]interface{}) {
|
||||
redactSensitiveData(values)
|
||||
}
|
||||
|
||||
func redactSensitiveData(value interface{}) {
|
||||
switch data := value.(type) {
|
||||
case map[string]interface{}:
|
||||
for key, item := range data {
|
||||
if isSensitiveConfigKey(key) {
|
||||
data[key] = ""
|
||||
} else {
|
||||
redactSensitiveData(item)
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for _, item := range data {
|
||||
redactSensitiveData(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func redactSensitiveAppParams(params []response.AppParam) {
|
||||
for i := range params {
|
||||
if strings.EqualFold(params[i].Type, "password") || isSensitiveConfigKey(params[i].Key) {
|
||||
params[i].Value = ""
|
||||
params[i].ShowValue = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func redactSensitiveEnvironments(environments []request.Environment) {
|
||||
for i := range environments {
|
||||
if isSensitiveConfigKey(environments[i].Key) {
|
||||
environments[i].Value = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkPort(key string, params map[string]interface{}) (int, error) {
|
||||
port, ok := params[key]
|
||||
if ok {
|
||||
@@ -408,15 +352,33 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
||||
if dir != nil {
|
||||
logStr := i18n.GetMsgByKey("Stop") + i18n.GetMsgByKey("App")
|
||||
t.Log(logStr)
|
||||
cleanupFailed := false
|
||||
|
||||
out, err := compose.Down(install.GetComposePath())
|
||||
if err != nil && !deleteReq.ForceDelete {
|
||||
return handleErr(install, err, out)
|
||||
if deleteReq.UseLifecycleScripts {
|
||||
if scriptErr := runScript(t, &install, "uninstall"); scriptErr != nil {
|
||||
cleanupFailed = true
|
||||
if !deleteReq.ForceDelete {
|
||||
return scriptErr
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out, downErr := compose.Down(install.GetComposePath())
|
||||
if downErr != nil {
|
||||
cleanupFailed = true
|
||||
if !deleteReq.ForceDelete {
|
||||
return handleErr(install, downErr, out)
|
||||
}
|
||||
}
|
||||
if scriptErr := runScript(t, &install, "uninstall"); scriptErr != nil {
|
||||
cleanupFailed = true
|
||||
if !deleteReq.ForceDelete {
|
||||
_, _ = compose.Up(install.GetComposePath())
|
||||
return scriptErr
|
||||
}
|
||||
}
|
||||
}
|
||||
t.LogSuccess(logStr)
|
||||
if err = runScript(t, &install, "uninstall"); err != nil {
|
||||
_, _ = compose.Up(install.GetComposePath())
|
||||
return err
|
||||
if !cleanupFailed {
|
||||
t.LogSuccess(logStr)
|
||||
}
|
||||
if deleteReq.DeleteImage {
|
||||
content, err := op.GetContent(install.GetEnvPath())
|
||||
@@ -516,8 +478,9 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
||||
}
|
||||
uninstallTask.AddSubTask(task.GetTaskName(install.Name, task.TaskUninstall, task.TaskScopeApp), uninstall, nil)
|
||||
go func() {
|
||||
if err := uninstallTask.Execute(); err != nil && !deleteReq.ForceDelete {
|
||||
if err := uninstallTask.Execute(); err != nil {
|
||||
install.Status = constant.StatusError
|
||||
install.Message = err.Error()
|
||||
_ = appInstallRepo.Save(context.Background(), &install)
|
||||
}
|
||||
}()
|
||||
@@ -1055,6 +1018,12 @@ func runScript(task *task.Task, appInstall *model.AppInstall, operate string) er
|
||||
scriptPath = path.Join(workDir, "scripts", "upgrade.sh")
|
||||
case "uninstall":
|
||||
scriptPath = path.Join(workDir, "scripts", "uninstall.sh")
|
||||
case "start":
|
||||
scriptPath = path.Join(workDir, "scripts", "start.sh")
|
||||
case "stop":
|
||||
scriptPath = path.Join(workDir, "scripts", "stop.sh")
|
||||
case "restart":
|
||||
scriptPath = path.Join(workDir, "scripts", "restart.sh")
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(scriptPath) {
|
||||
@@ -1064,7 +1033,11 @@ func runScript(task *task.Task, appInstall *model.AppInstall, operate string) er
|
||||
logStr := i18n.GetWithName("ExecShell", operate)
|
||||
task.LogStart(logStr)
|
||||
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute), cmd.WithWorkDir(workDir))
|
||||
timeout := 10 * time.Minute
|
||||
if operate == "start" || operate == "restart" {
|
||||
timeout = time.Hour
|
||||
}
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(timeout), cmd.WithWorkDir(workDir), cmd.WithTask(*task))
|
||||
if err := cmdMgr.Run("bash", scriptPath); err != nil {
|
||||
task.LogFailedWithErr(logStr, err)
|
||||
return err
|
||||
@@ -1099,12 +1072,15 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) error {
|
||||
func upApp(task *task.Task, appInstall *model.AppInstall, pullImages, useLifecycleScripts bool) error {
|
||||
upProject := func(appInstall *model.AppInstall) (err error) {
|
||||
var (
|
||||
out string
|
||||
errMsg string
|
||||
)
|
||||
if useLifecycleScripts {
|
||||
return runScript(task, appInstall, "start")
|
||||
}
|
||||
if pullImages && appInstall.App.Type != "php" {
|
||||
envByte, err := files.NewFileOp().GetContent(appInstall.GetEnvPath())
|
||||
if err != nil {
|
||||
@@ -1430,8 +1406,8 @@ func handleErr(install model.AppInstall, err error, out string) error {
|
||||
}
|
||||
|
||||
func doNotNeedSync(installed model.AppInstall) bool {
|
||||
return installed.Status == constant.StatusInstalling || installed.Status == constant.StatusRebuilding || installed.Status == constant.StatusUpgrading ||
|
||||
installed.Status == constant.StatusSyncing || installed.Status == constant.StatusUninstalling || installed.Status == constant.StatusInstallErr
|
||||
return appInstallOperationPending(installed.Status) || installed.Status == constant.StatusSyncing ||
|
||||
installed.Status == constant.StatusInstallErr || installed.Status == constant.StatusUpgradeErr || installed.Status == constant.StatusUpErr
|
||||
}
|
||||
|
||||
func synAppInstall(containers map[string]container.Summary, appInstall *model.AppInstall, force bool) {
|
||||
@@ -1443,9 +1419,7 @@ func synAppInstall(containers map[string]container.Summary, appInstall *model.Ap
|
||||
}
|
||||
appInstall.Status = constant.StatusError
|
||||
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
|
||||
if !global.CONF.Base.IsDemo {
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
}
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
return
|
||||
}
|
||||
notFoundNames := make([]string, 0)
|
||||
@@ -1504,12 +1478,10 @@ func synAppInstall(containers map[string]container.Summary, appInstall *model.Ap
|
||||
appInstall.Message = msg
|
||||
appInstall.Status = constant.StatusUnHealthy
|
||||
}
|
||||
if !global.CONF.Base.IsDemo {
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
}
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
}
|
||||
|
||||
func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpdate, readOnly bool) ([]response.AppInstallDTO, error) {
|
||||
func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpdate bool) ([]response.AppInstallDTO, error) {
|
||||
var (
|
||||
res []response.AppInstallDTO
|
||||
containersMap map[string]container.Summary
|
||||
@@ -1529,6 +1501,11 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
||||
}
|
||||
|
||||
for _, installed := range appInstallList {
|
||||
if sync && appInstallOperationPending(installed.Status) {
|
||||
if err := syncAppInstallStatus(&installed, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if updated && ignoreUpdate(installed) {
|
||||
continue
|
||||
}
|
||||
@@ -1540,9 +1517,6 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
||||
resourceKeys := getAppInstallResourceKeys(installed.ID)
|
||||
envMap := make(map[string]interface{})
|
||||
_ = json.Unmarshal([]byte(installed.Env), &envMap)
|
||||
if isDemoReadOnly(readOnly) {
|
||||
redactSensitiveValues(envMap)
|
||||
}
|
||||
installDTO := response.AppInstallDTO{
|
||||
ID: installed.ID,
|
||||
Name: installed.Name,
|
||||
@@ -1587,9 +1561,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
||||
continue
|
||||
}
|
||||
|
||||
if !isDemoReadOnly(readOnly) {
|
||||
installDTO.DockerCompose = installed.DockerCompose
|
||||
}
|
||||
installDTO.DockerCompose = installed.DockerCompose
|
||||
installDTO.IsEdit = isEditCompose(installed)
|
||||
|
||||
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
|
||||
@@ -2294,6 +2266,9 @@ func getAppVersions(key string, details []model.AppDetail) []string {
|
||||
hasLatest := false
|
||||
latestVersion := ""
|
||||
for _, detail := range details {
|
||||
if !canAccessVllmVersion(key, detail.Version) {
|
||||
continue
|
||||
}
|
||||
if key != "mssql" && strings.Contains(detail.Version, "latest") {
|
||||
hasLatest = true
|
||||
latestVersion = detail.Version
|
||||
|
||||
@@ -33,7 +33,7 @@ type IBackupService interface {
|
||||
CheckUsed(name string, isPublic bool) error
|
||||
|
||||
LoadBackupOptions() ([]dto.BackupOption, error)
|
||||
SearchWithPage(search dto.SearchPageWithType, readOnly ...bool) (int64, interface{}, error)
|
||||
SearchWithPage(search dto.SearchPageWithType) (int64, interface{}, error)
|
||||
Create(backupDto dto.BackupOperate) error
|
||||
CheckConn(req dto.BackupOperate) dto.BackupCheckRes
|
||||
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
|
||||
@@ -80,7 +80,7 @@ func (u *BackupService) GetLocalDir() (string, error) {
|
||||
return account.BackupPath, nil
|
||||
}
|
||||
|
||||
func (u *BackupService) SearchWithPage(req dto.SearchPageWithType, readOnly ...bool) (int64, interface{}, error) {
|
||||
func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, interface{}, error) {
|
||||
options := []repo.DBOption{repo.WithOrderDesc("created_at")}
|
||||
if len(req.Type) != 0 {
|
||||
options = append(options, repo.WithByType(req.Type))
|
||||
@@ -128,36 +128,11 @@ func (u *BackupService) SearchWithPage(req dto.SearchPageWithType, readOnly ...b
|
||||
itemVars, _ := json.Marshal(varMap)
|
||||
item.Vars = string(itemVars)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.AccessKey = ""
|
||||
item.Credential = ""
|
||||
item.Vars = sanitizeBackupVars(item.Vars)
|
||||
}
|
||||
data = append(data, item)
|
||||
}
|
||||
return count, data, nil
|
||||
}
|
||||
|
||||
func sanitizeBackupVars(vars string) string {
|
||||
if vars == "" {
|
||||
return vars
|
||||
}
|
||||
var values map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(vars), &values); err != nil {
|
||||
return ""
|
||||
}
|
||||
for key := range values {
|
||||
if isSensitiveConfigKey(key) || normalizeConfigKey(key) == "code" {
|
||||
delete(values, key)
|
||||
}
|
||||
}
|
||||
data, err := json.Marshal(values)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func (u *BackupService) CheckConn(req dto.BackupOperate) dto.BackupCheckRes {
|
||||
var res dto.BackupCheckRes
|
||||
var backup model.BackupAccount
|
||||
|
||||
@@ -582,6 +582,10 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
||||
if config.Image == "" {
|
||||
return fmt.Errorf("container image not found in backup file")
|
||||
}
|
||||
networkConf, extraNetworks := buildContainerRecoverNetworkConfig(recoverCtx.inspectInfo.NetworkSettings, hostConfig)
|
||||
if err := normalizeContainerEndpointSettings(ctx, recoverCtx.client, networkConf, extraNetworks); err != nil {
|
||||
return err
|
||||
}
|
||||
if !checkImageExist(recoverCtx.client, config.Image) {
|
||||
if err := pullImages(taskItem, recoverCtx.client, config.Image); err != nil {
|
||||
return err
|
||||
@@ -596,7 +600,7 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
||||
return err
|
||||
}
|
||||
|
||||
createRes, err := createContainerWithOldNetworks(ctx, recoverCtx.client, config, hostConfig, recoverCtx.inspectInfo.NetworkSettings, recoverCtx.targetName)
|
||||
createRes, err := createContainerWithNetworks(ctx, recoverCtx.client, config, hostConfig, networkConf, extraNetworks, recoverCtx.targetName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -604,7 +608,7 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeContainerEndpointSettings(ctx context.Context, cli *client.Client, primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) {
|
||||
func normalizeContainerEndpointSettings(ctx context.Context, cli *client.Client, primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) error {
|
||||
if cli.NewVersionError(ctx, "1.44", "specify mac-address per network") != nil {
|
||||
removeEndpointMacAddresses(primary, extras)
|
||||
}
|
||||
@@ -619,11 +623,14 @@ func normalizeContainerEndpointSettings(ctx context.Context, cli *client.Client,
|
||||
}
|
||||
info, err := cli.NetworkInspect(ctx, netName, network.InspectOptions{})
|
||||
if err != nil {
|
||||
continue
|
||||
return fmt.Errorf("inspect network %s failed: %w", netName, err)
|
||||
}
|
||||
if err := validateContainerEndpointStaticIP(netName, info, endpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
removeUnsupportedEndpointStaticIP(netName, info, endpoint)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeEndpointMacAddresses(primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) {
|
||||
@@ -641,24 +648,28 @@ func removeEndpointMacAddresses(primary *network.NetworkingConfig, extras map[st
|
||||
}
|
||||
}
|
||||
|
||||
func removeUnsupportedEndpointStaticIP(netName string, info network.Inspect, endpoint *network.EndpointSettings) {
|
||||
func validateContainerEndpointStaticIP(netName string, info network.Inspect, endpoint *network.EndpointSettings) error {
|
||||
if endpoint == nil || endpoint.IPAMConfig == nil {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
if isDefaultBridgeNetwork(netName, info) {
|
||||
endpoint.IPAMConfig = nil
|
||||
return
|
||||
ipam := endpoint.IPAMConfig
|
||||
if err := ipam.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid IP configuration for network %s: %w", netName, err)
|
||||
}
|
||||
if ipam.IPv4Address == "" && ipam.IPv6Address == "" {
|
||||
return nil
|
||||
}
|
||||
if netName == "host" || netName == "none" || isDefaultBridgeNetwork(netName, info) {
|
||||
return fmt.Errorf("network %s does not support static IP configuration", netName)
|
||||
}
|
||||
|
||||
if endpoint.IPAMConfig.IPv4Address != "" && !networkSupportsStaticIP(info, endpoint.IPAMConfig.IPv4Address, false) {
|
||||
endpoint.IPAMConfig.IPv4Address = ""
|
||||
if ipam.IPv4Address != "" && !networkSupportsStaticIP(info, ipam.IPv4Address, false) {
|
||||
return fmt.Errorf("static IPv4 address %s is not in a configured subnet of network %s", ipam.IPv4Address, netName)
|
||||
}
|
||||
if endpoint.IPAMConfig.IPv6Address != "" && !networkSupportsStaticIP(info, endpoint.IPAMConfig.IPv6Address, true) {
|
||||
endpoint.IPAMConfig.IPv6Address = ""
|
||||
}
|
||||
if endpoint.IPAMConfig.IPv4Address == "" && endpoint.IPAMConfig.IPv6Address == "" && len(endpoint.IPAMConfig.LinkLocalIPs) == 0 {
|
||||
endpoint.IPAMConfig = nil
|
||||
if ipam.IPv6Address != "" && !networkSupportsStaticIP(info, ipam.IPv6Address, true) {
|
||||
return fmt.Errorf("static IPv6 address %s is not in a configured subnet of network %s", ipam.IPv6Address, netName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isDefaultBridgeNetwork(netName string, info network.Inspect) bool {
|
||||
@@ -673,6 +684,7 @@ func networkSupportsStaticIP(info network.Inspect, ip string, isIPv6 bool) bool
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
addr = addr.Unmap()
|
||||
if addr.Is6() != isIPv6 {
|
||||
return false
|
||||
}
|
||||
@@ -813,11 +825,6 @@ func buildContainerRecoverNetworkConfig(networkSettings *container.NetworkSettin
|
||||
IPv6Address: endpoint.IPAMConfig.IPv6Address,
|
||||
LinkLocalIPs: append([]string(nil), endpoint.IPAMConfig.LinkLocalIPs...),
|
||||
}
|
||||
} else if name != "bridge" && (endpoint.IPAddress != "" || endpoint.GlobalIPv6Address != "") {
|
||||
endpointSetting.IPAMConfig = &network.EndpointIPAMConfig{
|
||||
IPv4Address: endpoint.IPAddress,
|
||||
IPv6Address: endpoint.GlobalIPv6Address,
|
||||
}
|
||||
}
|
||||
if name == primaryName {
|
||||
config.EndpointsConfig[name] = endpointSetting
|
||||
@@ -831,39 +838,6 @@ func buildContainerRecoverNetworkConfig(networkSettings *container.NetworkSettin
|
||||
return config, extraNetworks
|
||||
}
|
||||
|
||||
const unsupportedUserSpecifiedIPAddress = "user specified IP address is supported only when connecting to networks with user configured subnets"
|
||||
|
||||
func clearUnsupportedDynamicEndpointIPAM(err error, endpoints map[string]*network.EndpointSettings, networkSettings *container.NetworkSettings) bool {
|
||||
if err == nil || !strings.Contains(err.Error(), unsupportedUserSpecifiedIPAddress) {
|
||||
return false
|
||||
}
|
||||
for name, endpoint := range endpoints {
|
||||
if !isDynamicContainerNetwork(networkSettings, name) || endpoint == nil || endpoint.IPAMConfig == nil {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(err.Error(), "network "+name+":") {
|
||||
endpoint.IPAMConfig = nil
|
||||
return true
|
||||
}
|
||||
}
|
||||
cleared := false
|
||||
for name, endpoint := range endpoints {
|
||||
if isDynamicContainerNetwork(networkSettings, name) && endpoint != nil && endpoint.IPAMConfig != nil {
|
||||
endpoint.IPAMConfig = nil
|
||||
cleared = true
|
||||
}
|
||||
}
|
||||
return cleared
|
||||
}
|
||||
|
||||
func isDynamicContainerNetwork(networkSettings *container.NetworkSettings, name string) bool {
|
||||
if networkSettings == nil || name == "bridge" {
|
||||
return false
|
||||
}
|
||||
endpoint := networkSettings.Networks[name]
|
||||
return endpoint != nil && endpoint.IPAMConfig == nil && (endpoint.IPAddress != "" || endpoint.GlobalIPv6Address != "")
|
||||
}
|
||||
|
||||
func cloneContainerConfig(config *container.Config) *container.Config {
|
||||
if config == nil {
|
||||
return &container.Config{}
|
||||
|
||||
@@ -33,7 +33,7 @@ type ClamService struct {
|
||||
}
|
||||
|
||||
type IClamService interface {
|
||||
LoadBaseInfo(readOnly bool) (dto.ClamBaseInfo, error)
|
||||
LoadBaseInfo() (dto.ClamBaseInfo, error)
|
||||
Operate(operate string) error
|
||||
SearchWithPage(search dto.SearchClamWithPage) (int64, interface{}, error)
|
||||
Create(req dto.ClamCreate, operator string) error
|
||||
@@ -53,7 +53,7 @@ func NewIClamService() IClamService {
|
||||
return &ClamService{}
|
||||
}
|
||||
|
||||
func (c *ClamService) LoadBaseInfo(readOnly bool) (dto.ClamBaseInfo, error) {
|
||||
func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
var baseInfo dto.ClamBaseInfo
|
||||
baseInfo.Version = "-"
|
||||
baseInfo.FreshVersion = "-"
|
||||
@@ -96,7 +96,7 @@ func (c *ClamService) LoadBaseInfo(readOnly bool) (dto.ClamBaseInfo, error) {
|
||||
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
|
||||
}
|
||||
}
|
||||
} else if !readOnly && !global.CONF.Base.IsDemo {
|
||||
} else {
|
||||
_ = clam.CheckWithStopAll(false, clamRepo)
|
||||
}
|
||||
if baseInfo.FreshIsActive {
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
type ComposeTemplateService struct{}
|
||||
|
||||
type IComposeTemplateService interface {
|
||||
List(readOnly ...bool) ([]dto.ComposeTemplateInfo, error)
|
||||
SearchWithPage(search dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error)
|
||||
List() ([]dto.ComposeTemplateInfo, error)
|
||||
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
||||
Create(req dto.ComposeTemplateCreate) error
|
||||
Update(id uint, upMap map[string]interface{}) error
|
||||
Batch(req dto.ComposeTemplateBatch) error
|
||||
@@ -23,7 +23,7 @@ func NewIComposeTemplateService() IComposeTemplateService {
|
||||
return &ComposeTemplateService{}
|
||||
}
|
||||
|
||||
func (u *ComposeTemplateService) List(readOnly ...bool) ([]dto.ComposeTemplateInfo, error) {
|
||||
func (u *ComposeTemplateService) List() ([]dto.ComposeTemplateInfo, error) {
|
||||
composes, err := composeRepo.List()
|
||||
if err != nil {
|
||||
return nil, buserr.New("ErrRecordNotFound")
|
||||
@@ -34,15 +34,12 @@ func (u *ComposeTemplateService) List(readOnly ...bool) ([]dto.ComposeTemplateIn
|
||||
if err := copier.Copy(&item, &compose); err != nil {
|
||||
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Content = ""
|
||||
}
|
||||
dtoLists = append(dtoLists, item)
|
||||
}
|
||||
return dtoLists, err
|
||||
}
|
||||
|
||||
func (u *ComposeTemplateService) SearchWithPage(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error) {
|
||||
func (u *ComposeTemplateService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
total, composes, err := composeRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info))
|
||||
var dtoComposeTemplates []dto.ComposeTemplateInfo
|
||||
for _, compose := range composes {
|
||||
@@ -50,9 +47,6 @@ func (u *ComposeTemplateService) SearchWithPage(req dto.SearchWithPage, readOnly
|
||||
if err := copier.Copy(&item, &compose); err != nil {
|
||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Content = ""
|
||||
}
|
||||
dtoComposeTemplates = append(dtoComposeTemplates, item)
|
||||
}
|
||||
return total, dtoComposeTemplates, err
|
||||
|
||||
+71
-145
@@ -16,6 +16,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
@@ -65,7 +67,7 @@ type IContainerService interface {
|
||||
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
ListVolume() ([]dto.Options, error)
|
||||
|
||||
PageCompose(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error)
|
||||
PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
LoadComposeEnv(name string) (string, error)
|
||||
CreateCompose(req dto.ComposeCreate) error
|
||||
ComposeOperation(req dto.ComposeOperation) error
|
||||
@@ -77,9 +79,9 @@ type IContainerService interface {
|
||||
ContainerCreate(req dto.ContainerOperate, inThread bool) error
|
||||
ContainerUpdate(req dto.ContainerOperate) error
|
||||
ContainerUpgrade(req dto.ContainerUpgrade) error
|
||||
ContainerInfo(req dto.OperationWithName, readOnly ...bool) (*dto.ContainerOperate, error)
|
||||
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
||||
ContainerListStats() ([]dto.ContainerListStats, error)
|
||||
ContainerItemStats(req dto.OperationWithName) (dto.ContainerItemStats, error)
|
||||
ContainerItemStats(ctx context.Context, req dto.OperationWithName) (dto.ContainerItemStats, error)
|
||||
LoadResourceLimit() (*dto.ResourceLimit, error)
|
||||
ContainerRename(req dto.ContainerRename) error
|
||||
ContainerCommit(req dto.ContainerCommit) error
|
||||
@@ -246,15 +248,15 @@ func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
func (u *ContainerService) ContainerItemStats(req dto.OperationWithName) (dto.ContainerItemStats, error) {
|
||||
func (u *ContainerService) ContainerItemStats(ctx context.Context, req dto.OperationWithName) (dto.ContainerItemStats, error) {
|
||||
var data dto.ContainerItemStats
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
defer client.Close()
|
||||
if req.Name != "system" {
|
||||
defer client.Close()
|
||||
containerInfo, _, err := client.ContainerInspectWithRaw(context.Background(), req.Name, true)
|
||||
containerInfo, _, err := client.ContainerInspectWithRaw(ctx, req.Name, true)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
@@ -263,7 +265,7 @@ func (u *ContainerService) ContainerItemStats(req dto.OperationWithName) (dto.Co
|
||||
return data, nil
|
||||
}
|
||||
|
||||
usage, err := client.DiskUsage(context.Background(), types.DiskUsageOptions{})
|
||||
usage, err := client.DiskUsage(ctx, types.DiskUsageOptions{})
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
@@ -381,11 +383,6 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||
}
|
||||
|
||||
func (u *ContainerService) Prune(req dto.ContainerPrune) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
name := ""
|
||||
switch req.PruneType {
|
||||
case "container":
|
||||
@@ -406,6 +403,14 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
|
||||
}
|
||||
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error {
|
||||
if err := t.TaskCtx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
pruneFilters := filters.NewArgs()
|
||||
if req.WithTagAll {
|
||||
pruneFilters.Add("dangling", "false")
|
||||
@@ -429,10 +434,7 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
|
||||
}
|
||||
SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||
case "network":
|
||||
_, err := client.NetworksPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cleanUnusedNetworks(t, client)
|
||||
case "volume":
|
||||
versions, err := client.ServerVersion(context.Background())
|
||||
if err != nil {
|
||||
@@ -534,7 +536,9 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalizeContainerEndpointSettings(ctx, client, networkConf, nil)
|
||||
if err := normalizeContainerEndpointSettings(ctx, client, networkConf, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||
if err != nil {
|
||||
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
|
||||
@@ -568,7 +572,7 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
||||
return taskItem.Execute()
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerInfo(req dto.OperationWithName, readOnly ...bool) (*dto.ContainerOperate, error) {
|
||||
func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -612,9 +616,6 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName, readOnly ...
|
||||
data.Tty = oldContainer.Config.Tty
|
||||
data.Entrypoint = oldContainer.Config.Entrypoint
|
||||
data.Env = oldContainer.Config.Env
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
data.Env = nil
|
||||
}
|
||||
data.CPUShares = oldContainer.HostConfig.CPUShares
|
||||
for key, val := range oldContainer.Config.Labels {
|
||||
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
|
||||
@@ -647,14 +648,9 @@ func loadContainerNetworkInfo(name string, endpoint *network.EndpointSettings) d
|
||||
if endpoint.IPAMConfig != nil {
|
||||
item.LinkLocalIPs = append([]string(nil), endpoint.IPAMConfig.LinkLocalIPs...)
|
||||
}
|
||||
if name != "bridge" {
|
||||
if endpoint.IPAMConfig != nil {
|
||||
item.Ipv4 = endpoint.IPAMConfig.IPv4Address
|
||||
item.Ipv6 = endpoint.IPAMConfig.IPv6Address
|
||||
} else {
|
||||
item.Ipv4 = endpoint.IPAddress
|
||||
item.Ipv6 = endpoint.GlobalIPv6Address
|
||||
}
|
||||
if name != "bridge" && endpoint.IPAMConfig != nil {
|
||||
item.Ipv4 = endpoint.IPAMConfig.IPv4Address
|
||||
item.Ipv6 = endpoint.IPAMConfig.IPv6Address
|
||||
}
|
||||
return item
|
||||
}
|
||||
@@ -1681,30 +1677,42 @@ func checkImageLike(client *client.Client, imageName string) bool {
|
||||
|
||||
func pullImages(task *task.Task, client *client.Client, imageName string) error {
|
||||
dockerCli := docker.NewClientWithExist(client)
|
||||
repos, err := imageRepoRepo.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imageRepo := selectImageRepo(imageName, repos)
|
||||
if imageRepo == nil || !imageRepo.Auth {
|
||||
return dockerCli.PullImageWithProcess(task, imageName)
|
||||
}
|
||||
|
||||
options := image.PullOptions{}
|
||||
repos, _ := imageRepoRepo.List()
|
||||
if len(repos) != 0 {
|
||||
for _, repo := range repos {
|
||||
if strings.HasPrefix(imageName, repo.DownloadUrl) && repo.Auth {
|
||||
authConfig := registry.AuthConfig{
|
||||
Username: repo.Username,
|
||||
Password: repo.Password,
|
||||
}
|
||||
encodedJSON, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
|
||||
options.RegistryAuth = authStr
|
||||
}
|
||||
authConfig := registry.AuthConfig{
|
||||
Username: imageRepo.Username,
|
||||
Password: imageRepo.Password,
|
||||
}
|
||||
encodedJSON, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.RegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON)
|
||||
return dockerCli.PullImageWithProcessAndOptions(task, imageName, options)
|
||||
}
|
||||
|
||||
func selectImageRepo(imageName string, repos []model.ImageRepo) *model.ImageRepo {
|
||||
var selected *model.ImageRepo
|
||||
selectedURLLength := 0
|
||||
for i := range repos {
|
||||
downloadURL := strings.TrimRight(strings.TrimSpace(repos[i].DownloadUrl), "/")
|
||||
if downloadURL == "" || !strings.HasPrefix(imageName, downloadURL+"/") {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
hasAuth, authStr := loadAuthInfo(imageName)
|
||||
if hasAuth {
|
||||
options.RegistryAuth = authStr
|
||||
if len(downloadURL) > selectedURLLength {
|
||||
selected = &repos[i]
|
||||
selectedURLLength = len(downloadURL)
|
||||
}
|
||||
}
|
||||
return dockerCli.PullImageWithProcessAndOptions(task, imageName, options)
|
||||
return selected
|
||||
}
|
||||
|
||||
func loadCpuAndMem(client *client.Client, containerItem string) dto.ContainerListStats {
|
||||
@@ -1761,7 +1769,10 @@ func checkPortStats(ports []dto.PortHelper, checkInUse bool) (nat.PortMap, error
|
||||
}
|
||||
for i := 0; i <= hostEnd-hostStart; i++ {
|
||||
bindItem := nat.PortBinding{HostPort: strconv.Itoa(hostStart + i), HostIP: port.HostIP}
|
||||
portMap[nat.Port(fmt.Sprintf("%d/%s", containerStart+i, port.Protocol))] = []nat.PortBinding{bindItem}
|
||||
portKey := nat.Port(fmt.Sprintf("%d/%s", containerStart+i, port.Protocol))
|
||||
if !slices.Contains(portMap[portKey], bindItem) {
|
||||
portMap[portKey] = append(portMap[portKey], bindItem)
|
||||
}
|
||||
}
|
||||
for i := hostStart; i <= hostEnd; i++ {
|
||||
if checkInUse && common.ScanPortWithIP(port.HostIP, i) {
|
||||
@@ -1779,7 +1790,10 @@ func checkPortStats(ports []dto.PortHelper, checkInUse bool) (nat.PortMap, error
|
||||
return portMap, buserr.WithDetail("ErrPortInUsed", portItem, nil)
|
||||
}
|
||||
bindItem := nat.PortBinding{HostPort: strconv.Itoa(portItem), HostIP: port.HostIP}
|
||||
portMap[nat.Port(fmt.Sprintf("%s/%s", port.ContainerPort, port.Protocol))] = []nat.PortBinding{bindItem}
|
||||
portKey := nat.Port(fmt.Sprintf("%s/%s", port.ContainerPort, port.Protocol))
|
||||
if !slices.Contains(portMap[portKey], bindItem) {
|
||||
portMap[portKey] = append(portMap[portKey], bindItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
return portMap, nil
|
||||
@@ -1925,90 +1939,7 @@ func loadPortByInspect(id string, client *client.Client) ([]container.Port, erro
|
||||
return itemPorts, nil
|
||||
}
|
||||
func transPortToStr(ports []container.Port) []string {
|
||||
var (
|
||||
ipv4Ports []container.Port
|
||||
ipv6Ports []container.Port
|
||||
)
|
||||
for _, port := range ports {
|
||||
if strings.Contains(port.IP, ":") {
|
||||
ipv6Ports = append(ipv6Ports, port)
|
||||
} else {
|
||||
ipv4Ports = append(ipv4Ports, port)
|
||||
}
|
||||
}
|
||||
list1 := simplifyPort(ipv4Ports)
|
||||
list2 := simplifyPort(ipv6Ports)
|
||||
return append(list1, list2...)
|
||||
}
|
||||
func simplifyPort(ports []container.Port) []string {
|
||||
var datas []string
|
||||
if len(ports) == 0 {
|
||||
return datas
|
||||
}
|
||||
if len(ports) == 1 {
|
||||
ip := ""
|
||||
if len(ports[0].IP) != 0 {
|
||||
ip = ports[0].IP + ":"
|
||||
}
|
||||
itemPortStr := fmt.Sprintf("%s%v/%s", ip, ports[0].PrivatePort, ports[0].Type)
|
||||
if ports[0].PublicPort != 0 {
|
||||
itemPortStr = fmt.Sprintf("%s%v->%v/%s", ip, ports[0].PublicPort, ports[0].PrivatePort, ports[0].Type)
|
||||
}
|
||||
datas = append(datas, itemPortStr)
|
||||
return datas
|
||||
}
|
||||
|
||||
sort.Slice(ports, func(i, j int) bool {
|
||||
return ports[i].PrivatePort < ports[j].PrivatePort
|
||||
})
|
||||
start := ports[0]
|
||||
|
||||
for i := 1; i < len(ports); i++ {
|
||||
if ports[i].PrivatePort != ports[i-1].PrivatePort+1 || ports[i].IP != ports[i-1].IP || ports[i].PublicPort != ports[i-1].PublicPort+1 || ports[i].Type != ports[i-1].Type {
|
||||
if ports[i-1].PrivatePort == start.PrivatePort {
|
||||
itemPortStr := fmt.Sprintf("%s:%v/%s", start.IP, start.PrivatePort, start.Type)
|
||||
if start.PublicPort != 0 {
|
||||
itemPortStr = fmt.Sprintf("%s:%v->%v/%s", start.IP, start.PublicPort, start.PrivatePort, start.Type)
|
||||
}
|
||||
if len(start.IP) == 0 {
|
||||
itemPortStr = strings.TrimPrefix(itemPortStr, ":")
|
||||
}
|
||||
datas = append(datas, itemPortStr)
|
||||
} else {
|
||||
itemPortStr := fmt.Sprintf("%s:%v-%v/%s", start.IP, start.PrivatePort, ports[i-1].PrivatePort, start.Type)
|
||||
if start.PublicPort != 0 {
|
||||
itemPortStr = fmt.Sprintf("%s:%v-%v->%v-%v/%s", start.IP, start.PublicPort, ports[i-1].PublicPort, start.PrivatePort, ports[i-1].PrivatePort, start.Type)
|
||||
}
|
||||
if len(start.IP) == 0 {
|
||||
itemPortStr = strings.TrimPrefix(itemPortStr, ":")
|
||||
}
|
||||
datas = append(datas, itemPortStr)
|
||||
}
|
||||
start = ports[i]
|
||||
}
|
||||
if i == len(ports)-1 {
|
||||
if ports[i].PrivatePort == start.PrivatePort {
|
||||
itemPortStr := fmt.Sprintf("%s:%v/%s", start.IP, start.PrivatePort, start.Type)
|
||||
if start.PublicPort != 0 {
|
||||
itemPortStr = fmt.Sprintf("%s:%v->%v/%s", start.IP, start.PublicPort, start.PrivatePort, start.Type)
|
||||
}
|
||||
if len(start.IP) == 0 {
|
||||
itemPortStr = strings.TrimPrefix(itemPortStr, ":")
|
||||
}
|
||||
datas = append(datas, itemPortStr)
|
||||
} else {
|
||||
itemPortStr := fmt.Sprintf("%s:%v-%v/%s", start.IP, start.PrivatePort, ports[i].PrivatePort, start.Type)
|
||||
if start.PublicPort != 0 {
|
||||
itemPortStr = fmt.Sprintf("%s:%v-%v->%v-%v/%s", start.IP, start.PublicPort, ports[i].PublicPort, start.PrivatePort, ports[i].PrivatePort, start.Type)
|
||||
}
|
||||
if len(start.IP) == 0 {
|
||||
itemPortStr = strings.TrimPrefix(itemPortStr, ":")
|
||||
}
|
||||
datas = append(datas, itemPortStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
return datas
|
||||
return docker.SimplifyPorts(ports)
|
||||
}
|
||||
|
||||
func loadComposeCount(client *client.Client) int {
|
||||
@@ -2041,7 +1972,7 @@ func loadComposeCount(client *client.Client) int {
|
||||
}
|
||||
func loadContainerPortForInfo(itemPorts []container.Port) []dto.PortHelper {
|
||||
var exposedPorts []dto.PortHelper
|
||||
samePortMap := make(map[string]dto.PortHelper)
|
||||
seenPorts := make(map[dto.PortHelper]struct{})
|
||||
ports := transPortToStr(itemPorts)
|
||||
for _, item := range ports {
|
||||
itemStr := strings.Split(item, "->")
|
||||
@@ -2062,16 +1993,11 @@ func loadContainerPortForInfo(itemPorts []container.Port) []dto.PortHelper {
|
||||
}
|
||||
itemPort.ContainerPort = itemContainer[0]
|
||||
itemPort.Protocol = itemContainer[1]
|
||||
keyItem := fmt.Sprintf("%s->%s/%s", itemPort.HostPort, itemPort.ContainerPort, itemPort.Protocol)
|
||||
if val, ok := samePortMap[keyItem]; ok {
|
||||
val.HostIP = ""
|
||||
samePortMap[keyItem] = val
|
||||
} else {
|
||||
samePortMap[keyItem] = itemPort
|
||||
if _, exists := seenPorts[itemPort]; exists {
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, val := range samePortMap {
|
||||
exposedPorts = append(exposedPorts, val)
|
||||
seenPorts[itemPort] = struct{}{}
|
||||
exposedPorts = append(exposedPorts, itemPort)
|
||||
}
|
||||
return exposedPorts
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"gopkg.in/yaml.v3"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const composeProjectLabel = "com.docker.compose.project"
|
||||
@@ -35,7 +36,7 @@ const composeConfigLabel = "com.docker.compose.project.config_files"
|
||||
const composeWorkdirLabel = "com.docker.compose.project.working_dir"
|
||||
const composeCreatedBy = "createdBy"
|
||||
|
||||
func (u *ContainerService) PageCompose(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error) {
|
||||
func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
var (
|
||||
records []dto.ComposeInfo
|
||||
BackDatas []dto.ComposeInfo
|
||||
@@ -187,10 +188,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage, readOnly ...bool)
|
||||
}
|
||||
BackDatas = records[start:end]
|
||||
}
|
||||
listItem := BackDatas
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
listItem = loadEnv(BackDatas)
|
||||
}
|
||||
listItem := loadEnv(BackDatas)
|
||||
return int64(total), listItem, nil
|
||||
}
|
||||
|
||||
@@ -255,6 +253,10 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
|
||||
return err
|
||||
}
|
||||
req.Name = projectName
|
||||
recordName := strings.ToLower(req.Name)
|
||||
if err := saveComposeRecord(recordName, req.Path); err != nil {
|
||||
return fmt.Errorf("save compose record failed, err: %v", err)
|
||||
}
|
||||
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeCompose, req.TaskID, 1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new task for image build failed, err: %v", err)
|
||||
@@ -263,18 +265,7 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("ComposeCreate"), func(t *task.Task) error {
|
||||
err := compose.UpWithTask(req.Path, t, req.ForcePull, req.Name)
|
||||
t.LogWithStatus(i18n.GetMsgByKey("ComposeCreate"), err)
|
||||
if err != nil {
|
||||
_, _ = compose.Down(req.Path, req.Name)
|
||||
return err
|
||||
}
|
||||
recordName := strings.ToLower(req.Name)
|
||||
record, _ := composeRepo.GetRecord(repo.WithByName(recordName))
|
||||
if record.ID == 0 {
|
||||
_ = composeRepo.CreateRecord(&model.Compose{Name: recordName, Path: req.Path})
|
||||
} else {
|
||||
_ = composeRepo.UpdateRecord(recordName, map[string]interface{}{"path": req.Path})
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}, nil)
|
||||
_ = taskItem.Execute()
|
||||
}()
|
||||
@@ -282,6 +273,17 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveComposeRecord(name, composePath string) error {
|
||||
record, err := composeRepo.GetRecord(repo.WithByName(name))
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
if record.ID == 0 {
|
||||
return composeRepo.CreateRecord(&model.Compose{Name: name, Path: composePath})
|
||||
}
|
||||
return composeRepo.UpdateRecord(name, map[string]interface{}{"path": composePath})
|
||||
}
|
||||
|
||||
func checkComposeRecordName(name string) error {
|
||||
composeItem, _ := composeRepo.GetRecord(repo.WithByName(name))
|
||||
if composeItem.ID != 0 && len(composeItem.Path) != 0 {
|
||||
|
||||
@@ -2,16 +2,24 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/errdefs"
|
||||
)
|
||||
|
||||
var networkCleanupSlot = make(chan struct{}, 1)
|
||||
|
||||
func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
@@ -172,3 +180,114 @@ func (u *ContainerService) CreateNetwork(req dto.NetworkCreate) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanUnusedNetworks(t *task.Task, cli *client.Client) error {
|
||||
ctx := t.TaskCtx
|
||||
select {
|
||||
case networkCleanupSlot <- struct{}{}:
|
||||
defer func() { <-networkCleanupSlot }()
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
networks, err := cli.NetworkList(ctx, network.ListOptions{})
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
return err
|
||||
}
|
||||
deleted, skipped, failed := 0, 0, 0
|
||||
defer func() {
|
||||
t.Log(i18n.GetMsgWithMap("NetworkCleanupSummary", map[string]interface{}{
|
||||
"deleted": deleted, "skipped": skipped, "failed": failed,
|
||||
}))
|
||||
}()
|
||||
var used map[string]bool
|
||||
sort.Slice(networks, func(i, j int) bool { return networks[i].Name < networks[j].Name })
|
||||
for _, n := range networks {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
switch n.Name {
|
||||
case "none", "host", "bridge", "1panel-network":
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
if n.Scope != "local" || n.Ingress || n.ConfigOnly {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
values := map[string]interface{}{"name": n.Name, "id": n.ID}
|
||||
if used == nil {
|
||||
containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
return err
|
||||
}
|
||||
used = make(map[string]bool)
|
||||
for _, c := range containers {
|
||||
if c.NetworkSettings == nil {
|
||||
continue
|
||||
}
|
||||
for name, endpoint := range c.NetworkSettings.Networks {
|
||||
used[name] = true
|
||||
if endpoint != nil {
|
||||
used[endpoint.NetworkID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if used[n.Name] || used[n.ID] {
|
||||
skipped++
|
||||
t.Log(i18n.GetMsgWithMap("NetworkCleanupConnected", values))
|
||||
continue
|
||||
}
|
||||
inspected, err := cli.NetworkInspect(ctx, n.ID, network.InspectOptions{})
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
if errdefs.IsNotFound(err) {
|
||||
skipped++
|
||||
} else {
|
||||
failed++
|
||||
t.Logf("Failed to inspect network [%s] (%s): %v", n.Name, n.ID, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(inspected.Containers) > 0 {
|
||||
skipped++
|
||||
t.Log(i18n.GetMsgWithMap("NetworkCleanupConnected", values))
|
||||
continue
|
||||
}
|
||||
if err := cli.NetworkRemove(ctx, n.ID); err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
switch {
|
||||
case errdefs.IsNotFound(err):
|
||||
skipped++
|
||||
case errdefs.IsConflict(err):
|
||||
skipped++
|
||||
t.Log(i18n.GetMsgWithMap("NetworkCleanupConnected", values))
|
||||
default:
|
||||
failed++
|
||||
t.Logf("Failed to remove network [%s] (%s): %v", n.Name, n.ID, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if failed > 0 {
|
||||
return errors.New(i18n.GetMsgByKey("NetworkCleanupPartialFailure"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -18,6 +20,7 @@ import (
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
@@ -64,13 +67,13 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalizeContainerEndpointSettings(ctx, client, networkConf, nil)
|
||||
if err := normalizeContainerEndpointSettings(ctx, client, networkConf, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cleanupErr, err := switchContainer(ctx, client, req.Name, oldContainer, func() (container.CreateResponse, error) {
|
||||
return createContainerWithDynamicIPFallback(func() (container.CreateResponse, error) {
|
||||
return client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||
}, networkConf.EndpointsConfig, oldContainer.NetworkSettings)
|
||||
}, newContainerSwitchTaskLogger(t))
|
||||
return client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||
}, config.Tty, t)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update container failed, err: %v", err)
|
||||
}
|
||||
@@ -135,9 +138,15 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
|
||||
config.Image = req.Image
|
||||
hostConf := cloneContainerHostConfig(oldContainer.HostConfig)
|
||||
preserveContainerVolumeMounts(hostConf, oldContainer.Mounts)
|
||||
networkConf, extraNetworks := buildContainerRecoverNetworkConfig(oldContainer.NetworkSettings, hostConf)
|
||||
if err := normalizeContainerEndpointSettings(ctx, client, networkConf, extraNetworks); err != nil {
|
||||
upgradeErr := fmt.Errorf("prepare networks for container %s failed: %w", item, err)
|
||||
upgradeErrors = append(upgradeErrors, upgradeErr)
|
||||
return upgradeErr
|
||||
}
|
||||
cleanupErr, err := switchContainer(ctx, client, item, oldContainer, func() (container.CreateResponse, error) {
|
||||
return createContainerWithOldNetworks(ctx, client, config, hostConf, oldContainer.NetworkSettings, item)
|
||||
}, newContainerSwitchTaskLogger(t))
|
||||
return createContainerWithNetworks(ctx, client, config, hostConf, networkConf, extraNetworks, item)
|
||||
}, config.Tty, t)
|
||||
if err != nil {
|
||||
upgradeErr := fmt.Errorf("upgrade container %s failed: %w", item, err)
|
||||
upgradeErrors = append(upgradeErrors, upgradeErr)
|
||||
@@ -166,6 +175,7 @@ type containerSwitchClient interface {
|
||||
ContainerStart(context.Context, string, container.StartOptions) error
|
||||
ContainerRemove(context.Context, string, container.RemoveOptions) error
|
||||
ContainerInspect(context.Context, string) (container.InspectResponse, error)
|
||||
ContainerLogs(context.Context, string, container.LogsOptions) (io.ReadCloser, error)
|
||||
NetworkConnect(context.Context, string, string, *network.EndpointSettings) error
|
||||
NetworkDisconnect(context.Context, string, string, bool) error
|
||||
}
|
||||
@@ -238,22 +248,18 @@ func (l *containerOperationMutex) lock(names ...string) func() {
|
||||
}
|
||||
|
||||
type containerNetworkAttachment struct {
|
||||
name string
|
||||
endpoint *network.EndpointSettings
|
||||
isDynamic bool
|
||||
name string
|
||||
endpoint *network.EndpointSettings
|
||||
}
|
||||
|
||||
type containerSwitchLogFunc func(messageKey, containerName string, err error)
|
||||
|
||||
func newContainerSwitchTaskLogger(t *task.Task) containerSwitchLogFunc {
|
||||
return func(messageKey, containerName string, err error) {
|
||||
t.LogWithStatus(i18n.GetWithName(messageKey, containerName), err)
|
||||
}
|
||||
type containerSwitchLogger interface {
|
||||
LogWithStatus(string, error)
|
||||
Log(string)
|
||||
}
|
||||
|
||||
func logContainerSwitchStep(logger containerSwitchLogFunc, messageKey, containerName string, err error) {
|
||||
func logContainerSwitchStep(logger containerSwitchLogger, messageKey, containerName string, err error) {
|
||||
if logger != nil {
|
||||
logger(messageKey, containerName, err)
|
||||
logger.LogWithStatus(i18n.GetWithName(messageKey, containerName), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +270,8 @@ func switchContainer(
|
||||
name string,
|
||||
oldContainer container.InspectResponse,
|
||||
createNew func() (container.CreateResponse, error),
|
||||
logger containerSwitchLogFunc,
|
||||
tty bool,
|
||||
logger containerSwitchLogger,
|
||||
) (cleanupErr error, err error) {
|
||||
if oldContainer.ID == "" {
|
||||
return nil, fmt.Errorf("original container ID is empty")
|
||||
@@ -314,6 +321,7 @@ func switchContainer(
|
||||
}
|
||||
if err := cli.ContainerStart(ctx, created.ID, container.StartOptions{}); err != nil {
|
||||
logContainerSwitchStep(logger, "ContainerStartReplacement", name, err)
|
||||
logContainerStartupLogs(ctx, cli, created.ID, name, tty, logger)
|
||||
rollbackErr := restoreOriginalContainer(ctx, cli, oldContainer.ID, name, wasRunning, created.ID, disconnectedNetworks, logger)
|
||||
return nil, errors.Join(fmt.Errorf("start new container failed: %w", err), rollbackErr)
|
||||
}
|
||||
@@ -321,6 +329,7 @@ func switchContainer(
|
||||
if wasRunning {
|
||||
if err := waitContainerReady(ctx, cli, created.ID); err != nil {
|
||||
logContainerSwitchStep(logger, "ContainerWaitReplacement", name, err)
|
||||
logContainerStartupLogs(ctx, cli, created.ID, name, tty, logger)
|
||||
rollbackErr := restoreOriginalContainer(ctx, cli, oldContainer.ID, name, wasRunning, created.ID, disconnectedNetworks, logger)
|
||||
return nil, errors.Join(fmt.Errorf("new container readiness check failed: %w", err), rollbackErr)
|
||||
}
|
||||
@@ -337,8 +346,48 @@ const (
|
||||
containerStartPollInterval = time.Second
|
||||
containerHealthCheckMinWait = 30 * time.Second
|
||||
containerHealthCheckMaxWait = 10 * time.Minute
|
||||
containerDiagnosticLogTail = "200"
|
||||
)
|
||||
|
||||
func logContainerStartupLogs(ctx context.Context, cli containerSwitchClient, containerID, name string, tty bool, logger containerSwitchLogger) {
|
||||
if logger == nil {
|
||||
return
|
||||
}
|
||||
logger.Log(fmt.Sprintf("========== %s ==========", i18n.GetWithName("ContainerStartupDiagnostic", name)))
|
||||
diagnosticCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
reader, err := cli.ContainerLogs(diagnosticCtx, containerID, container.LogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Timestamps: true,
|
||||
Tail: containerDiagnosticLogTail,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Log(i18n.GetWithNameAndErr("ContainerDiagnosticLogsFailed", name, err))
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
var output bytes.Buffer
|
||||
if tty {
|
||||
_, err = io.Copy(&output, reader)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(&output, &output, reader)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Log(i18n.GetWithNameAndErr("ContainerDiagnosticLogsFailed", name, err))
|
||||
return
|
||||
}
|
||||
logs := strings.TrimSpace(output.String())
|
||||
logger.Log(fmt.Sprintf("---------- %s ----------", i18n.GetMsgByKey("ContainerRecentLogs")))
|
||||
if logs == "" {
|
||||
logger.Log(i18n.GetMsgByKey("ContainerDiagnosticLogsEmpty"))
|
||||
return
|
||||
}
|
||||
logger.Log(logs)
|
||||
}
|
||||
|
||||
func waitContainerReady(ctx context.Context, cli containerInspectClient, containerID string) error {
|
||||
info, err := cli.ContainerInspect(ctx, containerID)
|
||||
if err != nil {
|
||||
@@ -538,13 +587,13 @@ func disconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitc
|
||||
endpoints := make(map[string]*network.EndpointSettings, len(extras)+1)
|
||||
if primary != nil {
|
||||
for name, endpoint := range primary.EndpointsConfig {
|
||||
if name != "bridge" && endpoint != nil && endpoint.IPAMConfig != nil {
|
||||
if name != "bridge" && endpoint != nil {
|
||||
endpoints[name] = endpoint
|
||||
}
|
||||
}
|
||||
}
|
||||
for name, endpoint := range extras {
|
||||
if name != "bridge" && endpoint != nil && endpoint.IPAMConfig != nil {
|
||||
if name != "bridge" && endpoint != nil {
|
||||
endpoints[name] = endpoint
|
||||
}
|
||||
}
|
||||
@@ -560,9 +609,8 @@ func disconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitc
|
||||
return disconnected, fmt.Errorf("disconnect original container from network %s failed: %w", name, err)
|
||||
}
|
||||
disconnected = append(disconnected, containerNetworkAttachment{
|
||||
name: name,
|
||||
endpoint: endpoints[name],
|
||||
isDynamic: isDynamicContainerNetwork(oldContainer.NetworkSettings, name),
|
||||
name: name,
|
||||
endpoint: endpoints[name],
|
||||
})
|
||||
}
|
||||
return disconnected, nil
|
||||
@@ -572,10 +620,6 @@ func reconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitch
|
||||
var reconnectErr error
|
||||
for _, attachment := range attachments {
|
||||
err := cli.NetworkConnect(ctx, attachment.name, containerID, attachment.endpoint)
|
||||
if err != nil && attachment.isDynamic && strings.Contains(err.Error(), unsupportedUserSpecifiedIPAddress) {
|
||||
attachment.endpoint.IPAMConfig = nil
|
||||
err = cli.NetworkConnect(ctx, attachment.name, containerID, attachment.endpoint)
|
||||
}
|
||||
if err != nil {
|
||||
reconnectErr = errors.Join(reconnectErr, fmt.Errorf("reconnect original container to network %s failed: %w", attachment.name, err))
|
||||
}
|
||||
@@ -583,7 +627,7 @@ func reconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitch
|
||||
return reconnectErr
|
||||
}
|
||||
|
||||
func restoreOriginalContainer(ctx context.Context, cli containerSwitchClient, oldContainerID, originalName string, wasRunning bool, newContainer string, disconnectedNetworks []containerNetworkAttachment, logger containerSwitchLogFunc) error {
|
||||
func restoreOriginalContainer(ctx context.Context, cli containerSwitchClient, oldContainerID, originalName string, wasRunning bool, newContainer string, disconnectedNetworks []containerNetworkAttachment, logger containerSwitchLogger) error {
|
||||
var rollbackErr error
|
||||
backupName := containerSwitchBackupName(oldContainerID)
|
||||
if newContainer != "" {
|
||||
@@ -608,7 +652,7 @@ func restoreOriginalContainer(ctx context.Context, cli containerSwitchClient, ol
|
||||
reconnectErr := reconnectOriginalContainerNetworks(ctx, cli, oldContainerID, disconnectedNetworks)
|
||||
logContainerSwitchStep(logger, "ContainerRollbackReconnectOld", currentName, reconnectErr)
|
||||
rollbackErr = errors.Join(rollbackErr, reconnectErr)
|
||||
if wasRunning {
|
||||
if wasRunning && reconnectErr == nil {
|
||||
restartErr := restartOriginalContainer(ctx, cli, oldContainerID)
|
||||
logContainerSwitchStep(logger, "ContainerRollbackRestartOld", currentName, restartErr)
|
||||
rollbackErr = errors.Join(rollbackErr, restartErr)
|
||||
@@ -616,17 +660,8 @@ func restoreOriginalContainer(ctx context.Context, cli containerSwitchClient, ol
|
||||
return rollbackErr
|
||||
}
|
||||
|
||||
func createContainerWithOldNetworks(ctx context.Context, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkSettings *container.NetworkSettings, name string) (container.CreateResponse, error) {
|
||||
networkConf, extraNetworks := buildContainerRecoverNetworkConfig(networkSettings, hostConf)
|
||||
normalizeContainerEndpointSettings(ctx, client, networkConf, extraNetworks)
|
||||
var primaryEndpoints map[string]*network.EndpointSettings
|
||||
if networkConf != nil {
|
||||
primaryEndpoints = networkConf.EndpointsConfig
|
||||
}
|
||||
|
||||
created, err := createContainerWithDynamicIPFallback(func() (container.CreateResponse, error) {
|
||||
return client.ContainerCreate(ctx, config, hostConf, networkConf, nil, name)
|
||||
}, primaryEndpoints, networkSettings)
|
||||
func createContainerWithNetworks(ctx context.Context, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkConf *network.NetworkingConfig, extraNetworks map[string]*network.EndpointSettings, name string) (container.CreateResponse, error) {
|
||||
created, err := client.ContainerCreate(ctx, config, hostConf, networkConf, nil, name)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
@@ -638,9 +673,6 @@ func createContainerWithOldNetworks(ctx context.Context, client *client.Client,
|
||||
sort.Strings(extraNames)
|
||||
for _, item := range extraNames {
|
||||
err := client.NetworkConnect(ctx, item, created.ID, extraNetworks[item])
|
||||
if clearUnsupportedDynamicEndpointIPAM(err, map[string]*network.EndpointSettings{item: extraNetworks[item]}, networkSettings) {
|
||||
err = client.NetworkConnect(ctx, item, created.ID, extraNetworks[item])
|
||||
}
|
||||
if err != nil {
|
||||
_ = client.ContainerRemove(ctx, created.ID, container.RemoveOptions{Force: true})
|
||||
return created, err
|
||||
@@ -648,16 +680,3 @@ func createContainerWithOldNetworks(ctx context.Context, client *client.Client,
|
||||
}
|
||||
return created, nil
|
||||
}
|
||||
|
||||
func createContainerWithDynamicIPFallback(
|
||||
create func() (container.CreateResponse, error),
|
||||
endpoints map[string]*network.EndpointSettings,
|
||||
networkSettings *container.NetworkSettings,
|
||||
) (container.CreateResponse, error) {
|
||||
for {
|
||||
created, err := create()
|
||||
if err == nil || created.ID != "" || !clearUnsupportedDynamicEndpointIPAM(err, endpoints, networkSettings) {
|
||||
return created, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
@@ -25,7 +26,7 @@ import (
|
||||
type CronjobService struct{}
|
||||
|
||||
type ICronjobService interface {
|
||||
SearchWithPage(search dto.PageCronjob, readOnly ...bool) (int64, interface{}, error)
|
||||
SearchWithPage(search dto.PageCronjob) (int64, interface{}, error)
|
||||
SearchRecords(search dto.SearchRecord) (int64, interface{}, error)
|
||||
Create(cronjobDto dto.CronjobOperate, operator string) error
|
||||
LoadNextHandle(spec string) ([]string, error)
|
||||
@@ -50,7 +51,7 @@ func NewICronjobService() ICronjobService {
|
||||
return &CronjobService{}
|
||||
}
|
||||
|
||||
func (u *CronjobService) SearchWithPage(search dto.PageCronjob, readOnly ...bool) (int64, interface{}, error) {
|
||||
func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interface{}, error) {
|
||||
total, cronjobs, err := cronjobRepo.Page(search.Page,
|
||||
search.PageSize,
|
||||
repo.WithByGroups(search.GroupIDs),
|
||||
@@ -75,6 +76,7 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob, readOnly ...bool
|
||||
EntryID: cronjob.ID,
|
||||
}
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
|
||||
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||
if alertInfo.SendCount != 0 {
|
||||
item.AlertCount = alertInfo.SendCount
|
||||
} else {
|
||||
@@ -83,9 +85,6 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob, readOnly ...bool
|
||||
if cronjob.Type == "snapshot" && len(cronjob.SnapshotRule) != 0 {
|
||||
_ = json.Unmarshal([]byte(cronjob.SnapshotRule), &item.SnapshotRule)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Secret = ""
|
||||
}
|
||||
dtoCronjobs = append(dtoCronjobs, item)
|
||||
}
|
||||
return total, dtoCronjobs, err
|
||||
@@ -101,9 +100,11 @@ func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, err
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
}
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))))
|
||||
item.AlertMethod = alertInfo.Method
|
||||
if alertInfo.SendCount != 0 {
|
||||
item.AlertTitle = alertInfo.Title
|
||||
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||
if alertInfo.Status == constant.AlertEnable {
|
||||
item.AlertCount = alertInfo.SendCount
|
||||
} else {
|
||||
item.AlertCount = 0
|
||||
@@ -198,11 +199,12 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
||||
}
|
||||
}
|
||||
item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID)
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))), repo.WithByStatus(constant.AlertEnable))
|
||||
if alertInfo.SendCount != 0 {
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))))
|
||||
item.AlertTitle = alertInfo.Title
|
||||
item.AlertMethod = alertInfo.Method
|
||||
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||
if alertInfo.Status == constant.AlertEnable {
|
||||
item.AlertCount = alertInfo.SendCount
|
||||
item.AlertTitle = alertInfo.Title
|
||||
item.AlertMethod = alertInfo.Method
|
||||
} else {
|
||||
item.AlertCount = 0
|
||||
}
|
||||
@@ -216,6 +218,17 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
||||
}
|
||||
|
||||
func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
||||
for _, item := range req {
|
||||
advanced, err := cronJobAlertAdvancedParams(item.AlertTriggerMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if item.AlertCount != 0 {
|
||||
if err := (AlertService{}).validateCronJobAlertChannels(item.Type, advanced, item.AlertMethod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, item := range req {
|
||||
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
|
||||
if cronjobItem.ID != 0 {
|
||||
@@ -398,17 +411,27 @@ func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
||||
} else {
|
||||
cronjob.Status = constant.StatusDisable
|
||||
}
|
||||
_ = cronjobRepo.Create(&cronjob)
|
||||
if item.AlertCount != 0 && item.AlertTitle != "" && item.AlertMethod != "" {
|
||||
if err := cronjobRepo.Create(&cronjob); err != nil {
|
||||
return err
|
||||
}
|
||||
if item.AlertTitle != "" && item.AlertMethod != "" {
|
||||
advanced, _ := cronJobAlertAdvancedParams(item.AlertTriggerMode)
|
||||
status := constant.AlertEnable
|
||||
if item.AlertCount == 0 {
|
||||
status = constant.AlertDisable
|
||||
}
|
||||
createAlert := dto.AlertCreate{
|
||||
Title: item.AlertTitle,
|
||||
SendCount: item.AlertCount,
|
||||
Method: item.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
Title: item.AlertTitle,
|
||||
SendCount: item.AlertCount,
|
||||
Method: item.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: status,
|
||||
AdvancedParams: advanced,
|
||||
}
|
||||
if err := NewIAlertService().CreateAlert(createAlert, operator); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = NewIAlertService().CreateAlert(createAlert, operator)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -565,6 +588,15 @@ func (u *CronjobService) HandleOnce(id uint) error {
|
||||
}
|
||||
|
||||
func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
|
||||
advanced, err := cronJobAlertAdvancedParams(req.AlertTriggerMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.AlertCount != 0 {
|
||||
if err := (AlertService{}).validateCronJobAlertChannels(req.Type, advanced, req.AlertMethod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name))
|
||||
if cronjob.ID != 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
@@ -606,12 +638,13 @@ func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
|
||||
}
|
||||
if req.AlertCount != 0 && req.AlertTitle != "" && req.AlertMethod != "" {
|
||||
createAlert := dto.AlertCreate{
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
AdvancedParams: advanced,
|
||||
}
|
||||
err := NewIAlertService().CreateAlert(createAlert, operator)
|
||||
if err != nil {
|
||||
@@ -685,6 +718,10 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
|
||||
}
|
||||
|
||||
func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string) error {
|
||||
advanced, err := cronJobAlertAdvancedParams(req.AlertTriggerMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var cronjob model.Cronjob
|
||||
if err := copier.Copy(&cronjob, &req); err != nil {
|
||||
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
@@ -700,6 +737,20 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
||||
if err != nil {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if req.AlertCount != 0 {
|
||||
previous, _ := alertRepo.Get(alertRepo.WithByType(cronModel.Type), alertRepo.WithByProject(strconv.Itoa(int(id))))
|
||||
merged, err := prepareCronJobAlertParams(cronModel.Type, previous.AdvancedParams, advanced)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
method := req.AlertMethod
|
||||
if method == "" {
|
||||
method = previous.Method
|
||||
}
|
||||
if err := (AlertService{}).validateCronJobAlertChannels(cronModel.Type, merged, method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
upMap := make(map[string]interface{})
|
||||
cronjob.EntryIDs = cronModel.EntryIDs
|
||||
cronjob.Type = cronModel.Type
|
||||
@@ -756,11 +807,12 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
||||
return err
|
||||
}
|
||||
updateAlert := dto.AlertCreate{
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronModel.ID)),
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronModel.ID)),
|
||||
AdvancedParams: advanced,
|
||||
}
|
||||
err = NewIAlertService().ExternalUpdateAlert(updateAlert, operator)
|
||||
if err != nil {
|
||||
@@ -769,6 +821,17 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
||||
return nil
|
||||
}
|
||||
|
||||
func cronJobAlertAdvancedParams(mode string) (string, error) {
|
||||
if mode == "" {
|
||||
return "", nil
|
||||
}
|
||||
data, err := json.Marshal(map[string]string{"alertTriggerMode": mode})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return alertUtil.MergeCronJobAlertParams("", string(data))
|
||||
}
|
||||
|
||||
func (u *CronjobService) UpdateStatus(id uint, status string) error {
|
||||
cronjob, _ := cronjobRepo.Get(repo.WithByID(id))
|
||||
if cronjob.ID == 0 {
|
||||
|
||||
@@ -412,6 +412,7 @@ func addSkipTask(source string, taskItem *task.Task) {
|
||||
taskItem.Log(i18n.GetMsgByKey("NoSuchResource"))
|
||||
return nil
|
||||
}, nil)
|
||||
taskItem.SubTasks[len(taskItem.SubTasks)-1].StepAlias = cronJobSkippedStep
|
||||
}
|
||||
|
||||
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ntp"
|
||||
@@ -56,10 +58,11 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
_ = taskRepo.Save(context.Background(), taskItem.Task)
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||
return
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, nil))
|
||||
}()
|
||||
return
|
||||
}
|
||||
@@ -70,19 +73,20 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
record.TaskID = ""
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
if err := taskItem.Execute(); err != nil {
|
||||
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||
if len(taskItem.ID) == 0 {
|
||||
storedTask, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||
if len(storedTask.ID) == 0 {
|
||||
record.TaskID = ""
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||
} else {
|
||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, nil))
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -482,8 +486,33 @@ func hasBackup(cronjobType string) bool {
|
||||
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log" || cronjobType == "cutWebsiteLog"
|
||||
}
|
||||
|
||||
func handleCronJobAlert(cronjob *model.Cronjob) {
|
||||
const cronJobSkippedStep = "cronjob-skipped"
|
||||
|
||||
func cronJobAlertResult(taskItem *task.Task, err error) string {
|
||||
if errors.Is(err, context.Canceled) || taskItem.Task.Status == constant.StatusCanceled ||
|
||||
(taskItem.TaskCtx != nil && taskItem.TaskCtx.Err() != nil) {
|
||||
return ""
|
||||
}
|
||||
if err != nil {
|
||||
return alertUtil.CronJobAlertFailed
|
||||
}
|
||||
if taskItem.Task.Status != constant.StatusSuccess {
|
||||
return ""
|
||||
}
|
||||
for _, subTask := range taskItem.SubTasks {
|
||||
if subTask.StepAlias != cronJobSkippedStep {
|
||||
return alertUtil.CronJobAlertSuccess
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func handleCronJobAlert(cronjob *model.Cronjob, result string) {
|
||||
if result == "" {
|
||||
return
|
||||
}
|
||||
pushAlert := dto.PushAlert{
|
||||
Result: result,
|
||||
TaskName: cronjob.Name,
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
|
||||
+143
-105
@@ -18,8 +18,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
||||
@@ -243,9 +242,8 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
|
||||
currentInfo.SwapMemoryUsed = swapInfo.Used
|
||||
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
|
||||
|
||||
currentInfo.DiskData = loadDiskInfo()
|
||||
currentInfo.GPUData = loadGPUInfo()
|
||||
currentInfo.XPUData = loadXpuInfo()
|
||||
currentInfo.DiskData = loadDiskInfo(false)
|
||||
currentInfo.GPUData, currentInfo.NPUData, currentInfo.XPUData = loadAcceleratorInfo()
|
||||
|
||||
if ioOption == "all" {
|
||||
diskInfo, _ := disk.IOCounters()
|
||||
@@ -457,24 +455,9 @@ type diskInfo struct {
|
||||
Device string
|
||||
}
|
||||
|
||||
func loadDiskInfo() []dto.DiskInfo {
|
||||
func loadDiskInfo(forceRefresh bool) []dto.DiskInfo {
|
||||
var datas []dto.DiskInfo
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second))
|
||||
format := `NR>1 && !/tmpfs|snap\/core|udev/ {printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $1, $2, $3, $4, $5, $6, $7}`
|
||||
stdout, err := cmdMgr.RunPipe(
|
||||
cmd.PipeCommand{Name: "df", Args: []string{"-hT", "-P"}},
|
||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||
)
|
||||
if err != nil {
|
||||
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
|
||||
stdout, err = cmdMgr2.RunPipe(
|
||||
cmd.PipeCommand{Name: "df", Args: []string{"-lhT", "-P"}},
|
||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||
)
|
||||
if err != nil {
|
||||
return datas
|
||||
}
|
||||
}
|
||||
stdout := loadDiskMounts()
|
||||
lines := strings.Split(stdout, "\n")
|
||||
|
||||
var mounts []diskInfo
|
||||
@@ -522,43 +505,22 @@ func loadDiskInfo() []dto.DiskInfo {
|
||||
itemData.Type = mount.Type
|
||||
itemData.Device = mount.Device
|
||||
|
||||
type diskResult struct {
|
||||
state *disk.UsageStat
|
||||
err error
|
||||
}
|
||||
resultCh := make(chan diskResult, 1)
|
||||
|
||||
go func() {
|
||||
state, err := psutil.DISK.GetUsage(mount.Mount, false)
|
||||
resultCh <- diskResult{state: state, err: err}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
global.LOG.Errorf("load disk info from %s failed, err: timeout", mount.Mount)
|
||||
case result := <-resultCh:
|
||||
if result.err != nil {
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, result.err)
|
||||
return
|
||||
}
|
||||
itemData.Total = result.state.Total
|
||||
itemData.Free = result.state.Free
|
||||
itemData.Used = result.state.Used
|
||||
itemData.UsedPercent = result.state.UsedPercent
|
||||
itemData.InodesTotal = result.state.InodesTotal
|
||||
itemData.InodesUsed = result.state.InodesUsed
|
||||
itemData.InodesFree = result.state.InodesFree
|
||||
itemData.InodesUsedPercent = result.state.InodesUsedPercent
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
state, err := loadDiskUsageWithTimeout(mount.Mount, forceRefresh)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, err)
|
||||
} else {
|
||||
itemData.Total = state.Total
|
||||
itemData.Free = state.Free
|
||||
itemData.Used = state.Used
|
||||
itemData.UsedPercent = state.UsedPercent
|
||||
itemData.InodesTotal = state.InodesTotal
|
||||
itemData.InodesUsed = state.InodesUsed
|
||||
itemData.InodesFree = state.InodesFree
|
||||
itemData.InodesUsedPercent = state.InodesUsedPercent
|
||||
}
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
}(mounts[i])
|
||||
}
|
||||
wg.Wait()
|
||||
@@ -569,32 +531,134 @@ func loadDiskInfo() []dto.DiskInfo {
|
||||
return datas
|
||||
}
|
||||
|
||||
func loadGPUInfo() []dto.GPUInfo {
|
||||
ok, client := gpu.New()
|
||||
var list []interface{}
|
||||
if ok {
|
||||
info, err := client.LoadGpuInfo()
|
||||
if err != nil || len(info.GPUs) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, item := range info.GPUs {
|
||||
list = append(list, item)
|
||||
}
|
||||
var diskMountsMu sync.Mutex
|
||||
|
||||
func loadDiskMounts() string {
|
||||
if !diskMountsMu.TryLock() {
|
||||
return ""
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
resultCh := make(chan string, 1)
|
||||
go func() {
|
||||
var stdout string
|
||||
defer func() {
|
||||
diskMountsMu.Unlock()
|
||||
resultCh <- stdout
|
||||
}()
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second))
|
||||
format := `NR>1 && !/tmpfs|snap\/core|udev/ {printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $1, $2, $3, $4, $5, $6, $7}`
|
||||
output, err := cmdMgr.RunPipe(
|
||||
cmd.PipeCommand{Name: "df", Args: []string{"-hT", "-P"}},
|
||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||
)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load disk info with df -hT -P failed, err: %v", err)
|
||||
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
|
||||
output, err = cmdMgr2.RunPipe(
|
||||
cmd.PipeCommand{Name: "df", Args: []string{"-lhT", "-P"}},
|
||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||
)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load disk info with df -lhT -P failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
stdout = output
|
||||
}()
|
||||
timer := time.NewTimer(3 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case stdout := <-resultCh:
|
||||
return stdout
|
||||
case <-timer.C:
|
||||
global.LOG.Error("load disk mounts timed out; df collection is still running")
|
||||
return ""
|
||||
}
|
||||
var data []dto.GPUInfo
|
||||
for _, gpu := range list {
|
||||
var dataItem dto.GPUInfo
|
||||
if err := copier.Copy(&dataItem, &gpu); err != nil {
|
||||
}
|
||||
|
||||
func loadDiskUsageWithTimeout(path string, forceRefresh bool) (*disk.UsageStat, error) {
|
||||
type diskResult struct {
|
||||
state *disk.UsageStat
|
||||
err error
|
||||
}
|
||||
resultCh := make(chan diskResult, 1)
|
||||
go func() {
|
||||
state, err := psutil.DISK.GetUsage(path, forceRefresh)
|
||||
resultCh <- diskResult{state: state, err: err}
|
||||
}()
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
return nil, fmt.Errorf("load disk usage from %s: timeout", path)
|
||||
case result := <-resultCh:
|
||||
return result.state, result.err
|
||||
}
|
||||
}
|
||||
|
||||
func loadAcceleratorInfo() ([]dto.GPUInfo, []dto.NPUInfo, []dto.XPUInfo) {
|
||||
ok, client := accelerator.New()
|
||||
if !ok {
|
||||
return nil, nil, nil
|
||||
}
|
||||
snapshot, err := client.Collect(context.Background())
|
||||
if err != nil || len(snapshot.Devices) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
if warning := snapshot.Warning(); warning != nil {
|
||||
global.LOG.Warnf("load accelerator dashboard data partially failed, err: %v", warning)
|
||||
}
|
||||
|
||||
var (
|
||||
gpuData []dto.GPUInfo
|
||||
npuData []dto.NPUInfo
|
||||
xpuData []dto.XPUInfo
|
||||
)
|
||||
for _, device := range snapshot.Devices {
|
||||
if device.ParentID != "" {
|
||||
continue
|
||||
}
|
||||
dataItem.PowerUsage = dataItem.PowerDraw + " / " + dataItem.MaxPowerLimit
|
||||
dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal
|
||||
data = append(data, dataItem)
|
||||
switch device.Kind {
|
||||
case accelerator.KindGPU:
|
||||
if device.GPU == nil {
|
||||
continue
|
||||
}
|
||||
var dataItem dto.GPUInfo
|
||||
if err := copier.Copy(&dataItem, device.GPU); err != nil {
|
||||
continue
|
||||
}
|
||||
dataItem.MaxPowerLimit = device.GPU.PowerLimit
|
||||
dataItem.PowerUsage = dataItem.PowerDraw
|
||||
if dataItem.MaxPowerLimit != "" {
|
||||
dataItem.PowerUsage += " / " + dataItem.MaxPowerLimit
|
||||
}
|
||||
dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal
|
||||
gpuData = append(gpuData, dataItem)
|
||||
case accelerator.KindNPU:
|
||||
if device.NPU == nil {
|
||||
continue
|
||||
}
|
||||
var dataItem dto.NPUInfo
|
||||
if err := copier.Copy(&dataItem, device.NPU); err != nil {
|
||||
continue
|
||||
}
|
||||
npuData = append(npuData, dataItem)
|
||||
case accelerator.KindXPU:
|
||||
if device.XPU == nil {
|
||||
continue
|
||||
}
|
||||
xpuData = append(xpuData, dto.XPUInfo{
|
||||
DeviceID: device.Index,
|
||||
DeviceName: device.Name,
|
||||
PciBdfAddress: device.BusID,
|
||||
Memory: device.XPU.Basic.Memory,
|
||||
Temperature: device.Metrics.Temperature.Display,
|
||||
GPUUtil: device.Metrics.Utilization.Display,
|
||||
MemoryUsed: device.Metrics.MemoryUsed.Display,
|
||||
Power: device.Metrics.Power.Display,
|
||||
MemoryUtil: device.Metrics.MemoryUtil.Display,
|
||||
})
|
||||
}
|
||||
}
|
||||
return data
|
||||
return gpuData, npuData, xpuData
|
||||
}
|
||||
|
||||
type AppLauncher struct {
|
||||
@@ -610,32 +674,6 @@ func ArryContains(arr []string, element string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func loadXpuInfo() []dto.XPUInfo {
|
||||
var list []interface{}
|
||||
ok, xpuClient := xpu.New()
|
||||
if ok {
|
||||
xpus, err := xpuClient.LoadDashData()
|
||||
if err != nil || len(xpus) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, item := range xpus {
|
||||
list = append(list, item)
|
||||
}
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
var data []dto.XPUInfo
|
||||
for _, gpu := range list {
|
||||
var dataItem dto.XPUInfo
|
||||
if err := copier.Copy(&dataItem, &gpu); err != nil {
|
||||
continue
|
||||
}
|
||||
data = append(data, dataItem)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func loadOutboundIP() string {
|
||||
conn, err := network.Dial("udp", "8.8.8.8:80")
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ import (
|
||||
type DatabaseService struct{}
|
||||
|
||||
type IDatabaseService interface {
|
||||
Get(name string, readOnly ...bool) (dto.DatabaseInfo, error)
|
||||
SearchWithPage(search dto.DatabaseSearch, readOnly ...bool) (int64, interface{}, error)
|
||||
Get(name string) (dto.DatabaseInfo, error)
|
||||
SearchWithPage(search dto.DatabaseSearch) (int64, interface{}, error)
|
||||
CheckDatabase(req dto.DatabaseCreate) bool
|
||||
Create(req dto.DatabaseCreate) error
|
||||
Update(req dto.DatabaseUpdate) error
|
||||
@@ -39,7 +39,7 @@ func NewIDatabaseService() IDatabaseService {
|
||||
return &DatabaseService{}
|
||||
}
|
||||
|
||||
func (u *DatabaseService) SearchWithPage(search dto.DatabaseSearch, readOnly ...bool) (int64, interface{}, error) {
|
||||
func (u *DatabaseService) SearchWithPage(search dto.DatabaseSearch) (int64, interface{}, error) {
|
||||
total, dbs, err := databaseRepo.Page(search.Page, search.PageSize,
|
||||
databaseRepo.WithTypeList(search.Type),
|
||||
repo.WithByLikeName(search.Info),
|
||||
@@ -52,16 +52,12 @@ func (u *DatabaseService) SearchWithPage(search dto.DatabaseSearch, readOnly ...
|
||||
if err := copier.Copy(&item, &db); err != nil {
|
||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Password = ""
|
||||
item.ClientKey = ""
|
||||
}
|
||||
datas = append(datas, item)
|
||||
}
|
||||
return total, datas, err
|
||||
}
|
||||
|
||||
func (u *DatabaseService) Get(name string, readOnly ...bool) (dto.DatabaseInfo, error) {
|
||||
func (u *DatabaseService) Get(name string) (dto.DatabaseInfo, error) {
|
||||
var data dto.DatabaseInfo
|
||||
remote, err := databaseRepo.Get(repo.WithByName(name))
|
||||
if err != nil {
|
||||
@@ -70,10 +66,6 @@ func (u *DatabaseService) Get(name string, readOnly ...bool) (dto.DatabaseInfo,
|
||||
if err := copier.Copy(&data, &remote); err != nil {
|
||||
return data, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
data.Password = ""
|
||||
data.ClientKey = ""
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
type MongodbService struct{}
|
||||
|
||||
type IMongodbService interface {
|
||||
SearchWithPage(search dto.MongodbDBSearch, readOnly ...bool) (int64, interface{}, error)
|
||||
SearchWithPage(search dto.MongodbDBSearch) (int64, interface{}, error)
|
||||
Create(ctx context.Context, req dto.MongodbDBCreate) (*model.DatabaseMongodb, error)
|
||||
LoadFromRemote(req dto.MongodbLoadDB) error
|
||||
UpdateDescription(req dto.UpdateDescription) error
|
||||
@@ -40,7 +40,7 @@ func NewIMongodbService() IMongodbService {
|
||||
return &MongodbService{}
|
||||
}
|
||||
|
||||
func (u *MongodbService) SearchWithPage(search dto.MongodbDBSearch, readOnly ...bool) (int64, interface{}, error) {
|
||||
func (u *MongodbService) SearchWithPage(search dto.MongodbDBSearch) (int64, interface{}, error) {
|
||||
total, mongodbs, err := mongodbRepo.Page(
|
||||
search.Page,
|
||||
search.PageSize,
|
||||
@@ -54,9 +54,6 @@ func (u *MongodbService) SearchWithPage(search dto.MongodbDBSearch, readOnly ...
|
||||
if err := copier.Copy(&item, &mongodb); err != nil {
|
||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Password = ""
|
||||
}
|
||||
dtoMongodbs = append(dtoMongodbs, item)
|
||||
}
|
||||
return total, dtoMongodbs, err
|
||||
|
||||
@@ -46,7 +46,7 @@ type IMysqlService interface {
|
||||
DeleteCheck(req dto.MysqlDBDeleteCheck) ([]dto.DBResource, error)
|
||||
Delete(ctx context.Context, req dto.MysqlDBDelete) error
|
||||
|
||||
ListUsers(req dto.MysqlUserSearch, readOnly ...bool) ([]dto.MysqlUser, error)
|
||||
ListUsers(req dto.MysqlUserSearch) ([]dto.MysqlUser, error)
|
||||
ListGrants(req dto.MysqlUserSearch) ([]dto.MysqlGrant, error)
|
||||
ListGrantSummary(req dto.MysqlGrantSummarySearch) (map[string][]dto.MysqlUser, error)
|
||||
CreateUser(req dto.MysqlUserCreate) error
|
||||
@@ -506,7 +506,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
|
||||
return &createItem, nil
|
||||
}
|
||||
|
||||
func (u *MysqlService) ListUsers(req dto.MysqlUserSearch, readOnly ...bool) ([]dto.MysqlUser, error) {
|
||||
func (u *MysqlService) ListUsers(req dto.MysqlUserSearch) ([]dto.MysqlUser, error) {
|
||||
dbType, err := resolveDatabaseUserType(req.Database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -520,14 +520,10 @@ func (u *MysqlService) ListUsers(req dto.MysqlUserSearch, readOnly ...bool) ([]d
|
||||
if isMysqlSystemUser(user.Username) {
|
||||
continue
|
||||
}
|
||||
password := user.Password
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
password = ""
|
||||
}
|
||||
res = append(res, dto.MysqlUser{
|
||||
Username: user.Username,
|
||||
Host: user.Host,
|
||||
Password: password,
|
||||
Password: user.Password,
|
||||
Description: user.Description,
|
||||
IsDelete: user.IsDelete,
|
||||
})
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
type PostgresqlService struct{}
|
||||
|
||||
type IPostgresqlService interface {
|
||||
SearchWithPage(search dto.PostgresqlDBSearch, readOnly ...bool) (int64, interface{}, error)
|
||||
SearchWithPage(search dto.PostgresqlDBSearch) (int64, interface{}, error)
|
||||
ListDBOption() ([]dto.PostgresqlOption, error)
|
||||
BindUser(req dto.PostgresqlBindUser) error
|
||||
Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error)
|
||||
@@ -42,7 +42,7 @@ func NewIPostgresqlService() IPostgresqlService {
|
||||
return &PostgresqlService{}
|
||||
}
|
||||
|
||||
func (u *PostgresqlService) SearchWithPage(search dto.PostgresqlDBSearch, readOnly ...bool) (int64, interface{}, error) {
|
||||
func (u *PostgresqlService) SearchWithPage(search dto.PostgresqlDBSearch) (int64, interface{}, error) {
|
||||
total, postgresqls, err := postgresqlRepo.Page(search.Page, search.PageSize,
|
||||
postgresqlRepo.WithByPostgresqlName(search.Database),
|
||||
repo.WithByLikeName(search.Info),
|
||||
@@ -54,9 +54,6 @@ func (u *PostgresqlService) SearchWithPage(search dto.PostgresqlDBSearch, readOn
|
||||
if err := copier.Copy(&item, &pg); err != nil {
|
||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Password = ""
|
||||
}
|
||||
dtoPostgresqls = append(dtoPostgresqls, item)
|
||||
}
|
||||
return total, dtoPostgresqls, err
|
||||
|
||||
@@ -73,7 +73,7 @@ func (u *DeviceService) LoadBaseInfo() (dto.DeviceBaseInfo, error) {
|
||||
if baseInfo.SwapMemoryTotal != 0 {
|
||||
baseInfo.SwapDetails = loadSwap()
|
||||
}
|
||||
disks := loadDiskInfo()
|
||||
disks := loadDiskInfo(false)
|
||||
for _, item := range disks {
|
||||
baseInfo.MaxSize += item.Free
|
||||
}
|
||||
|
||||
+123
-1
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -14,14 +15,20 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
|
||||
dockerfirewall "github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
|
||||
)
|
||||
|
||||
const dockerNftablesMinVersion = "29.0.0"
|
||||
|
||||
type DockerService struct{}
|
||||
|
||||
type IDockerService interface {
|
||||
UpdateConf(req dto.SettingUpdate, withRestart bool) error
|
||||
UpdateFirewallBackend(backend string) error
|
||||
UpdateLogOption(req dto.LogOption) error
|
||||
UpdateIpv6Option(req dto.Ipv6Option) error
|
||||
UpdateConfByFile(info dto.DaemonJsonUpdateByFile) error
|
||||
@@ -30,6 +37,115 @@ type IDockerService interface {
|
||||
OperateDocker(req dto.DockerOperation) error
|
||||
}
|
||||
|
||||
func loadDockerEngineVersion(ctx context.Context) string {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err == nil {
|
||||
defer client.Close()
|
||||
if version, versionErr := client.ServerVersion(ctx); versionErr == nil && version.Version != "" {
|
||||
return version.Version
|
||||
}
|
||||
}
|
||||
if !cmd.Which("dockerd") {
|
||||
return ""
|
||||
}
|
||||
stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("dockerd", "--version")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(stdout)
|
||||
}
|
||||
|
||||
func dockerNftablesSupported(version string) bool {
|
||||
return version != "" && common.CompareAppVersion(version, dockerNftablesMinVersion)
|
||||
}
|
||||
|
||||
func applyDockerFirewallBackendConfig(daemonMap map[string]interface{}, backend, version string) error {
|
||||
switch backend {
|
||||
case constant.FirewallProviderNftables:
|
||||
if !dockerNftablesSupported(version) {
|
||||
return fmt.Errorf("Docker Engine %s or later is required for the nftables firewall backend", dockerNftablesMinVersion)
|
||||
}
|
||||
daemonMap["experimental"] = true
|
||||
daemonMap["firewall-backend"] = constant.FirewallProviderNftables
|
||||
case constant.FirewallProviderIptables:
|
||||
if dockerNftablesSupported(version) {
|
||||
daemonMap["firewall-backend"] = constant.FirewallProviderIptables
|
||||
} else {
|
||||
delete(daemonMap, "firewall-backend")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported Docker firewall backend %q", backend)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *DockerService) UpdateFirewallBackend(backend string) error {
|
||||
version := loadDockerEngineVersion(context.Background())
|
||||
if backend == constant.FirewallProviderNftables && !dockerNftablesSupported(version) {
|
||||
return fmt.Errorf("Docker Engine %s or later is required for the nftables firewall backend", dockerNftablesMinVersion)
|
||||
}
|
||||
if backend == constant.FirewallProviderNftables {
|
||||
if err := dockerfirewall.CheckIPv4Forwarding(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
original, readErr := os.ReadFile(constant.DaemonJsonPath)
|
||||
existed := readErr == nil
|
||||
if readErr != nil && !os.IsNotExist(readErr) {
|
||||
return readErr
|
||||
}
|
||||
daemonMap := make(map[string]interface{})
|
||||
if len(bytes.TrimSpace(original)) > 0 {
|
||||
if err := json.Unmarshal(original, &daemonMap); err != nil {
|
||||
return fmt.Errorf("failed to parse Docker configuration: %w", err)
|
||||
}
|
||||
}
|
||||
if err := applyDockerFirewallBackendConfig(daemonMap, backend, version); err != nil {
|
||||
return err
|
||||
}
|
||||
updated, err := json.MarshalIndent(daemonMap, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existed && bytes.Equal(bytes.TrimSpace(original), bytes.TrimSpace(updated)) {
|
||||
return nil
|
||||
}
|
||||
if err := os.MkdirAll(path.Dir(constant.DaemonJsonPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(constant.DaemonJsonPath, updated, 0640); err != nil {
|
||||
return err
|
||||
}
|
||||
restore := func() error {
|
||||
if existed {
|
||||
return os.WriteFile(constant.DaemonJsonPath, original, 0640)
|
||||
}
|
||||
err := os.Remove(constant.DaemonJsonPath)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
if restoreErr := restore(); restoreErr != nil {
|
||||
return fmt.Errorf("%v; failed to restore Docker configuration: %w", err, restoreErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := controller.HandleRestart("docker"); err != nil {
|
||||
cause := fmt.Errorf("failed to restart Docker: %w", err)
|
||||
if restoreErr := restore(); restoreErr != nil {
|
||||
return fmt.Errorf("%v; failed to restore Docker configuration: %w", cause, restoreErr)
|
||||
}
|
||||
if restoreRestartErr := controller.HandleRestart("docker"); restoreRestartErr != nil {
|
||||
return fmt.Errorf("%v; the previous configuration was restored but Docker could not be restarted: %w", cause, restoreRestartErr)
|
||||
}
|
||||
return cause
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewIDockerService() IDockerService {
|
||||
return &DockerService{}
|
||||
}
|
||||
@@ -167,7 +283,10 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate, withRestart bool) erro
|
||||
delete(daemonMap, "ipv6")
|
||||
delete(daemonMap, "fixed-cidr-v6")
|
||||
delete(daemonMap, "ip6tables")
|
||||
delete(daemonMap, "experimental")
|
||||
backend, _ := settingRepo.GetValueByKey(constant.FirewallDockerBackendKey)
|
||||
if !strings.EqualFold(strings.TrimSpace(backend), constant.FirewallProviderNftables) {
|
||||
delete(daemonMap, "experimental")
|
||||
}
|
||||
}
|
||||
case "LogOption":
|
||||
if req.Value == "disable" {
|
||||
@@ -368,6 +487,9 @@ func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
|
||||
if err := controller.Handle(req.Operation, service); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Operation == "start" || req.Operation == "restart" {
|
||||
return RestoreDockerPortGuard(context.Background())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -32,10 +32,11 @@ var (
|
||||
scriptRepo = repo.NewIScriptRepo()
|
||||
cronjobRepo = repo.NewICronjobRepo()
|
||||
|
||||
hostRepo = repo.NewIHostRepo()
|
||||
ftpRepo = repo.NewIFtpRepo()
|
||||
clamRepo = repo.NewIClamRepo()
|
||||
monitorRepo = repo.NewIMonitorRepo()
|
||||
hostRepo = repo.NewIHostRepo()
|
||||
ftpRepo = repo.NewIFtpRepo()
|
||||
clamRepo = repo.NewIClamRepo()
|
||||
monitorRepo = repo.NewIMonitorRepo()
|
||||
vllmMonitorRepo = &repo.VLLMMonitorRepo{}
|
||||
|
||||
settingRepo = repo.NewISettingRepo()
|
||||
backupRepo = repo.NewIBackupRepo()
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/toolbox"
|
||||
)
|
||||
|
||||
@@ -109,7 +108,7 @@ func (u *Fail2BanService) UpdateConf(req dto.Fail2BanUpdate) error {
|
||||
if req.Value == "firewallcmd-ipset" {
|
||||
itemName = "firewalld"
|
||||
}
|
||||
client, err := firewall.NewFirewallClient()
|
||||
client, err := NewSelectedSystemFirewallClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+40
-21
@@ -87,7 +87,7 @@ type IFileService interface {
|
||||
ConvertLog(req dto.PageInfo) (int64, []response.FileConvertLog, error)
|
||||
BatchGetRemarks(req request.FileRemarkBatch) map[string]string
|
||||
SetRemark(req request.FileRemarkUpdate) error
|
||||
AISearch(req request.FileAISearch, readOnly ...bool) (*response.FileAISearchResult, error)
|
||||
AISearch(req request.FileAISearch) (*response.FileAISearchResult, error)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -159,6 +159,10 @@ func (f *FileService) SearchUploadWithPage(req request.SearchUploadWithPage) (in
|
||||
})
|
||||
}
|
||||
|
||||
sort.SliceStable(files, func(i, j int) bool {
|
||||
return files[i].CreatedAt > files[j].CreatedAt
|
||||
})
|
||||
|
||||
total, start, end := len(files), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
backData = make([]response.UploadInfo, 0)
|
||||
@@ -435,13 +439,15 @@ func (f *FileService) Compress(c request.FileCompress) error {
|
||||
if err := preflightCompressTool(files.CompressType(c.Type)); err != nil {
|
||||
return err
|
||||
}
|
||||
taskItem, err := task.NewTask(c.Name, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||
taskName := i18n.GetMsgWithMap("FileTaskCompress", map[string]interface{}{"dst": strconv.Quote(filepath.Join(c.Dst, c.Name))})
|
||||
taskItem, err := task.NewTask(taskName, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
taskItem.AddSubTask(c.Name, func(t *task.Task) error {
|
||||
t.LogStart(c.Name)
|
||||
taskItem.AddSubTask(taskName, func(t *task.Task) error {
|
||||
logFileTaskSources(t, c.Files)
|
||||
t.Log(i18n.GetMsgWithMap("FileTaskFormat", map[string]interface{}{"format": strconv.Quote(c.Type)}))
|
||||
compressType := files.CompressType(c.Type)
|
||||
dstFile := filepath.Join(c.Dst, c.Name)
|
||||
success := false
|
||||
@@ -512,13 +518,15 @@ func (f *FileService) DeCompress(c request.FileDeCompress) error {
|
||||
if err := preflightDecompressTool(files.CompressType(c.Type)); err != nil {
|
||||
return err
|
||||
}
|
||||
taskItem, err := task.NewTask(c.Path, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||
taskName := i18n.GetMsgWithMap("FileTaskDecompress", map[string]interface{}{"dst": strconv.Quote(c.Dst)})
|
||||
taskItem, err := task.NewTask(taskName, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
taskItem.AddSubTask(c.Path, func(t *task.Task) error {
|
||||
t.LogStart(c.Path)
|
||||
taskItem.AddSubTask(taskName, func(t *task.Task) error {
|
||||
logFileTaskSources(t, []string{c.Path})
|
||||
t.Log(i18n.GetMsgWithMap("FileTaskFormat", map[string]interface{}{"format": strconv.Quote(c.Type)}))
|
||||
dstExisted := fo.Stat(c.Dst)
|
||||
parentDir := filepath.Dir(c.Dst)
|
||||
if !fo.Stat(parentDir) {
|
||||
@@ -892,6 +900,7 @@ func (f *FileService) Wget(w request.FileWget) (string, error) {
|
||||
key := "file-wget-" + common.GetUuid()
|
||||
options := files.DownloadOptions{
|
||||
IgnoreCertificate: w.IgnoreCertificate,
|
||||
UseServerFilename: w.UseServerFilename,
|
||||
}
|
||||
if w.UseProxy {
|
||||
systemProxy, err := NewISettingService().GetSystemProxy()
|
||||
@@ -909,6 +918,12 @@ func (f *FileService) Wget(w request.FileWget) (string, error) {
|
||||
return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key, options)
|
||||
}
|
||||
|
||||
func logFileTaskSources(t *task.Task, sources []string) {
|
||||
for _, source := range sources {
|
||||
t.Log(i18n.GetMsgWithMap("FileTaskSource", map[string]interface{}{"path": strconv.Quote(source)}))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FileService) MvFile(m request.FileMove) error {
|
||||
fo := files.NewFileOp()
|
||||
if err := validateFileMove(fo, m); err != nil {
|
||||
@@ -920,15 +935,24 @@ func (f *FileService) MvFile(m request.FileMove) error {
|
||||
if !fileTransferLocks.Acquire(m.TaskID, getFileTransferPaths(m)) {
|
||||
return buserr.New("TaskIsExecuting")
|
||||
}
|
||||
taskItem, err := task.NewTask(m.NewPath, task.TaskExec, task.TaskScopeTask, m.TaskID, 1)
|
||||
nameKey := "FileTaskCopy"
|
||||
if m.Type == "cut" {
|
||||
nameKey = "FileTaskMove"
|
||||
}
|
||||
taskName := i18n.GetMsgWithMap(nameKey, map[string]interface{}{"dst": strconv.Quote(m.NewPath)})
|
||||
taskItem, err := task.NewTask(taskName, task.TaskExec, task.TaskScopeTask, m.TaskID, 1)
|
||||
if err != nil {
|
||||
fileTransferLocks.Release(m.TaskID)
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
defer fileTransferLocks.Release(m.TaskID)
|
||||
taskItem.AddSubTaskWithOps(m.NewPath, func(t *task.Task) error {
|
||||
t.LogStart(m.NewPath)
|
||||
taskItem.AddSubTaskWithOps(taskName, func(t *task.Task) error {
|
||||
logFileTaskSources(t, m.OldPaths)
|
||||
logFileTaskSources(t, m.CoverPaths)
|
||||
if m.Name != "" {
|
||||
t.Log(i18n.GetMsgWithMap("FileTaskRename", map[string]interface{}{"name": strconv.Quote(m.Name)}))
|
||||
}
|
||||
err := f.moveFileWithContext(t.TaskCtx, m)
|
||||
if err != nil && t.TaskCtx.Err() != nil {
|
||||
return t.TaskCtx.Err()
|
||||
@@ -1286,7 +1310,7 @@ func (f *FileService) BatchCheckFiles(req request.FilePathsCheck) []response.Exi
|
||||
}
|
||||
|
||||
func (f *FileService) GetHostMount() []dto.DiskInfo {
|
||||
return loadDiskInfo()
|
||||
return loadDiskInfo(false)
|
||||
}
|
||||
|
||||
func (f *FileService) GetUsersAndGroups() (*response.UserGroupResponse, error) {
|
||||
@@ -1560,7 +1584,7 @@ func (f *FileService) ConvertLog(req dto.PageInfo) (total int64, data []response
|
||||
return total, data, nil
|
||||
}
|
||||
|
||||
func (f *FileService) AISearch(req request.FileAISearch, readOnly ...bool) (*response.FileAISearchResult, error) {
|
||||
func (f *FileService) AISearch(req request.FileAISearch) (*response.FileAISearchResult, error) {
|
||||
root := filepath.Clean(strings.TrimSpace(req.Path))
|
||||
if root == "" {
|
||||
return nil, buserr.WithDetail("ErrInvalidParams", "path is required", nil)
|
||||
@@ -1613,15 +1637,10 @@ func (f *FileService) AISearch(req request.FileAISearch, readOnly ...bool) (*res
|
||||
return nil, buserr.WithDetail("ErrFileAISearchBadPattern", err.Error(), nil)
|
||||
}
|
||||
|
||||
var cfg terminalai.GeneratorConfig
|
||||
var timeout time.Duration
|
||||
aiEnabled := false
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
cfg, timeout, err = terminalai.LoadFileAIRuntimeConfig()
|
||||
aiEnabled = err == nil
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, err
|
||||
}
|
||||
cfg, timeout, err := terminalai.LoadFileAIRuntimeConfig()
|
||||
aiEnabled := err == nil
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items, truncated, err := files.CollectDirInventory(root, containSub, maxItems)
|
||||
|
||||
@@ -37,14 +37,14 @@ var fileShareCodeRegexp = regexp.MustCompile(`^[A-Za-z0-9]{10,16}$`)
|
||||
|
||||
type IFileShareService interface {
|
||||
Create(req request.FileShareCreate) (*response.FileShareInfo, error)
|
||||
Page(req dto.PageInfo, readOnly ...bool) (int64, []response.FileShareInfo, error)
|
||||
GetByPath(path string, readOnly ...bool) (*response.FileShareInfo, error)
|
||||
GetByCode(code string, readOnly ...bool) (*response.FileShareInfo, error)
|
||||
GetPublicByCode(code string, readOnly ...bool) (*response.FileSharePublicInfo, error)
|
||||
Page(req dto.PageInfo) (int64, []response.FileShareInfo, error)
|
||||
GetByPath(path string) (*response.FileShareInfo, error)
|
||||
GetByCode(code string) (*response.FileShareInfo, error)
|
||||
GetPublicByCode(code string) (*response.FileSharePublicInfo, error)
|
||||
DeleteByPath(path string) error
|
||||
SharePathCodeMap() (map[string]string, error)
|
||||
Check(code, password string, readOnly ...bool) error
|
||||
PrepareDownload(code, password string, readOnly ...bool) (filePath, fileName string, err error)
|
||||
Check(code, password string) error
|
||||
PrepareDownload(code, password string) (filePath, fileName string, err error)
|
||||
}
|
||||
|
||||
func NewIFileShareService() IFileShareService {
|
||||
@@ -212,14 +212,14 @@ func (s *FileShareService) Create(req request.FileShareCreate) (*response.FileSh
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) Page(req dto.PageInfo, readOnly ...bool) (int64, []response.FileShareInfo, error) {
|
||||
func (s *FileShareService) Page(req dto.PageInfo) (int64, []response.FileShareInfo, error) {
|
||||
items, err := fileShareRepo.All()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
result := make([]response.FileShareInfo, 0, len(items))
|
||||
for _, item := range items {
|
||||
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
||||
if err := s.pruneInvalidShare(item); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
||||
@@ -239,7 +239,7 @@ func (s *FileShareService) Page(req dto.PageInfo, readOnly ...bool) (int64, []re
|
||||
return int64(total), result[start:end], nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) GetByPath(path string, readOnly ...bool) (*response.FileShareInfo, error) {
|
||||
func (s *FileShareService) GetByPath(path string) (*response.FileShareInfo, error) {
|
||||
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByPath(strings.TrimSpace(path)))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -247,7 +247,7 @@ func (s *FileShareService) GetByPath(path string, readOnly ...bool) (*response.F
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
||||
if err := s.pruneInvalidShare(item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
||||
@@ -258,7 +258,7 @@ func (s *FileShareService) GetByPath(path string, readOnly ...bool) (*response.F
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) GetByCode(code string, readOnly ...bool) (*response.FileShareInfo, error) {
|
||||
func (s *FileShareService) GetByCode(code string) (*response.FileShareInfo, error) {
|
||||
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByCode(strings.TrimSpace(code)))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -266,7 +266,7 @@ func (s *FileShareService) GetByCode(code string, readOnly ...bool) (*response.F
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
||||
if err := s.pruneInvalidShare(item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
||||
@@ -276,7 +276,7 @@ func (s *FileShareService) GetByCode(code string, readOnly ...bool) (*response.F
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) GetPublicByCode(code string, readOnly ...bool) (*response.FileSharePublicInfo, error) {
|
||||
func (s *FileShareService) GetPublicByCode(code string) (*response.FileSharePublicInfo, error) {
|
||||
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByCode(strings.TrimSpace(code)))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -284,7 +284,7 @@ func (s *FileShareService) GetPublicByCode(code string, readOnly ...bool) (*resp
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
||||
if err := s.pruneInvalidShare(item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
||||
@@ -324,38 +324,32 @@ func (s *FileShareService) SharePathCodeMap() (map[string]string, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) Check(code, password string, readOnly ...bool) error {
|
||||
_, err := s.check(code, password, readOnly...)
|
||||
func (s *FileShareService) Check(code, password string) error {
|
||||
_, err := s.check(code, password)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *FileShareService) PrepareDownload(code, password string, readOnly ...bool) (string, string, error) {
|
||||
item, err := s.check(code, password, readOnly...)
|
||||
func (s *FileShareService) PrepareDownload(code, password string) (string, string, error) {
|
||||
item, err := s.check(code, password)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return item.Path, item.FileName, nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) pruneInvalidShare(item model.FileShare, readOnly ...bool) error {
|
||||
func (s *FileShareService) pruneInvalidShare(item model.FileShare) error {
|
||||
now := time.Now().Unix()
|
||||
if item.ExpiresUnix > 0 && now > item.ExpiresUnix {
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
return nil
|
||||
}
|
||||
return fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||
}
|
||||
info, err := os.Stat(item.Path)
|
||||
if err != nil || info.IsDir() {
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
return nil
|
||||
}
|
||||
return fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) check(code, password string, readOnly ...bool) (*model.FileShare, error) {
|
||||
func (s *FileShareService) check(code, password string) (*model.FileShare, error) {
|
||||
code = strings.TrimSpace(code)
|
||||
password = strings.TrimSpace(password)
|
||||
if code == "" {
|
||||
@@ -372,9 +366,7 @@ func (s *FileShareService) check(code, password string, readOnly ...bool) (*mode
|
||||
|
||||
now := time.Now().Unix()
|
||||
if item.ExpiresUnix > 0 && now > item.ExpiresUnix {
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
_ = fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||
}
|
||||
_ = fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||
return nil, buserr.New("ErrFileShareExpired")
|
||||
}
|
||||
if item.PasswordHash != "" {
|
||||
@@ -385,9 +377,7 @@ func (s *FileShareService) check(code, password string, readOnly ...bool) (*mode
|
||||
|
||||
info, err := os.Stat(item.Path)
|
||||
if err != nil || info.IsDir() {
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
_ = fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||
}
|
||||
_ = fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||
return nil, buserr.New("ErrFileSharePath")
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user