mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-11 00:00:30 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
200ddbfa28 | ||
|
|
f34418da6d | ||
|
|
7ff86c0ee3 | ||
|
|
5a58f44548 | ||
|
|
d27d6db1ea | ||
|
|
c75cb5d7ce | ||
|
|
2207147e7f | ||
|
|
d2bb3813d9 | ||
|
|
cac73e6d45 | ||
|
|
b5604eab11 | ||
|
|
b498882708 | ||
|
|
4712ceaf2c | ||
|
|
65243c68c4 | ||
|
|
381d71663c | ||
|
|
f7f4135193 | ||
|
|
256e79ca81 | ||
|
|
dbf6d9e5fe | ||
|
|
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 {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, list, err := agentService.Page(req, helper.IsDemoRequest(c))
|
total, list, err := agentService.Page(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
@@ -447,7 +447,7 @@ func (b *BaseApi) PageAgentAccounts(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, list, err := agentService.PageAccounts(req, helper.IsDemoRequest(c))
|
total, list, err := agentService.PageAccounts(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ package v2
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -268,7 +271,7 @@ func (b *BaseApi) PageAlertConfig(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, configs, err := alertService.PageAlertConfig(req, helper.IsDemoRequest(c))
|
total, configs, err := alertService.PageAlertConfig(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -294,6 +297,34 @@ func (b *BaseApi) UpdateAlertConfig(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := alertService.UpdateAlertConfig(req, loadAuditUser(c)); err != nil {
|
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)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -346,6 +377,15 @@ func (b *BaseApi) TestAlertConfig(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
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)
|
flag, err := alertService.TestAlertConfig(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
version := c.Param("version")
|
version := c.Param("version")
|
||||||
appType := c.Param("type")
|
appType := c.Param("type")
|
||||||
appDetailDTO, err := appService.GetAppDetail(appID, version, appType, helper.IsDemoRequest(c))
|
appDetailDTO, err := appService.GetAppDetail(appID, version, appType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /apps/ignored/detail [get]
|
// @Router /apps/ignored/detail [get]
|
||||||
func (b *BaseApi) ListAppIgnored(c *gin.Context) {
|
func (b *BaseApi) ListAppIgnored(c *gin.Context) {
|
||||||
res, err := appIgnoreUpgradeService.List(helper.IsDemoRequest(c))
|
res, err := appIgnoreUpgradeService.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ func (b *BaseApi) SearchAppInstalled(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.ReadOnly = helper.IsDemoRequest(c)
|
|
||||||
if req.All {
|
if req.All {
|
||||||
list, err := appInstallService.SearchForWebsite(req)
|
list, err := appInstallService.SearchForWebsite(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -74,7 +73,6 @@ func (b *BaseApi) CheckAppInstalled(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.ReadOnly = helper.IsDemoRequest(c)
|
|
||||||
checkData, err := appInstallService.CheckExist(req)
|
checkData, err := appInstallService.CheckExist(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
@@ -117,7 +115,7 @@ func (b *BaseApi) LoadConnInfo(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn, err := appInstallService.LoadConnInfo(req, helper.IsDemoRequest(c))
|
conn, err := appInstallService.LoadConnInfo(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -139,7 +137,7 @@ func (b *BaseApi) DeleteCheck(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
checkData, err := appInstallService.DeleteCheck(appInstallId, helper.IsDemoRequest(c))
|
checkData, err := appInstallService.DeleteCheck(appInstallId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -279,7 +277,7 @@ func (b *BaseApi) GetParams(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content, err := appInstallService.GetParams(appInstallId, helper.IsDemoRequest(c))
|
content, err := appInstallService.GetParams(appInstallId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -343,7 +341,7 @@ func (b *BaseApi) GetAppInstallInfo(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info, err := appInstallService.GetAppInstallInfo(appInstallId, helper.IsDemoRequest(c))
|
info, err := appInstallService.GetAppInstallInfo(appInstallId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ func (b *BaseApi) SearchBackup(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := backupService.SearchWithPage(req, helper.IsDemoRequest(c))
|
total, list, err := backupService.SearchWithPage(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func (b *BaseApi) SearchClam(c *gin.Context) {
|
|||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /toolbox/clam/base [post]
|
// @Router /toolbox/clam/base [post]
|
||||||
func (b *BaseApi) LoadClamBaseInfo(c *gin.Context) {
|
func (b *BaseApi) LoadClamBaseInfo(c *gin.Context) {
|
||||||
info, err := clamService.LoadBaseInfo(helper.IsDemoRequest(c))
|
info, err := clamService.LoadBaseInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := composeTemplateService.SearchWithPage(req, helper.IsDemoRequest(c))
|
total, list, err := composeTemplateService.SearchWithPage(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -85,7 +85,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) {
|
|||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /containers/template [get]
|
// @Router /containers/template [get]
|
||||||
func (b *BaseApi) ListComposeTemplate(c *gin.Context) {
|
func (b *BaseApi) ListComposeTemplate(c *gin.Context) {
|
||||||
list, err := composeTemplateService.List(helper.IsDemoRequest(c))
|
list, err := composeTemplateService.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (b *BaseApi) SearchContainer(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := containerService.Page(req)
|
total, list, err := containerService.Page(c.Request.Context(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -249,9 +249,10 @@ func (b *BaseApi) ListContainerByImage(c *gin.Context) {
|
|||||||
// @Success 200 {object} dto.ContainerStatus
|
// @Success 200 {object} dto.ContainerStatus
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
|
// @Param containersOnly query boolean false "Only count containers"
|
||||||
// @Router /containers/status [get]
|
// @Router /containers/status [get]
|
||||||
func (b *BaseApi) LoadContainerStatus(c *gin.Context) {
|
func (b *BaseApi) LoadContainerStatus(c *gin.Context) {
|
||||||
data, err := containerService.LoadStatus()
|
data, err := containerService.LoadStatus(c.Request.Context(), c.Query("containersOnly") == "true")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -273,7 +274,7 @@ func (b *BaseApi) SearchCompose(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := containerService.PageCompose(req, helper.IsDemoRequest(c))
|
total, list, err := containerService.PageCompose(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -387,7 +388,7 @@ func (b *BaseApi) ContainerInfo(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := containerService.ContainerInfo(req, helper.IsDemoRequest(c))
|
data, err := containerService.ContainerInfo(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -415,9 +416,14 @@ func (b *BaseApi) LoadResourceLimit(c *gin.Context) {
|
|||||||
// @Success 200 {array} dto.ContainerListStats
|
// @Success 200 {array} dto.ContainerListStats
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
|
// @Param ids query string false "Comma-separated container IDs; omitted selects all containers"
|
||||||
// @Router /containers/list/stats [get]
|
// @Router /containers/list/stats [get]
|
||||||
func (b *BaseApi) ContainerListStats(c *gin.Context) {
|
func (b *BaseApi) ContainerListStats(c *gin.Context) {
|
||||||
data, err := containerService.ContainerListStats()
|
var ids []string
|
||||||
|
if _, supplied := c.Request.URL.Query()["ids"]; supplied {
|
||||||
|
ids = strings.FieldsFunc(c.Query("ids"), func(r rune) bool { return r == ',' })
|
||||||
|
}
|
||||||
|
data, err := containerService.ContainerListStats(c.Request.Context(), ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -439,7 +445,7 @@ func (b *BaseApi) ContainerItemStats(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := containerService.ContainerItemStats(req)
|
data, err := containerService.ContainerItemStats(c.Request.Context(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -964,3 +970,12 @@ func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
|
|||||||
|
|
||||||
containerService.StreamLogs(c, streamLog)
|
containerService.StreamLogs(c, streamLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) CleanNetworks(c *gin.Context) {
|
||||||
|
result, err := containerService.CleanNetworks()
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, result)
|
||||||
|
}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ func (b *BaseApi) SearchCronjob(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := cronjobService.SearchWithPage(req, helper.IsDemoRequest(c))
|
total, list, err := cronjobService.SearchWithPage(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ func (b *BaseApi) SearchDatabase(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := databaseService.SearchWithPage(req, helper.IsDemoRequest(c))
|
total, list, err := databaseService.SearchWithPage(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -147,7 +147,7 @@ func (b *BaseApi) GetDatabase(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := databaseService.Get(name, helper.IsDemoRequest(c))
|
data, err := databaseService.Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (b *BaseApi) SearchMongodb(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := mongodbService.SearchWithPage(req, helper.IsDemoRequest(c))
|
total, list, err := mongodbService.SearchWithPage(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func (b *BaseApi) ListMysqlUsers(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := mysqlService.ListUsers(req, helper.IsDemoRequest(c))
|
data, err := mysqlService.ListUsers(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ func (b *BaseApi) SearchPostgresql(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := postgresqlService.SearchWithPage(req, helper.IsDemoRequest(c))
|
total, list, err := postgresqlService.SearchWithPage(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -86,17 +86,38 @@ func (b *BaseApi) CheckHasCli(c *gin.Context) {
|
|||||||
|
|
||||||
// @Tags Database Redis
|
// @Tags Database Redis
|
||||||
// @Summary Install redis-cli
|
// @Summary Install redis-cli
|
||||||
// @Success 200
|
// @Accept json
|
||||||
|
// @Param request body dto.RedisCliInstall true "request"
|
||||||
|
// @Success 200 {object} dto.RedisCliStatus
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /databases/redis/install/cli [post]
|
// @Router /databases/redis/install/cli [post]
|
||||||
func (b *BaseApi) InstallCli(c *gin.Context) {
|
func (b *BaseApi) InstallCli(c *gin.Context) {
|
||||||
if err := redisService.InstallCli(); err != nil {
|
var req dto.RedisCliInstall
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := redisService.InstallCli(req)
|
||||||
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
helper.Success(c)
|
// @Tags Database Redis
|
||||||
|
// @Summary Load redis-cli installation status
|
||||||
|
// @Success 200 {object} dto.RedisCliStatus
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /databases/redis/cli/status [get]
|
||||||
|
func (b *BaseApi) LoadRedisCliStatus(c *gin.Context) {
|
||||||
|
data, err := redisService.LoadCliStatus()
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Database Redis
|
// @Tags Database Redis
|
||||||
|
|||||||
@@ -42,8 +42,9 @@ var (
|
|||||||
fileShareService = service.NewIFileShareService()
|
fileShareService = service.NewIFileShareService()
|
||||||
sshService = service.NewISSHService()
|
sshService = service.NewISSHService()
|
||||||
firewallService = service.NewIFirewallService()
|
firewallService = service.NewIFirewallService()
|
||||||
|
firewallSettingService = service.NewIFirewallSettingService()
|
||||||
forwardingService = service.NewIForwardingService()
|
forwardingService = service.NewIForwardingService()
|
||||||
iptablesService = service.NewIIptablesService()
|
dockerPortGuardService = service.NewIDockerPortGuardService()
|
||||||
monitorService = service.NewIMonitorService()
|
monitorService = service.NewIMonitorService()
|
||||||
systemService = service.NewISystemService()
|
systemService = service.NewISystemService()
|
||||||
runtimeDiagnosticsService = service.NewIRuntimeDiagnosticsService()
|
runtimeDiagnosticsService = service.NewIRuntimeDiagnosticsService()
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ func (b *BaseApi) FileAISearch(c *gin.Context) {
|
|||||||
if strings.TrimSpace(req.ResponseLanguage) == "" {
|
if strings.TrimSpace(req.ResponseLanguage) == "" {
|
||||||
req.ResponseLanguage = strings.TrimSpace(c.GetHeader("Accept-Language"))
|
req.ResponseLanguage = strings.TrimSpace(c.GetHeader("Accept-Language"))
|
||||||
}
|
}
|
||||||
res, err := fileService.AISearch(req, helper.IsDemoRequest(c))
|
res, err := fileService.AISearch(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -684,10 +684,35 @@ func (b *BaseApi) StopWget(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
files.CancelDownload(req.Key)
|
if err := files.CancelDownload(req.Key); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
helper.Success(c)
|
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
|
// @Tags File
|
||||||
// @Summary Move file
|
// @Summary Move file
|
||||||
// @Accept json
|
// @Accept json
|
||||||
@@ -1633,7 +1658,7 @@ func (b *BaseApi) SearchFileShare(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, list, err := fileShareService.Page(req, helper.IsDemoRequest(c))
|
total, list, err := fileShareService.Page(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -1657,7 +1682,7 @@ func (b *BaseApi) GetFileShareDetail(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info, err := fileShareService.GetByPath(req.Path, helper.IsDemoRequest(c))
|
info, err := fileShareService.GetByPath(req.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -1676,7 +1701,7 @@ func (b *BaseApi) GetPublicFileShareInfo(c *gin.Context) {
|
|||||||
helper.BadRequest(c, errors.New("code is required"))
|
helper.BadRequest(c, errors.New("code is required"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info, err := fileShareService.GetPublicByCode(code, helper.IsDemoRequest(c))
|
info, err := fileShareService.GetPublicByCode(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if be, ok := err.(buserr.BusinessError); ok {
|
if be, ok := err.(buserr.BusinessError); ok {
|
||||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
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"))
|
helper.BadRequest(c, errors.New("code is required"))
|
||||||
return
|
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 {
|
if be, ok := err.(buserr.BusinessError); ok {
|
||||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||||
return
|
return
|
||||||
@@ -1811,7 +1836,7 @@ func (b *BaseApi) CheckFileShare(c *gin.Context) {
|
|||||||
helper.BadRequest(c, errors.New("code is required"))
|
helper.BadRequest(c, errors.New("code is required"))
|
||||||
return
|
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 {
|
if be, ok := err.(buserr.BusinessError); ok {
|
||||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||||
return
|
return
|
||||||
@@ -1836,7 +1861,7 @@ func (b *BaseApi) DownloadFileShare(c *gin.Context) {
|
|||||||
helper.BadRequest(c, errors.New("code is required"))
|
helper.BadRequest(c, errors.New("code is required"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
filePath, displayName, err := fileShareService.PrepareDownload(code, password, helper.IsDemoRequest(c))
|
filePath, displayName, err := fileShareService.PrepareDownload(code, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if be, ok := err.(buserr.BusinessError); ok {
|
if be, ok := err.(buserr.BusinessError); ok {
|
||||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||||
|
|||||||
+625
-253
@@ -1,34 +1,55 @@
|
|||||||
package v2
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
|
|
||||||
|
"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"
|
"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
|
// @Tags Firewall
|
||||||
// @Summary Load firewall base info
|
// @Summary Load firewall base info
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.OperationWithName true "request"
|
// @Param request body dto.OperationWithName true "request"
|
||||||
// @Success 200 {object} dto.FirewallBaseInfo
|
// @Success 200 {object} dto.FirewallSubsystemStatus
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/base [post]
|
// @Router /hosts/firewall/base [post]
|
||||||
func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
|
func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
|
||||||
var req dto.OperationWithName
|
var request dto.OperationWithName
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
data, err := firewallService.LoadBaseInfo(request.Name)
|
||||||
data dto.FirewallBaseInfo
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if req.Name == "forward" {
|
|
||||||
data, err = forwardingService.LoadBaseInfo()
|
|
||||||
} else {
|
|
||||||
data, err = firewallService.LoadBaseInfo(req.Name)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -37,331 +58,682 @@ func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, data)
|
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
|
// @Tags Firewall
|
||||||
// @Summary Operate firewall
|
// @Summary Operate firewall
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.FirewallOperation true "request"
|
// @Param request body dto.FirewallLifecycleOperation true "request"
|
||||||
// @Success 200
|
// @Success 200 {object} dto.FirewallLifecycleOperationResponse
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/operate [post]
|
// @Router /hosts/firewall/operate [post]
|
||||||
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] 防火墙","formatEN":"[operation] firewall"}
|
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] 防火墙","formatEN":"[operation] firewall"}
|
||||||
func (b *BaseApi) OperateFirewall(c *gin.Context) {
|
func (b *BaseApi) OperateFirewall(c *gin.Context) {
|
||||||
var req dto.FirewallOperation
|
var request dto.FirewallLifecycleOperation
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := firewallService.OperateFirewall(req); err != nil {
|
result, err := firewallService.QueueFirewallOperation(request)
|
||||||
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.Success(c)
|
helper.SuccessWithData(c, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Create group
|
// @Summary Load forwarding base info
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.PortRuleOperate true "request"
|
// @Success 200 {object} dto.FirewallSubsystemStatus
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/port [post]
|
// @Router /hosts/firewall/forward/base [post]
|
||||||
// @x-panel-log {"bodyKeys":["port","strategy"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加端口规则 [strategy] [port]","formatEN":"create port rules [strategy][port]"}
|
func (b *BaseApi) LoadForwardingBaseInfo(c *gin.Context) {
|
||||||
func (b *BaseApi) OperatePortRule(c *gin.Context) {
|
data, err := forwardingService.LoadBaseInfo()
|
||||||
var req dto.PortRuleOperate
|
if err != nil {
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := firewallService.OperatePortRule(req, true); err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
helper.Success(c)
|
helper.SuccessWithData(c, data)
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Operate Ip rule
|
// @Summary Page forwarding rules
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.AddrRuleOperate true "request"
|
// @Param request body dto.ForwardRuleSearch 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"
|
|
||||||
// @Success 200 {object} dto.PageResult
|
// @Success 200 {object} dto.PageResult
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/filter/rule/search [post]
|
// @Router /hosts/firewall/forward/search [post]
|
||||||
func (b *BaseApi) SearchFilterRules(c *gin.Context) {
|
func (b *BaseApi) SearchForwardingRules(c *gin.Context) {
|
||||||
var req dto.SearchPageWithType
|
var request dto.ForwardRuleSearch
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
total, items, err := forwardingService.SearchRules(request)
|
||||||
total, list, err := iptablesService.Search(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.SuccessWithData(c, dto.PageResult{
|
helper.SuccessWithData(c, dto.PageResult{Items: items, Total: total})
|
||||||
Items: list,
|
|
||||||
Total: total,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Operate iptables filter rule
|
// @Summary Operate forwarding rules
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.IptablesRuleOp true "request"
|
// @Param request body dto.ForwardRuleOperate true "request"
|
||||||
// @Success 200
|
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/filter/rule/operate [post]
|
// @Router /hosts/firewall/forward/operate [post]
|
||||||
// @x-panel-log {"bodyKeys":["operation","chain"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] filter规则到 [chain]","formatEN":"[operation] filter rule to [chain]"}
|
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新端口转发规则","formatEN":"update port forward rules"}
|
||||||
func (b *BaseApi) OperateFilterRule(c *gin.Context) {
|
func (b *BaseApi) OperateForwardingRules(c *gin.Context) {
|
||||||
var req dto.IptablesRuleOp
|
var request dto.ForwardRuleOperate
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := iptablesService.OperateRule(req, true); err != nil {
|
|
||||||
|
result, err := forwardingService.OperateRules(request)
|
||||||
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
helper.SuccessWithData(c, result)
|
||||||
helper.Success(c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Batch operate iptables filter rules
|
// @Summary Enable forwarding
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.IptablesBatchOperate true "request"
|
// @Param request body dto.FirewallInitializationTask true "request"
|
||||||
// @Success 200
|
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/filter/rule/batch [post]
|
// @Router /hosts/firewall/forward/enable [post]
|
||||||
func (b *BaseApi) BatchOperateFilterRule(c *gin.Context) {
|
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"初始化并启用端口转发","formatEN":"initialize and enable port forwarding"}
|
||||||
var req dto.IptablesBatchOperate
|
func (b *BaseApi) EnableForwarding(c *gin.Context) {
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
var request dto.FirewallInitializationTask
|
||||||
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
result, err := forwardingService.QueueInitialization(request)
|
||||||
if err := iptablesService.BatchOperate(req); err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
helper.SuccessWithData(c, result)
|
||||||
helper.Success(c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Apply/Unload/Init iptables filter
|
// @Summary Apply/Unload/Init firewall filter chain
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.IptablesOp true "request"
|
// @Param request body dto.FilterChainOperation true "request"
|
||||||
// @Success 200
|
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/filter/operate [post]
|
// @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) {
|
func (b *BaseApi) OperateFilterChain(c *gin.Context) {
|
||||||
var req dto.IptablesOp
|
var request dto.FilterChainOperation
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var err error
|
if request.Operate == "init-base" {
|
||||||
if req.Operate == "init-forward" {
|
result, err := firewallService.QueueFilterChainInitialization(request)
|
||||||
err = forwardingService.Enable()
|
if err != nil {
|
||||||
} else {
|
helper.InternalServer(c, err)
|
||||||
err = iptablesService.Operate(req)
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, result)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err := firewallService.OperateFilterChain(request); err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
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 Adopt an external firewall rule
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.FirewallRuleAdopt true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Failure 400 {object} dto.Response
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/firewall/rules/adopt [post]
|
||||||
|
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"纳管防火墙规则","formatEN":"adopt firewall rule"}
|
||||||
|
func (b *BaseApi) AdoptFirewallRule(c *gin.Context) {
|
||||||
|
var request dto.FirewallRuleAdopt
|
||||||
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := firewallService.Adopt(c.Request.Context(), request); err != nil {
|
||||||
|
handleFirewallRuleError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
helper.Success(c)
|
helper.Success(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary load chain status with name
|
// @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
|
// @Accept json
|
||||||
// @Param request body dto.OperationWithName true "request"
|
// @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 Preview firewall rule synchronization
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.FirewallRuleSyncRequest true "request"
|
||||||
|
// @Success 200 {object} dto.FirewallRuleSyncPreview
|
||||||
|
// @Failure 400 {object} dto.Response
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/firewall/rules/sync/preview [post]
|
||||||
|
func (b *BaseApi) PreviewFirewallRuleSync(c *gin.Context) {
|
||||||
|
var request dto.FirewallRuleSyncRequest
|
||||||
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result, err := firewallService.PreviewRuleSync(c.Request.Context(), c.ClientIP(), request)
|
||||||
|
if err != nil {
|
||||||
|
handleFirewallRuleError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Firewall
|
||||||
|
// @Summary Load the currently executing firewall rule synchronization task
|
||||||
|
// @Success 200 {object} dto.FirewallRuleSyncTask
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/firewall/rules/sync/task [get]
|
||||||
|
func (b *BaseApi) LoadFirewallRuleSyncTask(c *gin.Context) {
|
||||||
|
result, err := firewallService.CurrentRuleSyncTask()
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Firewall
|
||||||
|
// @Summary Synchronize firewall rules to a target backend
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.FirewallRuleSyncRequest true "request"
|
||||||
|
// @Success 200 {object} dto.FirewallRuleSyncResult
|
||||||
|
// @Failure 400 {object} dto.Response
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/firewall/rules/sync [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["subsystem","sourceProvider","targetProvider"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 [subsystem] 防火墙规则到 [targetProvider]","formatEN":"sync [subsystem] firewall rules to [targetProvider]"}
|
||||||
|
func (b *BaseApi) SyncFirewallRules(c *gin.Context) {
|
||||||
|
var request dto.FirewallRuleSyncRequest
|
||||||
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result, err := firewallService.SyncRules(c.Request.Context(), c.ClientIP(), request)
|
||||||
|
if err != nil {
|
||||||
|
handleFirewallRuleError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Firewall
|
||||||
|
// @Summary Queue firewall rule deletion
|
||||||
|
// @Description Deletes managed rules by UUID or unprotected before-chain rules by 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 managed unified firewall v2 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":["uuid"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新防火墙规则 [uuid]","formatEN":"update firewall rule [uuid]"}
|
||||||
|
func (b *BaseApi) UpdateFirewallRule(c *gin.Context) {
|
||||||
|
var request dto.FirewallRuleUpdate
|
||||||
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !normalizeFirewallRuleUUID(c, &request.UUID) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := firewallService.Update(c.Request.Context(), c.ClientIP(), request); err != nil {
|
||||||
|
handleFirewallRuleError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Firewall
|
||||||
|
// @Summary Reorder a managed unified firewall v2 rule
|
||||||
|
// @Accept json
|
||||||
|
// @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":["uuid"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"调整防火墙规则顺序 [uuid]","formatEN":"reorder firewall rule [uuid]"}
|
||||||
|
func (b *BaseApi) ReorderFirewallRule(c *gin.Context) {
|
||||||
|
var request dto.FirewallRuleReorder
|
||||||
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !normalizeFirewallRuleUUID(c, &request.UUID) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := firewallService.Reorder(c.Request.Context(), c.ClientIP(), request); err != nil {
|
||||||
|
handleFirewallRuleError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeFirewallRuleUUID(c *gin.Context, value *string) bool {
|
||||||
|
if value == nil {
|
||||||
|
helper.BadRequest(c, repo.ErrFirewallPersistenceInvalid)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*value = strings.TrimSpace(*value)
|
||||||
|
if *value == "" {
|
||||||
|
helper.BadRequest(c, repo.ErrFirewallPersistenceInvalid)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
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, repo.ErrFirewallRuleRevisionConflict):
|
||||||
|
helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_REVISION_CONFLICT", "ErrInvalidParams", err)
|
||||||
|
case isBusinessError && businessErr.Msg == "ErrFirewallRuleScopeChange":
|
||||||
|
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_SCOPE_UNSUPPORTED", "ErrFirewallRuleScopeChange", 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), errors.Is(err, filter.ErrRuleConflict),
|
||||||
|
errors.Is(err, repo.ErrFirewallPersistenceInvalid):
|
||||||
|
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_RULE_UNSUPPORTED", "ErrInvalidParams", err)
|
||||||
|
case isBusinessError && businessErr.Msg == "ErrInvalidParams":
|
||||||
|
c.JSON(http.StatusOK, dto.Response{Code: http.StatusBadRequest, ErrorCode: "FW_RULE_UNSUPPORTED", Message: err.Error()})
|
||||||
|
c.Abort()
|
||||||
|
case errors.Is(err, filter.ErrVerificationFailed):
|
||||||
|
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_VERIFY_FAILED", "ErrInternalServer", err)
|
||||||
|
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 only. Missing rules are added on startup, restart, initialization, or synchronization; existing rules are not removed.
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.FirewallPortWhitelistCreate true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/filter/chain/status [post]
|
// @Router /hosts/firewall/settings/whitelist [post]
|
||||||
func (b *BaseApi) LoadChainStatus(c *gin.Context) {
|
// @x-panel-log {"bodyKeys":["rule"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建防火墙端口白名单","formatEN":"create firewall port whitelist"}
|
||||||
var req dto.OperationWithName
|
func (b *BaseApi) CreateFirewallPortWhitelist(c *gin.Context) {
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
var request dto.FirewallPortWhitelistCreate
|
||||||
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
return
|
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 only. Missing rules are added on startup, restart, initialization, or synchronization; 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 Saves whitelist configuration only. Missing rules are added on startup, restart, initialization, or synchronization; existing 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 Sync Docker port guard rules
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/firewall/docker/sync [post]
|
||||||
|
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 Docker 端口防护规则","formatEN":"sync Docker port guard rules"}
|
||||||
|
func (b *BaseApi) SyncDockerPortGuard(c *gin.Context) {
|
||||||
|
if err := dockerPortGuardService.Reconcile(c.Request.Context()); err != nil {
|
||||||
|
handleDockerPortGuardError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func (b *BaseApi) SearchFtp(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := ftpService.SearchWithPage(req, helper.IsDemoRequest(c))
|
total, list, err := ftpService.SearchWithPage(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
+8
-16
@@ -3,9 +3,8 @@ package v2
|
|||||||
import (
|
import (
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common"
|
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,27 +16,20 @@ import (
|
|||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /ai/gpu/load [get]
|
// @Router /ai/gpu/load [get]
|
||||||
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
|
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
|
||||||
ok, client := gpu.New()
|
ok, client := accelerator.New()
|
||||||
if ok {
|
if ok {
|
||||||
info, err := client.LoadGpuInfo()
|
snapshot, err := client.Collect(c.Request.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
helper.SuccessWithData(c, info)
|
if warning := snapshot.Warning(); warning != nil {
|
||||||
return
|
global.LOG.Warnf("load realtime accelerator data partially failed, err: %v", warning)
|
||||||
}
|
|
||||||
xpuOK, xpuClient := xpu.New()
|
|
||||||
if xpuOK {
|
|
||||||
info, err := xpuClient.LoadGpuInfo()
|
|
||||||
if err != nil {
|
|
||||||
helper.BadRequest(c, err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
helper.SuccessWithData(c, info)
|
helper.SuccessWithData(c, &snapshot.Info)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
helper.SuccessWithData(c, &common.GpuInfo{})
|
helper.SuccessWithData(c, &accelerator.Info{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags AI
|
// @Tags AI
|
||||||
|
|||||||
@@ -30,6 +30,16 @@ func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) {
|
|||||||
ctx.Abort()
|
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{}) {
|
func ErrorWithDetailAndData(ctx *gin.Context, code int, msgKey string, err error, data interface{}) {
|
||||||
res := dto.Response{
|
res := dto.Response{
|
||||||
Code: code,
|
Code: code,
|
||||||
@@ -48,10 +58,6 @@ func BadRequest(ctx *gin.Context, err error) {
|
|||||||
ErrorWithDetail(ctx, http.StatusBadRequest, "ErrInvalidParams", err)
|
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{}) {
|
func SuccessWithData(ctx *gin.Context, data interface{}) {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
data = gin.H{}
|
data = gin.H{}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ func (b *BaseApi) SearchHost(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := hostService.SearchWithPage(req, helper.IsDemoRequest(c))
|
total, list, err := hostService.SearchWithPage(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func (b *BaseApi) SearchRepo(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := imageRepoService.Page(req, helper.IsDemoRequest(c))
|
total, list, err := imageRepoService.Page(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func (b *BaseApi) PageMcpServers(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
list := mcpServerService.Page(req, helper.IsDemoRequest(c))
|
list := mcpServerService.Page(req)
|
||||||
helper.SuccessWithData(c, list)
|
helper.SuccessWithData(c, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ func (b *BaseApi) SearchRuntimes(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.ReadOnly = helper.IsDemoRequest(c)
|
|
||||||
total, items, err := runtimeService.Page(req)
|
total, items, err := runtimeService.Page(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
@@ -133,7 +132,7 @@ func (b *BaseApi) GetRuntime(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res, err := runtimeService.Get(id, helper.IsDemoRequest(c))
|
res, err := runtimeService.Get(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -170,7 +169,7 @@ func (b *BaseApi) GetNodePackageRunScript(c *gin.Context) {
|
|||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /runtimes/operate [post]
|
// @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) {
|
func (b *BaseApi) OperateRuntime(c *gin.Context) {
|
||||||
var req request.RuntimeOperate
|
var req request.RuntimeOperate
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
@@ -530,7 +529,7 @@ func (b *BaseApi) GetPHPContainerConfig(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := runtimeService.GetPHPContainerConfig(id, helper.IsDemoRequest(c))
|
data, err := runtimeService.GetPHPContainerConfig(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ func (b *BaseApi) LoadBaseDir(c *gin.Context) {
|
|||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /settings/ssh/conn [get]
|
// @Router /settings/ssh/conn [get]
|
||||||
func (b *BaseApi) LoadLocalConn(c *gin.Context) {
|
func (b *BaseApi) LoadLocalConn(c *gin.Context) {
|
||||||
helper.SuccessWithData(c, settingService.GetLocalConn(helper.IsDemoRequest(c)))
|
helper.SuccessWithData(c, settingService.GetLocalConn())
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags System Setting
|
// @Tags System Setting
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ func (b *BaseApi) SearchRootCert(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, data, err := sshService.SearchRootCerts(req, helper.IsDemoRequest(c))
|
total, data, err := sshService.SearchRootCerts(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func (b *BaseApi) PageTensorRTLLMs(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
list := tensorrtLLMService.Page(req, helper.IsDemoRequest(c))
|
list := tensorrtLLMService.Page(req)
|
||||||
helper.SuccessWithData(c, list)
|
helper.SuccessWithData(c, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+154
-32
@@ -1,11 +1,14 @@
|
|||||||
package v2
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||||
@@ -19,29 +22,35 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
gossh "golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @Tags Terminal
|
// @Tags Terminal
|
||||||
// @Summary Ws local terminal
|
// @Summary Ws local terminal
|
||||||
// @Param command query string false "command"
|
// @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
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/terminal/local [get]
|
// @Router /hosts/terminal/local [get]
|
||||||
func (b *BaseApi) WsLocalTerminal(c *gin.Context) {
|
func (b *BaseApi) WsLocalTerminal(c *gin.Context) {
|
||||||
b.runSSHSession(c, loadLocalConn, c.DefaultQuery("command", ""))
|
b.runSSHSession(c, "local", loadLocalConn, c.DefaultQuery("command", ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Terminal
|
// @Tags Terminal
|
||||||
// @Summary Ws host SSH
|
// @Summary Ws host SSH
|
||||||
// @Param id query integer false "id"
|
// @Param id query integer false "id"
|
||||||
// @Param command query string false "command"
|
// @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
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/terminal/ssh [get]
|
// @Router /hosts/terminal/ssh [get]
|
||||||
func (b *BaseApi) WsHostSSH(c *gin.Context) {
|
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"))
|
hostID, _ := strconv.Atoi(c.DefaultQuery("id", "0"))
|
||||||
if hostID <= 0 {
|
if hostID <= 0 {
|
||||||
return nil, errors.New("missing host id")
|
return nil, errors.New("missing host id")
|
||||||
@@ -65,26 +74,33 @@ func (b *BaseApi) WsContainerTerminal(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer wsConn.Close()
|
defer wsConn.Close()
|
||||||
|
identity, ok := loadTerminalIdentity(c)
|
||||||
slave, err := loadContainerTerminalCommand(c)
|
if !ok {
|
||||||
if wshandleError(wsConn, err) {
|
_ = wshandleError(wsConn, errors.New("missing terminal identity"))
|
||||||
return
|
|
||||||
}
|
|
||||||
defer slave.Close()
|
|
||||||
|
|
||||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
|
||||||
if wshandleError(wsConn, err) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
quitChan := make(chan bool, 3)
|
opts := terminal.SessionOptions{
|
||||||
tty.Start(quitChan)
|
Identity: identity,
|
||||||
go slave.Wait(quitChan)
|
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
|
func containerTerminalTarget(c *gin.Context) string {
|
||||||
|
query := c.Request.URL.Query()
|
||||||
global.LOG.Info("websocket finished")
|
for _, key := range []string{"cols", "rows", "session", "terminalRevalidate"} {
|
||||||
closeTerminalConn(wsConn)
|
query.Del(key)
|
||||||
|
}
|
||||||
|
sum := sha256.Sum256([]byte(query.Encode()))
|
||||||
|
return hex.EncodeToString(sum[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
|
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
|
return nil, 0, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if helper.IsDemoRequest(c) {
|
if global.CONF.Base.IsDemo {
|
||||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||||
return nil, 0, 0, false
|
return nil, 0, 0, false
|
||||||
}
|
}
|
||||||
@@ -115,32 +131,138 @@ func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
|
|||||||
return wsConn, cols, rows, true
|
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)
|
wsConn, cols, rows, ok := prepareTerminalSession(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer wsConn.Close()
|
defer wsConn.Close()
|
||||||
|
identity, ok := loadTerminalIdentity(c)
|
||||||
client, clientErr := connect()
|
if !ok {
|
||||||
if wshandleError(wsConn, errors.WithMessage(clientErr, "failed to set up the connection. Please check the host information")) {
|
_ = wshandleError(wsConn, errors.New("missing terminal identity"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, command)
|
hostID := 0
|
||||||
if wshandleError(wsConn, err) {
|
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
|
return
|
||||||
}
|
}
|
||||||
defer sws.Close()
|
helper.SuccessWithData(c, terminal.List(identity))
|
||||||
|
}
|
||||||
|
|
||||||
quitChan := make(chan bool, 3)
|
// @Tags Terminal
|
||||||
sws.Start(quitChan)
|
// @Summary Close a terminal session
|
||||||
go sws.Wait(quitChan)
|
// @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) {
|
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)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res, err := websiteService.GetWebsiteHTTPS(id, helper.IsDemoRequest(c))
|
res, err := websiteService.GetWebsiteHTTPS(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -1080,7 +1080,7 @@ func (b *BaseApi) GetWebsiteResource(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res, err := websiteService.GetWebsiteResource(id, helper.IsDemoRequest(c))
|
res, err := websiteService.GetWebsiteResource(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func (b *BaseApi) PageWebsiteAcmeAccount(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, accounts, err := websiteAcmeAccountService.Page(req, helper.IsDemoRequest(c))
|
total, accounts, err := websiteAcmeAccountService.Page(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func (b *BaseApi) PageWebsiteCA(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, cas, err := websiteCAService.Page(req, helper.IsDemoRequest(c))
|
total, cas, err := websiteCAService.Page(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -70,7 +70,7 @@ func (b *BaseApi) GetWebsiteCA(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res, err := websiteCAService.GetCA(id, helper.IsDemoRequest(c))
|
res, err := websiteCAService.GetCA(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func (b *BaseApi) PageWebsiteDnsAccount(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, accounts, err := websiteDnsAccountService.Page(req, helper.IsDemoRequest(c))
|
total, accounts, err := websiteDnsAccountService.Page(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (b *BaseApi) PageWebsiteSSL(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, accounts, err := websiteSSLService.Page(req, helper.IsDemoRequest(c))
|
total, accounts, err := websiteSSLService.Page(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -159,7 +159,7 @@ func (b *BaseApi) GetWebsiteSSLByWebsiteId(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
websiteSSL, err := websiteSSLService.GetWebsiteSSL(websiteId, helper.IsDemoRequest(c))
|
websiteSSL, err := websiteSSLService.GetWebsiteSSL(websiteId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -181,7 +181,7 @@ func (b *BaseApi) GetWebsiteSSLById(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
websiteSSL, err := websiteSSLService.GetSSL(id, helper.IsDemoRequest(c))
|
websiteSSL, err := websiteSSLService.GetSSL(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
+16
-7
@@ -162,16 +162,25 @@ type AgentWebsiteBindReq struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AgentModelConfigUpdateReq struct {
|
type AgentModelConfigUpdateReq struct {
|
||||||
AgentID uint `json:"agentId" validate:"required"`
|
AgentID uint `json:"agentId" validate:"required"`
|
||||||
AccountID uint `json:"accountId" validate:"required"`
|
AccountID uint `json:"accountId" validate:"required"`
|
||||||
Model string `json:"model" validate:"required"`
|
Model string `json:"model" validate:"required"`
|
||||||
Fallbacks []string `json:"fallbacks"`
|
Fallbacks []string `json:"fallbacks"`
|
||||||
|
Metadata []AgentModelMetadata `json:"metadata" validate:"dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentModelConfig struct {
|
type AgentModelConfig struct {
|
||||||
AccountID uint `json:"accountId"`
|
AccountID uint `json:"accountId"`
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Fallbacks []string `json:"fallbacks"`
|
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 {
|
type AgentHermesChatSessionItem struct {
|
||||||
|
|||||||
+31
-16
@@ -21,6 +21,7 @@ type AlertBase struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PushAlert struct {
|
type PushAlert struct {
|
||||||
|
Result string `json:"result,omitempty"`
|
||||||
TaskName string `json:"taskName"`
|
TaskName string `json:"taskName"`
|
||||||
AlertType string `json:"alertType"`
|
AlertType string `json:"alertType"`
|
||||||
EntryID uint `json:"entryID"`
|
EntryID uint `json:"entryID"`
|
||||||
@@ -53,6 +54,7 @@ type AlertDTO struct {
|
|||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Project string `json:"project"`
|
Project string `json:"project"`
|
||||||
|
TaskName string `json:"taskName,omitempty"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
SendCount uint `json:"sendCount"`
|
SendCount uint `json:"sendCount"`
|
||||||
AdvancedParams string `json:"advancedParams"`
|
AdvancedParams string `json:"advancedParams"`
|
||||||
@@ -151,16 +153,25 @@ type AlertLog struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AlertDetail struct {
|
type AlertDetail struct {
|
||||||
LicenseId string `json:"licenseId"`
|
LicenseId string `json:"licenseId"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
SubType string `json:"subType"`
|
SubType string `json:"subType"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
LicenseCode string `json:"licenseCode"`
|
LicenseCode string `json:"licenseCode"`
|
||||||
DeviceId string `json:"deviceId"`
|
DeviceId string `json:"deviceId"`
|
||||||
Project string `json:"project"`
|
Project string `json:"project"`
|
||||||
Params []Param `json:"params"`
|
Params []Param `json:"params"`
|
||||||
Phone string `json:"phone"`
|
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 {
|
type AlertRule struct {
|
||||||
@@ -295,15 +306,19 @@ type OfflineQueryRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AlertConfigUpdate struct {
|
type AlertConfigUpdate struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Config string `json:"config"`
|
Config string `json:"config"`
|
||||||
DisplayName string `json:"displayName"`
|
DisplayName string `json:"displayName"`
|
||||||
|
Revision *time.Time `json:"revision"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlertConfigTest struct {
|
type AlertConfigTest struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Config string `json:"config"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Sender string `json:"sender"`
|
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 {
|
type Response struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
Message string `json:"message"`
|
ErrorCode string `json:"errorCode,omitempty"`
|
||||||
Data interface{} `json:"data"`
|
Message string `json:"message"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
type NetworkCleanupReport struct {
|
||||||
|
Deleted []NetworkCleanupItem `json:"deleted"`
|
||||||
|
Skipped []NetworkCleanupItem `json:"skipped"`
|
||||||
|
Failed []NetworkCleanupItem `json:"failed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkCleanupItem struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkCleanupTask struct {
|
||||||
|
TaskID string `json:"taskID"`
|
||||||
|
}
|
||||||
@@ -51,9 +51,10 @@ type CronjobOperate struct {
|
|||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
Args string `json:"args"`
|
Args string `json:"args"`
|
||||||
|
|
||||||
AlertCount uint `json:"alertCount"`
|
AlertCount uint `json:"alertCount"`
|
||||||
AlertTitle string `json:"alertTitle"`
|
AlertTitle string `json:"alertTitle"`
|
||||||
AlertMethod string `json:"alertMethod"`
|
AlertMethod string `json:"alertMethod"`
|
||||||
|
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
|
||||||
|
|
||||||
CleanLogConfig
|
CleanLogConfig
|
||||||
}
|
}
|
||||||
@@ -126,7 +127,8 @@ type CronjobInfo struct {
|
|||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
Args string `json:"args"`
|
Args string `json:"args"`
|
||||||
|
|
||||||
AlertCount uint `json:"alertCount"`
|
AlertCount uint `json:"alertCount"`
|
||||||
|
AlertTriggerMode string `json:"alertTriggerMode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CronjobImport struct {
|
type CronjobImport struct {
|
||||||
@@ -169,9 +171,10 @@ type CronjobTrans struct {
|
|||||||
SourceAccounts []string `json:"sourceAccounts"`
|
SourceAccounts []string `json:"sourceAccounts"`
|
||||||
DownloadAccount string `json:"downloadAccount"`
|
DownloadAccount string `json:"downloadAccount"`
|
||||||
|
|
||||||
AlertCount uint `json:"alertCount"`
|
AlertCount uint `json:"alertCount"`
|
||||||
AlertTitle string `json:"alertTitle"`
|
AlertTitle string `json:"alertTitle"`
|
||||||
AlertMethod string `json:"alertMethod"`
|
AlertMethod string `json:"alertMethod"`
|
||||||
|
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
|
||||||
}
|
}
|
||||||
type TransHelper struct {
|
type TransHelper struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ type DashboardCurrent struct {
|
|||||||
NetBytesRecv uint64 `json:"netBytesRecv"`
|
NetBytesRecv uint64 `json:"netBytesRecv"`
|
||||||
|
|
||||||
GPUData []GPUInfo `json:"gpuData"`
|
GPUData []GPUInfo `json:"gpuData"`
|
||||||
|
NPUData []NPUInfo `json:"npuData"`
|
||||||
XPUData []XPUInfo `json:"xpuData"`
|
XPUData []XPUInfo `json:"xpuData"`
|
||||||
|
|
||||||
TopCPUItems []Process `json:"topCPUItems"`
|
TopCPUItems []Process `json:"topCPUItems"`
|
||||||
@@ -158,7 +159,10 @@ type DiskInfo struct {
|
|||||||
type GPUInfo struct {
|
type GPUInfo struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Index uint `json:"index"`
|
Index uint `json:"index"`
|
||||||
|
NPUIndex uint `json:"npuIndex"`
|
||||||
|
ChipIndex uint `json:"chipIndex"`
|
||||||
ProductName string `json:"productName"`
|
ProductName string `json:"productName"`
|
||||||
|
BusID string `json:"busID"`
|
||||||
GPUUtil string `json:"gpuUtil"`
|
GPUUtil string `json:"gpuUtil"`
|
||||||
Temperature string `json:"temperature"`
|
Temperature string `json:"temperature"`
|
||||||
PerformanceState string `json:"performanceState"`
|
PerformanceState string `json:"performanceState"`
|
||||||
@@ -171,6 +175,27 @@ type GPUInfo struct {
|
|||||||
FanSpeed string `json:"fanSpeed"`
|
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 {
|
type AppLauncher struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@@ -203,11 +228,13 @@ type LauncherOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type XPUInfo struct {
|
type XPUInfo struct {
|
||||||
DeviceID int `json:"deviceID"`
|
DeviceID int `json:"deviceID"`
|
||||||
DeviceName string `json:"deviceName"`
|
DeviceName string `json:"deviceName"`
|
||||||
Memory string `json:"memory"`
|
PciBdfAddress string `json:"pciBdfAddress"`
|
||||||
Temperature string `json:"temperature"`
|
Memory string `json:"memory"`
|
||||||
MemoryUsed string `json:"memoryUsed"`
|
Temperature string `json:"temperature"`
|
||||||
Power string `json:"power"`
|
GPUUtil string `json:"gpuUtil"`
|
||||||
MemoryUtil string `json:"memoryUtil"`
|
MemoryUsed string `json:"memoryUsed"`
|
||||||
|
Power string `json:"power"`
|
||||||
|
MemoryUtil string `json:"memoryUtil"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,17 @@ type DBBaseInfo struct {
|
|||||||
Port int64 `json:"port"`
|
Port int64 `json:"port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RedisCliInstall struct {
|
||||||
|
TaskID string `json:"taskID" validate:"omitempty,uuid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedisCliStatus struct {
|
||||||
|
Installed bool `json:"installed"`
|
||||||
|
TaskID string `json:"taskID"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
ErrorMsg string `json:"errorMsg"`
|
||||||
|
}
|
||||||
|
|
||||||
// mysql
|
// mysql
|
||||||
type MysqlDBSearch struct {
|
type MysqlDBSearch struct {
|
||||||
PageInfo
|
PageInfo
|
||||||
|
|||||||
+362
-73
@@ -1,100 +1,389 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
type FirewallBaseInfo struct {
|
import (
|
||||||
Name string `json:"name"`
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||||
IsExist bool `json:"isExist"`
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||||
IsActive bool `json:"isActive"`
|
firewallsync "github.com/1Panel-dev/1Panel/agent/utils/firewall/sync"
|
||||||
IsInit bool `json:"isInit"`
|
)
|
||||||
IsBind bool `json:"isBind"`
|
|
||||||
Version string `json:"version"`
|
type FirewallSubsystemStatus struct {
|
||||||
PingStatus string `json:"pingStatus"`
|
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"`
|
||||||
|
SyncError string `json:"syncError,omitempty"`
|
||||||
|
LifecycleTaskID string `json:"lifecycleTaskID,omitempty"`
|
||||||
|
IPv4 FirewallBackendFamilyStatus `json:"ipv4"`
|
||||||
|
IPv6 FirewallBackendFamilyStatus `json:"ipv6"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleSearch struct {
|
type FirewallLifecycleOperation struct {
|
||||||
PageInfo
|
|
||||||
Info string `json:"info"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Strategy string `json:"strategy"`
|
|
||||||
Type string `json:"type" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallOperation struct {
|
|
||||||
Operation string `json:"operation" validate:"required,oneof=start stop restart disableBanPing enableBanPing"`
|
Operation string `json:"operation" validate:"required,oneof=start stop restart disableBanPing enableBanPing"`
|
||||||
WithDockerRestart bool `json:"withDockerRestart"`
|
WithDockerRestart bool `json:"withDockerRestart"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PortRuleOperate struct {
|
type FirewallLifecycleOperationResponse struct {
|
||||||
ID uint `json:"id"`
|
TaskID string `json:"taskID,omitempty"`
|
||||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
Queued bool `json:"queued"`
|
||||||
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 UpdateFirewallDescription struct {
|
type FirewallBackendOption struct {
|
||||||
Type string `json:"type"`
|
Name string `json:"name"`
|
||||||
Chain string `json:"chain"`
|
Installed bool `json:"installed"`
|
||||||
SrcIP string `json:"srcIP"`
|
Active bool `json:"active"`
|
||||||
DstIP string `json:"dstIP"`
|
Initialized bool `json:"initialized"`
|
||||||
SrcPort string `json:"srcPort"`
|
Bound bool `json:"bound"`
|
||||||
DstPort string `json:"dstPort"`
|
Supported bool `json:"supported"`
|
||||||
Protocol string `json:"protocol"`
|
SupportReason string `json:"supportReason,omitempty"`
|
||||||
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
|
Implementation string `json:"implementation,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
Description string `json:"description"`
|
IPv4 FirewallBackendFamilyStatus `json:"ipv4"`
|
||||||
|
IPv6 FirewallBackendFamilyStatus `json:"ipv6"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddrRuleOperate struct {
|
type FirewallBackendFamilyStatus struct {
|
||||||
ID uint `json:"id"`
|
Available bool `json:"available"`
|
||||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
Initialized bool `json:"initialized"`
|
||||||
Address string `json:"address" validate:"required"`
|
Bound bool `json:"bound"`
|
||||||
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
|
Reason string `json:"reason,omitempty"`
|
||||||
|
ForwardPolicy string `json:"forwardPolicy,omitempty"`
|
||||||
Description string `json:"description"`
|
RAInterfaces []string `json:"raInterfaces,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PortRuleUpdate struct {
|
type FirewallBackendGroup struct {
|
||||||
OldRule PortRuleOperate `json:"oldRule"`
|
Selected string `json:"selected"`
|
||||||
NewRule PortRuleOperate `json:"newRule"`
|
Current string `json:"current,omitempty"`
|
||||||
|
Options []FirewallBackendOption `json:"options"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddrRuleUpdate struct {
|
type FirewallSettings struct {
|
||||||
OldRule AddrRuleOperate `json:"oldRule"`
|
System FirewallBackendGroup `json:"system"`
|
||||||
NewRule AddrRuleOperate `json:"newRule"`
|
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 FirewallPortWhitelistCreate struct {
|
||||||
Type string `json:"type" validate:"required"`
|
Rule filter.PortWhitelist `json:"rule" validate:"required"`
|
||||||
Rules []PortRuleOperate `json:"rules"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type IptablesOp struct {
|
type FirewallPortWhitelistUpdate struct {
|
||||||
Name string `json:"name" validate:"required,oneof=1PANEL_INPUT 1PANEL_OUTPUT 1PANEL_BASIC 1PANEL_FORWARD"`
|
OldRule filter.PortWhitelist `json:"oldRule" validate:"required"`
|
||||||
Operate string `json:"operate" validate:"required,oneof=init-base init-forward init-advance bind-base unbind-base bind unbind"`
|
Rule filter.PortWhitelist `json:"rule" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IptablesRuleOp struct {
|
type FirewallPortWhitelistDelete struct {
|
||||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
Rule *filter.PortWhitelist `json:"rule" validate:"required"`
|
||||||
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 IptablesBatchOperate struct {
|
type FirewallBackendOperation struct {
|
||||||
Rules []IptablesRuleOp `json:"rules"`
|
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 {
|
type FilterChainOperation struct {
|
||||||
IsBind bool `json:"isBind"`
|
Name string `json:"name" validate:"required,eq=1PANEL_BASIC"`
|
||||||
DefaultStrategy string `json:"defaultStrategy"`
|
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 {
|
||||||
|
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"`
|
||||||
|
ManagedTotal int64 `json:"managedTotal"`
|
||||||
|
Items []filter.InventoryItem `json:"items"`
|
||||||
|
Notices []filter.ScopeNotice `json:"notices,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleResetResponse struct {
|
||||||
|
Removed int `json:"removed"`
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleReset struct {
|
||||||
|
Provider filter.Provider `json:"provider,omitempty" validate:"omitempty,oneof=firewalld ufw iptables nftables"`
|
||||||
|
WithDockerRestart bool `json:"withDockerRestart"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleInventory struct {
|
||||||
|
Refresh bool `json:"refresh,omitempty"`
|
||||||
|
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"`
|
||||||
|
States []filter.InventoryState `json:"states,omitempty" validate:"omitempty,dive,oneof=managed adopted external drifted protected"`
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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"`
|
||||||
|
NativeAction string `json:"nativeAction,omitempty"`
|
||||||
|
ReadOnly bool `json:"readOnly,omitempty"`
|
||||||
|
Sources []string `json:"sources"`
|
||||||
|
Effective bool `json:"effective"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
Sources []string `json:"sources" validate:"dive,required,max=64"`
|
||||||
|
Description string `json:"description" validate:"max=256"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DockerPortGuardOperation struct {
|
||||||
|
Operation string `json:"operation" validate:"required,oneof=initialize bind unbind"`
|
||||||
|
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleAdopt struct {
|
||||||
|
Scope filter.Scope `json:"scope" validate:"required"`
|
||||||
|
InstanceKey string `json:"instanceKey,omitempty" validate:"omitempty,max=128"`
|
||||||
|
Rule *filter.FirewallRule `json:"rule,omitempty"`
|
||||||
|
Marker string `json:"marker,omitempty" validate:"max=256"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleCreateItem struct {
|
||||||
|
Rule filter.FirewallRule `json:"rule" validate:"required"`
|
||||||
|
SourceKind string `json:"sourceKind" validate:"omitempty,oneof=user imported"`
|
||||||
|
SourceID string `json:"sourceID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleCreate struct {
|
||||||
|
Items []FirewallRuleCreateItem `json:"items" validate:"required,min=1,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 FirewallRuleSyncRequest struct {
|
||||||
|
Subsystem string `json:"subsystem" validate:"omitempty,oneof=system forwarding docker"`
|
||||||
|
SourceProvider filter.Provider `json:"sourceProvider,omitempty" validate:"omitempty,oneof=firewalld ufw iptables nftables"`
|
||||||
|
TargetProvider filter.Provider `json:"targetProvider" validate:"required,oneof=firewalld ufw iptables nftables"`
|
||||||
|
ResetSource bool `json:"resetSource"`
|
||||||
|
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleSyncItem struct {
|
||||||
|
SourceUUID string `json:"sourceUUID"`
|
||||||
|
Rule *filter.FirewallRule `json:"rule,omitempty"`
|
||||||
|
ForwardRule *ForwardRule `json:"forwardRule,omitempty"`
|
||||||
|
DockerRule *DockerPortGuardEndpoint `json:"dockerRule,omitempty"`
|
||||||
|
Status firewallsync.Status `json:"status"`
|
||||||
|
ReasonCode firewallsync.ReasonCode `json:"reasonCode,omitempty"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleSyncPreview struct {
|
||||||
|
Subsystem string `json:"subsystem"`
|
||||||
|
SourceProvider filter.Provider `json:"sourceProvider,omitempty"`
|
||||||
|
TargetProvider filter.Provider `json:"targetProvider"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
Ready int `json:"ready"`
|
||||||
|
Existing int `json:"existing"`
|
||||||
|
Removed int `json:"removed"`
|
||||||
|
Blocked int `json:"blocked"`
|
||||||
|
Items []FirewallRuleSyncItem `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleSyncResult struct {
|
||||||
|
Subsystem string `json:"subsystem"`
|
||||||
|
SourceProvider filter.Provider `json:"sourceProvider,omitempty"`
|
||||||
|
TargetProvider filter.Provider `json:"targetProvider"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
Succeeded int `json:"succeeded"`
|
||||||
|
Skipped int `json:"skipped"`
|
||||||
|
Removed int `json:"removed"`
|
||||||
|
Failed int `json:"failed"`
|
||||||
|
Errors []FirewallRuleSyncFailure `json:"errors,omitempty"`
|
||||||
|
TaskID string `json:"taskID,omitempty"`
|
||||||
|
Queued bool `json:"queued,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleSyncTask struct {
|
||||||
|
TaskID string `json:"taskID,omitempty"`
|
||||||
|
Executing bool `json:"executing"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleSyncFailure struct {
|
||||||
|
SourceUUID string `json:"sourceUUID"`
|
||||||
|
Rule *filter.FirewallRule `json:"rule,omitempty"`
|
||||||
|
ForwardRule *ForwardRule `json:"forwardRule,omitempty"`
|
||||||
|
DockerRule *DockerPortGuardEndpoint `json:"dockerRule,omitempty"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleDelete struct {
|
||||||
|
UUIDs []string `json:"uuids" validate:"omitempty,dive,required,max=64"`
|
||||||
|
BeforeRules []FirewallRuleDeleteTarget `json:"beforeRules,omitempty" validate:"omitempty,dive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleUpdate struct {
|
||||||
|
UUID string `json:"uuid" validate:"required,max=64"`
|
||||||
|
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 {
|
||||||
|
UUID string `json:"uuid" validate:"required,max=64"`
|
||||||
|
TargetPosition *int64 `json:"targetPosition"`
|
||||||
|
Priority *int `json:"priority"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FirewallRuleSyncPreview) Add(item FirewallRuleSyncItem) {
|
||||||
|
p.Items = append(p.Items, item)
|
||||||
|
switch item.Status {
|
||||||
|
case firewallsync.StatusReady:
|
||||||
|
p.Ready++
|
||||||
|
p.Total++
|
||||||
|
case firewallsync.StatusExisting:
|
||||||
|
p.Existing++
|
||||||
|
p.Total++
|
||||||
|
case firewallsync.StatusRemove:
|
||||||
|
p.Removed++
|
||||||
|
case firewallsync.StatusBlocked:
|
||||||
|
p.Blocked++
|
||||||
|
if item.ReasonCode != firewallsync.ReasonReadOnlyRule {
|
||||||
|
p.Total++
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ package dto
|
|||||||
|
|
||||||
type ForwardRuleSearch struct {
|
type ForwardRuleSearch struct {
|
||||||
PageInfo
|
PageInfo
|
||||||
|
All bool `json:"all,omitempty"`
|
||||||
Info string `json:"info"`
|
Info string `json:"info"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Strategy string `json:"strategy"`
|
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 {
|
type ForwardRule struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Chain string `json:"chain"`
|
Chain string `json:"chain"`
|
||||||
@@ -25,16 +24,21 @@ type ForwardRule struct {
|
|||||||
|
|
||||||
UsedStatus string `json:"usedStatus"`
|
UsedStatus string `json:"usedStatus"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
IsDesired bool `json:"isDesired"`
|
||||||
|
IsRuntime bool `json:"isRuntime"`
|
||||||
|
SyncStatus string `json:"syncStatus"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ForwardRuleOperate struct {
|
type ForwardRuleOperate struct {
|
||||||
ForceDelete bool `json:"forceDelete"`
|
ForceDelete bool `json:"forceDelete"`
|
||||||
Rules []ForwardRuleOperation `json:"rules"`
|
Rules []ForwardRuleOperation `json:"rules" validate:"required,min=1,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ForwardRuleOperation struct {
|
type ForwardRuleOperation struct {
|
||||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||||
Num string `json:"num"`
|
Num string `json:"num"`
|
||||||
|
Family string `json:"family" validate:"omitempty,oneof=ipv4 ipv6"`
|
||||||
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
|
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
|
||||||
Interface string `json:"interface"`
|
Interface string `json:"interface"`
|
||||||
Port string `json:"port" validate:"required"`
|
Port string `json:"port" validate:"required"`
|
||||||
|
|||||||
+45
-11
@@ -44,7 +44,10 @@ type MonitorGPUOptions struct {
|
|||||||
Options []string `json:"options"`
|
Options []string `json:"options"`
|
||||||
}
|
}
|
||||||
type GPUChartHide struct {
|
type GPUChartHide struct {
|
||||||
|
DeviceID string `json:"deviceID"`
|
||||||
|
Legacy bool `json:"legacy"`
|
||||||
ProductName string `json:"productName"`
|
ProductName string `json:"productName"`
|
||||||
|
Type string `json:"type"`
|
||||||
Process bool `json:"process"`
|
Process bool `json:"process"`
|
||||||
GPU bool `json:"gpu"`
|
GPU bool `json:"gpu"`
|
||||||
Memory bool `json:"memory"`
|
Memory bool `json:"memory"`
|
||||||
@@ -54,23 +57,54 @@ type GPUChartHide struct {
|
|||||||
Speed bool `json:"speed"`
|
Speed bool `json:"speed"`
|
||||||
}
|
}
|
||||||
type MonitorGPUSearch struct {
|
type MonitorGPUSearch struct {
|
||||||
|
Aggregation string `json:"aggregation" validate:"omitempty,oneof=avg max"`
|
||||||
|
DeviceID string `json:"deviceID"`
|
||||||
|
Legacy bool `json:"legacy"`
|
||||||
ProductName string `json:"productName"`
|
ProductName string `json:"productName"`
|
||||||
StartTime time.Time `json:"startTime"`
|
StartTime time.Time `json:"startTime"`
|
||||||
EndTime time.Time `json:"endTime"`
|
EndTime time.Time `json:"endTime"`
|
||||||
}
|
}
|
||||||
type MonitorGPUData struct {
|
type MonitorGPUData struct {
|
||||||
Date []time.Time `json:"date"`
|
MemoryActivity []*float64 `json:"memoryActivity"`
|
||||||
GPUValue []float64 `json:"gpuValue"`
|
EncoderUtil []*float64 `json:"encoderUtil"`
|
||||||
TemperatureValue []float64 `json:"temperatureValue"`
|
DecoderUtil []*float64 `json:"decoderUtil"`
|
||||||
PowerTotal []float64 `json:"powerTotal"`
|
JPEGUtil []*float64 `json:"jpegUtil"`
|
||||||
PowerUsed []float64 `json:"powerUsed"`
|
OFAUtil []*float64 `json:"ofaUtil"`
|
||||||
PowerPercent []float64 `json:"powerPercent"`
|
MediaUtil []*float64 `json:"mediaUtil"`
|
||||||
MemoryTotal []float64 `json:"memoryTotal"`
|
ComputeUtil []*float64 `json:"computeUtil"`
|
||||||
MemoryUsed []float64 `json:"memoryUsed"`
|
CopyUtil []*float64 `json:"copyUtil"`
|
||||||
MemoryPercent []float64 `json:"memoryPercent"`
|
HotspotTemperature []*float64 `json:"hotspotTemperature"`
|
||||||
SpeedValue []int `json:"speedValue"`
|
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"`
|
GPUProcesses [][]GPUProcess `json:"gpuProcesses"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,13 +51,19 @@ const (
|
|||||||
CACHE NginxKey = "cache"
|
CACHE NginxKey = "cache"
|
||||||
HttpPer NginxKey = "http-per"
|
HttpPer NginxKey = "http-per"
|
||||||
ProxyCache NginxKey = "proxy-cache"
|
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{
|
var ScopeKeyMap = map[NginxKey][]string{
|
||||||
Index: {"index"},
|
Index: {"index"},
|
||||||
LimitConn: {"limit_conn", "limit_rate", "limit_conn_zone"},
|
LimitConn: {"limit_conn", "limit_rate", "limit_conn_zone"},
|
||||||
SSL: {"ssl_certificate", "ssl_certificate_key"},
|
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 {
|
var StaticFileKeyMap = map[NginxKey]struct {
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ type AppContainerConfig struct {
|
|||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
SpecifyIP string `json:"specifyIP"`
|
SpecifyIP string `json:"specifyIP"`
|
||||||
RestartPolicy string `json:"restartPolicy" validate:"omitempty,oneof=always unless-stopped no on-failure"`
|
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 {
|
type AppInstalledSearch struct {
|
||||||
@@ -62,13 +66,11 @@ type AppInstalledSearch struct {
|
|||||||
All bool `json:"all"`
|
All bool `json:"all"`
|
||||||
Sync bool `json:"sync"`
|
Sync bool `json:"sync"`
|
||||||
CheckUpdate bool `json:"checkUpdate"`
|
CheckUpdate bool `json:"checkUpdate"`
|
||||||
ReadOnly bool `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppInstalledInfo struct {
|
type AppInstalledInfo struct {
|
||||||
Key string `json:"key" validate:"required"`
|
Key string `json:"key" validate:"required"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ReadOnly bool `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppBackupSearch struct {
|
type AppBackupSearch struct {
|
||||||
@@ -94,6 +96,8 @@ type AppInstalledOperate struct {
|
|||||||
TaskID string `json:"taskID"`
|
TaskID string `json:"taskID"`
|
||||||
DeleteImage bool `json:"deleteImage"`
|
DeleteImage bool `json:"deleteImage"`
|
||||||
Favorite bool `json:"favorite"`
|
Favorite bool `json:"favorite"`
|
||||||
|
|
||||||
|
UseLifecycleScripts bool `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppInstallUpgrade struct {
|
type AppInstallUpgrade struct {
|
||||||
@@ -113,11 +117,14 @@ type AppInstallDelete struct {
|
|||||||
DeleteDB bool `json:"deleteDB"`
|
DeleteDB bool `json:"deleteDB"`
|
||||||
DeleteImage bool `json:"deleteImage"`
|
DeleteImage bool `json:"deleteImage"`
|
||||||
TaskID string `json:"taskID"`
|
TaskID string `json:"taskID"`
|
||||||
|
|
||||||
|
UseLifecycleScripts bool `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppInstalledUpdate struct {
|
type AppInstalledUpdate struct {
|
||||||
InstallId uint `json:"installId" validate:"required"`
|
InstallId uint `json:"installId" validate:"required"`
|
||||||
Params map[string]interface{} `json:"params" validate:"required"`
|
Params map[string]interface{} `json:"params" validate:"required"`
|
||||||
|
TaskID string `json:"-"`
|
||||||
AppContainerConfig
|
AppContainerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ type FileWget struct {
|
|||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
IgnoreCertificate bool `json:"ignoreCertificate"`
|
IgnoreCertificate bool `json:"ignoreCertificate"`
|
||||||
UseProxy bool `json:"useProxy"`
|
UseProxy bool `json:"useProxy"`
|
||||||
|
UseServerFilename bool `json:"useServerFilename"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileMove struct {
|
type FileMove struct {
|
||||||
@@ -158,6 +159,10 @@ type FileProcessReq struct {
|
|||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FileProcessRemoveReq struct {
|
||||||
|
Keys []string `json:"keys" validate:"required,min=1,max=1000"`
|
||||||
|
}
|
||||||
|
|
||||||
type FileRoleUpdate struct {
|
type FileRoleUpdate struct {
|
||||||
Path string `json:"path" validate:"required"`
|
Path string `json:"path" validate:"required"`
|
||||||
User string `json:"user" validate:"required"`
|
User string `json:"user" validate:"required"`
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ import (
|
|||||||
|
|
||||||
type RuntimeSearch struct {
|
type RuntimeSearch struct {
|
||||||
dto.PageInfo
|
dto.PageInfo
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
ReadOnly bool `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuntimeCreate struct {
|
type RuntimeCreate struct {
|
||||||
@@ -67,14 +66,15 @@ type RuntimeDelete struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RuntimeUpdate struct {
|
type RuntimeUpdate struct {
|
||||||
Name string `json:"name"`
|
AppDetailID uint `json:"appDetailId"`
|
||||||
ID uint `json:"id"`
|
Name string `json:"name"`
|
||||||
Image string `json:"image"`
|
ID uint `json:"id"`
|
||||||
Version string `json:"version"`
|
Image string `json:"image"`
|
||||||
Rebuild bool `json:"rebuild"`
|
Version string `json:"version"`
|
||||||
Source string `json:"source"`
|
Rebuild bool `json:"rebuild"`
|
||||||
CodeDir string `json:"codeDir"`
|
Source string `json:"source"`
|
||||||
Remark string `json:"remark"`
|
CodeDir string `json:"codeDir"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
|
||||||
Params map[string]interface{} `json:"params"`
|
Params map[string]interface{} `json:"params"`
|
||||||
NodeConfig
|
NodeConfig
|
||||||
|
|||||||
@@ -17,6 +17,17 @@ type NginxParam struct {
|
|||||||
Params []string `json:"params"`
|
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 {
|
type NginxAuthRes struct {
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
Items []dto.NginxAuth `json:"items"`
|
Items []dto.NginxAuth `json:"items"`
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ type SettingUpdate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AgentSettingUpdate 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"`
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
type Alert struct {
|
type Alert struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
|
|
||||||
@@ -18,10 +25,11 @@ type Alert struct {
|
|||||||
|
|
||||||
type AlertTask struct {
|
type AlertTask struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||||
Quota string `gorm:"type:varchar(64)" json:"quota"`
|
Quota string `gorm:"type:varchar(64)" json:"quota"`
|
||||||
QuotaType string `gorm:"type:varchar(64)" json:"quotaType"`
|
QuotaType string `gorm:"type:varchar(64)" json:"quotaType"`
|
||||||
Method string `gorm:"type:varchar(128);not null;default:'sms'" json:"method"`
|
Method string `gorm:"type:varchar(128);not null;default:'sms'" json:"method"`
|
||||||
|
DeliveryLogID *uint `gorm:"uniqueIndex" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlertLog struct {
|
type AlertLog struct {
|
||||||
@@ -41,12 +49,21 @@ type AlertLog struct {
|
|||||||
|
|
||||||
type AlertConfig struct {
|
type AlertConfig struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
UID string `gorm:"type:varchar(64);not null;uniqueIndex" json:"uid"`
|
||||||
Title string `gorm:"type:varchar(64);not null" json:"title"`
|
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
Title string `gorm:"type:varchar(64);not null" json:"title"`
|
||||||
Config string `gorm:"type:varchar(256);not null" json:"config"`
|
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||||
CreateUser string `gorm:"type:varchar(256)" json:"createUser"`
|
Config string `gorm:"type:text;not null" json:"config"`
|
||||||
UpdateUser string `gorm:"type:varchar(256)" json:"updateUser"`
|
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 {
|
type LoginLog struct {
|
||||||
|
|||||||
+46
-13
@@ -1,18 +1,51 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
type Firewall struct {
|
type DockerPortGuardPolicy struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
|
|
||||||
Type string `json:"type"`
|
UUID string `gorm:"uniqueIndex" json:"uuid"`
|
||||||
Port string `json:"port"` // Deprecated
|
ReadOnly bool `gorm:"default:false;uniqueIndex:idx_docker_port_guard_endpoint" json:"-"`
|
||||||
Address string `json:"address"` // Deprecated
|
Family string `gorm:"uniqueIndex:idx_docker_port_guard_endpoint" json:"family"`
|
||||||
|
HostIP string `gorm:"uniqueIndex:idx_docker_port_guard_endpoint" json:"hostIP"`
|
||||||
Chain string `json:"chain"`
|
HostPort uint16 `gorm:"uniqueIndex:idx_docker_port_guard_endpoint" json:"hostPort"`
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `gorm:"uniqueIndex:idx_docker_port_guard_endpoint" json:"protocol"`
|
||||||
SrcIP string `json:"srcIP"`
|
Mode string `json:"mode"`
|
||||||
SrcPort string `json:"srcPort"`
|
Sources string `gorm:"type:text" json:"-"`
|
||||||
DstIP string `json:"dstIP"`
|
Description string `gorm:"type:text" json:"description"`
|
||||||
DstPort string `json:"dstPort"`
|
NativeAction string `gorm:"default:''" json:"-"`
|
||||||
Strategy string `gorm:"not null" json:"strategy"`
|
NativeRules string `gorm:"type:text" json:"-"`
|
||||||
Description string `json:"description"`
|
Sequence int64 `gorm:"default:0" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForwardingRule struct {
|
||||||
|
BaseModel
|
||||||
|
|
||||||
|
Family string `gorm:"uniqueIndex:idx_forwarding_rule_identity" json:"family"`
|
||||||
|
Protocol string `gorm:"uniqueIndex:idx_forwarding_rule_identity" json:"protocol"`
|
||||||
|
Port string `gorm:"uniqueIndex:idx_forwarding_rule_identity" json:"port"`
|
||||||
|
TargetIP string `gorm:"uniqueIndex:idx_forwarding_rule_identity" json:"targetIP"`
|
||||||
|
TargetPort string `gorm:"uniqueIndex:idx_forwarding_rule_identity" json:"targetPort"`
|
||||||
|
Interface string `gorm:"default:'';uniqueIndex:idx_forwarding_rule_identity" json:"interface"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRule struct {
|
||||||
|
UUID string `gorm:"primaryKey" json:"uuid"`
|
||||||
|
Family string `json:"family"`
|
||||||
|
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
SourceAddress string `json:"sourceAddress"`
|
||||||
|
SourcePort string `json:"sourcePort"`
|
||||||
|
DestinationAddress string `json:"destinationAddress"`
|
||||||
|
DestinationPort string `json:"destinationPort"`
|
||||||
|
Interface string `json:"interface"`
|
||||||
|
ConnectionStates string `gorm:"type:text" json:"connectionStates"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
Description string `gorm:"type:text" json:"description"`
|
||||||
|
CompatibilityError string `gorm:"type:text" json:"compatibilityError,omitempty"`
|
||||||
|
Priority *int `json:"priority,omitempty"`
|
||||||
|
Sequence *int64 `gorm:"index" json:"sequence,omitempty"`
|
||||||
|
|
||||||
|
Origin string `json:"origin"`
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
Revision uint `gorm:"default:1" json:"revision"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,14 +33,45 @@ type MonitorNetwork struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MonitorGPU 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
|
BaseModel
|
||||||
ProductName string `json:"productName"`
|
ProductName string `json:"productName"`
|
||||||
GPUUtil float64 `json:"gpuUtil"`
|
GPUUtil *float64 `json:"gpuUtil"`
|
||||||
Temperature float64 `json:"temperature"`
|
Temperature *float64 `json:"temperature"`
|
||||||
PowerDraw float64 `json:"powerDraw"`
|
PowerDraw *float64 `json:"powerDraw"`
|
||||||
MaxPowerLimit float64 `json:"maxPowerLimit"`
|
MaxPowerLimit *float64 `json:"maxPowerLimit"`
|
||||||
MemUsed float64 `json:"memUsed"`
|
MemUsed *float64 `json:"memUsed"`
|
||||||
MemTotal float64 `json:"memTotal"`
|
MemTotal *float64 `json:"memTotal"`
|
||||||
FanSpeed int `json:"fanSpeed"`
|
FanSpeed *float64 `json:"fanSpeed"`
|
||||||
Processes string `json:"processes"`
|
Processes string `json:"processes"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,12 +40,21 @@ type Meta struct {
|
|||||||
var catalog = map[string]Meta{
|
var catalog = map[string]Meta{
|
||||||
"custom": {
|
"custom": {
|
||||||
Key: "custom", DisplayName: "Custom", Sort: 10, DefaultAPIType: "openai-completions", EnvKey: "CUSTOM_API_KEY",
|
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": {
|
"ollama": {
|
||||||
Key: "ollama", DisplayName: "Ollama", Sort: 15, DefaultAPIType: "openai-responses",
|
Key: "ollama", DisplayName: "Ollama", Sort: 15, DefaultAPIType: "openai-responses",
|
||||||
APIConfigs: editableAPIConfigs(false, "openai-responses", "openai-completions", "openai-embeddings"),
|
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": {
|
"vllm": {
|
||||||
Key: "vllm", DisplayName: "vLLM", Sort: 20, DefaultAPIType: "openai-completions", EnvKey: "VLLM_API_KEY",
|
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"),
|
APIConfigs: editableAPIConfigs(false, "openai-completions", "openai-responses", "anthropic-messages", "openai-images", "openai-embeddings"),
|
||||||
@@ -453,6 +462,7 @@ var legacyModelPrefixes = map[string][]string{
|
|||||||
"custom": {"custom"},
|
"custom": {"custom"},
|
||||||
"vllm": {"custom"},
|
"vllm": {"custom"},
|
||||||
"ollama": {"ollama"},
|
"ollama": {"ollama"},
|
||||||
|
"llmman": {"llmman"},
|
||||||
"deepseek": {"deepseek"},
|
"deepseek": {"deepseek"},
|
||||||
"bailian-coding-plan": {"bailian-coding-plan"},
|
"bailian-coding-plan": {"bailian-coding-plan"},
|
||||||
"ark-coding-plan": {"ark-coding-plan"},
|
"ark-coding-plan": {"ark-coding-plan"},
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ func BuildOpenClawProviderPatch(provider, modelName, apiType, authMode, baseURL,
|
|||||||
providerKey = "moonshot"
|
providerKey = "moonshot"
|
||||||
resolvedAPIType = "openai-completions"
|
resolvedAPIType = "openai-completions"
|
||||||
usesBearer = false
|
usesBearer = false
|
||||||
case "ollama":
|
case "ollama", "llmman":
|
||||||
apiKey = "ollama"
|
apiKey = provider
|
||||||
usesBearer = false
|
usesBearer = false
|
||||||
case "openai", "openrouter", "anthropic":
|
case "openai", "openrouter", "anthropic":
|
||||||
preserveQualifiedModel = strings.Contains(modelName, "/")
|
preserveQualifiedModel = strings.Contains(modelName, "/")
|
||||||
|
|||||||
@@ -27,11 +27,14 @@ type verifyErrorResponse struct {
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultVerifyTimeout = 30 * time.Second
|
const (
|
||||||
|
defaultVerifyTimeout = 30 * time.Second
|
||||||
|
defaultVerifyMaxTokens = 16
|
||||||
|
)
|
||||||
|
|
||||||
func SkipVerification(provider string) bool {
|
func SkipVerification(provider string) bool {
|
||||||
switch provider {
|
switch provider {
|
||||||
case "vllm", "ollama", "kimi-coding":
|
case "vllm", "ollama", "llmman", "kimi-coding":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@@ -116,20 +119,20 @@ func BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model stri
|
|||||||
}
|
}
|
||||||
headers["anthropic-version"] = "2023-06-01"
|
headers["anthropic-version"] = "2023-06-01"
|
||||||
request.Body = mustJSON(map[string]interface{}{
|
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"}}}},
|
"messages": []map[string]interface{}{{"role": "user", "content": []map[string]string{{"type": "text", "text": "test"}}}},
|
||||||
})
|
})
|
||||||
case "openai-responses":
|
case "openai-responses":
|
||||||
request.URL = baseURL + "/responses"
|
request.URL = baseURL + "/responses"
|
||||||
headers["Authorization"] = "Bearer " + apiKey
|
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:
|
default:
|
||||||
request.URL = baseURL + "/chat/completions"
|
request.URL = baseURL + "/chat/completions"
|
||||||
if provider != "ollama" || strings.TrimSpace(apiKey) != "" {
|
if (provider != "ollama" && provider != "llmman") || strings.TrimSpace(apiKey) != "" {
|
||||||
headers["Authorization"] = "Bearer " + apiKey
|
headers["Authorization"] = "Bearer " + apiKey
|
||||||
}
|
}
|
||||||
request.Body = mustJSON(map[string]interface{}{
|
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
|
return request
|
||||||
|
|||||||
+217
-9
@@ -1,20 +1,30 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/google/uuid"
|
||||||
"google.golang.org/genproto/googleapis/type/date"
|
"google.golang.org/genproto/googleapis/type/date"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"strconv"
|
"gorm.io/gorm/clause"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AlertRepo struct{}
|
type AlertRepo struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrAlertConfigRevisionConflict = errors.New("alert config revision conflict")
|
||||||
|
ErrAlertConfigRevisionRequired = errors.New("alert config revision is required")
|
||||||
|
)
|
||||||
|
|
||||||
type IAlertRepo interface {
|
type IAlertRepo interface {
|
||||||
WithByType(alertType string) DBOption
|
WithByType(alertType string) DBOption
|
||||||
WithByStatusIn(status []string) DBOption
|
WithByStatusIn(status []string) DBOption
|
||||||
@@ -24,6 +34,7 @@ type IAlertRepo interface {
|
|||||||
WithByCreateAt(date *date.Date) DBOption
|
WithByCreateAt(date *date.Date) DBOption
|
||||||
WithByLicenseId(licenseId string) DBOption
|
WithByLicenseId(licenseId string) DBOption
|
||||||
WithByRecordId(recordId uint) DBOption
|
WithByRecordId(recordId uint) DBOption
|
||||||
|
WithByDeliveryLogID(logID uint) DBOption
|
||||||
WithByAlertMethodContainsConfigID(id uint) DBOption
|
WithByAlertMethodContainsConfigID(id uint) DBOption
|
||||||
WithByMethodConfigIDs(ids []uint) DBOption
|
WithByMethodConfigIDs(ids []uint) DBOption
|
||||||
|
|
||||||
@@ -45,6 +56,8 @@ type IAlertRepo interface {
|
|||||||
CleanAlertLogs() error
|
CleanAlertLogs() error
|
||||||
|
|
||||||
CreateAlertTask(alertTaskBase *model.AlertTask) 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
|
DeleteAlertTask(opts ...DBOption) error
|
||||||
GetAlertTask(opts ...DBOption) (model.AlertTask, error)
|
GetAlertTask(opts ...DBOption) (model.AlertTask, error)
|
||||||
LoadTaskCount(alertType string, project string, method string) (uint, uint, error)
|
LoadTaskCount(alertType string, project string, method string) (uint, uint, error)
|
||||||
@@ -55,6 +68,7 @@ type IAlertRepo interface {
|
|||||||
GetConfigById(id uint) (model.AlertConfig, error)
|
GetConfigById(id uint) (model.AlertConfig, error)
|
||||||
AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error)
|
AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error)
|
||||||
UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) 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
|
CreateAlertConfig(config *model.AlertConfig) error
|
||||||
DeleteAlertConfig(opts ...DBOption) error
|
DeleteAlertConfig(opts ...DBOption) error
|
||||||
|
|
||||||
@@ -223,13 +237,78 @@ func (a *AlertRepo) DeleteLog(opts ...DBOption) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlertRepo) CleanAlertLogs() 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 {
|
func (a *AlertRepo) CreateAlertTask(alertTaskBase *model.AlertTask) error {
|
||||||
return global.AlertDB.Model(&model.AlertTask{}).Create(&alertTaskBase).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 {
|
func (a *AlertRepo) DeleteAlertTask(opts ...DBOption) error {
|
||||||
db, _ := getAlertDB(opts...)
|
db, _ := getAlertDB(opts...)
|
||||||
return db.Delete(&model.AlertTask{}).Error
|
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
|
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 {
|
func (a *AlertRepo) CreateAlertConfig(config *model.AlertConfig) error {
|
||||||
|
ensureAlertConfigUID(config)
|
||||||
return global.AlertDB.Model(&model.AlertConfig{}).Create(config).Error
|
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) {
|
func (a *AlertRepo) PageAlertConfig(page, size int, opts ...DBOption) (int64, []model.AlertConfig, error) {
|
||||||
var configs []model.AlertConfig
|
var configs []model.AlertConfig
|
||||||
db := global.AlertDB.Model(&model.AlertConfig{})
|
db := global.AlertDB.Model(&model.AlertConfig{})
|
||||||
@@ -378,26 +479,44 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
|||||||
return err
|
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)
|
oldConfigByType := make(map[string][]model.AlertConfig)
|
||||||
oldConfigByKey := make(map[string][]model.AlertConfig)
|
oldConfigByKey := make(map[string][]model.AlertConfig)
|
||||||
consumedConfigIDs := make(map[uint]struct{})
|
consumedConfigIDs := make(map[uint]struct{})
|
||||||
for _, item := range oldConfigs {
|
for _, item := range oldConfigs {
|
||||||
|
if strings.TrimSpace(item.UID) != "" {
|
||||||
|
oldConfigByUID[item.UID] = item
|
||||||
|
}
|
||||||
if singletonTypes[item.Type] {
|
if singletonTypes[item.Type] {
|
||||||
oldConfigMap[item.Type] = item.ID
|
oldConfigMap[item.Type] = item
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
oldConfigByType[item.Type] = append(oldConfigByType[item.Type], item)
|
oldConfigByType[item.Type] = append(oldConfigByType[item.Type], item)
|
||||||
oldConfigByKey[alertConfigSyncKey(item)] = append(oldConfigByKey[alertConfigSyncKey(item)], item)
|
oldConfigByKey[alertConfigSyncKey(item)] = append(oldConfigByKey[alertConfigSyncKey(item)], item)
|
||||||
}
|
}
|
||||||
for _, item := range data {
|
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 singletonTypes[item.Type] {
|
||||||
if val, ok := oldConfigMap[item.Type]; ok {
|
if matched, ok := oldConfigMap[item.Type]; ok {
|
||||||
item.ID = val
|
if err := inheritAlertConfigSyncState(&item, matched); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
delete(oldConfigMap, item.Type)
|
delete(oldConfigMap, item.Type)
|
||||||
consumedConfigIDs[item.ID] = struct{}{}
|
consumedConfigIDs[item.ID] = struct{}{}
|
||||||
} else {
|
} else {
|
||||||
item.ID = 0
|
item.ID = 0
|
||||||
|
ensureAlertConfigUID(&item)
|
||||||
|
if err := validateAlertConfigSyncSecret(&item); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if item.ID == 0 {
|
if item.ID == 0 {
|
||||||
if err := tx.Create(&item).Error; err != nil {
|
if err := tx.Create(&item).Error; err != nil {
|
||||||
@@ -411,9 +530,31 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
|||||||
continue
|
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)
|
key := alertConfigSyncKey(item)
|
||||||
if matched, ok := popAlertConfigByKey(oldConfigByKey, key); ok {
|
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{}{}
|
consumedConfigIDs[item.ID] = struct{}{}
|
||||||
if err := tx.Save(&item).Error; err != nil {
|
if err := tx.Save(&item).Error; err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
@@ -424,7 +565,12 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if matched, ok := popUnusedAlertConfigByType(oldConfigByType, usedConfigIDs, item.Type); ok {
|
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{}{}
|
consumedConfigIDs[item.ID] = struct{}{}
|
||||||
if err := tx.Save(&item).Error; err != nil {
|
if err := tx.Save(&item).Error; err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
@@ -434,6 +580,11 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
item.ID = 0
|
item.ID = 0
|
||||||
|
ensureAlertConfigUID(&item)
|
||||||
|
if err := validateAlertConfigSyncSecret(&item); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := tx.Create(&item).Error; err != nil {
|
if err := tx.Create(&item).Error; err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return err
|
return err
|
||||||
@@ -458,6 +609,63 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
|||||||
return nil
|
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) {
|
func loadUsedAlertConfigIDs(tx *gorm.DB) (map[uint]struct{}, error) {
|
||||||
var alerts []model.Alert
|
var alerts []model.Alert
|
||||||
if err := tx.Select("method").Find(&alerts).Error; err != nil {
|
if err := tx.Select("method").Find(&alerts).Error; err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IDockerPortGuardRepo interface {
|
||||||
|
ListManaged(context.Context) ([]model.DockerPortGuardPolicy, error)
|
||||||
|
DeleteBatch(context.Context, []string) error
|
||||||
|
UpsertBatch(context.Context, []model.DockerPortGuardPolicy) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DockerPortGuardRepo struct{}
|
||||||
|
|
||||||
|
func NewIDockerPortGuardRepo() IDockerPortGuardRepo { return &DockerPortGuardRepo{} }
|
||||||
|
|
||||||
|
func (r *DockerPortGuardRepo) ListManaged(ctx context.Context) ([]model.DockerPortGuardPolicy, error) {
|
||||||
|
var policies []model.DockerPortGuardPolicy
|
||||||
|
err := global.DB.WithContext(ctx).
|
||||||
|
Where("read_only = ?", false).
|
||||||
|
Order("family, host_ip, host_port, protocol").
|
||||||
|
Find(&policies).Error
|
||||||
|
return policies, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DockerPortGuardRepo) DeleteBatch(ctx context.Context, uuids []string) error {
|
||||||
|
return global.DB.WithContext(ctx).
|
||||||
|
Where("read_only = ? AND uuid IN ?", false, uuids).
|
||||||
|
Delete(&model.DockerPortGuardPolicy{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DockerPortGuardRepo) UpsertBatch(ctx context.Context, policies []model.DockerPortGuardPolicy) error {
|
||||||
|
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
for i := range policies {
|
||||||
|
policies[i].ReadOnly = false
|
||||||
|
if err := tx.Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "read_only"}, {Name: "family"}, {Name: "host_ip"}, {Name: "host_port"}, {Name: "protocol"}},
|
||||||
|
DoUpdates: clause.AssignmentColumns([]string{"mode", "sources", "description", "updated_at"}),
|
||||||
|
}).Create(&policies[i]).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFirewallRuleRevisionConflict = errors.New("firewall rule revision conflict")
|
||||||
|
ErrFirewallPersistenceInvalid = errors.New("invalid firewall persistence record")
|
||||||
|
)
|
||||||
|
|
||||||
|
type IFirewallRuleRepo interface {
|
||||||
|
Create(context.Context, *model.FirewallRule) error
|
||||||
|
GetByUUID(context.Context, string) (model.FirewallRule, error)
|
||||||
|
List(context.Context, ...DBOption) ([]model.FirewallRule, error)
|
||||||
|
UpdateWithRevision(context.Context, string, uint, map[string]interface{}) error
|
||||||
|
DeleteWithRevision(context.Context, string, uint) error
|
||||||
|
DeleteBatchWithRevision(context.Context, []model.FirewallRule) map[string]error
|
||||||
|
SaveResetOrder(context.Context, []model.FirewallRule) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallRuleRepo struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIFirewallRuleRepo() IFirewallRuleRepo {
|
||||||
|
return &FirewallRuleRepo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFirewallRuleRepo(db *gorm.DB) *FirewallRuleRepo {
|
||||||
|
return &FirewallRuleRepo{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FirewallRuleRepo) Create(ctx context.Context, rule *model.FirewallRule) error {
|
||||||
|
if err := prepareFirewallRule(rule); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.dbFor(ctx).Create(rule).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FirewallRuleRepo) GetByUUID(ctx context.Context, ruleUUID string) (model.FirewallRule, error) {
|
||||||
|
var rule model.FirewallRule
|
||||||
|
err := r.dbFor(ctx).Where("uuid = ?", ruleUUID).First(&rule).Error
|
||||||
|
return rule, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FirewallRuleRepo) List(ctx context.Context, opts ...DBOption) ([]model.FirewallRule, error) {
|
||||||
|
var rules []model.FirewallRule
|
||||||
|
db := r.dbFor(ctx).Model(&model.FirewallRule{})
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
return rules, db.Find(&rules).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FirewallRuleRepo) UpdateWithRevision(ctx context.Context, ruleUUID string, expectedRevision uint, updates map[string]interface{}) error {
|
||||||
|
updates = sanitizeRuleUpdates(updates)
|
||||||
|
updates["revision"] = gorm.Expr("revision + 1")
|
||||||
|
result := r.dbFor(ctx).Model(&model.FirewallRule{}).
|
||||||
|
Where("uuid = ? AND revision = ?", ruleUUID, expectedRevision).
|
||||||
|
Updates(updates)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return ErrFirewallRuleRevisionConflict
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FirewallRuleRepo) DeleteWithRevision(ctx context.Context, ruleUUID string, expectedRevision uint) error {
|
||||||
|
result := r.dbFor(ctx).
|
||||||
|
Where("uuid = ? AND revision = ?", ruleUUID, expectedRevision).
|
||||||
|
Delete(&model.FirewallRule{})
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return ErrFirewallRuleRevisionConflict
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FirewallRuleRepo) DeleteBatchWithRevision(ctx context.Context, rules []model.FirewallRule) map[string]error {
|
||||||
|
failures := make(map[string]error)
|
||||||
|
for start := 0; start < len(rules); start += 500 {
|
||||||
|
batch := rules[start:min(start+500, len(rules))]
|
||||||
|
ids := make([][]interface{}, 0, len(batch))
|
||||||
|
for _, rule := range batch {
|
||||||
|
ids = append(ids, []interface{}{rule.UUID, rule.Revision})
|
||||||
|
failures[rule.UUID] = ErrFirewallRuleRevisionConflict
|
||||||
|
}
|
||||||
|
var deleted []model.FirewallRule
|
||||||
|
err := r.dbFor(ctx).Clauses(clause.Returning{Columns: []clause.Column{{Name: "uuid"}}}).
|
||||||
|
Where("(uuid, revision) IN ?", ids).Delete(&deleted).Error
|
||||||
|
if err != nil {
|
||||||
|
for _, rule := range batch {
|
||||||
|
failures[rule.UUID] = err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, rule := range deleted {
|
||||||
|
delete(failures, rule.UUID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return failures
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FirewallRuleRepo) SaveResetOrder(ctx context.Context, rules []model.FirewallRule) error {
|
||||||
|
if len(rules) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return r.dbFor(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
for _, rule := range rules {
|
||||||
|
result := tx.Model(&model.FirewallRule{}).
|
||||||
|
Where("uuid = ? AND revision = ?", rule.UUID, rule.Revision).
|
||||||
|
Updates(map[string]interface{}{"sequence": rule.Sequence, "priority": rule.Priority, "revision": gorm.Expr("revision + 1")})
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return ErrFirewallRuleRevisionConflict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FirewallRuleRepo) dbFor(ctx context.Context) *gorm.DB {
|
||||||
|
return firewallDB(ctx, r.db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func firewallDB(ctx context.Context, fallback *gorm.DB) *gorm.DB {
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
if tx, ok := ctx.Value(constant.DB).(*gorm.DB); ok && tx != nil {
|
||||||
|
return tx.WithContext(ctx)
|
||||||
|
}
|
||||||
|
if fallback == nil {
|
||||||
|
fallback = global.DB
|
||||||
|
}
|
||||||
|
return fallback.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareFirewallRule(rule *model.FirewallRule) error {
|
||||||
|
if rule == nil {
|
||||||
|
return fmt.Errorf("%w: rule is nil", ErrFirewallPersistenceInvalid)
|
||||||
|
}
|
||||||
|
if rule.Family == "" || rule.Protocol == "" || rule.Action == "" {
|
||||||
|
return fmt.Errorf("%w: atomic rule identity fields are required", ErrFirewallPersistenceInvalid)
|
||||||
|
}
|
||||||
|
if rule.UUID == "" {
|
||||||
|
rule.UUID = uuid.NewString()
|
||||||
|
}
|
||||||
|
if rule.Revision == 0 {
|
||||||
|
rule.Revision = 1
|
||||||
|
}
|
||||||
|
if rule.Origin == "" {
|
||||||
|
rule.Origin = constant.FirewallRuleOriginCreated
|
||||||
|
}
|
||||||
|
if rule.Owner == "" {
|
||||||
|
rule.Owner = constant.FirewallRuleSourceUser
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizeRuleUpdates(updates map[string]interface{}) map[string]interface{} {
|
||||||
|
result := make(map[string]interface{}, len(updates)+1)
|
||||||
|
for key, value := range updates {
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
delete(result, "id")
|
||||||
|
delete(result, "uuid")
|
||||||
|
delete(result, "revision")
|
||||||
|
delete(result, "created_at")
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IForwardingRuleRepo interface {
|
||||||
|
List(context.Context) ([]model.ForwardingRule, error)
|
||||||
|
CreateBatch(context.Context, []model.ForwardingRule) error
|
||||||
|
DeleteBatch(context.Context, []uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForwardingRuleRepo struct{}
|
||||||
|
|
||||||
|
func NewIForwardingRuleRepo() IForwardingRuleRepo { return &ForwardingRuleRepo{} }
|
||||||
|
|
||||||
|
func (r *ForwardingRuleRepo) List(ctx context.Context) ([]model.ForwardingRule, error) {
|
||||||
|
var rules []model.ForwardingRule
|
||||||
|
err := global.DB.WithContext(ctx).Order("id ASC").Find(&rules).Error
|
||||||
|
return rules, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ForwardingRuleRepo) CreateBatch(ctx context.Context, rules []model.ForwardingRule) error {
|
||||||
|
if len(rules) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return global.DB.WithContext(ctx).CreateInBatches(&rules, 500).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ForwardingRuleRepo) DeleteBatch(ctx context.Context, ids []uint) error {
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return global.DB.WithContext(ctx).Where("id IN ?", ids).Delete(&model.ForwardingRule{}).Error
|
||||||
|
}
|
||||||
@@ -22,11 +22,6 @@ type IHostRepo interface {
|
|||||||
WithByPort(port uint) DBOption
|
WithByPort(port uint) DBOption
|
||||||
WithByUser(user string) 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
|
SyncCert(data []model.RootCert) error
|
||||||
GetCert(opts ...DBOption) (model.RootCert, error)
|
GetCert(opts ...DBOption) (model.RootCert, error)
|
||||||
PageCert(limit, offset int, opts ...DBOption) (int64, []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
|
SaveCert(cert *model.RootCert) error
|
||||||
UpdateCert(id uint, vars map[string]interface{}) error
|
UpdateCert(id uint, vars map[string]interface{}) error
|
||||||
DeleteCert(opts ...DBOption) error
|
DeleteCert(opts ...DBOption) error
|
||||||
|
|
||||||
WithByChain(chain string) DBOption
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIHostRepo() IHostRepo {
|
func NewIHostRepo() IHostRepo {
|
||||||
@@ -116,65 +109,6 @@ func (h *HostRepo) Delete(opts ...DBOption) error {
|
|||||||
return db.Delete(&model.Host{}).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) {
|
func (u *HostRepo) GetCert(opts ...DBOption) (model.RootCert, error) {
|
||||||
var cert model.RootCert
|
var cert model.RootCert
|
||||||
db := global.DB
|
db := global.DB
|
||||||
@@ -253,9 +187,3 @@ func (u *HostRepo) SyncCert(data []model.RootCert) error {
|
|||||||
tx.Commit()
|
tx.Commit()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *HostRepo) WithByChain(chain string) DBOption {
|
|
||||||
return func(g *gorm.DB) *gorm.DB {
|
|
||||||
return g.Where("chain = ?", chain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
@@ -10,9 +12,20 @@ import (
|
|||||||
|
|
||||||
type MonitorRepo struct{}
|
type MonitorRepo struct{}
|
||||||
|
|
||||||
|
type GPUHistoryPoint struct {
|
||||||
|
model.MonitorGPU
|
||||||
|
Bucket int64
|
||||||
|
PowerPercent *float64
|
||||||
|
MemoryPercent *float64
|
||||||
|
ProcessCount *float64
|
||||||
|
}
|
||||||
|
|
||||||
type IMonitorRepo interface {
|
type IMonitorRepo interface {
|
||||||
GetBase(opts ...DBOption) ([]model.MonitorBase, error)
|
GetBase(opts ...DBOption) ([]model.MonitorBase, error)
|
||||||
GetGPU(opts ...DBOption) ([]model.MonitorGPU, 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)
|
GetIO(opts ...DBOption) ([]model.MonitorIO, error)
|
||||||
GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error)
|
GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error)
|
||||||
|
|
||||||
@@ -26,6 +39,7 @@ type IMonitorRepo interface {
|
|||||||
DelMonitorNet(timeForDelete time.Time) error
|
DelMonitorNet(timeForDelete time.Time) error
|
||||||
|
|
||||||
WithByProductName(name string) DBOption
|
WithByProductName(name string) DBOption
|
||||||
|
WithByGPUDevice(deviceID, name string, legacy bool) DBOption
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIMonitorRepo() IMonitorRepo {
|
func NewIMonitorRepo() IMonitorRepo {
|
||||||
@@ -102,3 +116,68 @@ func (s *MonitorRepo) WithByProductName(name string) DBOption {
|
|||||||
return g.Where("product_name = ?", name)
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ type IAgentService interface {
|
|||||||
BatchUpgrade(req dto.AgentBatchUpgradeReq) ([]dto.AgentBatchUpgradeResult, error)
|
BatchUpgrade(req dto.AgentBatchUpgradeReq) ([]dto.AgentBatchUpgradeResult, error)
|
||||||
BatchInstallSkill(req dto.AgentBatchSkillInstallReq) ([]dto.AgentBatchSkillInstallResult, error)
|
BatchInstallSkill(req dto.AgentBatchSkillInstallReq) ([]dto.AgentBatchSkillInstallResult, error)
|
||||||
BatchOperate(req dto.AgentBatchOperateReq) ([]dto.AgentBatchOperateResult, 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)
|
DeleteCheck(req dto.AgentIDReq) ([]dto.AppResource, error)
|
||||||
Delete(req dto.AgentDeleteReq) error
|
Delete(req dto.AgentDeleteReq) error
|
||||||
ResetToken(req dto.AgentTokenResetReq) error
|
ResetToken(req dto.AgentTokenResetReq) error
|
||||||
@@ -76,7 +76,7 @@ type IAgentService interface {
|
|||||||
CreateAccount(req dto.AgentAccountCreateReq) error
|
CreateAccount(req dto.AgentAccountCreateReq) error
|
||||||
UpdateAccount(req dto.AgentAccountUpdateReq) error
|
UpdateAccount(req dto.AgentAccountUpdateReq) error
|
||||||
SyncAgentsByAccount(account *model.AgentAccount) 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)
|
CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error)
|
||||||
GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error)
|
GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error)
|
||||||
DiscoverAccountModels(req dto.AgentAccountModelDiscoverReq) ([]dto.AgentAccountModel, error)
|
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
|
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
|
var opts []repo.DBOption
|
||||||
if strings.TrimSpace(req.Info) != "" {
|
if strings.TrimSpace(req.Info) != "" {
|
||||||
opts = append(opts, repo.WithByLikeName(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))
|
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(item.AppInstallID))
|
||||||
appInstalls = append(appInstalls, appInstall)
|
appInstalls = append(appInstalls, appInstall)
|
||||||
}
|
}
|
||||||
readOnlyMode := isDemoReadOnly(readOnly)
|
syncAgentAppInstalls(appInstalls)
|
||||||
if !readOnlyMode {
|
|
||||||
syncAgentAppInstalls(appInstalls)
|
|
||||||
}
|
|
||||||
for index, item := range list {
|
for index, item := range list {
|
||||||
appInstall := appInstalls[index]
|
appInstall := appInstalls[index]
|
||||||
envMap := readInstallEnv(appInstall.Env)
|
envMap := readInstallEnv(appInstall.Env)
|
||||||
agentItem := buildAgentItem(&item, &appInstall, envMap)
|
agentItem := buildAgentItem(&item, &appInstall, envMap)
|
||||||
if readOnlyMode {
|
|
||||||
agentItem.Token = ""
|
|
||||||
agentItem.APIKey = ""
|
|
||||||
agentItem.DashboardPassword = ""
|
|
||||||
}
|
|
||||||
agentItem.Upgradable = checkAgentUpgradable(appInstall)
|
agentItem.Upgradable = checkAgentUpgradable(appInstall)
|
||||||
items = append(items, agentItem)
|
items = append(items, agentItem)
|
||||||
}
|
}
|
||||||
@@ -944,6 +936,7 @@ func (a AgentService) GetModelConfig(req dto.AgentIDReq) (*dto.AgentModelConfig,
|
|||||||
AccountID: agent.AccountID,
|
AccountID: agent.AccountID,
|
||||||
Model: model,
|
Model: model,
|
||||||
Fallbacks: extractOpenclawFallbackModelIDs(conf, account, models, model),
|
Fallbacks: extractOpenclawFallbackModelIDs(conf, account, models, model),
|
||||||
|
Metadata: extractOpenclawModelMetadata(conf, account, models),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -975,7 +968,7 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
|||||||
if agent.AgentType != constant.AppOpenclaw {
|
if agent.AgentType != constant.AppOpenclaw {
|
||||||
return fmt.Errorf("%s does not support", agent.AgentType)
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1136,7 +1129,7 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
|||||||
return nil
|
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
|
var opts []repo.DBOption
|
||||||
if strings.TrimSpace(req.Provider) != "" {
|
if strings.TrimSpace(req.Provider) != "" {
|
||||||
opts = append(opts, repo.WithByProvider(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))
|
items := make([]dto.AgentAccountInfo, 0, len(list))
|
||||||
for _, item := range list {
|
for _, item := range list {
|
||||||
apiKey := ""
|
apiKey := ""
|
||||||
if item.RememberAPIKey && !isDemoReadOnly(readOnly...) {
|
if item.RememberAPIKey {
|
||||||
apiKey = item.APIKey
|
apiKey = item.APIKey
|
||||||
}
|
}
|
||||||
items = append(items, dto.AgentAccountInfo{
|
items = append(items, dto.AgentAccountInfo{
|
||||||
@@ -1692,7 +1685,7 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fallbacks := extractOpenclawFallbackModelIDs(conf, account, accountModels, selectedAccountModel.ID)
|
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
|
return err
|
||||||
}
|
}
|
||||||
case constant.AppHermesAgent:
|
case constant.AppHermesAgent:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -1320,6 +1321,10 @@ func appendPluginAllow(conf map[string]interface{}, pluginID string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID string) error {
|
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)
|
workdir := path.Join(openclawPluginPackageTmpDir, pluginID)
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = mgr.Run("docker", "exec", containerName, "rm", "-rf", workdir)
|
_ = mgr.Run("docker", "exec", containerName, "rm", "-rf", workdir)
|
||||||
@@ -1341,7 +1346,19 @@ func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID
|
|||||||
if pkgPath == "" {
|
if pkgPath == "" {
|
||||||
return fmt.Errorf("openclaw plugin package not found")
|
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 {
|
func uninstallOpenclawPlugin(mgr *cmd.CommandHelper, containerName, pluginID string) error {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -737,9 +738,11 @@ type modelProvider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type modelEntry struct {
|
type modelEntry struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Input []string `json:"input,omitempty"`
|
Input []string `json:"input,omitempty"`
|
||||||
|
ContextWindow int `json:"contextWindow,omitempty"`
|
||||||
|
MaxTokens int `json:"maxTokens,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func requiresOpenclawProviderModels(provider string) bool {
|
func requiresOpenclawProviderModels(provider string) bool {
|
||||||
@@ -767,7 +770,7 @@ type browserConfig struct {
|
|||||||
DefaultProfile string `json:"defaultProfile"`
|
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) == "" {
|
if strings.TrimSpace(confDir) == "" {
|
||||||
return fmt.Errorf("config dir is required")
|
return fmt.Errorf("config dir is required")
|
||||||
}
|
}
|
||||||
@@ -852,6 +855,7 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
|||||||
}
|
}
|
||||||
conf = initial
|
conf = initial
|
||||||
} else {
|
} else {
|
||||||
|
preserveOpenclawModelMetadata(conf, cfg.Models)
|
||||||
if err := applyOpenclawModelsConfig(conf, cfg.Models); err != nil {
|
if err := applyOpenclawModelsConfig(conf, cfg.Models); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -906,6 +910,9 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
|||||||
if allowedOrigins != nil {
|
if allowedOrigins != nil {
|
||||||
setSecurityConfig(conf, dto.AgentSecurityConfig{AllowedOrigins: allowedOrigins})
|
setSecurityConfig(conf, dto.AgentSecurityConfig{AllowedOrigins: allowedOrigins})
|
||||||
}
|
}
|
||||||
|
if err := applyOpenclawModelMetadata(conf, account, metadata); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
|
if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -920,6 +927,144 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
|||||||
return writeAgentEnvMap(path.Join(confDir, ".env"), envMap, order)
|
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) {
|
func resolveOpenclawFallbackModels(account *model.AgentAccount, primaryModel string, fallbackIDs []string) ([]string, error) {
|
||||||
accountModels, err := loadAgentAccountModels(account)
|
accountModels, err := loadAgentAccountModels(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1041,7 +1186,7 @@ func prepareOpenclawInstallFiles(appInstall *model.AppInstall, account *model.Ag
|
|||||||
return fmt.Errorf("app install is required")
|
return fmt.Errorf("app install is required")
|
||||||
}
|
}
|
||||||
confDir := path.Join(appInstall.GetPath(), "data", "conf")
|
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
|
return err
|
||||||
}
|
}
|
||||||
dataDir := path.Join(appInstall.GetPath(), "data")
|
dataDir := path.Join(appInstall.GetPath(), "data")
|
||||||
@@ -1338,7 +1483,7 @@ func normalizeAgentAccountModel(account *model.AgentAccount, model dto.AgentAcco
|
|||||||
|
|
||||||
func requiresInitialAgentAccountModels(provider string) bool {
|
func requiresInitialAgentAccountModels(provider string) bool {
|
||||||
switch provider {
|
switch provider {
|
||||||
case "custom", "vllm", "ollama":
|
case "custom", "vllm", "ollama", "llmman":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
+425
-45
@@ -17,10 +17,14 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
|
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/cmd"
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/email"
|
"github.com/1Panel-dev/1Panel/agent/utils/email"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/xpack/providers"
|
||||||
"github.com/shirou/gopsutil/v4/disk"
|
"github.com/shirou/gopsutil/v4/disk"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,6 +38,28 @@ var communityAlertMethodTypeNames = map[string]string{
|
|||||||
constant.SMS: "SMS",
|
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 {
|
type IAlertService interface {
|
||||||
PageAlert(req dto.AlertSearch) (int64, []dto.AlertDTO, error)
|
PageAlert(req dto.AlertSearch) (int64, []dto.AlertDTO, error)
|
||||||
GetAlerts() ([]dto.AlertDTO, error)
|
GetAlerts() ([]dto.AlertDTO, error)
|
||||||
@@ -51,10 +77,12 @@ type IAlertService interface {
|
|||||||
GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error)
|
GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error)
|
||||||
|
|
||||||
GetAlertConfig(req dto.AlertConfigQuery) ([]model.AlertConfig, 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
|
UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error
|
||||||
|
UpdateAlertConfigStatus(req dto.AlertConfigStatusUpdate, operator string) error
|
||||||
DeleteAlertConfig(id uint) error
|
DeleteAlertConfig(id uint) error
|
||||||
TestAlertConfig(req dto.AlertConfigTest) (bool, error)
|
TestAlertConfig(req dto.AlertConfigTest) (bool, error)
|
||||||
|
TestCustomAlertConfig(req dto.AlertConfigTest) (dto.AlertConfigTestResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIAlertService() IAlertService {
|
func NewIAlertService() IAlertService {
|
||||||
@@ -82,7 +110,38 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
|||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cronjobProjects := make(map[string]uint)
|
||||||
|
var cronjobIDs []uint
|
||||||
for _, item := range alerts {
|
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{
|
result = append(result, dto.AlertDTO{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
@@ -92,6 +151,7 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
|||||||
Method: item.Method,
|
Method: item.Method,
|
||||||
Title: item.Title,
|
Title: item.Title,
|
||||||
Project: item.Project,
|
Project: item.Project,
|
||||||
|
TaskName: taskName,
|
||||||
Status: item.Status,
|
Status: item.Status,
|
||||||
SendCount: item.SendCount,
|
SendCount: item.SendCount,
|
||||||
AdvancedParams: item.AdvancedParams,
|
AdvancedParams: item.AdvancedParams,
|
||||||
@@ -163,6 +223,16 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
alertInfo.Status = constant.AlertEnable
|
||||||
if err := copier.Copy(&alertInfo, &create); err != nil {
|
if err := copier.Copy(&alertInfo, &create); err != nil {
|
||||||
return buserr.WithErr("ErrStructTransform", err)
|
return buserr.WithErr("ErrStructTransform", err)
|
||||||
@@ -180,9 +250,28 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) UpdateAlert(req dto.AlertUpdate, 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
|
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 := make(map[string]interface{})
|
||||||
upMap["id"] = req.ID
|
upMap["id"] = req.ID
|
||||||
@@ -240,7 +329,19 @@ func (a AlertService) UpdateStatus(id uint, status string) error {
|
|||||||
if alertInfo.ID == 0 {
|
if alertInfo.ID == 0 {
|
||||||
return buserr.New("ErrRecordNotFound")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -412,6 +513,7 @@ func (a AlertService) parseAlertLog(item model.AlertLog) (dto.AlertLogDTO, error
|
|||||||
if err := unmarshalAlertInfo(item.AlertDetail, &alertDetail); err != nil {
|
if err := unmarshalAlertInfo(item.AlertDetail, &alertDetail); err != nil {
|
||||||
return dto.AlertLogDTO{}, err
|
return dto.AlertLogDTO{}, err
|
||||||
}
|
}
|
||||||
|
alertDetail.Task = nil
|
||||||
if err := unmarshalAlertInfo(item.AlertRule, &alertRule); err != nil {
|
if err := unmarshalAlertInfo(item.AlertRule, &alertRule); err != nil {
|
||||||
return dto.AlertLogDTO{}, err
|
return dto.AlertLogDTO{}, err
|
||||||
}
|
}
|
||||||
@@ -494,10 +596,16 @@ func (a AlertService) GetAlertConfig(req dto.AlertConfigQuery) ([]model.AlertCon
|
|||||||
}
|
}
|
||||||
opts = append(opts, repo.WithByStatus(constant.AlertEnable))
|
opts = append(opts, repo.WithByStatus(constant.AlertEnable))
|
||||||
configs, err := alertRepo.AlertConfigList(opts...)
|
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{
|
opts := []repo.DBOption{
|
||||||
alertRepo.WithByTypeNotIn([]string{"common"}),
|
alertRepo.WithByTypeNotIn([]string{"common"}),
|
||||||
repo.WithOrderDesc("created_at"),
|
repo.WithOrderDesc("created_at"),
|
||||||
@@ -506,30 +614,48 @@ func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq, readOnly ...bo
|
|||||||
opts = append(opts, alertRepo.WithByTypeNotIn(req.ExcludeTypes))
|
opts = append(opts, alertRepo.WithByTypeNotIn(req.ExcludeTypes))
|
||||||
}
|
}
|
||||||
total, configs, err := alertRepo.PageAlertConfig(req.Page, req.PageSize, opts...)
|
total, configs, err := alertRepo.PageAlertConfig(req.Page, req.PageSize, opts...)
|
||||||
if err != nil || !isDemoReadOnly(readOnly...) {
|
if err != nil {
|
||||||
return total, configs, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
for i := range configs {
|
if err := exposeCustomAlertConfigSecrets(configs); err != nil {
|
||||||
var value interface{}
|
return 0, nil, err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
return total, configs, nil
|
return total, configs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error {
|
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 {
|
if err := a.validateCommunityAlertConfigType(req.Type); err != nil {
|
||||||
return err
|
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 {
|
if err := a.checkAlertConfigDisplayNameUnique(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -544,7 +670,7 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator stri
|
|||||||
upMap["status"] = req.Status
|
upMap["status"] = req.Status
|
||||||
upMap["config"] = req.Config
|
upMap["config"] = req.Config
|
||||||
upMap["update_user"] = operator
|
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
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -562,6 +688,99 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator stri
|
|||||||
return nil
|
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 {
|
func (a AlertService) checkAlertConfigSMSPhoneUnique(req dto.AlertConfigUpdate) error {
|
||||||
if req.Type != constant.SMSConfig {
|
if req.Type != constant.SMSConfig {
|
||||||
return nil
|
return nil
|
||||||
@@ -586,6 +805,9 @@ func (a AlertService) checkAlertConfigSMSPhoneUnique(req dto.AlertConfigUpdate)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) checkAlertConfigDisplayNameUnique(req dto.AlertConfigUpdate) error {
|
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)
|
displayName := alertConfigDisplayName(req.Type, req.Config)
|
||||||
if displayName == "" {
|
if displayName == "" {
|
||||||
return nil
|
return nil
|
||||||
@@ -609,37 +831,67 @@ func (a AlertService) checkAlertConfigDisplayNameUnique(req dto.AlertConfigUpdat
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) validateCommunityAlertMethod(method string) error {
|
func (a AlertService) validateCommunityAlertMethod(method string) error {
|
||||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
methodTypes, err := a.validateAlertMethodReferences(method)
|
||||||
return nil
|
if err != nil {
|
||||||
}
|
return err
|
||||||
if strings.TrimSpace(method) == "" {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
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, ",") {
|
for _, item := range strings.Split(method, ",") {
|
||||||
item = strings.TrimSpace(item)
|
item = strings.TrimSpace(item)
|
||||||
if item == "" {
|
if item == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
configType := ""
|
||||||
if configID, err := strconv.ParseUint(item, 10, 64); err == nil {
|
if configID, err := strconv.ParseUint(item, 10, 64); err == nil {
|
||||||
config, err := alertRepo.GetConfigById(uint(configID))
|
config, err := alertRepo.GetConfigById(uint(configID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if _, ok := communityAlertMethodTypeNames[config.Type]; ok {
|
configType = config.Type
|
||||||
return buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
} 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
|
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 buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) validateCommunityAlertConfigType(configType string) error {
|
func (a AlertService) validateCommunityAlertConfigType(configType string) error {
|
||||||
|
if configType == constant.Custom {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -651,7 +903,7 @@ func (a AlertService) validateCommunityAlertConfigType(configType string) error
|
|||||||
|
|
||||||
func alertConfigDisplayName(configType, configData string) string {
|
func alertConfigDisplayName(configType, configData string) string {
|
||||||
switch configType {
|
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 {
|
var cfg struct {
|
||||||
DisplayName string `json:"displayName"`
|
DisplayName string `json:"displayName"`
|
||||||
}
|
}
|
||||||
@@ -690,20 +942,24 @@ func (a AlertService) DeleteAlertConfig(id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
|
func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
|
||||||
username := req.UserName
|
emailConfig, err := resolveEmailTestConfig(req)
|
||||||
if username == "" {
|
if err != nil {
|
||||||
username = req.Sender
|
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{
|
cfg := email.SMTPConfig{
|
||||||
Host: req.Host,
|
Host: emailConfig.Host,
|
||||||
Port: req.Port,
|
Port: emailConfig.Port,
|
||||||
Sender: req.Sender,
|
Sender: emailConfig.Sender,
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: req.Password,
|
Password: emailConfig.Password,
|
||||||
From: fmt.Sprintf(`"%s" <%s>`, encodedDisplayName, req.Sender),
|
From: fmt.Sprintf(`"%s" <%s>`, encodedDisplayName, emailConfig.Sender),
|
||||||
Encryption: req.Encryption,
|
Encryption: emailConfig.Encryption,
|
||||||
Recipient: req.Recipient,
|
Recipient: emailConfig.Recipient,
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := email.EmailMessage{
|
msg := email.EmailMessage{
|
||||||
@@ -718,9 +974,94 @@ func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
|
|||||||
return true, nil
|
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 {
|
func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator string) error {
|
||||||
if err := a.validateCommunityAlertMethod(updateAlert.Method); err != nil {
|
var methodTypes []string
|
||||||
return err
|
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{})
|
upMap := make(map[string]interface{})
|
||||||
var newStatus string
|
var newStatus string
|
||||||
@@ -739,6 +1080,23 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
|||||||
alertRepo.WithByType(updateAlert.Type),
|
alertRepo.WithByType(updateAlert.Type),
|
||||||
alertRepo.WithByProject(updateAlert.Project),
|
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 {
|
if alertInfo.ID > 0 {
|
||||||
shouldUpdate := false
|
shouldUpdate := false
|
||||||
@@ -752,6 +1110,9 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
|||||||
if val, ok := upMap["method"]; ok && val != "" && val != alertInfo.Method {
|
if val, ok := upMap["method"]; ok && val != "" && val != alertInfo.Method {
|
||||||
shouldUpdate = true
|
shouldUpdate = true
|
||||||
}
|
}
|
||||||
|
if val, ok := upMap["advanced_params"]; ok && val != alertInfo.AdvancedParams {
|
||||||
|
shouldUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
if shouldUpdate {
|
if shouldUpdate {
|
||||||
if err := alertRepo.Update(
|
if err := alertRepo.Update(
|
||||||
@@ -773,3 +1134,22 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
|||||||
|
|
||||||
return nil
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
@@ -29,9 +30,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ResourceAlertInterval = 30
|
ResourceAlertInterval = 30
|
||||||
CheckIntervalSec = 3
|
CheckIntervalSec = 3
|
||||||
LoadCheckIntervalMin = 5
|
LoadCheckIntervalMin = 5
|
||||||
|
sshIPLoginWindow = 30 * time.Minute
|
||||||
|
sslAutoRenewAlertSkipDays = 31
|
||||||
)
|
)
|
||||||
|
|
||||||
type AlertTaskHelper struct {
|
type AlertTaskHelper struct {
|
||||||
@@ -512,10 +515,32 @@ func loadPanelLogin(alert dto.AlertDTO) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadSSHLogin(alert dto.AlertDTO) {
|
func loadSSHLogin(alert dto.AlertDTO) {
|
||||||
count, isAlert, err := alertUtil.CountRecentFailedSSHLog(alert.Cycle, alert.Count)
|
now := time.Now()
|
||||||
if err != nil {
|
failedWindow := time.Duration(alert.Cycle) * time.Minute
|
||||||
global.LOG.Errorf("Failed to count recent failed ssh login logs: %v", err)
|
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 {
|
if isAlert {
|
||||||
params := []dto.Param{
|
params := []dto.Param{
|
||||||
{
|
{
|
||||||
@@ -531,12 +556,6 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
|||||||
}
|
}
|
||||||
sendAlerts(alert, "sshLogin", strconv.Itoa(count), "sshLogin", params)
|
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 {
|
if len(records) > 0 {
|
||||||
quota := strings.Join(records, "\n")
|
quota := strings.Join(records, "\n")
|
||||||
params := []dto.Param{
|
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 {
|
func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string) []model.LoginLog {
|
||||||
filtered := make([]model.LoginLog, 0, len(records))
|
filtered := make([]model.LoginLog, 0, len(records))
|
||||||
for _, record := range records {
|
for _, record := range records {
|
||||||
@@ -565,20 +597,6 @@ func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string)
|
|||||||
return filtered
|
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 {
|
func isIPInWhitelist(ip string, whitelist []string) bool {
|
||||||
targetIP := net.ParseIP(strings.TrimSpace(ip))
|
targetIP := net.ParseIP(strings.TrimSpace(ip))
|
||||||
if targetIP == nil {
|
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) {
|
func sendAlertsByLegacyMethod(alert dto.AlertDTO, alertType, quota, quotaType string, params []dto.Param, method string) {
|
||||||
typeMap := map[string]string{
|
typeMap := map[string]string{
|
||||||
"mail": constant.Email,
|
"mail": constant.Email,
|
||||||
constant.Bark: constant.Bark,
|
constant.Bark: constant.Bark,
|
||||||
constant.SMS: constant.SMS,
|
constant.SMS: constant.SMS,
|
||||||
|
constant.Custom: constant.Custom,
|
||||||
}
|
}
|
||||||
configType, ok := typeMap[method]
|
configType, ok := typeMap[method]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -785,7 +804,7 @@ func doSendAlert(alert dto.AlertDTO, alertType, quota, quotaType string, params
|
|||||||
}
|
}
|
||||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
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)
|
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, methodStr)
|
||||||
if !isValid {
|
if !isValid {
|
||||||
return
|
return
|
||||||
@@ -798,12 +817,31 @@ func doSendAlert(alert dto.AlertDTO, alertType, quota, quotaType string, params
|
|||||||
}
|
}
|
||||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
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 {
|
if alertErr != nil {
|
||||||
global.LOG.Infof("%s alert webhook %s push faild, err: %v", alertType, methodStr, alertErr)
|
global.LOG.Infof("%s alert webhook %s push faild, err: %v", alertType, methodStr, alertErr)
|
||||||
return
|
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(
|
daysDiff := int(math.Ceil(
|
||||||
ssl.ExpireDate.Sub(currentDate).Hours() / 24,
|
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)
|
daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], ssl.PrimaryDomain)
|
||||||
projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate)
|
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
|
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) {
|
func calculateWebsiteExpiryDays(websites []model.Website, cycle uint) (map[int][]string, map[uint][]time.Time) {
|
||||||
currentDate := time.Now()
|
currentDate := time.Now()
|
||||||
daysDiffMap := make(map[int][]string)
|
daysDiffMap := make(map[int][]string)
|
||||||
@@ -1097,3 +1139,55 @@ func calculateMinutesDifference(newDate time.Time) int {
|
|||||||
minutesDifference := int(now.Sub(newDate).Minutes())
|
minutesDifference := int(now.Sub(newDate).Minutes())
|
||||||
return minutesDifference
|
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 {
|
} else {
|
||||||
s.sendBarkWithConfig(config, quota, params)
|
s.sendBarkWithConfig(config, quota, params)
|
||||||
}
|
}
|
||||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
case constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Custom:
|
||||||
if isResource {
|
if isResource {
|
||||||
s.sendResourceWebhookWithConfig(config, quota, params)
|
s.sendResourceWebhookWithConfig(config, quota, params)
|
||||||
} else {
|
} 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) {
|
func (s *AlertSender) sendByLegacyMethod(method string, quota string, params []dto.Param, isResource bool) {
|
||||||
alertRepo := repo.NewIAlertRepo()
|
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
|
configType := method
|
||||||
if mapped, ok := typeMap[method]; ok {
|
if mapped, ok := typeMap[method]; ok {
|
||||||
configType = mapped
|
configType = mapped
|
||||||
@@ -308,12 +308,31 @@ func (s *AlertSender) sendWebhookWithConfig(config model.AlertConfig, quota stri
|
|||||||
}
|
}
|
||||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
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 {
|
if err != nil {
|
||||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
||||||
return
|
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) {
|
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()
|
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
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)
|
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
||||||
return
|
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) {
|
func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method string) {
|
||||||
|
|||||||
+55
-53
@@ -48,7 +48,7 @@ type IAppService interface {
|
|||||||
PageApp(ctx *gin.Context, req request.AppSearch) (*response.AppRes, error)
|
PageApp(ctx *gin.Context, req request.AppSearch) (*response.AppRes, error)
|
||||||
GetAppTags(ctx *gin.Context) ([]response.TagDTO, error)
|
GetAppTags(ctx *gin.Context) ([]response.TagDTO, error)
|
||||||
GetApp(ctx *gin.Context, key string) (*response.AppDTO, 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)
|
Install(req request.AppInstallCreate, executeScript bool) (*model.AppInstall, error)
|
||||||
SyncAppListFromRemote(taskID string) error
|
SyncAppListFromRemote(taskID string) error
|
||||||
GetAppUpdate() (*response.AppUpdateRes, error)
|
GetAppUpdate() (*response.AppUpdateRes, error)
|
||||||
@@ -223,6 +223,9 @@ func (a AppService) GetAppDetailByKey(appKey, version string) (response.AppDetai
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return appDetailDTO, err
|
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))
|
appDetail, err := appDetailRepo.GetFirst(appDetailRepo.WithAppId(app.ID), appDetailRepo.WithVersion(version))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return appDetailDTO, err
|
return appDetailDTO, err
|
||||||
@@ -231,7 +234,7 @@ func (a AppService) GetAppDetailByKey(appKey, version string) (response.AppDetai
|
|||||||
return appDetailDTO, nil
|
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 (
|
var (
|
||||||
appDetailDTO response.AppDetailDTO
|
appDetailDTO response.AppDetailDTO
|
||||||
opts []repo.DBOption
|
opts []repo.DBOption
|
||||||
@@ -241,58 +244,56 @@ func (a AppService) GetAppDetail(appID uint, version, appType string, readOnly .
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return appDetailDTO, err
|
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.AppDetail = detail
|
||||||
appDetailDTO.Enable = true
|
appDetailDTO.Enable = true
|
||||||
readOnlyMode := isDemoReadOnly(readOnly...)
|
|
||||||
|
|
||||||
if appType == "runtime" {
|
if appType == "runtime" {
|
||||||
app, err := appRepo.GetFirst(repo.WithByID(appID))
|
|
||||||
if err != nil {
|
|
||||||
return appDetailDTO, err
|
|
||||||
}
|
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
|
|
||||||
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
|
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
|
||||||
versionExists := fileOp.Stat(versionPath)
|
if !fileOp.Stat(versionPath) || detail.Update {
|
||||||
if (!versionExists || detail.Update) && !readOnlyMode {
|
|
||||||
if err = downloadApp(app, detail, nil, nil); err != nil && !fileOp.Stat(versionPath) {
|
if err = downloadApp(app, detail, nil, nil); err != nil && !fileOp.Stat(versionPath) {
|
||||||
return appDetailDTO, err
|
return appDetailDTO, err
|
||||||
}
|
}
|
||||||
versionExists = fileOp.Stat(versionPath)
|
|
||||||
}
|
}
|
||||||
if versionExists {
|
switch app.Type {
|
||||||
switch app.Type {
|
case constant.RuntimePHP:
|
||||||
case constant.RuntimePHP:
|
paramsPath := filepath.Join(versionPath, "data.yml")
|
||||||
paramsPath := filepath.Join(versionPath, "data.yml")
|
if !fileOp.Stat(paramsPath) {
|
||||||
if !fileOp.Stat(paramsPath) {
|
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", paramsPath, nil)
|
||||||
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", paramsPath, nil)
|
}
|
||||||
}
|
param, err := fileOp.GetContent(paramsPath)
|
||||||
param, err := fileOp.GetContent(paramsPath)
|
if err != nil {
|
||||||
if err != nil {
|
return appDetailDTO, err
|
||||||
return appDetailDTO, err
|
}
|
||||||
}
|
paramMap := make(map[string]interface{})
|
||||||
paramMap := make(map[string]interface{})
|
if err = yaml.Unmarshal(param, ¶mMap); err != nil {
|
||||||
if err = yaml.Unmarshal(param, ¶mMap); err != nil {
|
return appDetailDTO, err
|
||||||
return appDetailDTO, err
|
}
|
||||||
}
|
appDetailDTO.Params = paramMap["additionalProperties"]
|
||||||
appDetailDTO.Params = paramMap["additionalProperties"]
|
composePath := filepath.Join(versionPath, "docker-compose.yml")
|
||||||
composePath := filepath.Join(versionPath, "docker-compose.yml")
|
if !fileOp.Stat(composePath) {
|
||||||
if !fileOp.Stat(composePath) {
|
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", composePath, nil)
|
||||||
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", composePath, nil)
|
}
|
||||||
}
|
compose, err := fileOp.GetContent(composePath)
|
||||||
compose, err := fileOp.GetContent(composePath)
|
if err != nil {
|
||||||
if err != nil {
|
return appDetailDTO, err
|
||||||
return appDetailDTO, err
|
}
|
||||||
}
|
composeMap := make(map[string]interface{})
|
||||||
composeMap := make(map[string]interface{})
|
if err := yaml.Unmarshal(compose, &composeMap); err != nil {
|
||||||
if err := yaml.Unmarshal(compose, &composeMap); err != nil {
|
return appDetailDTO, err
|
||||||
return appDetailDTO, err
|
}
|
||||||
}
|
if service, ok := composeMap["services"]; ok {
|
||||||
if service, ok := composeMap["services"]; ok {
|
servicesMap := service.(map[string]interface{})
|
||||||
servicesMap := service.(map[string]interface{})
|
for k := range servicesMap {
|
||||||
for k := range servicesMap {
|
appDetailDTO.Image = k
|
||||||
appDetailDTO.Image = k
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,7 +305,7 @@ func (a AppService) GetAppDetail(appID uint, version, appType string, readOnly .
|
|||||||
appDetailDTO.Params = paramMap
|
appDetailDTO.Params = paramMap
|
||||||
}
|
}
|
||||||
|
|
||||||
if appDetailDTO.DockerCompose == "" && !readOnlyMode {
|
if appDetailDTO.DockerCompose == "" {
|
||||||
filename := filepath.Base(appDetailDTO.DownloadUrl)
|
filename := filepath.Base(appDetailDTO.DownloadUrl)
|
||||||
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(appDetailDTO.DownloadUrl, filename), "docker-compose.yml")
|
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(appDetailDTO.DownloadUrl, filename), "docker-compose.yml")
|
||||||
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||||
@@ -324,10 +325,6 @@ func (a AppService) GetAppDetail(appID uint, version, appType string, readOnly .
|
|||||||
|
|
||||||
appDetailDTO.HostMode = isHostModel(appDetailDTO.DockerCompose)
|
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 {
|
if err := checkLimit(app); err != nil {
|
||||||
appDetailDTO.Enable = false
|
appDetailDTO.Enable = false
|
||||||
}
|
}
|
||||||
@@ -379,6 +376,9 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err = checkVllmVersionAccess(app.Key, appDetail.Version); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if DatabaseKeys[app.Key] > 0 {
|
if DatabaseKeys[app.Key] > 0 {
|
||||||
if existDatabases, _ := databaseRepo.GetList(repo.WithByName(req.Name)); len(existDatabases) > 0 {
|
if existDatabases, _ := databaseRepo.GetList(repo.WithByName(req.Name)); len(existDatabases) > 0 {
|
||||||
err = buserr.New("ErrRemoteExist")
|
err = buserr.New("ErrRemoteExist")
|
||||||
@@ -488,15 +488,17 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
|||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
newServiceName := strings.ToLower(appInstall.Name)
|
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]
|
servicesMap[newServiceName] = servicesMap[serviceName]
|
||||||
delete(servicesMap, serviceName)
|
delete(servicesMap, serviceName)
|
||||||
serviceName = newServiceName
|
serviceName = newServiceName
|
||||||
}
|
}
|
||||||
appInstall.ServiceName = serviceName
|
appInstall.ServiceName = serviceName
|
||||||
|
|
||||||
if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
if !req.SkipComposeCommonConfig {
|
||||||
return
|
if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
composeByte []byte
|
composeByte []byte
|
||||||
@@ -564,7 +566,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if executeScript {
|
if executeScript || req.UseLifecycleScripts {
|
||||||
if err = runScript(t, appInstall, "init"); err != nil {
|
if err = runScript(t, appInstall, "init"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -577,7 +579,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = upApp(t, appInstall, req.PullImage); err != nil {
|
if err = upApp(t, appInstall, req.PullImage, req.UseLifecycleScripts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
updateToolApp(appInstall)
|
updateToolApp(appInstall)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type AppIgnoreUpgradeService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IAppIgnoreUpgradeService interface {
|
type IAppIgnoreUpgradeService interface {
|
||||||
List(readOnly ...bool) ([]response.AppIgnoreUpgradeDTO, error)
|
List() ([]response.AppIgnoreUpgradeDTO, error)
|
||||||
CreateAppIgnore(req request.AppIgnoreUpgradeReq) error
|
CreateAppIgnore(req request.AppIgnoreUpgradeReq) error
|
||||||
Delete(req request.ReqWithID) error
|
Delete(req request.ReqWithID) error
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ func NewIAppIgnoreUpgradeService() IAppIgnoreUpgradeService {
|
|||||||
return AppIgnoreUpgradeService{}
|
return AppIgnoreUpgradeService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AppIgnoreUpgradeService) List(readOnly ...bool) ([]response.AppIgnoreUpgradeDTO, error) {
|
func (a AppIgnoreUpgradeService) List() ([]response.AppIgnoreUpgradeDTO, error) {
|
||||||
var res []response.AppIgnoreUpgradeDTO
|
var res []response.AppIgnoreUpgradeDTO
|
||||||
ignores, err := appIgnoreUpgradeRepo.List()
|
ignores, err := appIgnoreUpgradeRepo.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -37,18 +37,14 @@ func (a AppIgnoreUpgradeService) List(readOnly ...bool) ([]response.AppIgnoreUpg
|
|||||||
}
|
}
|
||||||
app, err := appRepo.GetFirst(repo.WithByID(ignore.AppID))
|
app, err := appRepo.GetFirst(repo.WithByID(ignore.AppID))
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
if !isDemoReadOnly(readOnly...) {
|
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dto.Name = app.Name
|
dto.Name = app.Name
|
||||||
if ignore.Scope == "version" {
|
if ignore.Scope == "version" {
|
||||||
appDetail, err := appDetailRepo.GetFirst(repo.WithByID(ignore.AppDetailID))
|
appDetail, err := appDetailRepo.GetFirst(repo.WithByID(ignore.AppDetailID))
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
if !isDemoReadOnly(readOnly...) {
|
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dto.Version = appDetail.Version
|
dto.Version = appDetail.Version
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -13,12 +14,14 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
"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/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
@@ -42,21 +45,21 @@ type IAppInstallService interface {
|
|||||||
Page(req request.AppInstalledSearch) (int64, []response.AppInstallDTO, error)
|
Page(req request.AppInstalledSearch) (int64, []response.AppInstallDTO, error)
|
||||||
CheckExist(req request.AppInstalledInfo) (*response.AppInstalledCheck, error)
|
CheckExist(req request.AppInstalledInfo) (*response.AppInstalledCheck, error)
|
||||||
LoadPort(req dto.OperationWithNameAndType) (int64, 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)
|
SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstallDTO, error)
|
||||||
Operate(req request.AppInstalledOperate) error
|
Operate(req request.AppInstalledOperate) error
|
||||||
Update(req request.AppInstalledUpdate) error
|
Update(req request.AppInstalledUpdate) error
|
||||||
SyncAll(systemInit bool) error
|
SyncAll(systemInit bool) error
|
||||||
GetServices(key string) ([]response.AppService, error)
|
GetServices(key string) ([]response.AppService, error)
|
||||||
GetUpdateVersions(req request.AppUpdateVersion) ([]dto.AppVersion, 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
|
ChangeAppPort(req request.PortUpdate) error
|
||||||
GetDefaultConfigByKey(key, name string) (string, 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
|
UpdateAppConfig(req request.AppConfigUpdate) error
|
||||||
GetInstallList() ([]dto.AppInstallInfo, error)
|
GetInstallList() ([]dto.AppInstallInfo, error)
|
||||||
GetAppInstallInfo(appInstallID uint, readOnly ...bool) (*response.AppInstallInfo, error)
|
GetAppInstallInfo(appInstallID uint) (*response.AppInstallInfo, error)
|
||||||
UpdateSort(req request.AppInstallSort) 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 {
|
if req.Update {
|
||||||
total = int64(len(installDTOs))
|
total = int64(len(installDTOs))
|
||||||
}
|
}
|
||||||
@@ -151,10 +154,8 @@ func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.
|
|||||||
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
|
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
if !req.ReadOnly {
|
if err = syncAppInstallStatus(&appInstall, false); err != nil {
|
||||||
if err = syncAppInstallStatus(&appInstall, false); err != nil {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.ContainerName = appInstall.ContainerName
|
res.ContainerName = appInstall.ContainerName
|
||||||
@@ -184,7 +185,7 @@ func (a *AppInstallService) LoadPort(req dto.OperationWithNameAndType) (int64, e
|
|||||||
return app.Port, nil
|
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
|
var data response.DatabaseConn
|
||||||
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
|
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -193,9 +194,6 @@ func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType, readO
|
|||||||
data.Status = app.Status
|
data.Status = app.Status
|
||||||
data.Username = app.UserName
|
data.Username = app.UserName
|
||||||
data.Password = app.Password
|
data.Password = app.Password
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
data.Password = ""
|
|
||||||
}
|
|
||||||
data.ServiceName = app.ServiceName
|
data.ServiceName = app.ServiceName
|
||||||
data.Port = app.Port
|
data.Port = app.Port
|
||||||
data.ContainerName = app.ContainerName
|
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 {
|
func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||||
@@ -257,6 +255,9 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
|||||||
return buserr.New("ErrInstallDirNotFound")
|
return buserr.New("ErrInstallDirNotFound")
|
||||||
}
|
}
|
||||||
dockerComposePath := install.GetComposePath()
|
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 {
|
switch req.Operate {
|
||||||
case constant.Rebuild:
|
case constant.Rebuild:
|
||||||
return rebuildApp(install)
|
return rebuildApp(install)
|
||||||
@@ -280,12 +281,13 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
|||||||
return syncAppInstallStatus(&install, false)
|
return syncAppInstallStatus(&install, false)
|
||||||
case constant.Delete:
|
case constant.Delete:
|
||||||
deleteReq := request.AppInstallDelete{
|
deleteReq := request.AppInstallDelete{
|
||||||
Install: install,
|
Install: install,
|
||||||
DeleteBackup: req.DeleteBackup,
|
DeleteBackup: req.DeleteBackup,
|
||||||
ForceDelete: req.ForceDelete,
|
ForceDelete: req.ForceDelete,
|
||||||
DeleteDB: req.DeleteDB,
|
DeleteDB: req.DeleteDB,
|
||||||
DeleteImage: req.DeleteImage,
|
DeleteImage: req.DeleteImage,
|
||||||
TaskID: req.TaskID,
|
TaskID: req.TaskID,
|
||||||
|
UseLifecycleScripts: req.UseLifecycleScripts,
|
||||||
}
|
}
|
||||||
if err = deleteAppInstall(deleteReq); err != nil && !req.ForceDelete {
|
if err = deleteAppInstall(deleteReq); err != nil && !req.ForceDelete {
|
||||||
return err
|
return err
|
||||||
@@ -317,6 +319,70 @@ 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 {
|
||||||
|
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 {
|
func (a *AppInstallService) UpdateAppConfig(req request.AppConfigUpdate) error {
|
||||||
installed, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
|
installed, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -379,8 +445,10 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
if !req.SkipComposeCommonConfig {
|
||||||
return err
|
if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composeByte, err := yaml.Marshal(composeMap)
|
composeByte, err := yaml.Marshal(composeMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -413,7 +481,7 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
backupEnvMaps := oldEnvMaps
|
backupEnvMaps := maps.Clone(oldEnvMaps)
|
||||||
handleMap(req.Params, oldEnvMaps)
|
handleMap(req.Params, oldEnvMaps)
|
||||||
paramByte, err := json.Marshal(oldEnvMaps)
|
paramByte, err := json.Marshal(oldEnvMaps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -425,13 +493,32 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
|||||||
}
|
}
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(installed.DockerCompose), constant.DirPerm)
|
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(installed.DockerCompose), constant.DirPerm)
|
||||||
if err := rebuildApp(installed); err != nil {
|
restoreConfig := func(operationErr error) {
|
||||||
_ = env.Write(backupEnvMaps, envPath)
|
_ = env.Write(backupEnvMaps, envPath)
|
||||||
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(backupDockerCompose), constant.DirPerm)
|
_ = 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
|
return err
|
||||||
}
|
}
|
||||||
installed.Status = constant.StatusRunning
|
if !req.UseLifecycleScripts {
|
||||||
_ = appInstallRepo.Save(context.Background(), &installed)
|
installed.Status = constant.StatusRunning
|
||||||
|
_ = appInstallRepo.Save(context.Background(), &installed)
|
||||||
|
}
|
||||||
|
|
||||||
proxyChanged := hasAppInstallProxyPassChanged(&oldInstalled, &installed)
|
proxyChanged := hasAppInstallProxyPassChanged(&oldInstalled, &installed)
|
||||||
currentProxy, currentProxyErr := getAppInstallProxyPass(&installed)
|
currentProxy, currentProxyErr := getAppInstallProxyPass(&installed)
|
||||||
@@ -588,6 +675,9 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
|
|||||||
return versions, err
|
return versions, err
|
||||||
}
|
}
|
||||||
for _, detail := range details {
|
for _, detail := range details {
|
||||||
|
if !canAccessVllmVersion(app.Key, detail.Version) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(detail.ID), appIgnoreUpgradeRepo.WithScope("version"))
|
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(detail.ID), appIgnoreUpgradeRepo.WithScope("version"))
|
||||||
if len(ignores) > 0 {
|
if len(ignores) > 0 {
|
||||||
continue
|
continue
|
||||||
@@ -669,7 +759,7 @@ func (a *AppInstallService) ChangeAppPort(req request.PortUpdate) error {
|
|||||||
return nil
|
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
|
var res []dto.AppResource
|
||||||
appInstall, err := appInstallRepo.GetFirst(repo.WithByID(installID))
|
appInstall, err := appInstallRepo.GetFirst(repo.WithByID(installID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -690,7 +780,7 @@ func (a *AppInstallService) DeleteCheck(installID uint, readOnly ...bool) ([]dto
|
|||||||
Type: "app",
|
Type: "app",
|
||||||
Name: linkInstall.Name,
|
Name: linkInstall.Name,
|
||||||
})
|
})
|
||||||
} else if !isDemoReadOnly(readOnly...) {
|
} else {
|
||||||
_ = appInstallResourceRepo.DeleteBy(context.Background(), appInstallResourceRepo.WithAppInstallId(resource.AppInstallId))
|
_ = appInstallResourceRepo.DeleteBy(context.Background(), appInstallResourceRepo.WithAppInstallId(resource.AppInstallId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -728,7 +818,7 @@ func (a *AppInstallService) GetDefaultConfigByKey(key, name string) (string, err
|
|||||||
return string(contentByte), nil
|
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 (
|
var (
|
||||||
params []response.AppParam
|
params []response.AppParam
|
||||||
appForm dto.AppForm
|
appForm dto.AppForm
|
||||||
@@ -823,14 +913,8 @@ func (a *AppInstallService) GetParams(id uint, readOnly ...bool) (*response.AppC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readOnlyMode := isDemoReadOnly(readOnly...)
|
|
||||||
if readOnlyMode {
|
|
||||||
redactSensitiveAppParams(params)
|
|
||||||
}
|
|
||||||
config := getAppCommonConfig(envs)
|
config := getAppCommonConfig(envs)
|
||||||
if !readOnlyMode {
|
config.DockerCompose = install.DockerCompose
|
||||||
config.DockerCompose = install.DockerCompose
|
|
||||||
}
|
|
||||||
res.Params = params
|
res.Params = params
|
||||||
if config.ContainerName == "" {
|
if config.ContainerName == "" {
|
||||||
config.ContainerName = install.ContainerName
|
config.ContainerName = install.ContainerName
|
||||||
@@ -840,16 +924,16 @@ func (a *AppInstallService) GetParams(id uint, readOnly ...bool) (*response.AppC
|
|||||||
res.RestartPolicy = getRestartPolicy(install.DockerCompose)
|
res.RestartPolicy = getRestartPolicy(install.DockerCompose)
|
||||||
res.WebUI = install.WebUI
|
res.WebUI = install.WebUI
|
||||||
res.Type = install.App.Type
|
res.Type = install.App.Type
|
||||||
if !readOnlyMode {
|
if rawCompose, err := getUpgradeCompose(install, detail); err == nil {
|
||||||
if rawCompose, err := getUpgradeCompose(install, detail); err == nil {
|
res.RawCompose = rawCompose
|
||||||
res.RawCompose = rawCompose
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncAppInstallStatus(appInstall *model.AppInstall, force bool) error {
|
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 {
|
switch appInstall.Status {
|
||||||
|
case constant.StatusInstalling, constant.StatusRebuilding, constant.StatusUpgrading, constant.StatusUninstalling,
|
||||||
|
constant.StatusStarting, constant.StatusRestarting, constant.StatusWaiting:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cli, err := docker.NewClient()
|
cli, err := docker.NewClient()
|
||||||
@@ -971,25 +1055,20 @@ func updateInstallInfoInDB(appKey, appName, param string, value interface{}) err
|
|||||||
return nil
|
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))
|
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(installID))
|
||||||
if appInstall.ID == 0 {
|
if appInstall.ID == 0 {
|
||||||
return &response.AppInstallInfo{
|
return &response.AppInstallInfo{
|
||||||
Status: constant.StatusDeleted,
|
Status: constant.StatusDeleted,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
if !isDemoReadOnly(readOnly...) {
|
_ = syncAppInstallStatus(&appInstall, false)
|
||||||
_ = syncAppInstallStatus(&appInstall, false)
|
|
||||||
}
|
|
||||||
appInstall, _ = appInstallRepo.GetFirst(repo.WithByID(installID))
|
appInstall, _ = appInstallRepo.GetFirst(repo.WithByID(installID))
|
||||||
var envMap map[string]interface{}
|
var envMap map[string]interface{}
|
||||||
err := json.Unmarshal([]byte(appInstall.Env), &envMap)
|
err := json.Unmarshal([]byte(appInstall.Env), &envMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
redactSensitiveValues(envMap)
|
|
||||||
}
|
|
||||||
res := &response.AppInstallInfo{
|
res := &response.AppInstallInfo{
|
||||||
ID: appInstall.ID,
|
ID: appInstall.ID,
|
||||||
Name: appInstall.Name,
|
Name: appInstall.Name,
|
||||||
|
|||||||
@@ -40,14 +40,13 @@ const (
|
|||||||
appUpgradeDown
|
appUpgradeDown
|
||||||
appUpgradeMutated
|
appUpgradeMutated
|
||||||
appUpgradeStarted
|
appUpgradeStarted
|
||||||
appUpgradeReady
|
|
||||||
appUpgradeCommitted
|
appUpgradeCommitted
|
||||||
)
|
)
|
||||||
|
|
||||||
const composeServiceLabel = "com.docker.compose.service"
|
|
||||||
|
|
||||||
var appUpgradeLocks sync.Map
|
var appUpgradeLocks sync.Map
|
||||||
|
|
||||||
|
const composeServiceLabel = "com.docker.compose.service"
|
||||||
|
|
||||||
type appUpgradeSnapshot interface {
|
type appUpgradeSnapshot interface {
|
||||||
Restore() error
|
Restore() error
|
||||||
Cleanup()
|
Cleanup()
|
||||||
@@ -107,6 +106,9 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)) {
|
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")
|
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)
|
t.LogSuccess(logStr)
|
||||||
u.phase = appUpgradeStarted
|
u.phase = appUpgradeStarted
|
||||||
|
|
||||||
t.LogStart(i18n.GetMsgByKey("UpgradeWaitReady"))
|
containerNames, discoverErr := discoverUpgradeContainerNames(u.candidate, u.envContent)
|
||||||
containerNames, err := waitAppContainersReady(context.Background(), u.candidate)
|
if discoverErr != nil {
|
||||||
if err != nil {
|
t.Logf("WARNING: discover upgraded application containers failed: %v", discoverErr)
|
||||||
t.LogFailedWithErr(i18n.GetMsgByKey("UpgradeWaitReady"), err)
|
} else if len(containerNames) > 0 {
|
||||||
return err
|
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.Status = constant.StatusRunning
|
||||||
u.candidate.Message = ""
|
u.candidate.Message = ""
|
||||||
|
|
||||||
@@ -459,9 +460,21 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
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 {
|
} else if err = appInstallRepo.Save(context.Background(), &u.candidate); err != nil {
|
||||||
return err
|
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.phase = appUpgradeCommitted
|
||||||
u.deleteOldImages(t)
|
u.deleteOldImages(t)
|
||||||
return nil
|
return nil
|
||||||
@@ -581,9 +594,6 @@ func (u *appUpgradeContext) rollback(t *task.Task) (rollbackErr error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *appUpgradeContext) finishRollback() error {
|
func (u *appUpgradeContext) finishRollback() error {
|
||||||
if _, err := waitAppContainersReady(context.Background(), u.original); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
restored := u.original
|
restored := u.original
|
||||||
if err := appInstallRepo.Save(context.Background(), &restored); err != nil {
|
if err := appInstallRepo.Save(context.Background(), &restored); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -715,6 +725,20 @@ func renderUpgradeEnv(install *model.AppInstall, original []byte) ([]byte, error
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
handleMap(envs, params)
|
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 {
|
if install.App.Key == constant.AppOpenresty {
|
||||||
for _, key := range []string{"CONTAINER_PACKAGE_URL", "RESTY_ADD_PACKAGE_BUILDDEPS", "RESTY_CONFIG_OPTIONS_MORE"} {
|
for _, key := range []string{"CONTAINER_PACKAGE_URL", "RESTY_ADD_PACKAGE_BUILDDEPS", "RESTY_CONFIG_OPTIONS_MORE"} {
|
||||||
if value, ok := originalEnv[key]; ok {
|
if value, ok := originalEnv[key]; ok {
|
||||||
@@ -857,28 +881,7 @@ func (s *upgradeFileSnapshot) Cleanup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type appContainerReadinessClient interface {
|
func discoverUpgradeContainerNames(install model.AppInstall, envContent []byte) ([]string, error) {
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
project, err := docker.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), envContent, false)
|
project, err := docker.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), envContent, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -892,36 +895,24 @@ func waitAppContainersReadyWithClient(ctx context.Context, client appContainerRe
|
|||||||
if len(expectedServices) == 0 {
|
if len(expectedServices) == 0 {
|
||||||
return strings.Split(install.ContainerName, ","), nil
|
return strings.Split(install.ContainerName, ","), nil
|
||||||
}
|
}
|
||||||
options := container.ListOptions{
|
client, err := docker.NewDockerClient()
|
||||||
All: true,
|
if err != nil {
|
||||||
Filters: filters.NewArgs(
|
return nil, err
|
||||||
filters.Arg("label", composeWorkdirLabel+"="+install.GetPath()),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
foundServices := make(map[string]bool, len(expectedServices))
|
|
||||||
containerNames := make([]string, 0, len(containers))
|
containerNames := make([]string, 0, len(containers))
|
||||||
for _, item := range containers {
|
for _, item := range containers {
|
||||||
serviceName := item.Labels[composeServiceLabel]
|
if _, ok := expectedServices[item.Labels[composeServiceLabel]]; ok && len(item.Names) > 0 {
|
||||||
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 {
|
|
||||||
containerNames = append(containerNames, strings.TrimPrefix(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)
|
sort.Strings(containerNames)
|
||||||
return containerNames, nil
|
return containerNames, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,62 +53,6 @@ var (
|
|||||||
Delete DatabaseOp = "delete"
|
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) {
|
func checkPort(key string, params map[string]interface{}) (int, error) {
|
||||||
port, ok := params[key]
|
port, ok := params[key]
|
||||||
if ok {
|
if ok {
|
||||||
@@ -408,15 +352,33 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
|||||||
if dir != nil {
|
if dir != nil {
|
||||||
logStr := i18n.GetMsgByKey("Stop") + i18n.GetMsgByKey("App")
|
logStr := i18n.GetMsgByKey("Stop") + i18n.GetMsgByKey("App")
|
||||||
t.Log(logStr)
|
t.Log(logStr)
|
||||||
|
cleanupFailed := false
|
||||||
|
|
||||||
out, err := compose.Down(install.GetComposePath())
|
if deleteReq.UseLifecycleScripts {
|
||||||
if err != nil && !deleteReq.ForceDelete {
|
if scriptErr := runScript(t, &install, "uninstall"); scriptErr != nil {
|
||||||
return handleErr(install, err, out)
|
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 !cleanupFailed {
|
||||||
if err = runScript(t, &install, "uninstall"); err != nil {
|
t.LogSuccess(logStr)
|
||||||
_, _ = compose.Up(install.GetComposePath())
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if deleteReq.DeleteImage {
|
if deleteReq.DeleteImage {
|
||||||
content, err := op.GetContent(install.GetEnvPath())
|
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)
|
uninstallTask.AddSubTask(task.GetTaskName(install.Name, task.TaskUninstall, task.TaskScopeApp), uninstall, nil)
|
||||||
go func() {
|
go func() {
|
||||||
if err := uninstallTask.Execute(); err != nil && !deleteReq.ForceDelete {
|
if err := uninstallTask.Execute(); err != nil {
|
||||||
install.Status = constant.StatusError
|
install.Status = constant.StatusError
|
||||||
|
install.Message = err.Error()
|
||||||
_ = appInstallRepo.Save(context.Background(), &install)
|
_ = 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")
|
scriptPath = path.Join(workDir, "scripts", "upgrade.sh")
|
||||||
case "uninstall":
|
case "uninstall":
|
||||||
scriptPath = path.Join(workDir, "scripts", "uninstall.sh")
|
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()
|
fileOp := files.NewFileOp()
|
||||||
if !fileOp.Stat(scriptPath) {
|
if !fileOp.Stat(scriptPath) {
|
||||||
@@ -1064,7 +1033,11 @@ func runScript(task *task.Task, appInstall *model.AppInstall, operate string) er
|
|||||||
logStr := i18n.GetWithName("ExecShell", operate)
|
logStr := i18n.GetWithName("ExecShell", operate)
|
||||||
task.LogStart(logStr)
|
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 {
|
if err := cmdMgr.Run("bash", scriptPath); err != nil {
|
||||||
task.LogFailedWithErr(logStr, err)
|
task.LogFailedWithErr(logStr, err)
|
||||||
return err
|
return err
|
||||||
@@ -1099,12 +1072,15 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
|
|||||||
return false, nil
|
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) {
|
upProject := func(appInstall *model.AppInstall) (err error) {
|
||||||
var (
|
var (
|
||||||
out string
|
out string
|
||||||
errMsg string
|
errMsg string
|
||||||
)
|
)
|
||||||
|
if useLifecycleScripts {
|
||||||
|
return runScript(task, appInstall, "start")
|
||||||
|
}
|
||||||
if pullImages && appInstall.App.Type != "php" {
|
if pullImages && appInstall.App.Type != "php" {
|
||||||
envByte, err := files.NewFileOp().GetContent(appInstall.GetEnvPath())
|
envByte, err := files.NewFileOp().GetContent(appInstall.GetEnvPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1431,7 +1407,8 @@ func handleErr(install model.AppInstall, err error, out string) error {
|
|||||||
|
|
||||||
func doNotNeedSync(installed model.AppInstall) bool {
|
func doNotNeedSync(installed model.AppInstall) bool {
|
||||||
return installed.Status == constant.StatusInstalling || installed.Status == constant.StatusRebuilding || installed.Status == constant.StatusUpgrading ||
|
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
|
installed.Status == constant.StatusSyncing || installed.Status == constant.StatusUninstalling || installed.Status == constant.StatusInstallErr ||
|
||||||
|
installed.Status == constant.StatusStarting || installed.Status == constant.StatusRestarting || installed.Status == constant.StatusWaiting
|
||||||
}
|
}
|
||||||
|
|
||||||
func synAppInstall(containers map[string]container.Summary, appInstall *model.AppInstall, force bool) {
|
func synAppInstall(containers map[string]container.Summary, appInstall *model.AppInstall, force bool) {
|
||||||
@@ -1443,9 +1420,7 @@ func synAppInstall(containers map[string]container.Summary, appInstall *model.Ap
|
|||||||
}
|
}
|
||||||
appInstall.Status = constant.StatusError
|
appInstall.Status = constant.StatusError
|
||||||
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
|
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
|
return
|
||||||
}
|
}
|
||||||
notFoundNames := make([]string, 0)
|
notFoundNames := make([]string, 0)
|
||||||
@@ -1504,12 +1479,10 @@ func synAppInstall(containers map[string]container.Summary, appInstall *model.Ap
|
|||||||
appInstall.Message = msg
|
appInstall.Message = msg
|
||||||
appInstall.Status = constant.StatusUnHealthy
|
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 (
|
var (
|
||||||
res []response.AppInstallDTO
|
res []response.AppInstallDTO
|
||||||
containersMap map[string]container.Summary
|
containersMap map[string]container.Summary
|
||||||
@@ -1540,9 +1513,6 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
|||||||
resourceKeys := getAppInstallResourceKeys(installed.ID)
|
resourceKeys := getAppInstallResourceKeys(installed.ID)
|
||||||
envMap := make(map[string]interface{})
|
envMap := make(map[string]interface{})
|
||||||
_ = json.Unmarshal([]byte(installed.Env), &envMap)
|
_ = json.Unmarshal([]byte(installed.Env), &envMap)
|
||||||
if isDemoReadOnly(readOnly) {
|
|
||||||
redactSensitiveValues(envMap)
|
|
||||||
}
|
|
||||||
installDTO := response.AppInstallDTO{
|
installDTO := response.AppInstallDTO{
|
||||||
ID: installed.ID,
|
ID: installed.ID,
|
||||||
Name: installed.Name,
|
Name: installed.Name,
|
||||||
@@ -1587,9 +1557,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isDemoReadOnly(readOnly) {
|
installDTO.DockerCompose = installed.DockerCompose
|
||||||
installDTO.DockerCompose = installed.DockerCompose
|
|
||||||
}
|
|
||||||
installDTO.IsEdit = isEditCompose(installed)
|
installDTO.IsEdit = isEditCompose(installed)
|
||||||
|
|
||||||
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
|
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
|
||||||
@@ -2294,6 +2262,9 @@ func getAppVersions(key string, details []model.AppDetail) []string {
|
|||||||
hasLatest := false
|
hasLatest := false
|
||||||
latestVersion := ""
|
latestVersion := ""
|
||||||
for _, detail := range details {
|
for _, detail := range details {
|
||||||
|
if !canAccessVllmVersion(key, detail.Version) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if key != "mssql" && strings.Contains(detail.Version, "latest") {
|
if key != "mssql" && strings.Contains(detail.Version, "latest") {
|
||||||
hasLatest = true
|
hasLatest = true
|
||||||
latestVersion = detail.Version
|
latestVersion = detail.Version
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type IBackupService interface {
|
|||||||
CheckUsed(name string, isPublic bool) error
|
CheckUsed(name string, isPublic bool) error
|
||||||
|
|
||||||
LoadBackupOptions() ([]dto.BackupOption, 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
|
Create(backupDto dto.BackupOperate) error
|
||||||
CheckConn(req dto.BackupOperate) dto.BackupCheckRes
|
CheckConn(req dto.BackupOperate) dto.BackupCheckRes
|
||||||
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
|
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
|
||||||
@@ -80,7 +80,7 @@ func (u *BackupService) GetLocalDir() (string, error) {
|
|||||||
return account.BackupPath, nil
|
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")}
|
options := []repo.DBOption{repo.WithOrderDesc("created_at")}
|
||||||
if len(req.Type) != 0 {
|
if len(req.Type) != 0 {
|
||||||
options = append(options, repo.WithByType(req.Type))
|
options = append(options, repo.WithByType(req.Type))
|
||||||
@@ -128,36 +128,11 @@ func (u *BackupService) SearchWithPage(req dto.SearchPageWithType, readOnly ...b
|
|||||||
itemVars, _ := json.Marshal(varMap)
|
itemVars, _ := json.Marshal(varMap)
|
||||||
item.Vars = string(itemVars)
|
item.Vars = string(itemVars)
|
||||||
}
|
}
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
item.AccessKey = ""
|
|
||||||
item.Credential = ""
|
|
||||||
item.Vars = sanitizeBackupVars(item.Vars)
|
|
||||||
}
|
|
||||||
data = append(data, item)
|
data = append(data, item)
|
||||||
}
|
}
|
||||||
return count, data, nil
|
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 {
|
func (u *BackupService) CheckConn(req dto.BackupOperate) dto.BackupCheckRes {
|
||||||
var res dto.BackupCheckRes
|
var res dto.BackupCheckRes
|
||||||
var backup model.BackupAccount
|
var backup model.BackupAccount
|
||||||
|
|||||||
@@ -582,6 +582,10 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
|||||||
if config.Image == "" {
|
if config.Image == "" {
|
||||||
return fmt.Errorf("container image not found in backup file")
|
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 !checkImageExist(recoverCtx.client, config.Image) {
|
||||||
if err := pullImages(taskItem, recoverCtx.client, config.Image); err != nil {
|
if err := pullImages(taskItem, recoverCtx.client, config.Image); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -596,7 +600,7 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
|||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -604,7 +608,7 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
|||||||
return nil
|
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 {
|
if cli.NewVersionError(ctx, "1.44", "specify mac-address per network") != nil {
|
||||||
removeEndpointMacAddresses(primary, extras)
|
removeEndpointMacAddresses(primary, extras)
|
||||||
}
|
}
|
||||||
@@ -619,11 +623,14 @@ func normalizeContainerEndpointSettings(ctx context.Context, cli *client.Client,
|
|||||||
}
|
}
|
||||||
info, err := cli.NetworkInspect(ctx, netName, network.InspectOptions{})
|
info, err := cli.NetworkInspect(ctx, netName, network.InspectOptions{})
|
||||||
if err != nil {
|
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) {
|
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 {
|
if endpoint == nil || endpoint.IPAMConfig == nil {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
if isDefaultBridgeNetwork(netName, info) {
|
ipam := endpoint.IPAMConfig
|
||||||
endpoint.IPAMConfig = nil
|
if err := ipam.Validate(); err != nil {
|
||||||
return
|
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) {
|
if ipam.IPv4Address != "" && !networkSupportsStaticIP(info, ipam.IPv4Address, false) {
|
||||||
endpoint.IPAMConfig.IPv4Address = ""
|
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) {
|
if ipam.IPv6Address != "" && !networkSupportsStaticIP(info, ipam.IPv6Address, true) {
|
||||||
endpoint.IPAMConfig.IPv6Address = ""
|
return fmt.Errorf("static IPv6 address %s is not in a configured subnet of network %s", ipam.IPv6Address, netName)
|
||||||
}
|
|
||||||
if endpoint.IPAMConfig.IPv4Address == "" && endpoint.IPAMConfig.IPv6Address == "" && len(endpoint.IPAMConfig.LinkLocalIPs) == 0 {
|
|
||||||
endpoint.IPAMConfig = nil
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isDefaultBridgeNetwork(netName string, info network.Inspect) bool {
|
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 {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
addr = addr.Unmap()
|
||||||
if addr.Is6() != isIPv6 {
|
if addr.Is6() != isIPv6 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -813,11 +825,6 @@ func buildContainerRecoverNetworkConfig(networkSettings *container.NetworkSettin
|
|||||||
IPv6Address: endpoint.IPAMConfig.IPv6Address,
|
IPv6Address: endpoint.IPAMConfig.IPv6Address,
|
||||||
LinkLocalIPs: append([]string(nil), endpoint.IPAMConfig.LinkLocalIPs...),
|
LinkLocalIPs: append([]string(nil), endpoint.IPAMConfig.LinkLocalIPs...),
|
||||||
}
|
}
|
||||||
} else if name != "bridge" && (endpoint.IPAddress != "" || endpoint.GlobalIPv6Address != "") {
|
|
||||||
endpointSetting.IPAMConfig = &network.EndpointIPAMConfig{
|
|
||||||
IPv4Address: endpoint.IPAddress,
|
|
||||||
IPv6Address: endpoint.GlobalIPv6Address,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if name == primaryName {
|
if name == primaryName {
|
||||||
config.EndpointsConfig[name] = endpointSetting
|
config.EndpointsConfig[name] = endpointSetting
|
||||||
@@ -831,39 +838,6 @@ func buildContainerRecoverNetworkConfig(networkSettings *container.NetworkSettin
|
|||||||
return config, extraNetworks
|
return config, extraNetworks
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsupportedUserSpecifiedIPAddress = "user specified IP address is supported only when connecting to networks with user configured subnets"
|
|
||||||
|
|
||||||
func clearUnsupportedDynamicEndpointIPAM(err error, endpoints map[string]*network.EndpointSettings, networkSettings *container.NetworkSettings) bool {
|
|
||||||
if err == nil || !strings.Contains(err.Error(), unsupportedUserSpecifiedIPAddress) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for name, endpoint := range endpoints {
|
|
||||||
if !isDynamicContainerNetwork(networkSettings, name) || endpoint == nil || endpoint.IPAMConfig == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(err.Error(), "network "+name+":") {
|
|
||||||
endpoint.IPAMConfig = nil
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cleared := false
|
|
||||||
for name, endpoint := range endpoints {
|
|
||||||
if isDynamicContainerNetwork(networkSettings, name) && endpoint != nil && endpoint.IPAMConfig != nil {
|
|
||||||
endpoint.IPAMConfig = nil
|
|
||||||
cleared = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cleared
|
|
||||||
}
|
|
||||||
|
|
||||||
func isDynamicContainerNetwork(networkSettings *container.NetworkSettings, name string) bool {
|
|
||||||
if networkSettings == nil || name == "bridge" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
endpoint := networkSettings.Networks[name]
|
|
||||||
return endpoint != nil && endpoint.IPAMConfig == nil && (endpoint.IPAddress != "" || endpoint.GlobalIPv6Address != "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloneContainerConfig(config *container.Config) *container.Config {
|
func cloneContainerConfig(config *container.Config) *container.Config {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return &container.Config{}
|
return &container.Config{}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type ClamService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IClamService interface {
|
type IClamService interface {
|
||||||
LoadBaseInfo(readOnly bool) (dto.ClamBaseInfo, error)
|
LoadBaseInfo() (dto.ClamBaseInfo, error)
|
||||||
Operate(operate string) error
|
Operate(operate string) error
|
||||||
SearchWithPage(search dto.SearchClamWithPage) (int64, interface{}, error)
|
SearchWithPage(search dto.SearchClamWithPage) (int64, interface{}, error)
|
||||||
Create(req dto.ClamCreate, operator string) error
|
Create(req dto.ClamCreate, operator string) error
|
||||||
@@ -53,7 +53,7 @@ func NewIClamService() IClamService {
|
|||||||
return &ClamService{}
|
return &ClamService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClamService) LoadBaseInfo(readOnly bool) (dto.ClamBaseInfo, error) {
|
func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||||
var baseInfo dto.ClamBaseInfo
|
var baseInfo dto.ClamBaseInfo
|
||||||
baseInfo.Version = "-"
|
baseInfo.Version = "-"
|
||||||
baseInfo.FreshVersion = "-"
|
baseInfo.FreshVersion = "-"
|
||||||
@@ -96,7 +96,7 @@ func (c *ClamService) LoadBaseInfo(readOnly bool) (dto.ClamBaseInfo, error) {
|
|||||||
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
|
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !readOnly && !global.CONF.Base.IsDemo {
|
} else {
|
||||||
_ = clam.CheckWithStopAll(false, clamRepo)
|
_ = clam.CheckWithStopAll(false, clamRepo)
|
||||||
}
|
}
|
||||||
if baseInfo.FreshIsActive {
|
if baseInfo.FreshIsActive {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
type ComposeTemplateService struct{}
|
type ComposeTemplateService struct{}
|
||||||
|
|
||||||
type IComposeTemplateService interface {
|
type IComposeTemplateService interface {
|
||||||
List(readOnly ...bool) ([]dto.ComposeTemplateInfo, error)
|
List() ([]dto.ComposeTemplateInfo, error)
|
||||||
SearchWithPage(search dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error)
|
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
||||||
Create(req dto.ComposeTemplateCreate) error
|
Create(req dto.ComposeTemplateCreate) error
|
||||||
Update(id uint, upMap map[string]interface{}) error
|
Update(id uint, upMap map[string]interface{}) error
|
||||||
Batch(req dto.ComposeTemplateBatch) error
|
Batch(req dto.ComposeTemplateBatch) error
|
||||||
@@ -23,7 +23,7 @@ func NewIComposeTemplateService() IComposeTemplateService {
|
|||||||
return &ComposeTemplateService{}
|
return &ComposeTemplateService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ComposeTemplateService) List(readOnly ...bool) ([]dto.ComposeTemplateInfo, error) {
|
func (u *ComposeTemplateService) List() ([]dto.ComposeTemplateInfo, error) {
|
||||||
composes, err := composeRepo.List()
|
composes, err := composeRepo.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, buserr.New("ErrRecordNotFound")
|
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 {
|
if err := copier.Copy(&item, &compose); err != nil {
|
||||||
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
}
|
}
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
item.Content = ""
|
|
||||||
}
|
|
||||||
dtoLists = append(dtoLists, item)
|
dtoLists = append(dtoLists, item)
|
||||||
}
|
}
|
||||||
return dtoLists, err
|
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))
|
total, composes, err := composeRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info))
|
||||||
var dtoComposeTemplates []dto.ComposeTemplateInfo
|
var dtoComposeTemplates []dto.ComposeTemplateInfo
|
||||||
for _, compose := range composes {
|
for _, compose := range composes {
|
||||||
@@ -50,9 +47,6 @@ func (u *ComposeTemplateService) SearchWithPage(req dto.SearchWithPage, readOnly
|
|||||||
if err := copier.Copy(&item, &compose); err != nil {
|
if err := copier.Copy(&item, &compose); err != nil {
|
||||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
}
|
}
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
item.Content = ""
|
|
||||||
}
|
|
||||||
dtoComposeTemplates = append(dtoComposeTemplates, item)
|
dtoComposeTemplates = append(dtoComposeTemplates, item)
|
||||||
}
|
}
|
||||||
return total, dtoComposeTemplates, err
|
return total, dtoComposeTemplates, err
|
||||||
|
|||||||
+159
-173
@@ -16,6 +16,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -24,6 +25,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"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/repo"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
@@ -56,16 +58,16 @@ type ContainerService struct{}
|
|||||||
var containerLogAnsiRegex = regexp.MustCompile("\x1b\\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>")
|
var containerLogAnsiRegex = regexp.MustCompile("\x1b\\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>")
|
||||||
|
|
||||||
type IContainerService interface {
|
type IContainerService interface {
|
||||||
Page(req dto.PageContainer) (int64, interface{}, error)
|
Page(ctx context.Context, req dto.PageContainer) (int64, interface{}, error)
|
||||||
List() []dto.ContainerOptions
|
List() []dto.ContainerOptions
|
||||||
ListByImage(imageName string) []dto.ContainerOptions
|
ListByImage(imageName string) []dto.ContainerOptions
|
||||||
LoadStatus() (dto.ContainerStatus, error)
|
LoadStatus(ctx context.Context, containersOnly bool) (dto.ContainerStatus, error)
|
||||||
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
|
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
|
||||||
ListNetwork() ([]dto.Options, error)
|
ListNetwork() ([]dto.Options, error)
|
||||||
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
||||||
ListVolume() ([]dto.Options, 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)
|
LoadComposeEnv(name string) (string, error)
|
||||||
CreateCompose(req dto.ComposeCreate) error
|
CreateCompose(req dto.ComposeCreate) error
|
||||||
ComposeOperation(req dto.ComposeOperation) error
|
ComposeOperation(req dto.ComposeOperation) error
|
||||||
@@ -77,9 +79,9 @@ type IContainerService interface {
|
|||||||
ContainerCreate(req dto.ContainerOperate, inThread bool) error
|
ContainerCreate(req dto.ContainerOperate, inThread bool) error
|
||||||
ContainerUpdate(req dto.ContainerOperate) error
|
ContainerUpdate(req dto.ContainerOperate) error
|
||||||
ContainerUpgrade(req dto.ContainerUpgrade) 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)
|
ContainerListStats(ctx context.Context, ids []string) ([]dto.ContainerListStats, error)
|
||||||
ContainerItemStats(req dto.OperationWithName) (dto.ContainerItemStats, error)
|
ContainerItemStats(ctx context.Context, req dto.OperationWithName) (dto.ContainerItemStats, error)
|
||||||
LoadResourceLimit() (*dto.ResourceLimit, error)
|
LoadResourceLimit() (*dto.ResourceLimit, error)
|
||||||
ContainerRename(req dto.ContainerRename) error
|
ContainerRename(req dto.ContainerRename) error
|
||||||
ContainerCommit(req dto.ContainerCommit) error
|
ContainerCommit(req dto.ContainerCommit) error
|
||||||
@@ -90,6 +92,7 @@ type IContainerService interface {
|
|||||||
|
|
||||||
Inspect(req dto.InspectReq) (string, error)
|
Inspect(req dto.InspectReq) (string, error)
|
||||||
DeleteNetwork(req dto.BatchDelete) error
|
DeleteNetwork(req dto.BatchDelete) error
|
||||||
|
CleanNetworks() (*dto.NetworkCleanupTask, error)
|
||||||
CreateNetwork(req dto.NetworkCreate) error
|
CreateNetwork(req dto.NetworkCreate) error
|
||||||
DeleteVolume(req dto.BatchDelete) error
|
DeleteVolume(req dto.BatchDelete) error
|
||||||
CreateVolume(req dto.VolumeCreate) error
|
CreateVolume(req dto.VolumeCreate) error
|
||||||
@@ -110,7 +113,9 @@ func NewIContainerService() IContainerService {
|
|||||||
return &ContainerService{}
|
return &ContainerService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
|
func (u *ContainerService) Page(ctx context.Context, req dto.PageContainer) (int64, interface{}, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
@@ -121,7 +126,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
|||||||
options.Filters = filters.NewArgs()
|
options.Filters = filters.NewArgs()
|
||||||
options.Filters.Add("label", req.Filters)
|
options.Filters.Add("label", req.Filters)
|
||||||
}
|
}
|
||||||
containers, err := client.ContainerList(context.Background(), options)
|
containers, err := client.ContainerList(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
@@ -204,27 +209,32 @@ func (u *ContainerService) ListByImage(imageName string) []dto.ContainerOptions
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
|
func (u *ContainerService) LoadStatus(ctx context.Context, containersOnly bool) (dto.ContainerStatus, error) {
|
||||||
var data dto.ContainerStatus
|
var data dto.ContainerStatus
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
c := context.Background()
|
c, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
images, _ := client.ImageList(c, image.ListOptions{All: true})
|
if !containersOnly {
|
||||||
data.ImageCount = len(images)
|
images, _ := client.ImageList(c, image.ListOptions{All: true})
|
||||||
repo, _ := imageRepoRepo.List()
|
data.ImageCount = len(images)
|
||||||
data.RepoCount = len(repo)
|
repo, _ := imageRepoRepo.List()
|
||||||
templates, _ := composeRepo.List()
|
data.RepoCount = len(repo)
|
||||||
data.ComposeTemplateCount = len(templates)
|
templates, _ := composeRepo.List()
|
||||||
networks, _ := client.NetworkList(c, network.ListOptions{})
|
data.ComposeTemplateCount = len(templates)
|
||||||
data.NetworkCount = len(networks)
|
networks, _ := client.NetworkList(c, network.ListOptions{})
|
||||||
volumes, _ := client.VolumeList(c, volume.ListOptions{})
|
data.NetworkCount = len(networks)
|
||||||
data.VolumeCount = len(volumes.Volumes)
|
volumes, _ := client.VolumeList(c, volume.ListOptions{})
|
||||||
data.ComposeCount = loadComposeCount(client)
|
data.VolumeCount = len(volumes.Volumes)
|
||||||
containers, _ := client.ContainerList(c, container.ListOptions{All: true})
|
data.ComposeCount = loadComposeCount(c, client)
|
||||||
|
}
|
||||||
|
containers, err := client.ContainerList(c, container.ListOptions{All: true})
|
||||||
|
if err != nil {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
data.ContainerCount = len(containers)
|
data.ContainerCount = len(containers)
|
||||||
for _, item := range containers {
|
for _, item := range containers {
|
||||||
switch item.State {
|
switch item.State {
|
||||||
@@ -246,15 +256,15 @@ func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
|
|||||||
}
|
}
|
||||||
return data, nil
|
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
|
var data dto.ContainerItemStats
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
|
defer client.Close()
|
||||||
if req.Name != "system" {
|
if req.Name != "system" {
|
||||||
defer client.Close()
|
containerInfo, _, err := client.ContainerInspectWithRaw(ctx, req.Name, true)
|
||||||
containerInfo, _, err := client.ContainerInspectWithRaw(context.Background(), req.Name, true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
@@ -263,7 +273,7 @@ func (u *ContainerService) ContainerItemStats(req dto.OperationWithName) (dto.Co
|
|||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
usage, err := client.DiskUsage(context.Background(), types.DiskUsageOptions{})
|
usage, err := client.DiskUsage(ctx, types.DiskUsageOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
@@ -290,27 +300,67 @@ func (u *ContainerService) ContainerItemStats(req dto.OperationWithName) (dto.Co
|
|||||||
}
|
}
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) {
|
func (u *ContainerService) ContainerListStats(ctx context.Context, ids []string) ([]dto.ContainerListStats, error) {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
list, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
options := container.ListOptions{All: true}
|
||||||
|
if ids != nil {
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return []dto.ContainerListStats{}, nil
|
||||||
|
}
|
||||||
|
options.Filters = filters.NewArgs()
|
||||||
|
for _, id := range ids {
|
||||||
|
options.Filters.Add("id", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list, err := client.ContainerList(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return collectContainerStats(ctx, list, func(ctx context.Context, id string) dto.ContainerListStats {
|
||||||
|
return loadCpuAndMem(ctx, client, id)
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A fixed worker pool bounds Docker stats requests, including for legacy callers
|
||||||
|
// that request all containers. Stopped containers do not need a stats sample.
|
||||||
|
func collectContainerStats(ctx context.Context, list []container.Summary, load func(context.Context, string) dto.ContainerListStats) []dto.ContainerListStats {
|
||||||
datas := make([]dto.ContainerListStats, len(list))
|
datas := make([]dto.ContainerListStats, len(list))
|
||||||
var wg sync.WaitGroup
|
for i, item := range list {
|
||||||
wg.Add(len(list))
|
datas[i].ContainerID = item.ID
|
||||||
for i := 0; i < len(list); i++ {
|
|
||||||
go func(index int, item container.Summary) {
|
|
||||||
datas[index] = loadCpuAndMem(client, item.ID)
|
|
||||||
wg.Done()
|
|
||||||
}(i, list[i])
|
|
||||||
}
|
}
|
||||||
|
jobs := make(chan int)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for worker := 0; worker < min(8, len(list)); worker++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for index := range jobs {
|
||||||
|
if ctx.Err() != nil || list[index].State != "running" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sampleCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||||
|
datas[index] = load(sampleCtx, list[index].ID)
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
dispatch:
|
||||||
|
for index := range list {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
break dispatch
|
||||||
|
case jobs <- index:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(jobs)
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
return datas, nil
|
return datas
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||||
@@ -482,6 +532,10 @@ func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bool) error {
|
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bool) error {
|
||||||
|
return u.containerCreate(req, inThread, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) containerCreate(req dto.ContainerOperate, inThread bool, taskName string) error {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -495,7 +549,10 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
|||||||
return buserr.New("ErrContainerName")
|
return buserr.New("ErrContainerName")
|
||||||
}
|
}
|
||||||
|
|
||||||
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
if taskName == "" {
|
||||||
|
taskName = task.GetTaskName(req.Name, task.TaskCreate, task.TaskScopeContainer)
|
||||||
|
}
|
||||||
|
taskItem, err := task.NewTask(taskName, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
unlock()
|
unlock()
|
||||||
_ = client.Close()
|
_ = client.Close()
|
||||||
@@ -534,7 +591,9 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
|
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
|
||||||
@@ -554,6 +613,11 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
if inThread {
|
if inThread {
|
||||||
|
if err := taskItem.Prepare(); err != nil {
|
||||||
|
unlock()
|
||||||
|
_ = client.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
defer unlock()
|
defer unlock()
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
@@ -568,7 +632,7 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
|||||||
return taskItem.Execute()
|
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()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -612,9 +676,6 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName, readOnly ...
|
|||||||
data.Tty = oldContainer.Config.Tty
|
data.Tty = oldContainer.Config.Tty
|
||||||
data.Entrypoint = oldContainer.Config.Entrypoint
|
data.Entrypoint = oldContainer.Config.Entrypoint
|
||||||
data.Env = oldContainer.Config.Env
|
data.Env = oldContainer.Config.Env
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
data.Env = nil
|
|
||||||
}
|
|
||||||
data.CPUShares = oldContainer.HostConfig.CPUShares
|
data.CPUShares = oldContainer.HostConfig.CPUShares
|
||||||
for key, val := range oldContainer.Config.Labels {
|
for key, val := range oldContainer.Config.Labels {
|
||||||
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
|
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
|
||||||
@@ -647,14 +708,9 @@ func loadContainerNetworkInfo(name string, endpoint *network.EndpointSettings) d
|
|||||||
if endpoint.IPAMConfig != nil {
|
if endpoint.IPAMConfig != nil {
|
||||||
item.LinkLocalIPs = append([]string(nil), endpoint.IPAMConfig.LinkLocalIPs...)
|
item.LinkLocalIPs = append([]string(nil), endpoint.IPAMConfig.LinkLocalIPs...)
|
||||||
}
|
}
|
||||||
if name != "bridge" {
|
if name != "bridge" && endpoint.IPAMConfig != nil {
|
||||||
if endpoint.IPAMConfig != nil {
|
item.Ipv4 = endpoint.IPAMConfig.IPv4Address
|
||||||
item.Ipv4 = endpoint.IPAMConfig.IPv4Address
|
item.Ipv6 = endpoint.IPAMConfig.IPv6Address
|
||||||
item.Ipv6 = endpoint.IPAMConfig.IPv6Address
|
|
||||||
} else {
|
|
||||||
item.Ipv4 = endpoint.IPAddress
|
|
||||||
item.Ipv6 = endpoint.GlobalIPv6Address
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
@@ -1681,37 +1737,49 @@ func checkImageLike(client *client.Client, imageName string) bool {
|
|||||||
|
|
||||||
func pullImages(task *task.Task, client *client.Client, imageName string) error {
|
func pullImages(task *task.Task, client *client.Client, imageName string) error {
|
||||||
dockerCli := docker.NewClientWithExist(client)
|
dockerCli := docker.NewClientWithExist(client)
|
||||||
options := image.PullOptions{}
|
repos, err := imageRepoRepo.List()
|
||||||
repos, _ := imageRepoRepo.List()
|
if err != nil {
|
||||||
if len(repos) != 0 {
|
return err
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hasAuth, authStr := loadAuthInfo(imageName)
|
|
||||||
if hasAuth {
|
|
||||||
options.RegistryAuth = authStr
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
imageRepo := selectImageRepo(imageName, repos)
|
||||||
|
if imageRepo == nil || !imageRepo.Auth {
|
||||||
|
return dockerCli.PullImageWithProcess(task, imageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
options := image.PullOptions{}
|
||||||
|
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)
|
return dockerCli.PullImageWithProcessAndOptions(task, imageName, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadCpuAndMem(client *client.Client, containerItem string) dto.ContainerListStats {
|
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
|
||||||
|
}
|
||||||
|
if len(downloadURL) > selectedURLLength {
|
||||||
|
selected = &repos[i]
|
||||||
|
selectedURLLength = len(downloadURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selected
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadCpuAndMem(ctx context.Context, client *client.Client, containerItem string) dto.ContainerListStats {
|
||||||
data := dto.ContainerListStats{
|
data := dto.ContainerListStats{
|
||||||
ContainerID: containerItem,
|
ContainerID: containerItem,
|
||||||
}
|
}
|
||||||
res, err := client.ContainerStats(context.Background(), containerItem, false)
|
res, err := client.ContainerStats(ctx, containerItem, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
@@ -1761,7 +1829,10 @@ func checkPortStats(ports []dto.PortHelper, checkInUse bool) (nat.PortMap, error
|
|||||||
}
|
}
|
||||||
for i := 0; i <= hostEnd-hostStart; i++ {
|
for i := 0; i <= hostEnd-hostStart; i++ {
|
||||||
bindItem := nat.PortBinding{HostPort: strconv.Itoa(hostStart + i), HostIP: port.HostIP}
|
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++ {
|
for i := hostStart; i <= hostEnd; i++ {
|
||||||
if checkInUse && common.ScanPortWithIP(port.HostIP, i) {
|
if checkInUse && common.ScanPortWithIP(port.HostIP, i) {
|
||||||
@@ -1779,7 +1850,10 @@ func checkPortStats(ports []dto.PortHelper, checkInUse bool) (nat.PortMap, error
|
|||||||
return portMap, buserr.WithDetail("ErrPortInUsed", portItem, nil)
|
return portMap, buserr.WithDetail("ErrPortInUsed", portItem, nil)
|
||||||
}
|
}
|
||||||
bindItem := nat.PortBinding{HostPort: strconv.Itoa(portItem), HostIP: port.HostIP}
|
bindItem := nat.PortBinding{HostPort: strconv.Itoa(portItem), HostIP: port.HostIP}
|
||||||
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
|
return portMap, nil
|
||||||
@@ -1925,97 +1999,14 @@ func loadPortByInspect(id string, client *client.Client) ([]container.Port, erro
|
|||||||
return itemPorts, nil
|
return itemPorts, nil
|
||||||
}
|
}
|
||||||
func transPortToStr(ports []container.Port) []string {
|
func transPortToStr(ports []container.Port) []string {
|
||||||
var (
|
return docker.SimplifyPorts(ports)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadComposeCount(client *client.Client) int {
|
func loadComposeCount(ctx context.Context, client *client.Client) int {
|
||||||
options := container.ListOptions{All: true}
|
options := container.ListOptions{All: true}
|
||||||
options.Filters = filters.NewArgs()
|
options.Filters = filters.NewArgs()
|
||||||
options.Filters.Add("label", composeProjectLabel)
|
options.Filters.Add("label", composeProjectLabel)
|
||||||
list, err := client.ContainerList(context.Background(), options)
|
list, err := client.ContainerList(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -2041,7 +2032,7 @@ func loadComposeCount(client *client.Client) int {
|
|||||||
}
|
}
|
||||||
func loadContainerPortForInfo(itemPorts []container.Port) []dto.PortHelper {
|
func loadContainerPortForInfo(itemPorts []container.Port) []dto.PortHelper {
|
||||||
var exposedPorts []dto.PortHelper
|
var exposedPorts []dto.PortHelper
|
||||||
samePortMap := make(map[string]dto.PortHelper)
|
seenPorts := make(map[dto.PortHelper]struct{})
|
||||||
ports := transPortToStr(itemPorts)
|
ports := transPortToStr(itemPorts)
|
||||||
for _, item := range ports {
|
for _, item := range ports {
|
||||||
itemStr := strings.Split(item, "->")
|
itemStr := strings.Split(item, "->")
|
||||||
@@ -2062,16 +2053,11 @@ func loadContainerPortForInfo(itemPorts []container.Port) []dto.PortHelper {
|
|||||||
}
|
}
|
||||||
itemPort.ContainerPort = itemContainer[0]
|
itemPort.ContainerPort = itemContainer[0]
|
||||||
itemPort.Protocol = itemContainer[1]
|
itemPort.Protocol = itemContainer[1]
|
||||||
keyItem := fmt.Sprintf("%s->%s/%s", itemPort.HostPort, itemPort.ContainerPort, itemPort.Protocol)
|
if _, exists := seenPorts[itemPort]; exists {
|
||||||
if val, ok := samePortMap[keyItem]; ok {
|
continue
|
||||||
val.HostIP = ""
|
|
||||||
samePortMap[keyItem] = val
|
|
||||||
} else {
|
|
||||||
samePortMap[keyItem] = itemPort
|
|
||||||
}
|
}
|
||||||
}
|
seenPorts[itemPort] = struct{}{}
|
||||||
for _, val := range samePortMap {
|
exposedPorts = append(exposedPorts, itemPort)
|
||||||
exposedPorts = append(exposedPorts, val)
|
|
||||||
}
|
}
|
||||||
return exposedPorts
|
return exposedPorts
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
const composeProjectLabel = "com.docker.compose.project"
|
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 composeWorkdirLabel = "com.docker.compose.project.working_dir"
|
||||||
const composeCreatedBy = "createdBy"
|
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 (
|
var (
|
||||||
records []dto.ComposeInfo
|
records []dto.ComposeInfo
|
||||||
BackDatas []dto.ComposeInfo
|
BackDatas []dto.ComposeInfo
|
||||||
@@ -187,10 +188,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage, readOnly ...bool)
|
|||||||
}
|
}
|
||||||
BackDatas = records[start:end]
|
BackDatas = records[start:end]
|
||||||
}
|
}
|
||||||
listItem := BackDatas
|
listItem := loadEnv(BackDatas)
|
||||||
if !isDemoReadOnly(readOnly...) {
|
|
||||||
listItem = loadEnv(BackDatas)
|
|
||||||
}
|
|
||||||
return int64(total), listItem, nil
|
return int64(total), listItem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,6 +253,10 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Name = projectName
|
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)
|
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeCompose, req.TaskID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("new task for image build failed, err: %v", err)
|
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 {
|
taskItem.AddSubTask(i18n.GetMsgByKey("ComposeCreate"), func(t *task.Task) error {
|
||||||
err := compose.UpWithTask(req.Path, t, req.ForcePull, req.Name)
|
err := compose.UpWithTask(req.Path, t, req.ForcePull, req.Name)
|
||||||
t.LogWithStatus(i18n.GetMsgByKey("ComposeCreate"), err)
|
t.LogWithStatus(i18n.GetMsgByKey("ComposeCreate"), err)
|
||||||
if err != nil {
|
return err
|
||||||
_, _ = 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
|
|
||||||
}, nil)
|
}, nil)
|
||||||
_ = taskItem.Execute()
|
_ = taskItem.Execute()
|
||||||
}()
|
}()
|
||||||
@@ -282,6 +273,17 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
|
|||||||
return nil
|
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 {
|
func checkComposeRecordName(name string) error {
|
||||||
composeItem, _ := composeRepo.GetRecord(repo.WithByName(name))
|
composeItem, _ := composeRepo.GetRecord(repo.WithByName(name))
|
||||||
if composeItem.ID != 0 && len(composeItem.Path) != 0 {
|
if composeItem.ID != 0 && len(composeItem.Path) != 0 {
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||||
|
)
|
||||||
|
|
||||||
|
var networkCleanupMu sync.Mutex
|
||||||
|
|
||||||
|
func (u *ContainerService) CleanNetworks() (*dto.NetworkCleanupTask, error) {
|
||||||
|
taskItem, err := task.NewTaskWithOps(i18n.GetMsgByKey("Network"), task.TaskClean, task.TaskScopeContainer, "", 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error {
|
||||||
|
networkCleanupMu.Lock()
|
||||||
|
defer networkCleanupMu.Unlock()
|
||||||
|
if err := t.TaskCtx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cli, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
return executeNetworkCleanup(t, cli)
|
||||||
|
}, nil)
|
||||||
|
go func() {
|
||||||
|
if err := taskItem.Execute(); err != nil {
|
||||||
|
global.LOG.Errorf("network cleanup task %s failed: %v", taskItem.TaskID, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return &dto.NetworkCleanupTask{TaskID: taskItem.TaskID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeNetworkCleanup(t *task.Task, cli docker.NetworkCleanupClient) error {
|
||||||
|
t.Log(i18n.GetMsgByKey("PruneStart"))
|
||||||
|
report, err := docker.CleanUnusedNetworks(t.TaskCtx, cli, func(status string, item dto.NetworkCleanupItem) {
|
||||||
|
key := "NetworkCleanupDeleted"
|
||||||
|
if status == "skipped" || status == "failed" {
|
||||||
|
key = map[string]string{
|
||||||
|
"protected": "NetworkCleanupProtected",
|
||||||
|
"container_connected": "NetworkCleanupConnected",
|
||||||
|
"network_in_use": "NetworkCleanupConnected",
|
||||||
|
"unsupported_network": "NetworkCleanupUnsupported",
|
||||||
|
"already_removed": "NetworkCleanupGone",
|
||||||
|
"inspect_failed": "NetworkCleanupInspectFailed",
|
||||||
|
"remove_failed": "NetworkCleanupRemoveFailed",
|
||||||
|
}[item.Reason]
|
||||||
|
}
|
||||||
|
t.Log(i18n.GetMsgWithMap(key, map[string]interface{}{"name": item.Name, "id": item.ID}))
|
||||||
|
})
|
||||||
|
if report != nil {
|
||||||
|
t.Log(i18n.GetMsgWithMap("NetworkCleanupSummary", map[string]interface{}{
|
||||||
|
"deleted": len(report.Deleted), "skipped": len(report.Skipped), "failed": len(report.Failed),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(report.Failed) > 0 {
|
||||||
|
return fmt.Errorf("%s", i18n.GetMsgByKey("NetworkCleanupPartialFailure"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
|
"github.com/glebarez/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type networkTaskClient struct{ fail bool }
|
||||||
|
|
||||||
|
func (f networkTaskClient) NetworkList(context.Context, network.ListOptions) ([]network.Inspect, error) {
|
||||||
|
return []network.Inspect{{ID: "reserved", Name: "1panel-network", Scope: "local"}, {ID: "connected", Name: "busy", Scope: "local"}, {ID: "unused", Name: "free", Scope: "local"}, {ID: "race", Name: "race", Scope: "local"}}, nil
|
||||||
|
}
|
||||||
|
func (f networkTaskClient) ContainerList(context.Context, container.ListOptions) ([]container.Summary, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (f networkTaskClient) NetworkInspect(_ context.Context, id string, _ network.InspectOptions) (network.Inspect, error) {
|
||||||
|
n := network.Inspect{}
|
||||||
|
if id == "connected" {
|
||||||
|
n.Containers = map[string]network.EndpointResource{"container-id": {}}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
func (f networkTaskClient) NetworkRemove(_ context.Context, id string) error {
|
||||||
|
if id == "race" {
|
||||||
|
return errdefs.Conflict(errors.New("has active endpoints"))
|
||||||
|
}
|
||||||
|
if id != "unused" {
|
||||||
|
return errors.New("unexpected removal")
|
||||||
|
}
|
||||||
|
if f.fail {
|
||||||
|
return errors.New("remove failed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkCleanupTaskPersistsLogsAndStatus(t *testing.T) {
|
||||||
|
oldDB, oldTaskDB, oldDir, oldI18n := global.DB, global.TaskDB, global.Dir, global.I18n
|
||||||
|
t.Cleanup(func() { global.DB = oldDB; global.TaskDB = oldTaskDB; global.Dir = oldDir; global.I18n = oldI18n })
|
||||||
|
dir := t.TempDir()
|
||||||
|
db, err := gorm.Open(sqlite.Open(filepath.Join(dir, "tasks.db")), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sqlDB, err := db.DB()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer sqlDB.Close()
|
||||||
|
if err := db.AutoMigrate(&model.Task{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
global.DB = nil
|
||||||
|
global.TaskDB = db
|
||||||
|
global.Dir.TaskDir = dir
|
||||||
|
i18n.Init()
|
||||||
|
for _, fail := range []bool{false, true} {
|
||||||
|
name := "success"
|
||||||
|
wantStatus := constant.StatusSuccess
|
||||||
|
if fail {
|
||||||
|
name = "partial failure"
|
||||||
|
wantStatus = constant.StatusFailed
|
||||||
|
}
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
item, err := task.NewTaskWithOps("Network", task.TaskClean, task.TaskScopeContainer, "", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
item.AddSubTask("Clean", func(t *task.Task) error { return executeNetworkCleanup(t, networkTaskClient{fail: fail}) }, nil)
|
||||||
|
err = item.Execute()
|
||||||
|
if (err != nil) != fail {
|
||||||
|
t.Fatalf("unexpected execution error: %v", err)
|
||||||
|
}
|
||||||
|
var saved model.Task
|
||||||
|
if err := db.First(&saved, "id = ?", item.TaskID).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if saved.Status != wantStatus {
|
||||||
|
t.Fatalf("status %s, want %s", saved.Status, wantStatus)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(saved.LogFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, want := range []string{"[busy] (connected): containers connected", "[race] (race): containers connected", "[1panel-network] (reserved): reserved network", "Network cleanup finished:", "[TASK-END]"} {
|
||||||
|
if !strings.Contains(string(content), want) {
|
||||||
|
t.Fatalf("missing %q in log: %s", want, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
want := "Deleted network [free]"
|
||||||
|
if fail {
|
||||||
|
want = "Failed to remove network [free]"
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(content), want) {
|
||||||
|
t.Fatalf("missing %q", want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -18,6 +20,7 @@ import (
|
|||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/docker/docker/api/types/mount"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,13 +67,13 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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) {
|
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)
|
||||||
return client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
}, config.Tty, t)
|
||||||
}, networkConf.EndpointsConfig, oldContainer.NetworkSettings)
|
|
||||||
}, newContainerSwitchTaskLogger(t))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("update container failed, err: %v", err)
|
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
|
config.Image = req.Image
|
||||||
hostConf := cloneContainerHostConfig(oldContainer.HostConfig)
|
hostConf := cloneContainerHostConfig(oldContainer.HostConfig)
|
||||||
preserveContainerVolumeMounts(hostConf, oldContainer.Mounts)
|
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) {
|
cleanupErr, err := switchContainer(ctx, client, item, oldContainer, func() (container.CreateResponse, error) {
|
||||||
return createContainerWithOldNetworks(ctx, client, config, hostConf, oldContainer.NetworkSettings, item)
|
return createContainerWithNetworks(ctx, client, config, hostConf, networkConf, extraNetworks, item)
|
||||||
}, newContainerSwitchTaskLogger(t))
|
}, config.Tty, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
upgradeErr := fmt.Errorf("upgrade container %s failed: %w", item, err)
|
upgradeErr := fmt.Errorf("upgrade container %s failed: %w", item, err)
|
||||||
upgradeErrors = append(upgradeErrors, upgradeErr)
|
upgradeErrors = append(upgradeErrors, upgradeErr)
|
||||||
@@ -166,6 +175,7 @@ type containerSwitchClient interface {
|
|||||||
ContainerStart(context.Context, string, container.StartOptions) error
|
ContainerStart(context.Context, string, container.StartOptions) error
|
||||||
ContainerRemove(context.Context, string, container.RemoveOptions) error
|
ContainerRemove(context.Context, string, container.RemoveOptions) error
|
||||||
ContainerInspect(context.Context, string) (container.InspectResponse, 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
|
NetworkConnect(context.Context, string, string, *network.EndpointSettings) error
|
||||||
NetworkDisconnect(context.Context, string, string, bool) error
|
NetworkDisconnect(context.Context, string, string, bool) error
|
||||||
}
|
}
|
||||||
@@ -238,22 +248,18 @@ func (l *containerOperationMutex) lock(names ...string) func() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type containerNetworkAttachment struct {
|
type containerNetworkAttachment struct {
|
||||||
name string
|
name string
|
||||||
endpoint *network.EndpointSettings
|
endpoint *network.EndpointSettings
|
||||||
isDynamic bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type containerSwitchLogFunc func(messageKey, containerName string, err error)
|
type containerSwitchLogger interface {
|
||||||
|
LogWithStatus(string, error)
|
||||||
func newContainerSwitchTaskLogger(t *task.Task) containerSwitchLogFunc {
|
Log(string)
|
||||||
return func(messageKey, containerName string, err error) {
|
|
||||||
t.LogWithStatus(i18n.GetWithName(messageKey, containerName), err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func logContainerSwitchStep(logger containerSwitchLogFunc, messageKey, containerName string, err error) {
|
func logContainerSwitchStep(logger containerSwitchLogger, messageKey, containerName string, err error) {
|
||||||
if logger != nil {
|
if logger != nil {
|
||||||
logger(messageKey, containerName, err)
|
logger.LogWithStatus(i18n.GetWithName(messageKey, containerName), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +270,8 @@ func switchContainer(
|
|||||||
name string,
|
name string,
|
||||||
oldContainer container.InspectResponse,
|
oldContainer container.InspectResponse,
|
||||||
createNew func() (container.CreateResponse, error),
|
createNew func() (container.CreateResponse, error),
|
||||||
logger containerSwitchLogFunc,
|
tty bool,
|
||||||
|
logger containerSwitchLogger,
|
||||||
) (cleanupErr error, err error) {
|
) (cleanupErr error, err error) {
|
||||||
if oldContainer.ID == "" {
|
if oldContainer.ID == "" {
|
||||||
return nil, fmt.Errorf("original container ID is empty")
|
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 {
|
if err := cli.ContainerStart(ctx, created.ID, container.StartOptions{}); err != nil {
|
||||||
logContainerSwitchStep(logger, "ContainerStartReplacement", name, err)
|
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)
|
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)
|
return nil, errors.Join(fmt.Errorf("start new container failed: %w", err), rollbackErr)
|
||||||
}
|
}
|
||||||
@@ -321,6 +329,7 @@ func switchContainer(
|
|||||||
if wasRunning {
|
if wasRunning {
|
||||||
if err := waitContainerReady(ctx, cli, created.ID); err != nil {
|
if err := waitContainerReady(ctx, cli, created.ID); err != nil {
|
||||||
logContainerSwitchStep(logger, "ContainerWaitReplacement", name, err)
|
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)
|
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)
|
return nil, errors.Join(fmt.Errorf("new container readiness check failed: %w", err), rollbackErr)
|
||||||
}
|
}
|
||||||
@@ -337,8 +346,48 @@ const (
|
|||||||
containerStartPollInterval = time.Second
|
containerStartPollInterval = time.Second
|
||||||
containerHealthCheckMinWait = 30 * time.Second
|
containerHealthCheckMinWait = 30 * time.Second
|
||||||
containerHealthCheckMaxWait = 10 * time.Minute
|
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 {
|
func waitContainerReady(ctx context.Context, cli containerInspectClient, containerID string) error {
|
||||||
info, err := cli.ContainerInspect(ctx, containerID)
|
info, err := cli.ContainerInspect(ctx, containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -538,13 +587,13 @@ func disconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitc
|
|||||||
endpoints := make(map[string]*network.EndpointSettings, len(extras)+1)
|
endpoints := make(map[string]*network.EndpointSettings, len(extras)+1)
|
||||||
if primary != nil {
|
if primary != nil {
|
||||||
for name, endpoint := range primary.EndpointsConfig {
|
for name, endpoint := range primary.EndpointsConfig {
|
||||||
if name != "bridge" && endpoint != nil && endpoint.IPAMConfig != nil {
|
if name != "bridge" && endpoint != nil {
|
||||||
endpoints[name] = endpoint
|
endpoints[name] = endpoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for name, endpoint := range extras {
|
for name, endpoint := range extras {
|
||||||
if name != "bridge" && endpoint != nil && endpoint.IPAMConfig != nil {
|
if name != "bridge" && endpoint != nil {
|
||||||
endpoints[name] = endpoint
|
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)
|
return disconnected, fmt.Errorf("disconnect original container from network %s failed: %w", name, err)
|
||||||
}
|
}
|
||||||
disconnected = append(disconnected, containerNetworkAttachment{
|
disconnected = append(disconnected, containerNetworkAttachment{
|
||||||
name: name,
|
name: name,
|
||||||
endpoint: endpoints[name],
|
endpoint: endpoints[name],
|
||||||
isDynamic: isDynamicContainerNetwork(oldContainer.NetworkSettings, name),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return disconnected, nil
|
return disconnected, nil
|
||||||
@@ -572,10 +620,6 @@ func reconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitch
|
|||||||
var reconnectErr error
|
var reconnectErr error
|
||||||
for _, attachment := range attachments {
|
for _, attachment := range attachments {
|
||||||
err := cli.NetworkConnect(ctx, attachment.name, containerID, attachment.endpoint)
|
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 {
|
if err != nil {
|
||||||
reconnectErr = errors.Join(reconnectErr, fmt.Errorf("reconnect original container to network %s failed: %w", attachment.name, err))
|
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
|
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
|
var rollbackErr error
|
||||||
backupName := containerSwitchBackupName(oldContainerID)
|
backupName := containerSwitchBackupName(oldContainerID)
|
||||||
if newContainer != "" {
|
if newContainer != "" {
|
||||||
@@ -608,7 +652,7 @@ func restoreOriginalContainer(ctx context.Context, cli containerSwitchClient, ol
|
|||||||
reconnectErr := reconnectOriginalContainerNetworks(ctx, cli, oldContainerID, disconnectedNetworks)
|
reconnectErr := reconnectOriginalContainerNetworks(ctx, cli, oldContainerID, disconnectedNetworks)
|
||||||
logContainerSwitchStep(logger, "ContainerRollbackReconnectOld", currentName, reconnectErr)
|
logContainerSwitchStep(logger, "ContainerRollbackReconnectOld", currentName, reconnectErr)
|
||||||
rollbackErr = errors.Join(rollbackErr, reconnectErr)
|
rollbackErr = errors.Join(rollbackErr, reconnectErr)
|
||||||
if wasRunning {
|
if wasRunning && reconnectErr == nil {
|
||||||
restartErr := restartOriginalContainer(ctx, cli, oldContainerID)
|
restartErr := restartOriginalContainer(ctx, cli, oldContainerID)
|
||||||
logContainerSwitchStep(logger, "ContainerRollbackRestartOld", currentName, restartErr)
|
logContainerSwitchStep(logger, "ContainerRollbackRestartOld", currentName, restartErr)
|
||||||
rollbackErr = errors.Join(rollbackErr, restartErr)
|
rollbackErr = errors.Join(rollbackErr, restartErr)
|
||||||
@@ -616,17 +660,8 @@ func restoreOriginalContainer(ctx context.Context, cli containerSwitchClient, ol
|
|||||||
return rollbackErr
|
return rollbackErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func createContainerWithOldNetworks(ctx context.Context, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkSettings *container.NetworkSettings, name string) (container.CreateResponse, error) {
|
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) {
|
||||||
networkConf, extraNetworks := buildContainerRecoverNetworkConfig(networkSettings, hostConf)
|
created, err := client.ContainerCreate(ctx, config, hostConf, networkConf, nil, name)
|
||||||
normalizeContainerEndpointSettings(ctx, client, networkConf, extraNetworks)
|
|
||||||
var primaryEndpoints map[string]*network.EndpointSettings
|
|
||||||
if networkConf != nil {
|
|
||||||
primaryEndpoints = networkConf.EndpointsConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
created, err := createContainerWithDynamicIPFallback(func() (container.CreateResponse, error) {
|
|
||||||
return client.ContainerCreate(ctx, config, hostConf, networkConf, nil, name)
|
|
||||||
}, primaryEndpoints, networkSettings)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
@@ -638,9 +673,6 @@ func createContainerWithOldNetworks(ctx context.Context, client *client.Client,
|
|||||||
sort.Strings(extraNames)
|
sort.Strings(extraNames)
|
||||||
for _, item := range extraNames {
|
for _, item := range extraNames {
|
||||||
err := client.NetworkConnect(ctx, item, created.ID, extraNetworks[item])
|
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 {
|
if err != nil {
|
||||||
_ = client.ContainerRemove(ctx, created.ID, container.RemoveOptions{Force: true})
|
_ = client.ContainerRemove(ctx, created.ID, container.RemoveOptions{Force: true})
|
||||||
return created, err
|
return created, err
|
||||||
@@ -648,16 +680,3 @@ func createContainerWithOldNetworks(ctx context.Context, client *client.Client,
|
|||||||
}
|
}
|
||||||
return created, nil
|
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/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -25,7 +26,7 @@ import (
|
|||||||
type CronjobService struct{}
|
type CronjobService struct{}
|
||||||
|
|
||||||
type ICronjobService interface {
|
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)
|
SearchRecords(search dto.SearchRecord) (int64, interface{}, error)
|
||||||
Create(cronjobDto dto.CronjobOperate, operator string) error
|
Create(cronjobDto dto.CronjobOperate, operator string) error
|
||||||
LoadNextHandle(spec string) ([]string, error)
|
LoadNextHandle(spec string) ([]string, error)
|
||||||
@@ -50,7 +51,7 @@ func NewICronjobService() ICronjobService {
|
|||||||
return &CronjobService{}
|
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,
|
total, cronjobs, err := cronjobRepo.Page(search.Page,
|
||||||
search.PageSize,
|
search.PageSize,
|
||||||
repo.WithByGroups(search.GroupIDs),
|
repo.WithByGroups(search.GroupIDs),
|
||||||
@@ -75,6 +76,7 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob, readOnly ...bool
|
|||||||
EntryID: cronjob.ID,
|
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))), repo.WithByStatus(constant.AlertEnable))
|
||||||
|
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||||
if alertInfo.SendCount != 0 {
|
if alertInfo.SendCount != 0 {
|
||||||
item.AlertCount = alertInfo.SendCount
|
item.AlertCount = alertInfo.SendCount
|
||||||
} else {
|
} else {
|
||||||
@@ -83,9 +85,6 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob, readOnly ...bool
|
|||||||
if cronjob.Type == "snapshot" && len(cronjob.SnapshotRule) != 0 {
|
if cronjob.Type == "snapshot" && len(cronjob.SnapshotRule) != 0 {
|
||||||
_ = json.Unmarshal([]byte(cronjob.SnapshotRule), &item.SnapshotRule)
|
_ = json.Unmarshal([]byte(cronjob.SnapshotRule), &item.SnapshotRule)
|
||||||
}
|
}
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
item.Secret = ""
|
|
||||||
}
|
|
||||||
dtoCronjobs = append(dtoCronjobs, item)
|
dtoCronjobs = append(dtoCronjobs, item)
|
||||||
}
|
}
|
||||||
return total, dtoCronjobs, err
|
return total, dtoCronjobs, err
|
||||||
@@ -101,9 +100,11 @@ func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, err
|
|||||||
AlertType: cronjob.Type,
|
AlertType: cronjob.Type,
|
||||||
EntryID: cronjob.ID,
|
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
|
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
|
item.AlertCount = alertInfo.SendCount
|
||||||
} else {
|
} else {
|
||||||
item.AlertCount = 0
|
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)
|
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))
|
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))))
|
||||||
if alertInfo.SendCount != 0 {
|
item.AlertTitle = alertInfo.Title
|
||||||
|
item.AlertMethod = alertInfo.Method
|
||||||
|
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||||
|
if alertInfo.Status == constant.AlertEnable {
|
||||||
item.AlertCount = alertInfo.SendCount
|
item.AlertCount = alertInfo.SendCount
|
||||||
item.AlertTitle = alertInfo.Title
|
|
||||||
item.AlertMethod = alertInfo.Method
|
|
||||||
} else {
|
} else {
|
||||||
item.AlertCount = 0
|
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 {
|
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 {
|
for _, item := range req {
|
||||||
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
|
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
|
||||||
if cronjobItem.ID != 0 {
|
if cronjobItem.ID != 0 {
|
||||||
@@ -398,17 +411,27 @@ func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
|||||||
} else {
|
} else {
|
||||||
cronjob.Status = constant.StatusDisable
|
cronjob.Status = constant.StatusDisable
|
||||||
}
|
}
|
||||||
_ = cronjobRepo.Create(&cronjob)
|
if err := cronjobRepo.Create(&cronjob); err != nil {
|
||||||
if item.AlertCount != 0 && item.AlertTitle != "" && item.AlertMethod != "" {
|
return err
|
||||||
|
}
|
||||||
|
if item.AlertTitle != "" && item.AlertMethod != "" {
|
||||||
|
advanced, _ := cronJobAlertAdvancedParams(item.AlertTriggerMode)
|
||||||
|
status := constant.AlertEnable
|
||||||
|
if item.AlertCount == 0 {
|
||||||
|
status = constant.AlertDisable
|
||||||
|
}
|
||||||
createAlert := dto.AlertCreate{
|
createAlert := dto.AlertCreate{
|
||||||
Title: item.AlertTitle,
|
Title: item.AlertTitle,
|
||||||
SendCount: item.AlertCount,
|
SendCount: item.AlertCount,
|
||||||
Method: item.AlertMethod,
|
Method: item.AlertMethod,
|
||||||
Type: cronjob.Type,
|
Type: cronjob.Type,
|
||||||
Project: strconv.Itoa(int(cronjob.ID)),
|
Project: strconv.Itoa(int(cronjob.ID)),
|
||||||
Status: constant.AlertEnable,
|
Status: status,
|
||||||
|
AdvancedParams: advanced,
|
||||||
|
}
|
||||||
|
if err := NewIAlertService().CreateAlert(createAlert, operator); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
_ = NewIAlertService().CreateAlert(createAlert, operator)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -565,6 +588,15 @@ func (u *CronjobService) HandleOnce(id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *CronjobService) Create(req dto.CronjobOperate, operator string) 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))
|
cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name))
|
||||||
if cronjob.ID != 0 {
|
if cronjob.ID != 0 {
|
||||||
return buserr.New("ErrRecordExist")
|
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 != "" {
|
if req.AlertCount != 0 && req.AlertTitle != "" && req.AlertMethod != "" {
|
||||||
createAlert := dto.AlertCreate{
|
createAlert := dto.AlertCreate{
|
||||||
Title: req.AlertTitle,
|
Title: req.AlertTitle,
|
||||||
SendCount: req.AlertCount,
|
SendCount: req.AlertCount,
|
||||||
Method: req.AlertMethod,
|
Method: req.AlertMethod,
|
||||||
Type: cronjob.Type,
|
Type: cronjob.Type,
|
||||||
Project: strconv.Itoa(int(cronjob.ID)),
|
Project: strconv.Itoa(int(cronjob.ID)),
|
||||||
Status: constant.AlertEnable,
|
Status: constant.AlertEnable,
|
||||||
|
AdvancedParams: advanced,
|
||||||
}
|
}
|
||||||
err := NewIAlertService().CreateAlert(createAlert, operator)
|
err := NewIAlertService().CreateAlert(createAlert, operator)
|
||||||
if err != nil {
|
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 {
|
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
|
var cronjob model.Cronjob
|
||||||
if err := copier.Copy(&cronjob, &req); err != nil {
|
if err := copier.Copy(&cronjob, &req); err != nil {
|
||||||
return buserr.WithDetail("ErrStructTransform", err.Error(), 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 {
|
if err != nil {
|
||||||
return buserr.New("ErrRecordNotFound")
|
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{})
|
upMap := make(map[string]interface{})
|
||||||
cronjob.EntryIDs = cronModel.EntryIDs
|
cronjob.EntryIDs = cronModel.EntryIDs
|
||||||
cronjob.Type = cronModel.Type
|
cronjob.Type = cronModel.Type
|
||||||
@@ -756,11 +807,12 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
updateAlert := dto.AlertCreate{
|
updateAlert := dto.AlertCreate{
|
||||||
Title: req.AlertTitle,
|
Title: req.AlertTitle,
|
||||||
SendCount: req.AlertCount,
|
SendCount: req.AlertCount,
|
||||||
Method: req.AlertMethod,
|
Method: req.AlertMethod,
|
||||||
Type: cronjob.Type,
|
Type: cronjob.Type,
|
||||||
Project: strconv.Itoa(int(cronModel.ID)),
|
Project: strconv.Itoa(int(cronModel.ID)),
|
||||||
|
AdvancedParams: advanced,
|
||||||
}
|
}
|
||||||
err = NewIAlertService().ExternalUpdateAlert(updateAlert, operator)
|
err = NewIAlertService().ExternalUpdateAlert(updateAlert, operator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -769,6 +821,17 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
|||||||
return nil
|
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 {
|
func (u *CronjobService) UpdateStatus(id uint, status string) error {
|
||||||
cronjob, _ := cronjobRepo.Get(repo.WithByID(id))
|
cronjob, _ := cronjobRepo.Get(repo.WithByID(id))
|
||||||
if cronjob.ID == 0 {
|
if cronjob.ID == 0 {
|
||||||
|
|||||||
@@ -412,6 +412,7 @@ func addSkipTask(source string, taskItem *task.Task) {
|
|||||||
taskItem.Log(i18n.GetMsgByKey("NoSuchResource"))
|
taskItem.Log(i18n.GetMsgByKey("NoSuchResource"))
|
||||||
return nil
|
return nil
|
||||||
}, nil)
|
}, nil)
|
||||||
|
taskItem.SubTasks[len(taskItem.SubTasks)-1].StepAlias = cronJobSkippedStep
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -23,6 +24,7 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
"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/cmd"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ntp"
|
"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)
|
_ = taskRepo.Save(context.Background(), taskItem.Task)
|
||||||
}
|
}
|
||||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||||
handleCronJobAlert(cronjob)
|
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
||||||
|
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, nil))
|
||||||
}()
|
}()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -70,19 +73,20 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
|||||||
record.TaskID = ""
|
record.TaskID = ""
|
||||||
}
|
}
|
||||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||||
handleCronJobAlert(cronjob)
|
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
if err := taskItem.Execute(); err != nil {
|
if err := taskItem.Execute(); err != nil {
|
||||||
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
storedTask, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||||
if len(taskItem.ID) == 0 {
|
if len(storedTask.ID) == 0 {
|
||||||
record.TaskID = ""
|
record.TaskID = ""
|
||||||
}
|
}
|
||||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||||
handleCronJobAlert(cronjob)
|
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||||
} else {
|
} else {
|
||||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
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"
|
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{
|
pushAlert := dto.PushAlert{
|
||||||
|
Result: result,
|
||||||
TaskName: cronjob.Name,
|
TaskName: cronjob.Name,
|
||||||
AlertType: cronjob.Type,
|
AlertType: cronjob.Type,
|
||||||
EntryID: cronjob.ID,
|
EntryID: cronjob.ID,
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
||||||
@@ -244,8 +243,7 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
|
|||||||
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
|
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
|
||||||
|
|
||||||
currentInfo.DiskData = loadDiskInfo()
|
currentInfo.DiskData = loadDiskInfo()
|
||||||
currentInfo.GPUData = loadGPUInfo()
|
currentInfo.GPUData, currentInfo.NPUData, currentInfo.XPUData = loadAcceleratorInfo()
|
||||||
currentInfo.XPUData = loadXpuInfo()
|
|
||||||
|
|
||||||
if ioOption == "all" {
|
if ioOption == "all" {
|
||||||
diskInfo, _ := disk.IOCounters()
|
diskInfo, _ := disk.IOCounters()
|
||||||
@@ -466,12 +464,14 @@ func loadDiskInfo() []dto.DiskInfo {
|
|||||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||||
)
|
)
|
||||||
if err != nil {
|
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))
|
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
|
||||||
stdout, err = cmdMgr2.RunPipe(
|
stdout, err = cmdMgr2.RunPipe(
|
||||||
cmd.PipeCommand{Name: "df", Args: []string{"-lhT", "-P"}},
|
cmd.PipeCommand{Name: "df", Args: []string{"-lhT", "-P"}},
|
||||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
global.LOG.Errorf("load disk info with df -lhT -P failed, err: %v", err)
|
||||||
return datas
|
return datas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -569,32 +569,71 @@ func loadDiskInfo() []dto.DiskInfo {
|
|||||||
return datas
|
return datas
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadGPUInfo() []dto.GPUInfo {
|
func loadAcceleratorInfo() ([]dto.GPUInfo, []dto.NPUInfo, []dto.XPUInfo) {
|
||||||
ok, client := gpu.New()
|
ok, client := accelerator.New()
|
||||||
var list []interface{}
|
if !ok {
|
||||||
if ok {
|
return nil, nil, nil
|
||||||
info, err := client.LoadGpuInfo()
|
|
||||||
if err != nil || len(info.GPUs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, item := range info.GPUs {
|
|
||||||
list = append(list, item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(list) == 0 {
|
snapshot, err := client.Collect(context.Background())
|
||||||
return nil
|
if err != nil || len(snapshot.Devices) == 0 {
|
||||||
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
var data []dto.GPUInfo
|
if warning := snapshot.Warning(); warning != nil {
|
||||||
for _, gpu := range list {
|
global.LOG.Warnf("load accelerator dashboard data partially failed, err: %v", warning)
|
||||||
var dataItem dto.GPUInfo
|
}
|
||||||
if err := copier.Copy(&dataItem, &gpu); err != nil {
|
|
||||||
|
var (
|
||||||
|
gpuData []dto.GPUInfo
|
||||||
|
npuData []dto.NPUInfo
|
||||||
|
xpuData []dto.XPUInfo
|
||||||
|
)
|
||||||
|
for _, device := range snapshot.Devices {
|
||||||
|
if device.ParentID != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dataItem.PowerUsage = dataItem.PowerDraw + " / " + dataItem.MaxPowerLimit
|
switch device.Kind {
|
||||||
dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal
|
case accelerator.KindGPU:
|
||||||
data = append(data, dataItem)
|
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 {
|
type AppLauncher struct {
|
||||||
@@ -610,32 +649,6 @@ func ArryContains(arr []string, element string) bool {
|
|||||||
return false
|
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 {
|
func loadOutboundIP() string {
|
||||||
conn, err := network.Dial("udp", "8.8.8.8:80")
|
conn, err := network.Dial("udp", "8.8.8.8:80")
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import (
|
|||||||
type DatabaseService struct{}
|
type DatabaseService struct{}
|
||||||
|
|
||||||
type IDatabaseService interface {
|
type IDatabaseService interface {
|
||||||
Get(name string, readOnly ...bool) (dto.DatabaseInfo, error)
|
Get(name string) (dto.DatabaseInfo, error)
|
||||||
SearchWithPage(search dto.DatabaseSearch, readOnly ...bool) (int64, interface{}, error)
|
SearchWithPage(search dto.DatabaseSearch) (int64, interface{}, error)
|
||||||
CheckDatabase(req dto.DatabaseCreate) bool
|
CheckDatabase(req dto.DatabaseCreate) bool
|
||||||
Create(req dto.DatabaseCreate) error
|
Create(req dto.DatabaseCreate) error
|
||||||
Update(req dto.DatabaseUpdate) error
|
Update(req dto.DatabaseUpdate) error
|
||||||
@@ -39,7 +39,7 @@ func NewIDatabaseService() IDatabaseService {
|
|||||||
return &DatabaseService{}
|
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,
|
total, dbs, err := databaseRepo.Page(search.Page, search.PageSize,
|
||||||
databaseRepo.WithTypeList(search.Type),
|
databaseRepo.WithTypeList(search.Type),
|
||||||
repo.WithByLikeName(search.Info),
|
repo.WithByLikeName(search.Info),
|
||||||
@@ -52,16 +52,12 @@ func (u *DatabaseService) SearchWithPage(search dto.DatabaseSearch, readOnly ...
|
|||||||
if err := copier.Copy(&item, &db); err != nil {
|
if err := copier.Copy(&item, &db); err != nil {
|
||||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
}
|
}
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
item.Password = ""
|
|
||||||
item.ClientKey = ""
|
|
||||||
}
|
|
||||||
datas = append(datas, item)
|
datas = append(datas, item)
|
||||||
}
|
}
|
||||||
return total, datas, err
|
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
|
var data dto.DatabaseInfo
|
||||||
remote, err := databaseRepo.Get(repo.WithByName(name))
|
remote, err := databaseRepo.Get(repo.WithByName(name))
|
||||||
if err != nil {
|
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 {
|
if err := copier.Copy(&data, &remote); err != nil {
|
||||||
return data, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return data, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
}
|
}
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
data.Password = ""
|
|
||||||
data.ClientKey = ""
|
|
||||||
}
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
type MongodbService struct{}
|
type MongodbService struct{}
|
||||||
|
|
||||||
type IMongodbService interface {
|
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)
|
Create(ctx context.Context, req dto.MongodbDBCreate) (*model.DatabaseMongodb, error)
|
||||||
LoadFromRemote(req dto.MongodbLoadDB) error
|
LoadFromRemote(req dto.MongodbLoadDB) error
|
||||||
UpdateDescription(req dto.UpdateDescription) error
|
UpdateDescription(req dto.UpdateDescription) error
|
||||||
@@ -40,7 +40,7 @@ func NewIMongodbService() IMongodbService {
|
|||||||
return &MongodbService{}
|
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(
|
total, mongodbs, err := mongodbRepo.Page(
|
||||||
search.Page,
|
search.Page,
|
||||||
search.PageSize,
|
search.PageSize,
|
||||||
@@ -54,9 +54,6 @@ func (u *MongodbService) SearchWithPage(search dto.MongodbDBSearch, readOnly ...
|
|||||||
if err := copier.Copy(&item, &mongodb); err != nil {
|
if err := copier.Copy(&item, &mongodb); err != nil {
|
||||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
}
|
}
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
item.Password = ""
|
|
||||||
}
|
|
||||||
dtoMongodbs = append(dtoMongodbs, item)
|
dtoMongodbs = append(dtoMongodbs, item)
|
||||||
}
|
}
|
||||||
return total, dtoMongodbs, err
|
return total, dtoMongodbs, err
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ type IMysqlService interface {
|
|||||||
DeleteCheck(req dto.MysqlDBDeleteCheck) ([]dto.DBResource, error)
|
DeleteCheck(req dto.MysqlDBDeleteCheck) ([]dto.DBResource, error)
|
||||||
Delete(ctx context.Context, req dto.MysqlDBDelete) 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)
|
ListGrants(req dto.MysqlUserSearch) ([]dto.MysqlGrant, error)
|
||||||
ListGrantSummary(req dto.MysqlGrantSummarySearch) (map[string][]dto.MysqlUser, error)
|
ListGrantSummary(req dto.MysqlGrantSummarySearch) (map[string][]dto.MysqlUser, error)
|
||||||
CreateUser(req dto.MysqlUserCreate) error
|
CreateUser(req dto.MysqlUserCreate) error
|
||||||
@@ -506,7 +506,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
|
|||||||
return &createItem, nil
|
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)
|
dbType, err := resolveDatabaseUserType(req.Database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -520,14 +520,10 @@ func (u *MysqlService) ListUsers(req dto.MysqlUserSearch, readOnly ...bool) ([]d
|
|||||||
if isMysqlSystemUser(user.Username) {
|
if isMysqlSystemUser(user.Username) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
password := user.Password
|
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
password = ""
|
|
||||||
}
|
|
||||||
res = append(res, dto.MysqlUser{
|
res = append(res, dto.MysqlUser{
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
Host: user.Host,
|
Host: user.Host,
|
||||||
Password: password,
|
Password: user.Password,
|
||||||
Description: user.Description,
|
Description: user.Description,
|
||||||
IsDelete: user.IsDelete,
|
IsDelete: user.IsDelete,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
type PostgresqlService struct{}
|
type PostgresqlService struct{}
|
||||||
|
|
||||||
type IPostgresqlService interface {
|
type IPostgresqlService interface {
|
||||||
SearchWithPage(search dto.PostgresqlDBSearch, readOnly ...bool) (int64, interface{}, error)
|
SearchWithPage(search dto.PostgresqlDBSearch) (int64, interface{}, error)
|
||||||
ListDBOption() ([]dto.PostgresqlOption, error)
|
ListDBOption() ([]dto.PostgresqlOption, error)
|
||||||
BindUser(req dto.PostgresqlBindUser) error
|
BindUser(req dto.PostgresqlBindUser) error
|
||||||
Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error)
|
Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error)
|
||||||
@@ -42,7 +42,7 @@ func NewIPostgresqlService() IPostgresqlService {
|
|||||||
return &PostgresqlService{}
|
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,
|
total, postgresqls, err := postgresqlRepo.Page(search.Page, search.PageSize,
|
||||||
postgresqlRepo.WithByPostgresqlName(search.Database),
|
postgresqlRepo.WithByPostgresqlName(search.Database),
|
||||||
repo.WithByLikeName(search.Info),
|
repo.WithByLikeName(search.Info),
|
||||||
@@ -54,9 +54,6 @@ func (u *PostgresqlService) SearchWithPage(search dto.PostgresqlDBSearch, readOn
|
|||||||
if err := copier.Copy(&item, &pg); err != nil {
|
if err := copier.Copy(&item, &pg); err != nil {
|
||||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
}
|
}
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
item.Password = ""
|
|
||||||
}
|
|
||||||
dtoPostgresqls = append(dtoPostgresqls, item)
|
dtoPostgresqls = append(dtoPostgresqls, item)
|
||||||
}
|
}
|
||||||
return total, dtoPostgresqls, err
|
return total, dtoPostgresqls, err
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
@@ -23,6 +29,11 @@ import (
|
|||||||
|
|
||||||
type RedisService struct{}
|
type RedisService struct{}
|
||||||
|
|
||||||
|
const redisCliTaskName = "RedisCliEnable"
|
||||||
|
|
||||||
|
// The CLI container is shared by all remote Redis databases on this node.
|
||||||
|
var redisCliInstallMutex sync.Mutex
|
||||||
|
|
||||||
type IRedisService interface {
|
type IRedisService interface {
|
||||||
UpdateConf(req dto.RedisConfUpdate) error
|
UpdateConf(req dto.RedisConfUpdate) error
|
||||||
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
|
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
|
||||||
@@ -33,7 +44,8 @@ type IRedisService interface {
|
|||||||
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
|
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
|
||||||
|
|
||||||
CheckHasCli() bool
|
CheckHasCli() bool
|
||||||
InstallCli() error
|
InstallCli(req dto.RedisCliInstall) (*dto.RedisCliStatus, error)
|
||||||
|
LoadCliStatus() (*dto.RedisCliStatus, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIRedisService() IRedisService {
|
func NewIRedisService() IRedisService {
|
||||||
@@ -71,20 +83,62 @@ func (u *RedisService) CheckHasCli() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, item := range containerLists {
|
for _, item := range containerLists {
|
||||||
if strings.ReplaceAll(item.Names[0], "/", "") == "1Panel-redis-cli-tools" {
|
if len(item.Names) > 0 && strings.TrimPrefix(item.Names[0], "/") == "1Panel-redis-cli-tools" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *RedisService) InstallCli() error {
|
func (u *RedisService) LoadCliStatus() (*dto.RedisCliStatus, error) {
|
||||||
|
result := &dto.RedisCliStatus{}
|
||||||
|
latest, err := taskRepo.GetFirst(repo.WithByName(redisCliTaskName), repo.WithByType(task.TaskScopeContainer), repo.WithOrderDesc("created_at"))
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
result.Installed = u.CheckHasCli()
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result.TaskID = latest.ID
|
||||||
|
result.Status = latest.Status
|
||||||
|
result.ErrorMsg = latest.ErrorMsg
|
||||||
|
result.Installed = u.CheckHasCli()
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RedisService) InstallCli(req dto.RedisCliInstall) (*dto.RedisCliStatus, error) {
|
||||||
|
if !redisCliInstallMutex.TryLock() {
|
||||||
|
return nil, buserr.New("TaskIsExecuting")
|
||||||
|
}
|
||||||
|
defer redisCliInstallMutex.Unlock()
|
||||||
|
status, err := u.LoadCliStatus()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if status.Status == constant.StatusExecuting || status.Installed {
|
||||||
|
return status, nil
|
||||||
|
}
|
||||||
|
if req.TaskID == "" {
|
||||||
|
req.TaskID = uuid.NewString()
|
||||||
|
}
|
||||||
|
// Never reuse an existing task ID: doing so would truncate its log.
|
||||||
|
if _, err := taskRepo.GetFirst(taskRepo.WithByID(req.TaskID)); !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, buserr.New("TaskIsExecuting")
|
||||||
|
}
|
||||||
item := dto.ContainerOperate{
|
item := dto.ContainerOperate{
|
||||||
|
TaskID: req.TaskID,
|
||||||
Name: "1Panel-redis-cli-tools",
|
Name: "1Panel-redis-cli-tools",
|
||||||
Image: "redis:7.4.4",
|
Image: "redis:7.4.4",
|
||||||
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
|
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
|
||||||
}
|
}
|
||||||
return NewIContainerService().ContainerCreate(item, false)
|
if err := (&ContainerService{}).containerCreate(item, true, redisCliTaskName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &dto.RedisCliStatus{TaskID: req.TaskID, Status: constant.StatusExecuting}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
||||||
|
|||||||
+120
-1
@@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -14,14 +15,20 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
"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/controller"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
"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 DockerService struct{}
|
||||||
|
|
||||||
type IDockerService interface {
|
type IDockerService interface {
|
||||||
UpdateConf(req dto.SettingUpdate, withRestart bool) error
|
UpdateConf(req dto.SettingUpdate, withRestart bool) error
|
||||||
|
UpdateFirewallBackend(backend string) error
|
||||||
UpdateLogOption(req dto.LogOption) error
|
UpdateLogOption(req dto.LogOption) error
|
||||||
UpdateIpv6Option(req dto.Ipv6Option) error
|
UpdateIpv6Option(req dto.Ipv6Option) error
|
||||||
UpdateConfByFile(info dto.DaemonJsonUpdateByFile) error
|
UpdateConfByFile(info dto.DaemonJsonUpdateByFile) error
|
||||||
@@ -30,6 +37,115 @@ type IDockerService interface {
|
|||||||
OperateDocker(req dto.DockerOperation) error
|
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 {
|
func NewIDockerService() IDockerService {
|
||||||
return &DockerService{}
|
return &DockerService{}
|
||||||
}
|
}
|
||||||
@@ -167,7 +283,10 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate, withRestart bool) erro
|
|||||||
delete(daemonMap, "ipv6")
|
delete(daemonMap, "ipv6")
|
||||||
delete(daemonMap, "fixed-cidr-v6")
|
delete(daemonMap, "fixed-cidr-v6")
|
||||||
delete(daemonMap, "ip6tables")
|
delete(daemonMap, "ip6tables")
|
||||||
delete(daemonMap, "experimental")
|
backend, _ := settingRepo.GetValueByKey(constant.FirewallDockerBackendKey)
|
||||||
|
if !strings.EqualFold(strings.TrimSpace(backend), constant.FirewallProviderNftables) {
|
||||||
|
delete(daemonMap, "experimental")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "LogOption":
|
case "LogOption":
|
||||||
if req.Value == "disable" {
|
if req.Value == "disable" {
|
||||||
|
|||||||
@@ -37,8 +37,9 @@ var (
|
|||||||
clamRepo = repo.NewIClamRepo()
|
clamRepo = repo.NewIClamRepo()
|
||||||
monitorRepo = repo.NewIMonitorRepo()
|
monitorRepo = repo.NewIMonitorRepo()
|
||||||
|
|
||||||
settingRepo = repo.NewISettingRepo()
|
settingRepo = repo.NewISettingRepo()
|
||||||
backupRepo = repo.NewIBackupRepo()
|
forwardingRuleRepo = repo.NewIForwardingRuleRepo()
|
||||||
|
backupRepo = repo.NewIBackupRepo()
|
||||||
|
|
||||||
websiteRepo = repo.NewIWebsiteRepo()
|
websiteRepo = repo.NewIWebsiteRepo()
|
||||||
websiteDomainRepo = repo.NewIWebsiteDomainRepo()
|
websiteDomainRepo = repo.NewIWebsiteDomainRepo()
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/toolbox"
|
"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" {
|
if req.Value == "firewallcmd-ipset" {
|
||||||
itemName = "firewalld"
|
itemName = "firewalld"
|
||||||
}
|
}
|
||||||
client, err := firewall.NewFirewallClient()
|
client, err := NewSelectedSystemFirewallClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-20
@@ -87,7 +87,7 @@ type IFileService interface {
|
|||||||
ConvertLog(req dto.PageInfo) (int64, []response.FileConvertLog, error)
|
ConvertLog(req dto.PageInfo) (int64, []response.FileConvertLog, error)
|
||||||
BatchGetRemarks(req request.FileRemarkBatch) map[string]string
|
BatchGetRemarks(req request.FileRemarkBatch) map[string]string
|
||||||
SetRemark(req request.FileRemarkUpdate) error
|
SetRemark(req request.FileRemarkUpdate) error
|
||||||
AISearch(req request.FileAISearch, readOnly ...bool) (*response.FileAISearchResult, error)
|
AISearch(req request.FileAISearch) (*response.FileAISearchResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
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
|
total, start, end := len(files), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||||
if start > total {
|
if start > total {
|
||||||
backData = make([]response.UploadInfo, 0)
|
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 {
|
if err := preflightCompressTool(files.CompressType(c.Type)); err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
taskItem.AddSubTask(c.Name, func(t *task.Task) error {
|
taskItem.AddSubTask(taskName, func(t *task.Task) error {
|
||||||
t.LogStart(c.Name)
|
logFileTaskSources(t, c.Files)
|
||||||
|
t.Log(i18n.GetMsgWithMap("FileTaskFormat", map[string]interface{}{"format": strconv.Quote(c.Type)}))
|
||||||
compressType := files.CompressType(c.Type)
|
compressType := files.CompressType(c.Type)
|
||||||
dstFile := filepath.Join(c.Dst, c.Name)
|
dstFile := filepath.Join(c.Dst, c.Name)
|
||||||
success := false
|
success := false
|
||||||
@@ -512,13 +518,15 @@ func (f *FileService) DeCompress(c request.FileDeCompress) error {
|
|||||||
if err := preflightDecompressTool(files.CompressType(c.Type)); err != nil {
|
if err := preflightDecompressTool(files.CompressType(c.Type)); err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
taskItem.AddSubTask(c.Path, func(t *task.Task) error {
|
taskItem.AddSubTask(taskName, func(t *task.Task) error {
|
||||||
t.LogStart(c.Path)
|
logFileTaskSources(t, []string{c.Path})
|
||||||
|
t.Log(i18n.GetMsgWithMap("FileTaskFormat", map[string]interface{}{"format": strconv.Quote(c.Type)}))
|
||||||
dstExisted := fo.Stat(c.Dst)
|
dstExisted := fo.Stat(c.Dst)
|
||||||
parentDir := filepath.Dir(c.Dst)
|
parentDir := filepath.Dir(c.Dst)
|
||||||
if !fo.Stat(parentDir) {
|
if !fo.Stat(parentDir) {
|
||||||
@@ -892,6 +900,7 @@ func (f *FileService) Wget(w request.FileWget) (string, error) {
|
|||||||
key := "file-wget-" + common.GetUuid()
|
key := "file-wget-" + common.GetUuid()
|
||||||
options := files.DownloadOptions{
|
options := files.DownloadOptions{
|
||||||
IgnoreCertificate: w.IgnoreCertificate,
|
IgnoreCertificate: w.IgnoreCertificate,
|
||||||
|
UseServerFilename: w.UseServerFilename,
|
||||||
}
|
}
|
||||||
if w.UseProxy {
|
if w.UseProxy {
|
||||||
systemProxy, err := NewISettingService().GetSystemProxy()
|
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)
|
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 {
|
func (f *FileService) MvFile(m request.FileMove) error {
|
||||||
fo := files.NewFileOp()
|
fo := files.NewFileOp()
|
||||||
if err := validateFileMove(fo, m); err != nil {
|
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)) {
|
if !fileTransferLocks.Acquire(m.TaskID, getFileTransferPaths(m)) {
|
||||||
return buserr.New("TaskIsExecuting")
|
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 {
|
if err != nil {
|
||||||
fileTransferLocks.Release(m.TaskID)
|
fileTransferLocks.Release(m.TaskID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
defer fileTransferLocks.Release(m.TaskID)
|
defer fileTransferLocks.Release(m.TaskID)
|
||||||
taskItem.AddSubTaskWithOps(m.NewPath, func(t *task.Task) error {
|
taskItem.AddSubTaskWithOps(taskName, func(t *task.Task) error {
|
||||||
t.LogStart(m.NewPath)
|
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)
|
err := f.moveFileWithContext(t.TaskCtx, m)
|
||||||
if err != nil && t.TaskCtx.Err() != nil {
|
if err != nil && t.TaskCtx.Err() != nil {
|
||||||
return t.TaskCtx.Err()
|
return t.TaskCtx.Err()
|
||||||
@@ -1560,7 +1584,7 @@ func (f *FileService) ConvertLog(req dto.PageInfo) (total int64, data []response
|
|||||||
return total, data, nil
|
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))
|
root := filepath.Clean(strings.TrimSpace(req.Path))
|
||||||
if root == "" {
|
if root == "" {
|
||||||
return nil, buserr.WithDetail("ErrInvalidParams", "path is required", nil)
|
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)
|
return nil, buserr.WithDetail("ErrFileAISearchBadPattern", err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg terminalai.GeneratorConfig
|
cfg, timeout, err := terminalai.LoadFileAIRuntimeConfig()
|
||||||
var timeout time.Duration
|
aiEnabled := err == nil
|
||||||
aiEnabled := false
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
if !isDemoReadOnly(readOnly...) {
|
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)
|
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 {
|
type IFileShareService interface {
|
||||||
Create(req request.FileShareCreate) (*response.FileShareInfo, error)
|
Create(req request.FileShareCreate) (*response.FileShareInfo, error)
|
||||||
Page(req dto.PageInfo, readOnly ...bool) (int64, []response.FileShareInfo, error)
|
Page(req dto.PageInfo) (int64, []response.FileShareInfo, error)
|
||||||
GetByPath(path string, readOnly ...bool) (*response.FileShareInfo, error)
|
GetByPath(path string) (*response.FileShareInfo, error)
|
||||||
GetByCode(code string, readOnly ...bool) (*response.FileShareInfo, error)
|
GetByCode(code string) (*response.FileShareInfo, error)
|
||||||
GetPublicByCode(code string, readOnly ...bool) (*response.FileSharePublicInfo, error)
|
GetPublicByCode(code string) (*response.FileSharePublicInfo, error)
|
||||||
DeleteByPath(path string) error
|
DeleteByPath(path string) error
|
||||||
SharePathCodeMap() (map[string]string, error)
|
SharePathCodeMap() (map[string]string, error)
|
||||||
Check(code, password string, readOnly ...bool) error
|
Check(code, password string) error
|
||||||
PrepareDownload(code, password string, readOnly ...bool) (filePath, fileName string, err error)
|
PrepareDownload(code, password string) (filePath, fileName string, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIFileShareService() IFileShareService {
|
func NewIFileShareService() IFileShareService {
|
||||||
@@ -212,14 +212,14 @@ func (s *FileShareService) Create(req request.FileShareCreate) (*response.FileSh
|
|||||||
return &res, nil
|
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()
|
items, err := fileShareRepo.All()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
result := make([]response.FileShareInfo, 0, len(items))
|
result := make([]response.FileShareInfo, 0, len(items))
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
if err := s.pruneInvalidShare(item); err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
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
|
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)))
|
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByPath(strings.TrimSpace(path)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -247,7 +247,7 @@ func (s *FileShareService) GetByPath(path string, readOnly ...bool) (*response.F
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
if err := s.pruneInvalidShare(item); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
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
|
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)))
|
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByCode(strings.TrimSpace(code)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -266,7 +266,7 @@ func (s *FileShareService) GetByCode(code string, readOnly ...bool) (*response.F
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
if err := s.pruneInvalidShare(item); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
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
|
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)))
|
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByCode(strings.TrimSpace(code)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -284,7 +284,7 @@ func (s *FileShareService) GetPublicByCode(code string, readOnly ...bool) (*resp
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
if err := s.pruneInvalidShare(item); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
||||||
@@ -324,38 +324,32 @@ func (s *FileShareService) SharePathCodeMap() (map[string]string, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileShareService) Check(code, password string, readOnly ...bool) error {
|
func (s *FileShareService) Check(code, password string) error {
|
||||||
_, err := s.check(code, password, readOnly...)
|
_, err := s.check(code, password)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileShareService) PrepareDownload(code, password string, readOnly ...bool) (string, string, error) {
|
func (s *FileShareService) PrepareDownload(code, password string) (string, string, error) {
|
||||||
item, err := s.check(code, password, readOnly...)
|
item, err := s.check(code, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
return item.Path, item.FileName, nil
|
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()
|
now := time.Now().Unix()
|
||||||
if item.ExpiresUnix > 0 && now > item.ExpiresUnix {
|
if item.ExpiresUnix > 0 && now > item.ExpiresUnix {
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fileShareRepo.Delete(repo.WithByID(item.ID))
|
return fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||||
}
|
}
|
||||||
info, err := os.Stat(item.Path)
|
info, err := os.Stat(item.Path)
|
||||||
if err != nil || info.IsDir() {
|
if err != nil || info.IsDir() {
|
||||||
if isDemoReadOnly(readOnly...) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fileShareRepo.Delete(repo.WithByID(item.ID))
|
return fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||||
}
|
}
|
||||||
return nil
|
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)
|
code = strings.TrimSpace(code)
|
||||||
password = strings.TrimSpace(password)
|
password = strings.TrimSpace(password)
|
||||||
if code == "" {
|
if code == "" {
|
||||||
@@ -372,9 +366,7 @@ func (s *FileShareService) check(code, password string, readOnly ...bool) (*mode
|
|||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
if item.ExpiresUnix > 0 && now > item.ExpiresUnix {
|
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")
|
return nil, buserr.New("ErrFileShareExpired")
|
||||||
}
|
}
|
||||||
if item.PasswordHash != "" {
|
if item.PasswordHash != "" {
|
||||||
@@ -385,9 +377,7 @@ func (s *FileShareService) check(code, password string, readOnly ...bool) (*mode
|
|||||||
|
|
||||||
info, err := os.Stat(item.Path)
|
info, err := os.Stat(item.Path)
|
||||||
if err != nil || info.IsDir() {
|
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")
|
return nil, buserr.New("ErrFileSharePath")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1613
-553
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user