mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3c7fe320d |
@@ -124,7 +124,7 @@ func (b *BaseApi) PageAgents(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, list, err := agentService.Page(req)
|
||||
total, list, err := agentService.Page(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
@@ -447,7 +447,7 @@ func (b *BaseApi) PageAgentAccounts(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, list, err := agentService.PageAccounts(req)
|
||||
total, list, err := agentService.PageAccounts(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
|
||||
@@ -2,14 +2,11 @@ package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -271,7 +268,7 @@ func (b *BaseApi) PageAlertConfig(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, configs, err := alertService.PageAlertConfig(req)
|
||||
total, configs, err := alertService.PageAlertConfig(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -297,34 +294,6 @@ func (b *BaseApi) UpdateAlertConfig(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := alertService.UpdateAlertConfig(req, loadAuditUser(c)); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, repo.ErrAlertConfigRevisionConflict):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusConflict, "ALERT_CONFIG_REVISION_CONFLICT", "ErrInvalidParams", err)
|
||||
case errors.Is(err, repo.ErrAlertConfigRevisionRequired):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusConflict, "ALERT_CONFIG_REVISION_REQUIRED", "ErrInvalidParams", err)
|
||||
default:
|
||||
helper.InternalServer(c, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Update alert config status
|
||||
// @Accept json
|
||||
// @Param request body dto.AlertConfigStatusUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/config/status [post]
|
||||
// @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警配置状态 [id][status]","formatEN":"update alert config status [id][status]"}
|
||||
func (b *BaseApi) UpdateAlertConfigStatus(c *gin.Context) {
|
||||
var req dto.AlertConfigStatusUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := alertService.UpdateAlertConfigStatus(req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -377,15 +346,6 @@ func (b *BaseApi) TestAlertConfig(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if req.Type == constant.Custom {
|
||||
result, err := alertService.TestCustomAlertConfig(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
return
|
||||
}
|
||||
flag, err := alertService.TestAlertConfig(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
|
||||
@@ -120,7 +120,7 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) {
|
||||
}
|
||||
version := c.Param("version")
|
||||
appType := c.Param("type")
|
||||
appDetailDTO, err := appService.GetAppDetail(appID, version, appType)
|
||||
appDetailDTO, err := appService.GetAppDetail(appID, version, appType, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// @Security Timestamp
|
||||
// @Router /apps/ignored/detail [get]
|
||||
func (b *BaseApi) ListAppIgnored(c *gin.Context) {
|
||||
res, err := appIgnoreUpgradeService.List()
|
||||
res, err := appIgnoreUpgradeService.List(helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -21,6 +21,7 @@ func (b *BaseApi) SearchAppInstalled(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
req.ReadOnly = helper.IsDemoRequest(c)
|
||||
if req.All {
|
||||
list, err := appInstallService.SearchForWebsite(req)
|
||||
if err != nil {
|
||||
@@ -73,6 +74,7 @@ func (b *BaseApi) CheckAppInstalled(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
req.ReadOnly = helper.IsDemoRequest(c)
|
||||
checkData, err := appInstallService.CheckExist(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
@@ -115,7 +117,7 @@ func (b *BaseApi) LoadConnInfo(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
conn, err := appInstallService.LoadConnInfo(req)
|
||||
conn, err := appInstallService.LoadConnInfo(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -137,7 +139,7 @@ func (b *BaseApi) DeleteCheck(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
checkData, err := appInstallService.DeleteCheck(appInstallId)
|
||||
checkData, err := appInstallService.DeleteCheck(appInstallId, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -277,7 +279,7 @@ func (b *BaseApi) GetParams(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
content, err := appInstallService.GetParams(appInstallId)
|
||||
content, err := appInstallService.GetParams(appInstallId, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -341,7 +343,7 @@ func (b *BaseApi) GetAppInstallInfo(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
info, err := appInstallService.GetAppInstallInfo(appInstallId)
|
||||
info, err := appInstallService.GetAppInstallInfo(appInstallId, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -209,7 +209,7 @@ func (b *BaseApi) SearchBackup(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := backupService.SearchWithPage(req)
|
||||
total, list, err := backupService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -106,7 +106,7 @@ func (b *BaseApi) SearchClam(c *gin.Context) {
|
||||
// @Security Timestamp
|
||||
// @Router /toolbox/clam/base [post]
|
||||
func (b *BaseApi) LoadClamBaseInfo(c *gin.Context) {
|
||||
info, err := clamService.LoadBaseInfo()
|
||||
info, err := clamService.LoadBaseInfo(helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -65,7 +65,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := composeTemplateService.SearchWithPage(req)
|
||||
total, list, err := composeTemplateService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -85,7 +85,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) {
|
||||
// @Security Timestamp
|
||||
// @Router /containers/template [get]
|
||||
func (b *BaseApi) ListComposeTemplate(c *gin.Context) {
|
||||
list, err := composeTemplateService.List()
|
||||
list, err := composeTemplateService.List(helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -273,7 +273,7 @@ func (b *BaseApi) SearchCompose(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := containerService.PageCompose(req)
|
||||
total, list, err := containerService.PageCompose(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -387,7 +387,7 @@ func (b *BaseApi) ContainerInfo(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := containerService.ContainerInfo(req)
|
||||
data, err := containerService.ContainerInfo(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -439,7 +439,7 @@ func (b *BaseApi) ContainerItemStats(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := containerService.ContainerItemStats(c.Request.Context(), req)
|
||||
data, err := containerService.ContainerItemStats(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -144,7 +144,7 @@ func (b *BaseApi) SearchCronjob(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := cronjobService.SearchWithPage(req)
|
||||
total, list, err := cronjobService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -78,7 +78,7 @@ func (b *BaseApi) SearchDatabase(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := databaseService.SearchWithPage(req)
|
||||
total, list, err := databaseService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -147,7 +147,7 @@ func (b *BaseApi) GetDatabase(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
data, err := databaseService.Get(name)
|
||||
data, err := databaseService.Get(name, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -54,7 +54,7 @@ func (b *BaseApi) SearchMongodb(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := mongodbService.SearchWithPage(req)
|
||||
total, list, err := mongodbService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -53,7 +53,7 @@ func (b *BaseApi) ListMysqlUsers(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := mysqlService.ListUsers(req)
|
||||
data, err := mysqlService.ListUsers(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -151,7 +151,7 @@ func (b *BaseApi) SearchPostgresql(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := postgresqlService.SearchWithPage(req)
|
||||
total, list, err := postgresqlService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -42,9 +42,8 @@ var (
|
||||
fileShareService = service.NewIFileShareService()
|
||||
sshService = service.NewISSHService()
|
||||
firewallService = service.NewIFirewallService()
|
||||
firewallSettingService = service.NewIFirewallSettingService()
|
||||
forwardingService = service.NewIForwardingService()
|
||||
dockerPortGuardService = service.NewIDockerPortGuardService()
|
||||
iptablesService = service.NewIIptablesService()
|
||||
monitorService = service.NewIMonitorService()
|
||||
systemService = service.NewISystemService()
|
||||
runtimeDiagnosticsService = service.NewIRuntimeDiagnosticsService()
|
||||
|
||||
@@ -148,7 +148,7 @@ func (b *BaseApi) FileAISearch(c *gin.Context) {
|
||||
if strings.TrimSpace(req.ResponseLanguage) == "" {
|
||||
req.ResponseLanguage = strings.TrimSpace(c.GetHeader("Accept-Language"))
|
||||
}
|
||||
res, err := fileService.AISearch(req)
|
||||
res, err := fileService.AISearch(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -684,35 +684,10 @@ func (b *BaseApi) StopWget(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := files.CancelDownload(req.Key); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
files.CancelDownload(req.Key)
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Remove finished download progress records without deleting files
|
||||
// @Accept json
|
||||
// @Param request body request.FileProcessRemoveReq true "request"
|
||||
// @Success 200 {object} response.FileProcessKeys
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/wget/process/remove [post]
|
||||
// @x-panel-log {"bodyKeys":["keys"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"移除已结束下载记录 [keys]","formatEN":"Remove finished download records [keys]"}
|
||||
func (b *BaseApi) RemoveWgetRecords(c *gin.Context) {
|
||||
var req request.FileProcessRemoveReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
keys, err := files.RemoveDownloadRecords(req.Keys)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, response.FileProcessKeys{Keys: keys})
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Move file
|
||||
// @Accept json
|
||||
@@ -1658,7 +1633,7 @@ func (b *BaseApi) SearchFileShare(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, list, err := fileShareService.Page(req)
|
||||
total, list, err := fileShareService.Page(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -1682,7 +1657,7 @@ func (b *BaseApi) GetFileShareDetail(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
info, err := fileShareService.GetByPath(req.Path)
|
||||
info, err := fileShareService.GetByPath(req.Path, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -1701,7 +1676,7 @@ func (b *BaseApi) GetPublicFileShareInfo(c *gin.Context) {
|
||||
helper.BadRequest(c, errors.New("code is required"))
|
||||
return
|
||||
}
|
||||
info, err := fileShareService.GetPublicByCode(code)
|
||||
info, err := fileShareService.GetPublicByCode(code, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
if be, ok := err.(buserr.BusinessError); ok {
|
||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||
@@ -1754,7 +1729,7 @@ func (b *BaseApi) GetFileShareQRCode(c *gin.Context) {
|
||||
helper.BadRequest(c, errors.New("code is required"))
|
||||
return
|
||||
}
|
||||
if _, err := fileShareService.GetByCode(code); err != nil {
|
||||
if _, err := fileShareService.GetByCode(code, helper.IsDemoRequest(c)); err != nil {
|
||||
if be, ok := err.(buserr.BusinessError); ok {
|
||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||
return
|
||||
@@ -1836,7 +1811,7 @@ func (b *BaseApi) CheckFileShare(c *gin.Context) {
|
||||
helper.BadRequest(c, errors.New("code is required"))
|
||||
return
|
||||
}
|
||||
if err := fileShareService.Check(code, password); err != nil {
|
||||
if err := fileShareService.Check(code, password, helper.IsDemoRequest(c)); err != nil {
|
||||
if be, ok := err.(buserr.BusinessError); ok {
|
||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||
return
|
||||
@@ -1861,7 +1836,7 @@ func (b *BaseApi) DownloadFileShare(c *gin.Context) {
|
||||
helper.BadRequest(c, errors.New("code is required"))
|
||||
return
|
||||
}
|
||||
filePath, displayName, err := fileShareService.PrepareDownload(code, password)
|
||||
filePath, displayName, err := fileShareService.PrepareDownload(code, password, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
if be, ok := err.(buserr.BusinessError); ok {
|
||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||
|
||||
+252
-624
@@ -1,55 +1,34 @@
|
||||
package v2
|
||||
|
||||
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/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"
|
||||
)
|
||||
|
||||
func (b *BaseApi) UpdatePanelFirewallPort(c *gin.Context) {
|
||||
if !global.IsMaster {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
var request struct {
|
||||
OldPort uint `json:"oldPort" validate:"required,min=1,max=65535"`
|
||||
NewPort uint `json:"newPort" validate:"required,min=1,max=65535"`
|
||||
}
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := firewallService.UpdatePanelPort(c.Request.Context(), request.OldPort, request.NewPort); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Load firewall base info
|
||||
// @Accept json
|
||||
// @Param request body dto.OperationWithName true "request"
|
||||
// @Success 200 {object} dto.FirewallSubsystemStatus
|
||||
// @Success 200 {object} dto.FirewallBaseInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/base [post]
|
||||
func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
|
||||
var request dto.OperationWithName
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
var req dto.OperationWithName
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := firewallService.LoadBaseInfo(request.Name)
|
||||
var (
|
||||
data dto.FirewallBaseInfo
|
||||
err error
|
||||
)
|
||||
if req.Name == "forward" {
|
||||
data, err = forwardingService.LoadBaseInfo()
|
||||
} else {
|
||||
data, err = firewallService.LoadBaseInfo(req.Name)
|
||||
}
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -58,682 +37,331 @@ func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Page firewall rules
|
||||
// @Accept json
|
||||
// @Param request body dto.RuleSearch true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/search [post]
|
||||
func (b *BaseApi) SearchFirewallRule(c *gin.Context) {
|
||||
var req dto.RuleSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
total int64
|
||||
list interface{}
|
||||
err error
|
||||
)
|
||||
if req.Type == "forward" {
|
||||
total, list, err = forwardingService.SearchWithPage(dto.ForwardRuleSearch{
|
||||
PageInfo: req.PageInfo,
|
||||
Info: req.Info,
|
||||
Status: req.Status,
|
||||
Strategy: req.Strategy,
|
||||
})
|
||||
} else {
|
||||
total, list, err = firewallService.SearchWithPage(req)
|
||||
}
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Operate firewall
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallLifecycleOperation true "request"
|
||||
// @Success 200 {object} dto.FirewallLifecycleOperationResponse
|
||||
// @Param request body dto.FirewallOperation true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] 防火墙","formatEN":"[operation] firewall"}
|
||||
func (b *BaseApi) OperateFirewall(c *gin.Context) {
|
||||
var request dto.FirewallLifecycleOperation
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
var req dto.FirewallOperation
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result, err := firewallService.QueueFirewallOperation(request)
|
||||
if err != nil {
|
||||
if err := firewallService.OperateFirewall(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, result)
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Load forwarding base info
|
||||
// @Summary Create group
|
||||
// @Accept json
|
||||
// @Success 200 {object} dto.FirewallSubsystemStatus
|
||||
// @Param request body dto.PortRuleOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/forward/base [post]
|
||||
func (b *BaseApi) LoadForwardingBaseInfo(c *gin.Context) {
|
||||
data, err := forwardingService.LoadBaseInfo()
|
||||
if err != nil {
|
||||
// @Router /hosts/firewall/port [post]
|
||||
// @x-panel-log {"bodyKeys":["port","strategy"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加端口规则 [strategy] [port]","formatEN":"create port rules [strategy][port]"}
|
||||
func (b *BaseApi) OperatePortRule(c *gin.Context) {
|
||||
var req dto.PortRuleOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.OperatePortRule(req, true); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// OperateForwardRule
|
||||
// @Tags Firewall
|
||||
// @Summary Operate forward rule
|
||||
// @Accept json
|
||||
// @Param request body dto.ForwardRuleOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/forward [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新端口转发规则","formatEN":"update port forward rules"}
|
||||
func (b *BaseApi) OperateForwardRule(c *gin.Context) {
|
||||
var req dto.ForwardRuleOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := forwardingService.Operate(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Page forwarding rules
|
||||
// @Summary Operate Ip rule
|
||||
// @Accept json
|
||||
// @Param request body dto.ForwardRuleSearch true "request"
|
||||
// @Param request body dto.AddrRuleOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/ip [post]
|
||||
// @x-panel-log {"bodyKeys":["strategy","address"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 ip 规则 [strategy] [address]","formatEN":"create address rules [strategy][address]"}
|
||||
func (b *BaseApi) OperateIPRule(c *gin.Context) {
|
||||
var req dto.AddrRuleOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.OperateAddressRule(req, true); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Batch operate rule
|
||||
// @Accept json
|
||||
// @Param request body dto.BatchRuleOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/batch [post]
|
||||
func (b *BaseApi) BatchOperateRule(c *gin.Context) {
|
||||
var req dto.BatchRuleOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.BatchOperateRule(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Update rule description
|
||||
// @Accept json
|
||||
// @Param request body dto.UpdateFirewallDescription true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/update/description [post]
|
||||
func (b *BaseApi) UpdateFirewallDescription(c *gin.Context) {
|
||||
var req dto.UpdateFirewallDescription
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.UpdateDescription(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Update port rule
|
||||
// @Accept json
|
||||
// @Param request body dto.PortRuleUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/update/port [post]
|
||||
func (b *BaseApi) UpdatePortRule(c *gin.Context) {
|
||||
var req dto.PortRuleUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.UpdatePortRule(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Update Ip rule
|
||||
// @Accept json
|
||||
// @Param request body dto.AddrRuleUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/update/addr [post]
|
||||
func (b *BaseApi) UpdateAddrRule(c *gin.Context) {
|
||||
var req dto.AddrRuleUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.UpdateAddrRule(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary search iptables filter rules
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchPageWithType true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/forward/search [post]
|
||||
func (b *BaseApi) SearchForwardingRules(c *gin.Context) {
|
||||
var request dto.ForwardRuleSearch
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
// @Router /hosts/firewall/filter/rule/search [post]
|
||||
func (b *BaseApi) SearchFilterRules(c *gin.Context) {
|
||||
var req dto.SearchPageWithType
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, items, err := forwardingService.SearchRules(request)
|
||||
|
||||
total, list, err := iptablesService.Search(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{Items: items, Total: total})
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Operate forwarding rules
|
||||
// @Summary Operate iptables filter rule
|
||||
// @Accept json
|
||||
// @Param request body dto.ForwardRuleOperate true "request"
|
||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||
// @Param request body dto.IptablesRuleOp true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/forward/operate [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新端口转发规则","formatEN":"update port forward rules"}
|
||||
func (b *BaseApi) OperateForwardingRules(c *gin.Context) {
|
||||
var request dto.ForwardRuleOperate
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
// @Router /hosts/firewall/filter/rule/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operation","chain"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] filter规则到 [chain]","formatEN":"[operation] filter rule to [chain]"}
|
||||
func (b *BaseApi) OperateFilterRule(c *gin.Context) {
|
||||
var req dto.IptablesRuleOp
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result, err := forwardingService.OperateRules(request)
|
||||
if err != nil {
|
||||
if err := iptablesService.OperateRule(req, true); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Enable forwarding
|
||||
// @Summary Batch operate iptables filter rules
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallInitializationTask true "request"
|
||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||
// @Param request body dto.IptablesBatchOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/forward/enable [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"初始化并启用端口转发","formatEN":"initialize and enable port forwarding"}
|
||||
func (b *BaseApi) EnableForwarding(c *gin.Context) {
|
||||
var request dto.FirewallInitializationTask
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
// @Router /hosts/firewall/filter/rule/batch [post]
|
||||
func (b *BaseApi) BatchOperateFilterRule(c *gin.Context) {
|
||||
var req dto.IptablesBatchOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := forwardingService.QueueInitialization(request)
|
||||
if err != nil {
|
||||
|
||||
if err := iptablesService.BatchOperate(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Apply/Unload/Init firewall filter chain
|
||||
// @Summary Apply/Unload/Init iptables filter
|
||||
// @Accept json
|
||||
// @Param request body dto.FilterChainOperation true "request"
|
||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||
// @Param request body dto.IptablesOp true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/filter/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 防火墙过滤链","formatEN":"[operate] firewall filter chain"}
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] iptables filter 防火墙","formatEN":"[operate] iptables filter firewall"}
|
||||
func (b *BaseApi) OperateFilterChain(c *gin.Context) {
|
||||
var request dto.FilterChainOperation
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
var req dto.IptablesOp
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if request.Operate == "init-base" {
|
||||
result, err := firewallService.QueueFilterChainInitialization(request)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
return
|
||||
var err error
|
||||
if req.Operate == "init-forward" {
|
||||
err = forwardingService.Enable()
|
||||
} else {
|
||||
err = iptablesService.Operate(req)
|
||||
}
|
||||
if err := firewallService.OperateFilterChain(request); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, dto.FilterChainOperationResponse{})
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary List unified firewall v2 rules
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleInventory true "request"
|
||||
// @Success 200 {object} dto.FirewallRuleInventoryResponse
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/search [post]
|
||||
func (b *BaseApi) SearchFirewallRules(c *gin.Context) {
|
||||
var request dto.FirewallRuleInventory
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
inventory, err := firewallService.Inventory(c.Request.Context(), request)
|
||||
if err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, inventory)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Reset firewall rules
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleReset true "request"
|
||||
// @Success 200 {object} dto.FirewallRuleResetResponse
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/reset [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"重置防火墙规则","formatEN":"reset firewall rules"}
|
||||
func (b *BaseApi) ResetFirewallRules(c *gin.Context) {
|
||||
var request dto.FirewallRuleReset
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := firewallService.Reset(c.Request.Context(), request)
|
||||
if err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Load one provider-native firewall object definition
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallNativeDetail true "request"
|
||||
// @Success 200 {string} string
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/native/detail [post]
|
||||
func (b *BaseApi) LoadFirewallNativeDetail(c *gin.Context) {
|
||||
var request dto.FirewallNativeDetail
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
info, err := firewallService.LoadFirewallNativeDetail(c.Request.Context(), request)
|
||||
if err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary 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)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Queue firewall rule creation
|
||||
// @Description Creation and import return a taskID immediately; validation and execution results are written to the task log.
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleCreate true "request"
|
||||
// @Success 200 {object} dto.FirewallRuleCreateResponse
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Failure 409 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加防火墙规则","formatEN":"create firewall rules"}
|
||||
func (b *BaseApi) CreateFirewallRules(c *gin.Context) {
|
||||
var request dto.FirewallRuleCreate
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := firewallService.Create(c.Request.Context(), request)
|
||||
if err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary 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
|
||||
// @Summary load chain status with name
|
||||
// @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"
|
||||
// @Param request body dto.OperationWithName true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/settings/whitelist [post]
|
||||
// @x-panel-log {"bodyKeys":["rule"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建防火墙端口白名单","formatEN":"create firewall port whitelist"}
|
||||
func (b *BaseApi) CreateFirewallPortWhitelist(c *gin.Context) {
|
||||
var request dto.FirewallPortWhitelistCreate
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
// @Router /hosts/firewall/filter/chain/status [post]
|
||||
func (b *BaseApi) LoadChainStatus(c *gin.Context) {
|
||||
var req dto.OperationWithName
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := firewallSettingService.CreatePortWhitelist(c.Request.Context(), request); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @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)
|
||||
helper.SuccessWithData(c, iptablesService.LoadChainStatus(req))
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func (b *BaseApi) SearchFtp(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := ftpService.SearchWithPage(req)
|
||||
total, list, err := ftpService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
+17
-9
@@ -3,8 +3,9 @@ package v2
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -16,20 +17,27 @@ import (
|
||||
// @Security Timestamp
|
||||
// @Router /ai/gpu/load [get]
|
||||
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
|
||||
ok, client := accelerator.New()
|
||||
ok, client := gpu.New()
|
||||
if ok {
|
||||
snapshot, err := client.Collect(c.Request.Context())
|
||||
info, err := client.LoadGpuInfo()
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
if warning := snapshot.Warning(); warning != nil {
|
||||
global.LOG.Warnf("load realtime accelerator data partially failed, err: %v", warning)
|
||||
}
|
||||
helper.SuccessWithData(c, &snapshot.Info)
|
||||
helper.SuccessWithData(c, info)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, &accelerator.Info{})
|
||||
xpuOK, xpuClient := xpu.New()
|
||||
if xpuOK {
|
||||
info, err := xpuClient.LoadGpuInfo()
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, &common.GpuInfo{})
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
|
||||
@@ -30,16 +30,6 @@ func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) {
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func ErrorWithBusinessCode(ctx *gin.Context, code int, businessCode, msgKey string, err error) {
|
||||
res := dto.Response{
|
||||
Code: code,
|
||||
ErrorCode: businessCode,
|
||||
Message: i18n.GetMsgWithDetail(msgKey, err.Error()),
|
||||
}
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func ErrorWithDetailAndData(ctx *gin.Context, code int, msgKey string, err error, data interface{}) {
|
||||
res := dto.Response{
|
||||
Code: code,
|
||||
@@ -58,6 +48,10 @@ func BadRequest(ctx *gin.Context, err error) {
|
||||
ErrorWithDetail(ctx, http.StatusBadRequest, "ErrInvalidParams", err)
|
||||
}
|
||||
|
||||
func IsDemoRequest(ctx *gin.Context) bool {
|
||||
return global.CONF.Base.IsDemo || ctx.GetHeader(constant.DemoModeHeader) == strconv.FormatBool(true)
|
||||
}
|
||||
|
||||
func SuccessWithData(ctx *gin.Context, data interface{}) {
|
||||
if data == nil {
|
||||
data = gin.H{}
|
||||
|
||||
@@ -100,7 +100,7 @@ func (b *BaseApi) SearchHost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := hostService.SearchWithPage(req)
|
||||
total, list, err := hostService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ func (b *BaseApi) SearchRepo(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := imageRepoService.Page(req)
|
||||
total, list, err := imageRepoService.Page(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -19,7 +19,7 @@ func (b *BaseApi) PageMcpServers(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
list := mcpServerService.Page(req)
|
||||
list := mcpServerService.Page(req, helper.IsDemoRequest(c))
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ func (b *BaseApi) SearchRuntimes(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
req.ReadOnly = helper.IsDemoRequest(c)
|
||||
total, items, err := runtimeService.Page(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
@@ -132,7 +133,7 @@ func (b *BaseApi) GetRuntime(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
res, err := runtimeService.Get(id)
|
||||
res, err := runtimeService.Get(id, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -529,7 +530,7 @@ func (b *BaseApi) GetPHPContainerConfig(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
data, err := runtimeService.GetPHPContainerConfig(id)
|
||||
data, err := runtimeService.GetPHPContainerConfig(id, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -191,7 +191,7 @@ func (b *BaseApi) LoadBaseDir(c *gin.Context) {
|
||||
// @Security Timestamp
|
||||
// @Router /settings/ssh/conn [get]
|
||||
func (b *BaseApi) LoadLocalConn(c *gin.Context) {
|
||||
helper.SuccessWithData(c, settingService.GetLocalConn())
|
||||
helper.SuccessWithData(c, settingService.GetLocalConn(helper.IsDemoRequest(c)))
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
|
||||
@@ -144,7 +144,7 @@ func (b *BaseApi) SearchRootCert(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, data, err := sshService.SearchRootCerts(req)
|
||||
total, data, err := sshService.SearchRootCerts(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -19,7 +19,7 @@ func (b *BaseApi) PageTensorRTLLMs(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
list := tensorrtLLMService.Page(req)
|
||||
list := tensorrtLLMService.Page(req, helper.IsDemoRequest(c))
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
|
||||
+32
-154
@@ -1,14 +1,11 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
@@ -22,35 +19,29 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// @Tags Terminal
|
||||
// @Summary Ws local terminal
|
||||
// @Param command query string false "command"
|
||||
// @Param session query string false "session id to reattach"
|
||||
// @Param terminalPersistent query boolean false "allow recovery after an unexpected disconnect"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/terminal/local [get]
|
||||
func (b *BaseApi) WsLocalTerminal(c *gin.Context) {
|
||||
b.runSSHSession(c, "local", loadLocalConn, c.DefaultQuery("command", ""))
|
||||
b.runSSHSession(c, loadLocalConn, c.DefaultQuery("command", ""))
|
||||
}
|
||||
|
||||
// @Tags Terminal
|
||||
// @Summary Ws host SSH
|
||||
// @Param id query integer false "id"
|
||||
// @Param command query string false "command"
|
||||
// @Param session query string false "session id to reattach"
|
||||
// @Param title query string false "session title shown in the session list"
|
||||
// @Param terminalPersistent query boolean false "allow recovery after an unexpected disconnect"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/terminal/ssh [get]
|
||||
func (b *BaseApi) WsHostSSH(c *gin.Context) {
|
||||
b.runSSHSession(c, "ssh", func() (*ssh.SSHClient, error) {
|
||||
b.runSSHSession(c, func() (*ssh.SSHClient, error) {
|
||||
hostID, _ := strconv.Atoi(c.DefaultQuery("id", "0"))
|
||||
if hostID <= 0 {
|
||||
return nil, errors.New("missing host id")
|
||||
@@ -74,33 +65,26 @@ func (b *BaseApi) WsContainerTerminal(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
identity, ok := loadTerminalIdentity(c)
|
||||
if !ok {
|
||||
_ = wshandleError(wsConn, errors.New("missing terminal identity"))
|
||||
|
||||
slave, err := loadContainerTerminalCommand(c)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
|
||||
opts := terminal.SessionOptions{
|
||||
Identity: identity,
|
||||
Kind: "container",
|
||||
Target: containerTerminalTarget(c),
|
||||
Cols: cols,
|
||||
Rows: rows,
|
||||
}
|
||||
if err := terminal.ServeCommand(wsConn, strings.TrimSpace(c.Query("session")), opts, func() (*terminal.LocalCommand, error) {
|
||||
return loadContainerTerminalCommand(c)
|
||||
}); err != nil {
|
||||
_ = wshandleError(wsConn, err)
|
||||
}
|
||||
}
|
||||
quitChan := make(chan bool, 3)
|
||||
tty.Start(quitChan)
|
||||
go slave.Wait(quitChan)
|
||||
|
||||
func containerTerminalTarget(c *gin.Context) string {
|
||||
query := c.Request.URL.Query()
|
||||
for _, key := range []string{"cols", "rows", "session", "terminalRevalidate"} {
|
||||
query.Del(key)
|
||||
}
|
||||
sum := sha256.Sum256([]byte(query.Encode()))
|
||||
return hex.EncodeToString(sum[:])
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
closeTerminalConn(wsConn)
|
||||
}
|
||||
|
||||
func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
|
||||
@@ -114,7 +98,7 @@ func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
|
||||
return nil, 0, 0, false
|
||||
}
|
||||
|
||||
if global.CONF.Base.IsDemo {
|
||||
if helper.IsDemoRequest(c) {
|
||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||
return nil, 0, 0, false
|
||||
}
|
||||
@@ -131,138 +115,32 @@ func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
|
||||
return wsConn, cols, rows, true
|
||||
}
|
||||
|
||||
func (b *BaseApi) runSSHSession(c *gin.Context, kind string, connect func() (*ssh.SSHClient, error), command string) {
|
||||
func (b *BaseApi) runSSHSession(c *gin.Context, connect func() (*ssh.SSHClient, error), command string) {
|
||||
wsConn, cols, rows, ok := prepareTerminalSession(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
identity, ok := loadTerminalIdentity(c)
|
||||
if !ok {
|
||||
_ = wshandleError(wsConn, errors.New("missing terminal identity"))
|
||||
return
|
||||
}
|
||||
|
||||
hostID := 0
|
||||
if kind == "ssh" {
|
||||
hostID, _ = strconv.Atoi(c.DefaultQuery("id", "0"))
|
||||
client, clientErr := connect()
|
||||
if wshandleError(wsConn, errors.WithMessage(clientErr, "failed to set up the connection. Please check the host information")) {
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// @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"))
|
||||
sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, command)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, terminal.List(identity))
|
||||
}
|
||||
defer sws.Close()
|
||||
|
||||
// @Tags Terminal
|
||||
// @Summary Close a terminal session
|
||||
// @Accept json
|
||||
// @Param request body dto.TerminalSessionClose true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/terminal/sessions/close [post]
|
||||
func (b *BaseApi) CloseTerminalSession(c *gin.Context) {
|
||||
var req dto.TerminalSessionClose
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
identity, ok := loadTerminalIdentity(c)
|
||||
if !ok {
|
||||
helper.BadRequest(c, errors.New("missing terminal identity"))
|
||||
return
|
||||
}
|
||||
if err := terminal.CloseSession(req.ID, identity); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
quitChan := make(chan bool, 3)
|
||||
sws.Start(quitChan)
|
||||
go sws.Wait(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)
|
||||
}
|
||||
<-quitChan
|
||||
|
||||
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
|
||||
closeTerminalConn(wsConn)
|
||||
}
|
||||
|
||||
func closeTerminalConn(wsConn *websocket.Conn) {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (b *BaseApi) TerminalCapabilities(c *gin.Context) {
|
||||
helper.SuccessWithData(c, gin.H{"apiKeyLeaseVersion": 1})
|
||||
}
|
||||
@@ -255,7 +255,7 @@ func (b *BaseApi) GetHTTPSConfig(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
res, err := websiteService.GetWebsiteHTTPS(id)
|
||||
res, err := websiteService.GetWebsiteHTTPS(id, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -1080,7 +1080,7 @@ func (b *BaseApi) GetWebsiteResource(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
res, err := websiteService.GetWebsiteResource(id)
|
||||
res, err := websiteService.GetWebsiteResource(id, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ func (b *BaseApi) PageWebsiteAcmeAccount(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, accounts, err := websiteAcmeAccountService.Page(req)
|
||||
total, accounts, err := websiteAcmeAccountService.Page(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -24,7 +24,7 @@ func (b *BaseApi) PageWebsiteCA(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, cas, err := websiteCAService.Page(req)
|
||||
total, cas, err := websiteCAService.Page(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -70,7 +70,7 @@ func (b *BaseApi) GetWebsiteCA(c *gin.Context) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res, err := websiteCAService.GetCA(id)
|
||||
res, err := websiteCAService.GetCA(id, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ func (b *BaseApi) PageWebsiteDnsAccount(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, accounts, err := websiteDnsAccountService.Page(req)
|
||||
total, accounts, err := websiteDnsAccountService.Page(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -28,7 +28,7 @@ func (b *BaseApi) PageWebsiteSSL(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, accounts, err := websiteSSLService.Page(req)
|
||||
total, accounts, err := websiteSSLService.Page(req, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -159,7 +159,7 @@ func (b *BaseApi) GetWebsiteSSLByWebsiteId(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
websiteSSL, err := websiteSSLService.GetWebsiteSSL(websiteId)
|
||||
websiteSSL, err := websiteSSLService.GetWebsiteSSL(websiteId, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -181,7 +181,7 @@ func (b *BaseApi) GetWebsiteSSLById(c *gin.Context) {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
websiteSSL, err := websiteSSLService.GetSSL(id)
|
||||
websiteSSL, err := websiteSSLService.GetSSL(id, helper.IsDemoRequest(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
+7
-16
@@ -162,25 +162,16 @@ type AgentWebsiteBindReq struct {
|
||||
}
|
||||
|
||||
type AgentModelConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
Model string `json:"model" validate:"required"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
Metadata []AgentModelMetadata `json:"metadata" validate:"dive"`
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
Model string `json:"model" validate:"required"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
}
|
||||
|
||||
type AgentModelConfig struct {
|
||||
AccountID uint `json:"accountId"`
|
||||
Model string `json:"model"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
Metadata []AgentModelMetadata `json:"metadata"`
|
||||
}
|
||||
|
||||
type AgentModelMetadata struct {
|
||||
Model string `json:"model" validate:"required"`
|
||||
InputMode string `json:"inputMode" validate:"required,oneof=auto text image"`
|
||||
ContextWindow int `json:"contextWindow" validate:"min=0"`
|
||||
MaxTokens int `json:"maxTokens" validate:"min=0"`
|
||||
AccountID uint `json:"accountId"`
|
||||
Model string `json:"model"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
}
|
||||
|
||||
type AgentHermesChatSessionItem struct {
|
||||
|
||||
+16
-29
@@ -151,25 +151,16 @@ type AlertLog struct {
|
||||
}
|
||||
|
||||
type AlertDetail struct {
|
||||
LicenseId string `json:"licenseId"`
|
||||
Type string `json:"type"`
|
||||
SubType string `json:"subType"`
|
||||
Title string `json:"title"`
|
||||
Method string `json:"method"`
|
||||
LicenseCode string `json:"licenseCode"`
|
||||
DeviceId string `json:"deviceId"`
|
||||
Project string `json:"project"`
|
||||
Params []Param `json:"params"`
|
||||
Phone string `json:"phone"`
|
||||
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"`
|
||||
LicenseId string `json:"licenseId"`
|
||||
Type string `json:"type"`
|
||||
SubType string `json:"subType"`
|
||||
Title string `json:"title"`
|
||||
Method string `json:"method"`
|
||||
LicenseCode string `json:"licenseCode"`
|
||||
DeviceId string `json:"deviceId"`
|
||||
Project string `json:"project"`
|
||||
Params []Param `json:"params"`
|
||||
Phone string `json:"phone"`
|
||||
}
|
||||
|
||||
type AlertRule struct {
|
||||
@@ -304,19 +295,15 @@ type OfflineQueryRequest struct {
|
||||
}
|
||||
|
||||
type AlertConfigUpdate struct {
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Config string `json:"config"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Revision *time.Time `json:"revision"`
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Config string `json:"config"`
|
||||
DisplayName string `json:"displayName"`
|
||||
}
|
||||
|
||||
type AlertConfigTest struct {
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Config string `json:"config"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Sender string `json:"sender"`
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
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,10 +6,9 @@ type PageResult struct {
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
ErrorCode string `json:"errorCode,omitempty"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
|
||||
@@ -121,7 +121,6 @@ type DashboardCurrent struct {
|
||||
NetBytesRecv uint64 `json:"netBytesRecv"`
|
||||
|
||||
GPUData []GPUInfo `json:"gpuData"`
|
||||
NPUData []NPUInfo `json:"npuData"`
|
||||
XPUData []XPUInfo `json:"xpuData"`
|
||||
|
||||
TopCPUItems []Process `json:"topCPUItems"`
|
||||
@@ -159,10 +158,7 @@ type DiskInfo struct {
|
||||
type GPUInfo struct {
|
||||
Type string `json:"type"`
|
||||
Index uint `json:"index"`
|
||||
NPUIndex uint `json:"npuIndex"`
|
||||
ChipIndex uint `json:"chipIndex"`
|
||||
ProductName string `json:"productName"`
|
||||
BusID string `json:"busID"`
|
||||
GPUUtil string `json:"gpuUtil"`
|
||||
Temperature string `json:"temperature"`
|
||||
PerformanceState string `json:"performanceState"`
|
||||
@@ -175,27 +171,6 @@ type GPUInfo struct {
|
||||
FanSpeed string `json:"fanSpeed"`
|
||||
}
|
||||
|
||||
type NPUInfo struct {
|
||||
Type string `json:"type"`
|
||||
Index uint `json:"index"`
|
||||
NPUIndex uint `json:"npuIndex"`
|
||||
ChipIndex uint `json:"chipIndex"`
|
||||
ProductName string `json:"productName"`
|
||||
BusID string `json:"busID"`
|
||||
Health string `json:"health"`
|
||||
Temperature string `json:"temperature"`
|
||||
PowerDraw string `json:"powerDraw"`
|
||||
AICore string `json:"aiCore"`
|
||||
MemUsed string `json:"memUsed"`
|
||||
MemTotal string `json:"memTotal"`
|
||||
MemoryUsed string `json:"memoryUsed"`
|
||||
MemoryTotal string `json:"memoryTotal"`
|
||||
HBMUsed string `json:"hbmUsed"`
|
||||
HBMTotal string `json:"hbmTotal"`
|
||||
HugepagesUsed string `json:"hugepagesUsed"`
|
||||
HugepagesTotal string `json:"hugepagesTotal"`
|
||||
}
|
||||
|
||||
type AppLauncher struct {
|
||||
Key string `json:"key"`
|
||||
Type string `json:"type"`
|
||||
@@ -228,13 +203,11 @@ type LauncherOption struct {
|
||||
}
|
||||
|
||||
type XPUInfo struct {
|
||||
DeviceID int `json:"deviceID"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
PciBdfAddress string `json:"pciBdfAddress"`
|
||||
Memory string `json:"memory"`
|
||||
Temperature string `json:"temperature"`
|
||||
GPUUtil string `json:"gpuUtil"`
|
||||
MemoryUsed string `json:"memoryUsed"`
|
||||
Power string `json:"power"`
|
||||
MemoryUtil string `json:"memoryUtil"`
|
||||
DeviceID int `json:"deviceID"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
Memory string `json:"memory"`
|
||||
Temperature string `json:"temperature"`
|
||||
MemoryUsed string `json:"memoryUsed"`
|
||||
Power string `json:"power"`
|
||||
MemoryUtil string `json:"memoryUtil"`
|
||||
}
|
||||
|
||||
+73
-362
@@ -1,389 +1,100 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||
firewallsync "github.com/1Panel-dev/1Panel/agent/utils/firewall/sync"
|
||||
)
|
||||
|
||||
type FirewallSubsystemStatus struct {
|
||||
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 FirewallBaseInfo struct {
|
||||
Name string `json:"name"`
|
||||
IsExist bool `json:"isExist"`
|
||||
IsActive bool `json:"isActive"`
|
||||
IsInit bool `json:"isInit"`
|
||||
IsBind bool `json:"isBind"`
|
||||
Version string `json:"version"`
|
||||
PingStatus string `json:"pingStatus"`
|
||||
}
|
||||
|
||||
type FirewallLifecycleOperation struct {
|
||||
type RuleSearch struct {
|
||||
PageInfo
|
||||
Info string `json:"info"`
|
||||
Status string `json:"status"`
|
||||
Strategy string `json:"strategy"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
}
|
||||
|
||||
type FirewallOperation struct {
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop restart disableBanPing enableBanPing"`
|
||||
WithDockerRestart bool `json:"withDockerRestart"`
|
||||
}
|
||||
|
||||
type FirewallLifecycleOperationResponse struct {
|
||||
TaskID string `json:"taskID,omitempty"`
|
||||
Queued bool `json:"queued"`
|
||||
type PortRuleOperate struct {
|
||||
ID uint `json:"id"`
|
||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||
Chain string `json:"chain"`
|
||||
Address string `json:"address"`
|
||||
Port string `json:"port" validate:"required"`
|
||||
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
|
||||
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
|
||||
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type FirewallBackendOption struct {
|
||||
Name string `json:"name"`
|
||||
Installed bool `json:"installed"`
|
||||
Active bool `json:"active"`
|
||||
Initialized bool `json:"initialized"`
|
||||
Bound bool `json:"bound"`
|
||||
Supported bool `json:"supported"`
|
||||
SupportReason string `json:"supportReason,omitempty"`
|
||||
Implementation string `json:"implementation,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
IPv4 FirewallBackendFamilyStatus `json:"ipv4"`
|
||||
IPv6 FirewallBackendFamilyStatus `json:"ipv6"`
|
||||
type UpdateFirewallDescription struct {
|
||||
Type string `json:"type"`
|
||||
Chain string `json:"chain"`
|
||||
SrcIP string `json:"srcIP"`
|
||||
DstIP string `json:"dstIP"`
|
||||
SrcPort string `json:"srcPort"`
|
||||
DstPort string `json:"dstPort"`
|
||||
Protocol string `json:"protocol"`
|
||||
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
|
||||
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type FirewallBackendFamilyStatus struct {
|
||||
Available bool `json:"available"`
|
||||
Initialized bool `json:"initialized"`
|
||||
Bound bool `json:"bound"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
ForwardPolicy string `json:"forwardPolicy,omitempty"`
|
||||
RAInterfaces []string `json:"raInterfaces,omitempty"`
|
||||
type AddrRuleOperate struct {
|
||||
ID uint `json:"id"`
|
||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||
Address string `json:"address" validate:"required"`
|
||||
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
|
||||
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type FirewallBackendGroup struct {
|
||||
Selected string `json:"selected"`
|
||||
Current string `json:"current,omitempty"`
|
||||
Options []FirewallBackendOption `json:"options"`
|
||||
type PortRuleUpdate struct {
|
||||
OldRule PortRuleOperate `json:"oldRule"`
|
||||
NewRule PortRuleOperate `json:"newRule"`
|
||||
}
|
||||
|
||||
type FirewallSettings struct {
|
||||
System FirewallBackendGroup `json:"system"`
|
||||
Forwarding FirewallBackendGroup `json:"forwarding"`
|
||||
Docker FirewallBackendGroup `json:"docker"`
|
||||
PingStatus string `json:"pingStatus"`
|
||||
PortWhitelist []filter.PortWhitelist `json:"portWhiteList"`
|
||||
PanelPort string `json:"panelPort"`
|
||||
SSHPort string `json:"sshPort"`
|
||||
type AddrRuleUpdate struct {
|
||||
OldRule AddrRuleOperate `json:"oldRule"`
|
||||
NewRule AddrRuleOperate `json:"newRule"`
|
||||
}
|
||||
|
||||
type FirewallPortWhitelistCreate struct {
|
||||
Rule filter.PortWhitelist `json:"rule" validate:"required"`
|
||||
type BatchRuleOperate struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
Rules []PortRuleOperate `json:"rules"`
|
||||
}
|
||||
|
||||
type FirewallPortWhitelistUpdate struct {
|
||||
OldRule filter.PortWhitelist `json:"oldRule" validate:"required"`
|
||||
Rule filter.PortWhitelist `json:"rule" validate:"required"`
|
||||
type IptablesOp struct {
|
||||
Name string `json:"name" validate:"required,oneof=1PANEL_INPUT 1PANEL_OUTPUT 1PANEL_BASIC 1PANEL_FORWARD"`
|
||||
Operate string `json:"operate" validate:"required,oneof=init-base init-forward init-advance bind-base unbind-base bind unbind"`
|
||||
}
|
||||
|
||||
type FirewallPortWhitelistDelete struct {
|
||||
Rule *filter.PortWhitelist `json:"rule" validate:"required"`
|
||||
type IptablesRuleOp struct {
|
||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||
ID uint `json:"id"`
|
||||
Chain string `json:"chain" validate:"required,oneof=1PANEL_BASIC 1PANEL_BASIC_BEFORE 1PANEL_INPUT 1PANEL_OUTPUT"`
|
||||
Protocol string `json:"protocol"`
|
||||
SrcIP string `json:"srcIP"`
|
||||
SrcPort uint `json:"srcPort"`
|
||||
DstIP string `json:"dstIP"`
|
||||
DstPort uint `json:"dstPort"`
|
||||
Strategy string `json:"strategy" validate:"required,oneof=accept drop reject"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type FirewallBackendOperation struct {
|
||||
Subsystem string `json:"subsystem" validate:"required,oneof=system forwarding docker"`
|
||||
Backend string `json:"backend" validate:"required,oneof=firewalld ufw iptables nftables"`
|
||||
Operation string `json:"operation" validate:"required,oneof=select initialize cleanup"`
|
||||
type IptablesBatchOperate struct {
|
||||
Rules []IptablesRuleOp `json:"rules"`
|
||||
}
|
||||
|
||||
type FilterChainOperation struct {
|
||||
Name string `json:"name" validate:"required,eq=1PANEL_BASIC"`
|
||||
Operate string `json:"operate" validate:"required,oneof=init-base bind-base unbind-base"`
|
||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
||||
}
|
||||
|
||||
type FilterChainOperationResponse struct {
|
||||
TaskID string `json:"taskID"`
|
||||
Queued bool `json:"queued"`
|
||||
}
|
||||
|
||||
type FirewallInitializationTask struct {
|
||||
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++
|
||||
}
|
||||
}
|
||||
type IptablesChainStatus struct {
|
||||
IsBind bool `json:"isBind"`
|
||||
DefaultStrategy string `json:"defaultStrategy"`
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ package dto
|
||||
|
||||
type ForwardRuleSearch struct {
|
||||
PageInfo
|
||||
All bool `json:"all,omitempty"`
|
||||
Info string `json:"info"`
|
||||
Status string `json:"status"`
|
||||
Strategy string `json:"strategy"`
|
||||
}
|
||||
|
||||
// ForwardRule preserves the existing firewall search response shape while
|
||||
// keeping forwarding data separate from the filter client model.
|
||||
type ForwardRule struct {
|
||||
ID uint `json:"id"`
|
||||
Chain string `json:"chain"`
|
||||
@@ -24,21 +25,16 @@ type ForwardRule struct {
|
||||
|
||||
UsedStatus string `json:"usedStatus"`
|
||||
Description string `json:"description"`
|
||||
|
||||
IsDesired bool `json:"isDesired"`
|
||||
IsRuntime bool `json:"isRuntime"`
|
||||
SyncStatus string `json:"syncStatus"`
|
||||
}
|
||||
|
||||
type ForwardRuleOperate struct {
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
Rules []ForwardRuleOperation `json:"rules" validate:"required,min=1,dive"`
|
||||
Rules []ForwardRuleOperation `json:"rules"`
|
||||
}
|
||||
|
||||
type ForwardRuleOperation struct {
|
||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||
Num string `json:"num"`
|
||||
Family string `json:"family" validate:"omitempty,oneof=ipv4 ipv6"`
|
||||
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
|
||||
Interface string `json:"interface"`
|
||||
Port string `json:"port" validate:"required"`
|
||||
|
||||
@@ -45,7 +45,6 @@ type MonitorGPUOptions struct {
|
||||
}
|
||||
type GPUChartHide struct {
|
||||
ProductName string `json:"productName"`
|
||||
Type string `json:"type"`
|
||||
Process bool `json:"process"`
|
||||
GPU bool `json:"gpu"`
|
||||
Memory bool `json:"memory"`
|
||||
|
||||
@@ -51,19 +51,13 @@ const (
|
||||
CACHE NginxKey = "cache"
|
||||
HttpPer NginxKey = "http-per"
|
||||
ProxyCache NginxKey = "proxy-cache"
|
||||
Brotli NginxKey = "brotli"
|
||||
)
|
||||
|
||||
// BrotliKeys are served from the panel-managed http.d file rather than
|
||||
// nginx.conf, because the module is optional: its directives must disappear
|
||||
// together with the module, otherwise nginx refuses to start.
|
||||
var BrotliKeys = []string{"brotli", "brotli_comp_level", "brotli_min_length", "brotli_types"}
|
||||
|
||||
var ScopeKeyMap = map[NginxKey][]string{
|
||||
Index: {"index"},
|
||||
LimitConn: {"limit_conn", "limit_rate", "limit_conn_zone"},
|
||||
SSL: {"ssl_certificate", "ssl_certificate_key"},
|
||||
HttpPer: {"server_names_hash_bucket_size", "client_header_buffer_size", "client_max_body_size", "keepalive_timeout", "gzip", "gzip_min_length", "gzip_comp_level", "gzip_types", "gzip_vary", "gzip_proxied"},
|
||||
HttpPer: {"server_names_hash_bucket_size", "client_header_buffer_size", "client_max_body_size", "keepalive_timeout", "gzip", "gzip_min_length", "gzip_comp_level"},
|
||||
}
|
||||
|
||||
var StaticFileKeyMap = map[NginxKey]struct {
|
||||
|
||||
@@ -50,10 +50,6 @@ type AppContainerConfig struct {
|
||||
Type string `json:"type"`
|
||||
SpecifyIP string `json:"specifyIP"`
|
||||
RestartPolicy string `json:"restartPolicy" validate:"omitempty,oneof=always unless-stopped no on-failure"`
|
||||
|
||||
KeepServiceName bool `json:"-"`
|
||||
SkipComposeCommonConfig bool `json:"-"`
|
||||
UseLifecycleScripts bool `json:"-"`
|
||||
}
|
||||
|
||||
type AppInstalledSearch struct {
|
||||
@@ -66,11 +62,13 @@ type AppInstalledSearch struct {
|
||||
All bool `json:"all"`
|
||||
Sync bool `json:"sync"`
|
||||
CheckUpdate bool `json:"checkUpdate"`
|
||||
ReadOnly bool `json:"-"`
|
||||
}
|
||||
|
||||
type AppInstalledInfo struct {
|
||||
Key string `json:"key" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
ReadOnly bool `json:"-"`
|
||||
}
|
||||
|
||||
type AppBackupSearch struct {
|
||||
@@ -96,8 +94,6 @@ type AppInstalledOperate struct {
|
||||
TaskID string `json:"taskID"`
|
||||
DeleteImage bool `json:"deleteImage"`
|
||||
Favorite bool `json:"favorite"`
|
||||
|
||||
UseLifecycleScripts bool `json:"-"`
|
||||
}
|
||||
|
||||
type AppInstallUpgrade struct {
|
||||
@@ -117,14 +113,11 @@ type AppInstallDelete struct {
|
||||
DeleteDB bool `json:"deleteDB"`
|
||||
DeleteImage bool `json:"deleteImage"`
|
||||
TaskID string `json:"taskID"`
|
||||
|
||||
UseLifecycleScripts bool `json:"-"`
|
||||
}
|
||||
|
||||
type AppInstalledUpdate struct {
|
||||
InstallId uint `json:"installId" validate:"required"`
|
||||
Params map[string]interface{} `json:"params" validate:"required"`
|
||||
TaskID string `json:"-"`
|
||||
AppContainerConfig
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,6 @@ type FileWget struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
IgnoreCertificate bool `json:"ignoreCertificate"`
|
||||
UseProxy bool `json:"useProxy"`
|
||||
UseServerFilename bool `json:"useServerFilename"`
|
||||
}
|
||||
|
||||
type FileMove struct {
|
||||
@@ -159,10 +158,6 @@ type FileProcessReq struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type FileProcessRemoveReq struct {
|
||||
Keys []string `json:"keys" validate:"required,min=1,max=1000"`
|
||||
}
|
||||
|
||||
type FileRoleUpdate struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
User string `json:"user" validate:"required"`
|
||||
|
||||
@@ -6,9 +6,10 @@ import (
|
||||
|
||||
type RuntimeSearch struct {
|
||||
dto.PageInfo
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
ReadOnly bool `json:"-"`
|
||||
}
|
||||
|
||||
type RuntimeCreate struct {
|
||||
@@ -66,15 +67,14 @@ type RuntimeDelete struct {
|
||||
}
|
||||
|
||||
type RuntimeUpdate struct {
|
||||
AppDetailID uint `json:"appDetailId"`
|
||||
Name string `json:"name"`
|
||||
ID uint `json:"id"`
|
||||
Image string `json:"image"`
|
||||
Version string `json:"version"`
|
||||
Rebuild bool `json:"rebuild"`
|
||||
Source string `json:"source"`
|
||||
CodeDir string `json:"codeDir"`
|
||||
Remark string `json:"remark"`
|
||||
Name string `json:"name"`
|
||||
ID uint `json:"id"`
|
||||
Image string `json:"image"`
|
||||
Version string `json:"version"`
|
||||
Rebuild bool `json:"rebuild"`
|
||||
Source string `json:"source"`
|
||||
CodeDir string `json:"codeDir"`
|
||||
Remark string `json:"remark"`
|
||||
|
||||
Params map[string]interface{} `json:"params"`
|
||||
NodeConfig
|
||||
|
||||
@@ -17,17 +17,6 @@ type NginxParam struct {
|
||||
Params []string `json:"params"`
|
||||
}
|
||||
|
||||
// NginxBrotliRes carries the brotli settings together with where they live.
|
||||
// ManagedExternally is true when the user defined brotli by hand, in which
|
||||
// case the panel only reports the values and must not write its own copy.
|
||||
// ManagedUnavailable is true when the panel could not wire the managed
|
||||
// configuration into nginx.conf at all, so the reported values are inert.
|
||||
type NginxBrotliRes struct {
|
||||
Params []NginxParam `json:"params"`
|
||||
ManagedExternally bool `json:"managedExternally"`
|
||||
ManagedUnavailable bool `json:"managedUnavailable"`
|
||||
}
|
||||
|
||||
type NginxAuthRes struct {
|
||||
Enable bool `json:"enable"`
|
||||
Items []dto.NginxAuth `json:"items"`
|
||||
|
||||
@@ -35,7 +35,7 @@ type SettingUpdate struct {
|
||||
}
|
||||
|
||||
type AgentSettingUpdate struct {
|
||||
Key string `json:"key" validate:"required,oneof=SystemIP DockerSockPath FileRecycleBin"`
|
||||
Key string `json:"key" validate:"required,oneof=SystemIP DockerSockPath FileRecycleBin FirewallPortWhiteList"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
+10
-27
@@ -1,12 +1,5 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Alert struct {
|
||||
BaseModel
|
||||
|
||||
@@ -25,11 +18,10 @@ type Alert struct {
|
||||
|
||||
type AlertTask struct {
|
||||
BaseModel
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Quota string `gorm:"type:varchar(64)" json:"quota"`
|
||||
QuotaType string `gorm:"type:varchar(64)" json:"quotaType"`
|
||||
Method string `gorm:"type:varchar(128);not null;default:'sms'" json:"method"`
|
||||
DeliveryLogID *uint `gorm:"uniqueIndex" json:"-"`
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Quota string `gorm:"type:varchar(64)" json:"quota"`
|
||||
QuotaType string `gorm:"type:varchar(64)" json:"quotaType"`
|
||||
Method string `gorm:"type:varchar(128);not null;default:'sms'" json:"method"`
|
||||
}
|
||||
|
||||
type AlertLog struct {
|
||||
@@ -49,21 +41,12 @@ type AlertLog struct {
|
||||
|
||||
type AlertConfig struct {
|
||||
BaseModel
|
||||
UID string `gorm:"type:varchar(64);not null;uniqueIndex" json:"uid"`
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Title string `gorm:"type:varchar(64);not null" json:"title"`
|
||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||
Config string `gorm:"type:text;not null" json:"config"`
|
||||
SecretConfig string `gorm:"type:text;not null;default:''" json:"-"`
|
||||
CreateUser string `gorm:"type:varchar(256)" json:"createUser"`
|
||||
UpdateUser string `gorm:"type:varchar(256)" json:"updateUser"`
|
||||
}
|
||||
|
||||
func (a *AlertConfig) BeforeCreate(_ *gorm.DB) error {
|
||||
if strings.TrimSpace(a.UID) == "" {
|
||||
a.UID = uuid.NewString()
|
||||
}
|
||||
return nil
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Title string `gorm:"type:varchar(64);not null" json:"title"`
|
||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||
Config string `gorm:"type:varchar(256);not null" json:"config"`
|
||||
CreateUser string `gorm:"type:varchar(256)" json:"createUser"`
|
||||
UpdateUser string `gorm:"type:varchar(256)" json:"updateUser"`
|
||||
}
|
||||
|
||||
type LoginLog struct {
|
||||
|
||||
+13
-46
@@ -1,51 +1,18 @@
|
||||
package model
|
||||
|
||||
type DockerPortGuardPolicy struct {
|
||||
type Firewall struct {
|
||||
BaseModel
|
||||
|
||||
UUID string `gorm:"uniqueIndex" json:"uuid"`
|
||||
ReadOnly bool `gorm:"default:false;uniqueIndex:idx_docker_port_guard_endpoint" json:"-"`
|
||||
Family string `gorm:"uniqueIndex:idx_docker_port_guard_endpoint" json:"family"`
|
||||
HostIP string `gorm:"uniqueIndex:idx_docker_port_guard_endpoint" json:"hostIP"`
|
||||
HostPort uint16 `gorm:"uniqueIndex:idx_docker_port_guard_endpoint" json:"hostPort"`
|
||||
Protocol string `gorm:"uniqueIndex:idx_docker_port_guard_endpoint" json:"protocol"`
|
||||
Mode string `json:"mode"`
|
||||
Sources string `gorm:"type:text" json:"-"`
|
||||
Description string `gorm:"type:text" json:"description"`
|
||||
NativeAction string `gorm:"default:''" json:"-"`
|
||||
NativeRules string `gorm:"type:text" json:"-"`
|
||||
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"`
|
||||
Type string `json:"type"`
|
||||
Port string `json:"port"` // Deprecated
|
||||
Address string `json:"address"` // Deprecated
|
||||
|
||||
Chain string `json:"chain"`
|
||||
Protocol string `json:"protocol"`
|
||||
SrcIP string `json:"srcIP"`
|
||||
SrcPort string `json:"srcPort"`
|
||||
DstIP string `json:"dstIP"`
|
||||
DstPort string `json:"dstPort"`
|
||||
Strategy string `gorm:"not null" json:"strategy"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
@@ -40,21 +40,12 @@ type Meta struct {
|
||||
var catalog = map[string]Meta{
|
||||
"custom": {
|
||||
Key: "custom", DisplayName: "Custom", Sort: 10, DefaultAPIType: "openai-completions", EnvKey: "CUSTOM_API_KEY",
|
||||
APIConfigs: editableAPIConfigs(true, "openai-completions", "openai-responses", "anthropic-messages", "openai-images", "dashscope-images", "openai-embeddings"),
|
||||
APIConfigs: editableAPIConfigs(true, "openai-completions", "openai-responses", "anthropic-messages", "openai-images", "openai-embeddings"),
|
||||
},
|
||||
"ollama": {
|
||||
Key: "ollama", DisplayName: "Ollama", Sort: 15, DefaultAPIType: "openai-responses",
|
||||
APIConfigs: editableAPIConfigs(false, "openai-responses", "openai-completions", "openai-embeddings"),
|
||||
},
|
||||
// llmman (https://github.com/llmmanorg/llmman): local runner with Ollama/OpenAI-compatible routes on 127.0.0.1:17434.
|
||||
"llmman": {
|
||||
Key: "llmman", DisplayName: "llmman", Sort: 16, DefaultAPIType: "openai-responses",
|
||||
APIConfigs: []APIConfig{
|
||||
{APIType: "openai-responses", BaseURL: "http://127.0.0.1:17434/v1", EditableBaseURL: true},
|
||||
{APIType: "openai-completions", BaseURL: "http://127.0.0.1:17434/v1", EditableBaseURL: true},
|
||||
{APIType: "openai-embeddings", BaseURL: "http://127.0.0.1:17434/v1", EditableBaseURL: true},
|
||||
},
|
||||
},
|
||||
"vllm": {
|
||||
Key: "vllm", DisplayName: "vLLM", Sort: 20, DefaultAPIType: "openai-completions", EnvKey: "VLLM_API_KEY",
|
||||
APIConfigs: editableAPIConfigs(false, "openai-completions", "openai-responses", "anthropic-messages", "openai-images", "openai-embeddings"),
|
||||
@@ -462,7 +453,6 @@ var legacyModelPrefixes = map[string][]string{
|
||||
"custom": {"custom"},
|
||||
"vllm": {"custom"},
|
||||
"ollama": {"ollama"},
|
||||
"llmman": {"llmman"},
|
||||
"deepseek": {"deepseek"},
|
||||
"bailian-coding-plan": {"bailian-coding-plan"},
|
||||
"ark-coding-plan": {"ark-coding-plan"},
|
||||
|
||||
@@ -43,8 +43,8 @@ func BuildOpenClawProviderPatch(provider, modelName, apiType, authMode, baseURL,
|
||||
providerKey = "moonshot"
|
||||
resolvedAPIType = "openai-completions"
|
||||
usesBearer = false
|
||||
case "ollama", "llmman":
|
||||
apiKey = provider
|
||||
case "ollama":
|
||||
apiKey = "ollama"
|
||||
usesBearer = false
|
||||
case "openai", "openrouter", "anthropic":
|
||||
preserveQualifiedModel = strings.Contains(modelName, "/")
|
||||
|
||||
@@ -27,14 +27,11 @@ type verifyErrorResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
const (
|
||||
defaultVerifyTimeout = 30 * time.Second
|
||||
defaultVerifyMaxTokens = 16
|
||||
)
|
||||
const defaultVerifyTimeout = 30 * time.Second
|
||||
|
||||
func SkipVerification(provider string) bool {
|
||||
switch provider {
|
||||
case "vllm", "ollama", "llmman", "kimi-coding":
|
||||
case "vllm", "ollama", "kimi-coding":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -119,20 +116,20 @@ func BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model stri
|
||||
}
|
||||
headers["anthropic-version"] = "2023-06-01"
|
||||
request.Body = mustJSON(map[string]interface{}{
|
||||
"model": model, "max_tokens": defaultVerifyMaxTokens, "stream": false,
|
||||
"model": model, "max_tokens": 1, "stream": false,
|
||||
"messages": []map[string]interface{}{{"role": "user", "content": []map[string]string{{"type": "text", "text": "test"}}}},
|
||||
})
|
||||
case "openai-responses":
|
||||
request.URL = baseURL + "/responses"
|
||||
headers["Authorization"] = "Bearer " + apiKey
|
||||
request.Body = mustJSON(map[string]interface{}{"model": model, "input": "test", "max_output_tokens": defaultVerifyMaxTokens, "stream": false})
|
||||
request.Body = mustJSON(map[string]interface{}{"model": model, "input": "test", "max_output_tokens": 1, "stream": false})
|
||||
default:
|
||||
request.URL = baseURL + "/chat/completions"
|
||||
if (provider != "ollama" && provider != "llmman") || strings.TrimSpace(apiKey) != "" {
|
||||
if provider != "ollama" || strings.TrimSpace(apiKey) != "" {
|
||||
headers["Authorization"] = "Bearer " + apiKey
|
||||
}
|
||||
request.Body = mustJSON(map[string]interface{}{
|
||||
"model": model, "messages": []map[string]string{{"role": "user", "content": "test"}}, "max_tokens": defaultVerifyMaxTokens, "stream": false,
|
||||
"model": model, "messages": []map[string]string{{"role": "user", "content": "test"}}, "max_tokens": 1, "stream": false,
|
||||
})
|
||||
}
|
||||
return request
|
||||
|
||||
+9
-217
@@ -1,30 +1,20 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/genproto/googleapis/type/date"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AlertRepo struct{}
|
||||
|
||||
var (
|
||||
ErrAlertConfigRevisionConflict = errors.New("alert config revision conflict")
|
||||
ErrAlertConfigRevisionRequired = errors.New("alert config revision is required")
|
||||
)
|
||||
|
||||
type IAlertRepo interface {
|
||||
WithByType(alertType string) DBOption
|
||||
WithByStatusIn(status []string) DBOption
|
||||
@@ -34,7 +24,6 @@ type IAlertRepo interface {
|
||||
WithByCreateAt(date *date.Date) DBOption
|
||||
WithByLicenseId(licenseId string) DBOption
|
||||
WithByRecordId(recordId uint) DBOption
|
||||
WithByDeliveryLogID(logID uint) DBOption
|
||||
WithByAlertMethodContainsConfigID(id uint) DBOption
|
||||
WithByMethodConfigIDs(ids []uint) DBOption
|
||||
|
||||
@@ -56,8 +45,6 @@ type IAlertRepo interface {
|
||||
CleanAlertLogs() error
|
||||
|
||||
CreateAlertTask(alertTaskBase *model.AlertTask) error
|
||||
CreatePendingAlertTask(logID, alertID uint, alertTask *model.AlertTask) (bool, error)
|
||||
FinalizePendingAlertTask(logID uint, succeeded bool, message string, fallback *model.AlertTask) (bool, error)
|
||||
DeleteAlertTask(opts ...DBOption) error
|
||||
GetAlertTask(opts ...DBOption) (model.AlertTask, error)
|
||||
LoadTaskCount(alertType string, project string, method string) (uint, uint, error)
|
||||
@@ -68,7 +55,6 @@ type IAlertRepo interface {
|
||||
GetConfigById(id uint) (model.AlertConfig, error)
|
||||
AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error)
|
||||
UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error
|
||||
UpdateAlertConfigWithRevision(maps map[string]interface{}, revision *time.Time, opts ...DBOption) error
|
||||
CreateAlertConfig(config *model.AlertConfig) error
|
||||
DeleteAlertConfig(opts ...DBOption) error
|
||||
|
||||
@@ -237,78 +223,13 @@ func (a *AlertRepo) DeleteLog(opts ...DBOption) error {
|
||||
}
|
||||
|
||||
func (a *AlertRepo) CleanAlertLogs() error {
|
||||
return global.AlertDB.Where("status <> ?", constant.AlertPushing).Delete(&model.AlertLog{}).Error
|
||||
return global.AlertDB.Where("1 = 1").Delete(&model.AlertLog{}).Error
|
||||
}
|
||||
|
||||
func (a *AlertRepo) CreateAlertTask(alertTaskBase *model.AlertTask) error {
|
||||
return global.AlertDB.Model(&model.AlertTask{}).Create(&alertTaskBase).Error
|
||||
}
|
||||
|
||||
func (a *AlertRepo) CreatePendingAlertTask(logID, alertID uint, alertTask *model.AlertTask) (bool, error) {
|
||||
if alertTask == nil {
|
||||
return false, fmt.Errorf("pending alert task is required")
|
||||
}
|
||||
created := false
|
||||
err := global.AlertDB.Transaction(func(tx *gorm.DB) error {
|
||||
var log model.AlertLog
|
||||
if err := tx.Where("id = ? AND status = ?", logID, constant.AlertPushing).First(&log).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if log.AlertId != alertID || log.Type != alertTask.Type || log.Method != alertTask.Method {
|
||||
return fmt.Errorf("pending alert task does not match delivery log %d", logID)
|
||||
}
|
||||
alertTask.DeliveryLogID = &logID
|
||||
result := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "delivery_log_id"}},
|
||||
DoNothing: true,
|
||||
}).Create(alertTask)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
created = result.RowsAffected > 0
|
||||
return nil
|
||||
})
|
||||
return created, err
|
||||
}
|
||||
|
||||
func (a *AlertRepo) FinalizePendingAlertTask(logID uint, succeeded bool, message string, fallback *model.AlertTask) (bool, error) {
|
||||
finalized := false
|
||||
err := global.AlertDB.Transaction(func(tx *gorm.DB) error {
|
||||
status := constant.AlertError
|
||||
if succeeded {
|
||||
status = constant.AlertSuccess
|
||||
message = ""
|
||||
}
|
||||
result := tx.Model(&model.AlertLog{}).
|
||||
Where("id = ? AND status = ?", logID, constant.AlertPushing).
|
||||
Updates(map[string]interface{}{"status": status, "message": message})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return nil
|
||||
}
|
||||
finalized = true
|
||||
if !succeeded {
|
||||
return tx.Where("delivery_log_id = ?", logID).Delete(&model.AlertTask{}).Error
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := tx.Model(&model.AlertTask{}).Where("delivery_log_id = ?", logID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
if fallback == nil {
|
||||
return fmt.Errorf("pending alert task metadata is unavailable for delivery log %d", logID)
|
||||
}
|
||||
fallback.DeliveryLogID = &logID
|
||||
return tx.Create(fallback).Error
|
||||
})
|
||||
return finalized, err
|
||||
}
|
||||
|
||||
func (a *AlertRepo) DeleteAlertTask(opts ...DBOption) error {
|
||||
db, _ := getAlertDB(opts...)
|
||||
return db.Delete(&model.AlertTask{}).Error
|
||||
@@ -389,23 +310,7 @@ func (a *AlertRepo) UpdateAlertConfig(maps map[string]interface{}, opts ...DBOpt
|
||||
return db.Model(&model.AlertConfig{}).Updates(maps).Error
|
||||
}
|
||||
|
||||
func (a *AlertRepo) UpdateAlertConfigWithRevision(maps map[string]interface{}, revision *time.Time, opts ...DBOption) error {
|
||||
if revision == nil {
|
||||
return a.UpdateAlertConfig(maps, opts...)
|
||||
}
|
||||
db, _ := getAlertDB(opts...)
|
||||
result := db.Model(&model.AlertConfig{}).Where("updated_at = ?", *revision).Updates(maps)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return ErrAlertConfigRevisionConflict
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AlertRepo) CreateAlertConfig(config *model.AlertConfig) error {
|
||||
ensureAlertConfigUID(config)
|
||||
return global.AlertDB.Model(&model.AlertConfig{}).Create(config).Error
|
||||
}
|
||||
|
||||
@@ -433,12 +338,6 @@ func (a *AlertRepo) WithByTypeNotIn(types []string) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AlertRepo) WithByDeliveryLogID(logID uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("delivery_log_id = ?", logID)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AlertRepo) PageAlertConfig(page, size int, opts ...DBOption) (int64, []model.AlertConfig, error) {
|
||||
var configs []model.AlertConfig
|
||||
db := global.AlertDB.Model(&model.AlertConfig{})
|
||||
@@ -479,44 +378,26 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
oldConfigMap := make(map[string]model.AlertConfig)
|
||||
oldConfigByUID := make(map[string]model.AlertConfig)
|
||||
oldConfigMap := make(map[string]uint)
|
||||
oldConfigByType := make(map[string][]model.AlertConfig)
|
||||
oldConfigByKey := make(map[string][]model.AlertConfig)
|
||||
consumedConfigIDs := make(map[uint]struct{})
|
||||
for _, item := range oldConfigs {
|
||||
if strings.TrimSpace(item.UID) != "" {
|
||||
oldConfigByUID[item.UID] = item
|
||||
}
|
||||
if singletonTypes[item.Type] {
|
||||
oldConfigMap[item.Type] = item
|
||||
oldConfigMap[item.Type] = item.ID
|
||||
continue
|
||||
}
|
||||
oldConfigByType[item.Type] = append(oldConfigByType[item.Type], item)
|
||||
oldConfigByKey[alertConfigSyncKey(item)] = append(oldConfigByKey[alertConfigSyncKey(item)], item)
|
||||
}
|
||||
for _, item := range data {
|
||||
if uid := strings.TrimSpace(item.UID); uid != "" {
|
||||
if matched, ok := oldConfigByUID[uid]; ok && matched.Type != item.Type {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("alert config UID %q belongs to type %q, not %q", uid, matched.Type, item.Type)
|
||||
}
|
||||
}
|
||||
if singletonTypes[item.Type] {
|
||||
if matched, ok := oldConfigMap[item.Type]; ok {
|
||||
if err := inheritAlertConfigSyncState(&item, matched); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if val, ok := oldConfigMap[item.Type]; ok {
|
||||
item.ID = val
|
||||
delete(oldConfigMap, item.Type)
|
||||
consumedConfigIDs[item.ID] = struct{}{}
|
||||
} else {
|
||||
item.ID = 0
|
||||
ensureAlertConfigUID(&item)
|
||||
if err := validateAlertConfigSyncSecret(&item); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
if item.ID == 0 {
|
||||
if err := tx.Create(&item).Error; err != nil {
|
||||
@@ -530,31 +411,9 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.TrimSpace(item.UID) != "" {
|
||||
if matched, ok := oldConfigByUID[item.UID]; ok {
|
||||
delete(oldConfigByUID, item.UID)
|
||||
if err := inheritAlertConfigSyncState(&item, matched); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
consumedConfigIDs[item.ID] = struct{}{}
|
||||
if err := tx.Save(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
deleteAlertConfigByID(oldConfigByType, matched.ID)
|
||||
deleteAlertConfigByID(oldConfigByKey, matched.ID)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
key := alertConfigSyncKey(item)
|
||||
if matched, ok := popAlertConfigByKey(oldConfigByKey, key); ok {
|
||||
delete(oldConfigByUID, matched.UID)
|
||||
if err := inheritAlertConfigSyncState(&item, matched); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
item.ID = matched.ID
|
||||
consumedConfigIDs[item.ID] = struct{}{}
|
||||
if err := tx.Save(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
@@ -565,12 +424,7 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
||||
}
|
||||
|
||||
if matched, ok := popUnusedAlertConfigByType(oldConfigByType, usedConfigIDs, item.Type); ok {
|
||||
delete(oldConfigByUID, matched.UID)
|
||||
deleteAlertConfigByID(oldConfigByKey, matched.ID)
|
||||
if err := inheritAlertConfigSyncState(&item, matched); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
item.ID = matched.ID
|
||||
consumedConfigIDs[item.ID] = struct{}{}
|
||||
if err := tx.Save(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
@@ -580,11 +434,6 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
||||
}
|
||||
|
||||
item.ID = 0
|
||||
ensureAlertConfigUID(&item)
|
||||
if err := validateAlertConfigSyncSecret(&item); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
@@ -609,63 +458,6 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureAlertConfigUID(config *model.AlertConfig) {
|
||||
if config != nil && strings.TrimSpace(config.UID) == "" {
|
||||
config.UID = uuid.NewString()
|
||||
}
|
||||
}
|
||||
|
||||
func inheritAlertConfigSyncState(incoming *model.AlertConfig, existing model.AlertConfig) error {
|
||||
if incoming.Type != existing.Type {
|
||||
return fmt.Errorf("alert config UID %q belongs to type %q, not %q", incoming.UID, existing.Type, incoming.Type)
|
||||
}
|
||||
preserveExistingCustom := incoming.Type == constant.Custom &&
|
||||
existing.Status == constant.AlertDisable &&
|
||||
incoming.Title == existing.Title &&
|
||||
incoming.Status == existing.Status &&
|
||||
incoming.Config == existing.Config &&
|
||||
(incoming.SecretConfig == "" || incoming.SecretConfig == existing.SecretConfig)
|
||||
incoming.ID = existing.ID
|
||||
if strings.TrimSpace(incoming.UID) == "" {
|
||||
incoming.UID = existing.UID
|
||||
}
|
||||
if incoming.Type == constant.Custom && incoming.SecretConfig == "" {
|
||||
incoming.SecretConfig = existing.SecretConfig
|
||||
}
|
||||
if preserveExistingCustom {
|
||||
return nil
|
||||
}
|
||||
return validateAlertConfigSyncSecret(incoming)
|
||||
}
|
||||
|
||||
func validateAlertConfigSyncSecret(incoming *model.AlertConfig) error {
|
||||
if incoming.Type != constant.Custom {
|
||||
incoming.SecretConfig = ""
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(incoming.SecretConfig) == "" {
|
||||
return fmt.Errorf("custom webhook sync secret is missing")
|
||||
}
|
||||
var version struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(incoming.Config), &version); err != nil || version.SchemaVersion != 1 {
|
||||
return fmt.Errorf("custom webhook sync config must use schemaVersion 1")
|
||||
}
|
||||
secret := incoming.SecretConfig
|
||||
for _, prefix := range []string{"core:v1:", "agent:v1:"} {
|
||||
if !strings.HasPrefix(secret, prefix) {
|
||||
continue
|
||||
}
|
||||
ciphertext, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(secret, prefix))
|
||||
if err != nil || len(ciphertext) < 32 || len(ciphertext)%16 != 0 {
|
||||
return fmt.Errorf("custom webhook sync secret envelope is invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("custom webhook sync secret must use a versioned envelope")
|
||||
}
|
||||
|
||||
func loadUsedAlertConfigIDs(tx *gorm.DB) (map[uint]struct{}, error) {
|
||||
var alerts []model.Alert
|
||||
if err := tx.Select("method").Find(&alerts).Error; err != nil {
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
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
|
||||
})
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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,6 +22,11 @@ type IHostRepo interface {
|
||||
WithByPort(port uint) DBOption
|
||||
WithByUser(user string) DBOption
|
||||
|
||||
GetFirewallRecord(opts ...DBOption) (model.Firewall, error)
|
||||
ListFirewallRecord(opts ...DBOption) ([]model.Firewall, error)
|
||||
SaveFirewallRecord(firewall *model.Firewall) error
|
||||
DeleteFirewallRecordByID(id uint) error
|
||||
|
||||
SyncCert(data []model.RootCert) error
|
||||
GetCert(opts ...DBOption) (model.RootCert, error)
|
||||
PageCert(limit, offset int, opts ...DBOption) (int64, []model.RootCert, error)
|
||||
@@ -29,6 +34,8 @@ type IHostRepo interface {
|
||||
SaveCert(cert *model.RootCert) error
|
||||
UpdateCert(id uint, vars map[string]interface{}) error
|
||||
DeleteCert(opts ...DBOption) error
|
||||
|
||||
WithByChain(chain string) DBOption
|
||||
}
|
||||
|
||||
func NewIHostRepo() IHostRepo {
|
||||
@@ -109,6 +116,65 @@ func (h *HostRepo) Delete(opts ...DBOption) error {
|
||||
return db.Delete(&model.Host{}).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) GetFirewallRecord(opts ...DBOption) (model.Firewall, error) {
|
||||
var firewall model.Firewall
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&firewall).Error
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
func (h *HostRepo) ListFirewallRecord(opts ...DBOption) ([]model.Firewall, error) {
|
||||
var firewalls []model.Firewall
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
if err := global.DB.Find(&firewalls).Error; err != nil {
|
||||
return firewalls, nil
|
||||
}
|
||||
return firewalls, nil
|
||||
}
|
||||
|
||||
func (h *HostRepo) SaveFirewallRecord(firewall *model.Firewall) error {
|
||||
if firewall.ID != 0 {
|
||||
return global.DB.Save(firewall).Error
|
||||
}
|
||||
var data model.Firewall
|
||||
switch firewall.Type {
|
||||
case "port":
|
||||
_ = global.DB.Where("type = ? AND dst_port = ? AND protocol = ? AND src_ip = ? AND strategy = ?", "port",
|
||||
firewall.DstPort,
|
||||
firewall.Protocol,
|
||||
firewall.SrcIP,
|
||||
firewall.Strategy,
|
||||
).First(&data).Error
|
||||
case "ip":
|
||||
_ = global.DB.Where("type = ? AND src_ip = ? AND strategy = ?", "address", firewall.SrcIP, firewall.Strategy).First(&data)
|
||||
default:
|
||||
_ = global.DB.Where("type = ? AND chain = ? AND src_port = ? AND dst_port = ? AND protocol = ? AND src_ip = ? AND dst_ip = ? AND strategy = ?",
|
||||
firewall.Type,
|
||||
firewall.Chain,
|
||||
firewall.SrcPort,
|
||||
firewall.DstPort,
|
||||
firewall.Protocol,
|
||||
firewall.SrcIP,
|
||||
firewall.DstIP,
|
||||
firewall.Strategy,
|
||||
).First(&data).Error
|
||||
}
|
||||
if data.ID != 0 {
|
||||
firewall.ID = data.ID
|
||||
}
|
||||
return global.DB.Save(firewall).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) DeleteFirewallRecordByID(id uint) error {
|
||||
return global.DB.Where("id = ?", id).Delete(&model.Firewall{}).Error
|
||||
}
|
||||
|
||||
func (u *HostRepo) GetCert(opts ...DBOption) (model.RootCert, error) {
|
||||
var cert model.RootCert
|
||||
db := global.DB
|
||||
@@ -187,3 +253,9 @@ func (u *HostRepo) SyncCert(data []model.RootCert) error {
|
||||
tx.Commit()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *HostRepo) WithByChain(chain string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("chain = ?", chain)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ type IAgentService interface {
|
||||
BatchUpgrade(req dto.AgentBatchUpgradeReq) ([]dto.AgentBatchUpgradeResult, error)
|
||||
BatchInstallSkill(req dto.AgentBatchSkillInstallReq) ([]dto.AgentBatchSkillInstallResult, error)
|
||||
BatchOperate(req dto.AgentBatchOperateReq) ([]dto.AgentBatchOperateResult, error)
|
||||
Page(req dto.SearchWithPage) (int64, []dto.AgentItem, error)
|
||||
Page(req dto.SearchWithPage, readOnly bool) (int64, []dto.AgentItem, error)
|
||||
DeleteCheck(req dto.AgentIDReq) ([]dto.AppResource, error)
|
||||
Delete(req dto.AgentDeleteReq) error
|
||||
ResetToken(req dto.AgentTokenResetReq) error
|
||||
@@ -76,7 +76,7 @@ type IAgentService interface {
|
||||
CreateAccount(req dto.AgentAccountCreateReq) error
|
||||
UpdateAccount(req dto.AgentAccountUpdateReq) error
|
||||
SyncAgentsByAccount(account *model.AgentAccount) error
|
||||
PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error)
|
||||
PageAccounts(req dto.AgentAccountSearch, readOnly ...bool) (int64, []dto.AgentAccountInfo, error)
|
||||
CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error)
|
||||
GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error)
|
||||
DiscoverAccountModels(req dto.AgentAccountModelDiscoverReq) ([]dto.AgentAccountModel, error)
|
||||
@@ -745,7 +745,7 @@ func setAgentWebUIParams(params map[string]interface{}, agentType, appVersion st
|
||||
params["PANEL_APP_PORT_HTTP"] = webUIPort
|
||||
}
|
||||
|
||||
func (a AgentService) Page(req dto.SearchWithPage) (int64, []dto.AgentItem, error) {
|
||||
func (a AgentService) Page(req dto.SearchWithPage, readOnly bool) (int64, []dto.AgentItem, error) {
|
||||
var opts []repo.DBOption
|
||||
if strings.TrimSpace(req.Info) != "" {
|
||||
opts = append(opts, repo.WithByLikeName(req.Info))
|
||||
@@ -760,11 +760,19 @@ func (a AgentService) Page(req dto.SearchWithPage) (int64, []dto.AgentItem, erro
|
||||
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(item.AppInstallID))
|
||||
appInstalls = append(appInstalls, appInstall)
|
||||
}
|
||||
syncAgentAppInstalls(appInstalls)
|
||||
readOnlyMode := isDemoReadOnly(readOnly)
|
||||
if !readOnlyMode {
|
||||
syncAgentAppInstalls(appInstalls)
|
||||
}
|
||||
for index, item := range list {
|
||||
appInstall := appInstalls[index]
|
||||
envMap := readInstallEnv(appInstall.Env)
|
||||
agentItem := buildAgentItem(&item, &appInstall, envMap)
|
||||
if readOnlyMode {
|
||||
agentItem.Token = ""
|
||||
agentItem.APIKey = ""
|
||||
agentItem.DashboardPassword = ""
|
||||
}
|
||||
agentItem.Upgradable = checkAgentUpgradable(appInstall)
|
||||
items = append(items, agentItem)
|
||||
}
|
||||
@@ -936,7 +944,6 @@ func (a AgentService) GetModelConfig(req dto.AgentIDReq) (*dto.AgentModelConfig,
|
||||
AccountID: agent.AccountID,
|
||||
Model: model,
|
||||
Fallbacks: extractOpenclawFallbackModelIDs(conf, account, models, model),
|
||||
Metadata: extractOpenclawModelMetadata(conf, account, models),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -968,7 +975,7 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
||||
if agent.AgentType != constant.AppOpenclaw {
|
||||
return fmt.Errorf("%s does not support", agent.AgentType)
|
||||
}
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, req.Fallbacks, req.Metadata); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, req.Fallbacks); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1129,7 +1136,7 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error) {
|
||||
func (a AgentService) PageAccounts(req dto.AgentAccountSearch, readOnly ...bool) (int64, []dto.AgentAccountInfo, error) {
|
||||
var opts []repo.DBOption
|
||||
if strings.TrimSpace(req.Provider) != "" {
|
||||
opts = append(opts, repo.WithByProvider(req.Provider))
|
||||
@@ -1150,7 +1157,7 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.Age
|
||||
items := make([]dto.AgentAccountInfo, 0, len(list))
|
||||
for _, item := range list {
|
||||
apiKey := ""
|
||||
if item.RememberAPIKey {
|
||||
if item.RememberAPIKey && !isDemoReadOnly(readOnly...) {
|
||||
apiKey = item.APIKey
|
||||
}
|
||||
items = append(items, dto.AgentAccountInfo{
|
||||
@@ -1685,7 +1692,7 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
|
||||
return err
|
||||
}
|
||||
fallbacks := extractOpenclawFallbackModelIDs(conf, account, accountModels, selectedAccountModel.ID)
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, fallbacks, nil); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, fallbacks); err != nil {
|
||||
return err
|
||||
}
|
||||
case constant.AppHermesAgent:
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -1321,10 +1320,6 @@ func appendPluginAllow(conf map[string]interface{}, pluginID string) {
|
||||
}
|
||||
|
||||
func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID string) error {
|
||||
help, err := cmd.RunDockerExecWithStdout(time.Minute, containerName, "openclaw", "plugins", "install", "--help")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workdir := path.Join(openclawPluginPackageTmpDir, pluginID)
|
||||
defer func() {
|
||||
_ = mgr.Run("docker", "exec", containerName, "rm", "-rf", workdir)
|
||||
@@ -1346,19 +1341,7 @@ func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID
|
||||
if pkgPath == "" {
|
||||
return fmt.Errorf("openclaw plugin package not found")
|
||||
}
|
||||
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...)
|
||||
return mgr.Run("docker", "exec", containerName, "openclaw", "plugins", "install", pkgPath, "--dangerously-force-unsafe-install")
|
||||
}
|
||||
|
||||
func uninstallOpenclawPlugin(mgr *cmd.CommandHelper, containerName, pluginID string) error {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -738,11 +737,9 @@ type modelProvider struct {
|
||||
}
|
||||
|
||||
type modelEntry struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Input []string `json:"input,omitempty"`
|
||||
ContextWindow int `json:"contextWindow,omitempty"`
|
||||
MaxTokens int `json:"maxTokens,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Input []string `json:"input,omitempty"`
|
||||
}
|
||||
|
||||
func requiresOpenclawProviderModels(provider string) bool {
|
||||
@@ -770,7 +767,7 @@ type browserConfig struct {
|
||||
DefaultProfile string `json:"defaultProfile"`
|
||||
}
|
||||
|
||||
func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, token string, allowedOrigins []string, fallbacks []string, metadata []dto.AgentModelMetadata) error {
|
||||
func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, token string, allowedOrigins []string, fallbacks []string) error {
|
||||
if strings.TrimSpace(confDir) == "" {
|
||||
return fmt.Errorf("config dir is required")
|
||||
}
|
||||
@@ -855,7 +852,6 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
}
|
||||
conf = initial
|
||||
} else {
|
||||
preserveOpenclawModelMetadata(conf, cfg.Models)
|
||||
if err := applyOpenclawModelsConfig(conf, cfg.Models); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -910,9 +906,6 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
if allowedOrigins != nil {
|
||||
setSecurityConfig(conf, dto.AgentSecurityConfig{AllowedOrigins: allowedOrigins})
|
||||
}
|
||||
if err := applyOpenclawModelMetadata(conf, account, metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -927,144 +920,6 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
return writeAgentEnvMap(path.Join(confDir, ".env"), envMap, order)
|
||||
}
|
||||
|
||||
func readOpenclawModelsConfig(conf map[string]interface{}) *modelsConfig {
|
||||
raw, ok := conf["models"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
payload, err := json.Marshal(raw)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var models modelsConfig
|
||||
if err := json.Unmarshal(payload, &models); err != nil {
|
||||
return nil
|
||||
}
|
||||
return &models
|
||||
}
|
||||
|
||||
func preserveOpenclawModelMetadata(conf map[string]interface{}, next *modelsConfig) {
|
||||
current := readOpenclawModelsConfig(conf)
|
||||
if current == nil || next == nil {
|
||||
return
|
||||
}
|
||||
for providerID, nextProvider := range next.Providers {
|
||||
currentProvider, ok := current.Providers[providerID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
byID := make(map[string]modelEntry, len(currentProvider.Models))
|
||||
for _, entry := range currentProvider.Models {
|
||||
byID[entry.ID] = entry
|
||||
}
|
||||
for index := range nextProvider.Models {
|
||||
currentEntry, ok := byID[nextProvider.Models[index].ID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
nextProvider.Models[index].Input = currentEntry.Input
|
||||
nextProvider.Models[index].ContextWindow = currentEntry.ContextWindow
|
||||
nextProvider.Models[index].MaxTokens = currentEntry.MaxTokens
|
||||
}
|
||||
next.Providers[providerID] = nextProvider
|
||||
}
|
||||
}
|
||||
|
||||
func extractOpenclawModelMetadata(conf map[string]interface{}, account *model.AgentAccount, accountModels []dto.AgentAccountModel) []dto.AgentModelMetadata {
|
||||
result := make([]dto.AgentModelMetadata, 0, len(accountModels))
|
||||
configured := readOpenclawModelsConfig(conf)
|
||||
for _, item := range accountModels {
|
||||
_, inferred, providerID, _, err := buildOpenclawAccountModelConfig(account, item)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
metadata := dto.AgentModelMetadata{Model: item.ID, InputMode: "auto"}
|
||||
if configured != nil {
|
||||
for _, entry := range configured.Providers[providerID].Models {
|
||||
if entry.ID != inferred.ID {
|
||||
continue
|
||||
}
|
||||
metadata.ContextWindow = entry.ContextWindow
|
||||
metadata.MaxTokens = entry.MaxTokens
|
||||
if len(entry.Input) > 0 && !slices.Equal(entry.Input, inferred.Input) {
|
||||
if slices.Contains(entry.Input, "image") {
|
||||
metadata.InputMode = "image"
|
||||
} else {
|
||||
metadata.InputMode = "text"
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
result = append(result, metadata)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func applyOpenclawModelMetadata(conf map[string]interface{}, account *model.AgentAccount, requested []dto.AgentModelMetadata) error {
|
||||
if len(requested) == 0 {
|
||||
return nil
|
||||
}
|
||||
configured := readOpenclawModelsConfig(conf)
|
||||
if configured == nil {
|
||||
return fmt.Errorf("model metadata is not supported for provider %s", account.Provider)
|
||||
}
|
||||
accountModels, err := loadAgentAccountModels(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
available := make(map[string]dto.AgentAccountModel, len(accountModels))
|
||||
for _, item := range accountModels {
|
||||
available[item.ID] = item
|
||||
}
|
||||
seen := make(map[string]struct{}, len(requested))
|
||||
for _, metadata := range requested {
|
||||
item, ok := available[metadata.Model]
|
||||
if !ok {
|
||||
return buserr.New("ErrAgentModelNotInAccount")
|
||||
}
|
||||
if _, ok := seen[metadata.Model]; ok {
|
||||
return fmt.Errorf("duplicate model metadata: %s", metadata.Model)
|
||||
}
|
||||
seen[metadata.Model] = struct{}{}
|
||||
_, inferred, providerID, _, err := buildOpenclawAccountModelConfig(account, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
provider := configured.Providers[providerID]
|
||||
found := false
|
||||
for index := range provider.Models {
|
||||
if provider.Models[index].ID != inferred.ID {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
provider.Models[index].ContextWindow = metadata.ContextWindow
|
||||
provider.Models[index].MaxTokens = metadata.MaxTokens
|
||||
switch metadata.InputMode {
|
||||
case "auto":
|
||||
provider.Models[index].Input = inferred.Input
|
||||
case "text":
|
||||
provider.Models[index].Input = []string{"text"}
|
||||
case "image":
|
||||
provider.Models[index].Input = []string{"text", "image"}
|
||||
default:
|
||||
return fmt.Errorf("unsupported model input mode: %s", metadata.InputMode)
|
||||
}
|
||||
break
|
||||
}
|
||||
if !found {
|
||||
return buserr.New("ErrAgentModelNotInAccount")
|
||||
}
|
||||
configured.Providers[providerID] = provider
|
||||
}
|
||||
modelsMap, err := structToMap(configured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conf["models"] = modelsMap
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveOpenclawFallbackModels(account *model.AgentAccount, primaryModel string, fallbackIDs []string) ([]string, error) {
|
||||
accountModels, err := loadAgentAccountModels(account)
|
||||
if err != nil {
|
||||
@@ -1186,7 +1041,7 @@ func prepareOpenclawInstallFiles(appInstall *model.AppInstall, account *model.Ag
|
||||
return fmt.Errorf("app install is required")
|
||||
}
|
||||
confDir := path.Join(appInstall.GetPath(), "data", "conf")
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, token, allowedOrigins, nil, nil); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, token, allowedOrigins, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
dataDir := path.Join(appInstall.GetPath(), "data")
|
||||
@@ -1483,7 +1338,7 @@ func normalizeAgentAccountModel(account *model.AgentAccount, model dto.AgentAcco
|
||||
|
||||
func requiresInitialAgentAccountModels(provider string) bool {
|
||||
switch provider {
|
||||
case "custom", "vllm", "ollama", "llmman":
|
||||
case "custom", "vllm", "ollama":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
+43
-325
@@ -17,13 +17,10 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
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/copier"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/email"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack/providers"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
)
|
||||
|
||||
@@ -37,28 +34,6 @@ var communityAlertMethodTypeNames = map[string]string{
|
||||
constant.SMS: "SMS",
|
||||
}
|
||||
|
||||
var legacyAlertMethodTypeMap = map[string]string{
|
||||
"mail": constant.Email,
|
||||
constant.Email: constant.Email,
|
||||
constant.SMS: constant.SMS,
|
||||
constant.Bark: constant.Bark,
|
||||
constant.WeChat: constant.WeCom,
|
||||
constant.WeCom: constant.WeCom,
|
||||
constant.DingTalk: constant.DingTalk,
|
||||
constant.FeiShu: constant.FeiShu,
|
||||
constant.Custom: constant.Custom,
|
||||
}
|
||||
|
||||
var supportedAlertMethodTypes = map[string]struct{}{
|
||||
constant.Email: {},
|
||||
constant.SMS: {},
|
||||
constant.Bark: {},
|
||||
constant.WeCom: {},
|
||||
constant.DingTalk: {},
|
||||
constant.FeiShu: {},
|
||||
constant.Custom: {},
|
||||
}
|
||||
|
||||
type IAlertService interface {
|
||||
PageAlert(req dto.AlertSearch) (int64, []dto.AlertDTO, error)
|
||||
GetAlerts() ([]dto.AlertDTO, error)
|
||||
@@ -76,12 +51,10 @@ type IAlertService interface {
|
||||
GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error)
|
||||
|
||||
GetAlertConfig(req dto.AlertConfigQuery) ([]model.AlertConfig, error)
|
||||
PageAlertConfig(req dto.AlertConfigPageReq) (int64, []model.AlertConfig, error)
|
||||
PageAlertConfig(req dto.AlertConfigPageReq, readOnly ...bool) (int64, []model.AlertConfig, error)
|
||||
UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error
|
||||
UpdateAlertConfigStatus(req dto.AlertConfigStatusUpdate, operator string) error
|
||||
DeleteAlertConfig(id uint) error
|
||||
TestAlertConfig(req dto.AlertConfigTest) (bool, error)
|
||||
TestCustomAlertConfig(req dto.AlertConfigTest) (dto.AlertConfigTestResult, error)
|
||||
}
|
||||
|
||||
func NewIAlertService() IAlertService {
|
||||
@@ -207,15 +180,9 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlert(req dto.AlertUpdate, operator string) error {
|
||||
methodTypes, err := a.validateAlertMethodReferences(req.Method)
|
||||
if err != nil {
|
||||
if err := a.validateCommunityAlertMethod(req.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Status != constant.AlertDisable {
|
||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["id"] = req.ID
|
||||
@@ -273,16 +240,7 @@ func (a AlertService) UpdateStatus(id uint, status string) error {
|
||||
if alertInfo.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
methodTypes, err := a.validateAlertMethodReferences(alertInfo.Method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status == constant.AlertEnable {
|
||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = alertRepo.Update(map[string]interface{}{"status": status}, repo.WithByID(alertInfo.ID))
|
||||
err := alertRepo.Update(map[string]interface{}{"status": status}, repo.WithByID(alertInfo.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -454,7 +412,6 @@ func (a AlertService) parseAlertLog(item model.AlertLog) (dto.AlertLogDTO, error
|
||||
if err := unmarshalAlertInfo(item.AlertDetail, &alertDetail); err != nil {
|
||||
return dto.AlertLogDTO{}, err
|
||||
}
|
||||
alertDetail.Task = nil
|
||||
if err := unmarshalAlertInfo(item.AlertRule, &alertRule); err != nil {
|
||||
return dto.AlertLogDTO{}, err
|
||||
}
|
||||
@@ -537,16 +494,10 @@ func (a AlertService) GetAlertConfig(req dto.AlertConfigQuery) ([]model.AlertCon
|
||||
}
|
||||
opts = append(opts, repo.WithByStatus(constant.AlertEnable))
|
||||
configs, err := alertRepo.AlertConfigList(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := exposeCustomAlertConfigSecrets(configs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configs, nil
|
||||
return configs, err
|
||||
}
|
||||
|
||||
func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq) (int64, []model.AlertConfig, error) {
|
||||
func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq, readOnly ...bool) (int64, []model.AlertConfig, error) {
|
||||
opts := []repo.DBOption{
|
||||
alertRepo.WithByTypeNotIn([]string{"common"}),
|
||||
repo.WithOrderDesc("created_at"),
|
||||
@@ -555,48 +506,30 @@ func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq) (int64, []mode
|
||||
opts = append(opts, alertRepo.WithByTypeNotIn(req.ExcludeTypes))
|
||||
}
|
||||
total, configs, err := alertRepo.PageAlertConfig(req.Page, req.PageSize, opts...)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
if err != nil || !isDemoReadOnly(readOnly...) {
|
||||
return total, configs, err
|
||||
}
|
||||
if err := exposeCustomAlertConfigSecrets(configs); err != nil {
|
||||
return 0, nil, err
|
||||
for i := range configs {
|
||||
var value interface{}
|
||||
if err := json.Unmarshal([]byte(configs[i].Config), &value); err != nil {
|
||||
configs[i].Config = ""
|
||||
continue
|
||||
}
|
||||
redactSensitiveData(value)
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
configs[i].Config = ""
|
||||
continue
|
||||
}
|
||||
configs[i].Config = string(data)
|
||||
}
|
||||
return total, configs, nil
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error {
|
||||
if req.Type == constant.Custom {
|
||||
if req.ID != 0 && req.Revision == nil {
|
||||
return repo.ErrAlertConfigRevisionRequired
|
||||
}
|
||||
return a.updateCustomAlertConfig(req, operator)
|
||||
}
|
||||
usesMutation, err := alertconfig.UsesMutation(req.Type, req.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.ID != 0 && usesMutation && req.Revision == nil {
|
||||
return repo.ErrAlertConfigRevisionRequired
|
||||
}
|
||||
var existing *model.AlertConfig
|
||||
if req.ID != 0 {
|
||||
stored, err := alertRepo.GetConfigById(req.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stored.Type != req.Type {
|
||||
return fmt.Errorf("alert config %d has type %s, not %s", req.ID, stored.Type, req.Type)
|
||||
}
|
||||
existing = &stored
|
||||
}
|
||||
if err := a.validateCommunityAlertConfigType(req.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
prepared, err := alertconfig.Prepare(req.Type, req.Config, req.Status, existing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Config = prepared
|
||||
if err := a.checkAlertConfigDisplayNameUnique(req); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -611,7 +544,7 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator stri
|
||||
upMap["status"] = req.Status
|
||||
upMap["config"] = req.Config
|
||||
upMap["update_user"] = operator
|
||||
if err := alertRepo.UpdateAlertConfigWithRevision(upMap, req.Revision, repo.WithByID(req.ID)); err != nil {
|
||||
if err := alertRepo.UpdateAlertConfig(upMap, repo.WithByID(req.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -629,99 +562,6 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) updateCustomAlertConfig(req dto.AlertConfigUpdate, operator string) error {
|
||||
if err := validateAlertConfigStatus(req.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var existing *model.AlertConfig
|
||||
if req.ID != 0 {
|
||||
config, err := alertRepo.GetConfigById(req.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if config.Type != constant.Custom {
|
||||
return fmt.Errorf("alert config %d is not a custom webhook", req.ID)
|
||||
}
|
||||
existing = &config
|
||||
}
|
||||
prepared, err := alertwebhook.Prepare(req.Config, req.Status, existing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
validatedReq := req
|
||||
validatedReq.Config = prepared.Config
|
||||
if err := a.checkAlertConfigDisplayNameUnique(validatedReq); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if existing != nil {
|
||||
return alertRepo.UpdateAlertConfigWithRevision(map[string]interface{}{
|
||||
"type": constant.Custom,
|
||||
"title": req.Title,
|
||||
"status": req.Status,
|
||||
"config": prepared.Config,
|
||||
"secret_config": prepared.SecretConfig,
|
||||
"update_user": operator,
|
||||
}, req.Revision, repo.WithByID(req.ID))
|
||||
}
|
||||
|
||||
return alertRepo.CreateAlertConfig(&model.AlertConfig{
|
||||
Type: constant.Custom,
|
||||
Title: req.Title,
|
||||
Status: req.Status,
|
||||
Config: prepared.Config,
|
||||
SecretConfig: prepared.SecretConfig,
|
||||
CreateUser: operator,
|
||||
UpdateUser: operator,
|
||||
})
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlertConfigStatus(req dto.AlertConfigStatusUpdate, operator string) error {
|
||||
if err := validateAlertConfigStatus(req.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
config, err := alertRepo.GetConfigById(req.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Status == constant.AlertEnable {
|
||||
if err := a.validateCommunityAlertConfigType(config.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
if config.Type == constant.Custom {
|
||||
if _, err := alertwebhook.Resolve(config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return alertRepo.UpdateAlertConfig(map[string]interface{}{
|
||||
"status": req.Status,
|
||||
"update_user": operator,
|
||||
}, repo.WithByID(req.ID))
|
||||
}
|
||||
|
||||
func validateAlertConfigStatus(status string) error {
|
||||
if status != constant.AlertEnable && status != constant.AlertDisable {
|
||||
return fmt.Errorf("alert config status must be Enable or Disable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func exposeCustomAlertConfigSecrets(configs []model.AlertConfig) error {
|
||||
for index := range configs {
|
||||
if configs[index].Type != constant.Custom {
|
||||
continue
|
||||
}
|
||||
view, err := alertwebhook.PlainView(configs[index])
|
||||
if err != nil {
|
||||
return fmt.Errorf("build editable custom alert config %d: %w", configs[index].ID, err)
|
||||
}
|
||||
configs[index].Config = view
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) checkAlertConfigSMSPhoneUnique(req dto.AlertConfigUpdate) error {
|
||||
if req.Type != constant.SMSConfig {
|
||||
return nil
|
||||
@@ -746,9 +586,6 @@ func (a AlertService) checkAlertConfigSMSPhoneUnique(req dto.AlertConfigUpdate)
|
||||
}
|
||||
|
||||
func (a AlertService) checkAlertConfigDisplayNameUnique(req dto.AlertConfigUpdate) error {
|
||||
if req.Type != constant.Custom && (global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn") {
|
||||
return nil
|
||||
}
|
||||
displayName := alertConfigDisplayName(req.Type, req.Config)
|
||||
if displayName == "" {
|
||||
return nil
|
||||
@@ -772,67 +609,37 @@ func (a AlertService) checkAlertConfigDisplayNameUnique(req dto.AlertConfigUpdat
|
||||
}
|
||||
|
||||
func (a AlertService) validateCommunityAlertMethod(method string) error {
|
||||
methodTypes, err := a.validateAlertMethodReferences(method)
|
||||
if err != nil {
|
||||
return err
|
||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
||||
return nil
|
||||
}
|
||||
return a.validateAlertMethodEntitlement(methodTypes)
|
||||
}
|
||||
|
||||
func (a AlertService) validateAlertMethodReferences(method string) ([]string, error) {
|
||||
if strings.TrimSpace(method) == "" {
|
||||
return nil, buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
return nil
|
||||
}
|
||||
methodTypes := make([]string, 0)
|
||||
|
||||
for _, item := range strings.Split(method, ",") {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
configType := ""
|
||||
if configID, err := strconv.ParseUint(item, 10, 64); err == nil {
|
||||
config, err := alertRepo.GetConfigById(uint(configID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
configType = config.Type
|
||||
} else {
|
||||
var ok bool
|
||||
configType, ok = legacyAlertMethodTypeMap[item]
|
||||
if !ok {
|
||||
return nil, buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
if _, ok := communityAlertMethodTypeNames[config.Type]; ok {
|
||||
return buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
}
|
||||
}
|
||||
if _, ok := supportedAlertMethodTypes[configType]; !ok {
|
||||
return nil, buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
}
|
||||
methodTypes = append(methodTypes, configType)
|
||||
}
|
||||
if len(methodTypes) == 0 {
|
||||
return nil, buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
}
|
||||
return methodTypes, nil
|
||||
}
|
||||
|
||||
func (a AlertService) validateAlertMethodEntitlement(methodTypes []string) error {
|
||||
for _, configType := range methodTypes {
|
||||
if configType == constant.Custom {
|
||||
continue
|
||||
}
|
||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
||||
continue
|
||||
}
|
||||
if _, ok := communityAlertMethodTypeNames[configType]; ok {
|
||||
if _, ok := communityAlertMethodTypeNames[item]; ok {
|
||||
return buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) validateCommunityAlertConfigType(configType string) error {
|
||||
if configType == constant.Custom {
|
||||
return nil
|
||||
}
|
||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
||||
return nil
|
||||
}
|
||||
@@ -844,7 +651,7 @@ func (a AlertService) validateCommunityAlertConfigType(configType string) error
|
||||
|
||||
func alertConfigDisplayName(configType, configData string) string {
|
||||
switch configType {
|
||||
case constant.Email, constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Bark, constant.SMS, constant.Custom:
|
||||
case constant.Email, constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Bark, constant.SMS:
|
||||
var cfg struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
}
|
||||
@@ -883,24 +690,20 @@ func (a AlertService) DeleteAlertConfig(id uint) error {
|
||||
}
|
||||
|
||||
func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
|
||||
emailConfig, err := resolveEmailTestConfig(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
username := emailConfig.UserName
|
||||
username := req.UserName
|
||||
if username == "" {
|
||||
username = emailConfig.Sender
|
||||
username = req.Sender
|
||||
}
|
||||
encodedDisplayName := mime.BEncoding.Encode("UTF-8", emailConfig.DisplayName)
|
||||
encodedDisplayName := mime.BEncoding.Encode("UTF-8", req.DisplayName)
|
||||
cfg := email.SMTPConfig{
|
||||
Host: emailConfig.Host,
|
||||
Port: emailConfig.Port,
|
||||
Sender: emailConfig.Sender,
|
||||
Host: req.Host,
|
||||
Port: req.Port,
|
||||
Sender: req.Sender,
|
||||
Username: username,
|
||||
Password: emailConfig.Password,
|
||||
From: fmt.Sprintf(`"%s" <%s>`, encodedDisplayName, emailConfig.Sender),
|
||||
Encryption: emailConfig.Encryption,
|
||||
Recipient: emailConfig.Recipient,
|
||||
Password: req.Password,
|
||||
From: fmt.Sprintf(`"%s" <%s>`, encodedDisplayName, req.Sender),
|
||||
Encryption: req.Encryption,
|
||||
Recipient: req.Recipient,
|
||||
}
|
||||
|
||||
msg := email.EmailMessage{
|
||||
@@ -915,94 +718,9 @@ func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func resolveEmailTestConfig(req dto.AlertConfigTest) (dto.AlertEmailConfig, error) {
|
||||
emailConfig := dto.AlertEmailConfig{
|
||||
Host: req.Host,
|
||||
Port: req.Port,
|
||||
Sender: req.Sender,
|
||||
UserName: req.UserName,
|
||||
Password: req.Password,
|
||||
DisplayName: req.DisplayName,
|
||||
Encryption: req.Encryption,
|
||||
Recipient: req.Recipient,
|
||||
}
|
||||
if strings.TrimSpace(req.Config) != "" {
|
||||
configType := req.Type
|
||||
if configType == "" {
|
||||
configType = constant.EmailConfig
|
||||
}
|
||||
if configType != constant.EmailConfig {
|
||||
return dto.AlertEmailConfig{}, fmt.Errorf("alert config test type must be email")
|
||||
}
|
||||
var existing *model.AlertConfig
|
||||
if req.ID != 0 {
|
||||
stored, err := alertRepo.GetConfigById(req.ID)
|
||||
if err != nil {
|
||||
return dto.AlertEmailConfig{}, err
|
||||
}
|
||||
existing = &stored
|
||||
}
|
||||
prepared, err := alertconfig.Prepare(configType, req.Config, constant.AlertEnable, existing)
|
||||
if err != nil {
|
||||
return dto.AlertEmailConfig{}, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(prepared), &emailConfig); err != nil {
|
||||
return dto.AlertEmailConfig{}, fmt.Errorf("decode email alert config: %w", err)
|
||||
}
|
||||
}
|
||||
return emailConfig, nil
|
||||
}
|
||||
|
||||
func (a AlertService) TestCustomAlertConfig(req dto.AlertConfigTest) (dto.AlertConfigTestResult, error) {
|
||||
if req.Type != constant.Custom {
|
||||
return dto.AlertConfigTestResult{}, fmt.Errorf("alert config test type must be custom")
|
||||
}
|
||||
var existing *model.AlertConfig
|
||||
if req.ID != 0 {
|
||||
config, err := alertRepo.GetConfigById(req.ID)
|
||||
if err != nil {
|
||||
return dto.AlertConfigTestResult{}, err
|
||||
}
|
||||
if config.Type != constant.Custom {
|
||||
return dto.AlertConfigTestResult{}, fmt.Errorf("alert config %d is not a custom webhook", req.ID)
|
||||
}
|
||||
existing = &config
|
||||
}
|
||||
prepared, err := alertwebhook.Prepare(req.Config, constant.AlertEnable, existing)
|
||||
if err != nil {
|
||||
return dto.AlertConfigTestResult{}, err
|
||||
}
|
||||
resolved, err := alertwebhook.Resolve(model.AlertConfig{
|
||||
Type: constant.Custom,
|
||||
Config: prepared.Config,
|
||||
SecretConfig: prepared.SecretConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return dto.AlertConfigTestResult{}, err
|
||||
}
|
||||
tester, ok := xpack.AlertProvider.(providers.CustomWebhookTester)
|
||||
if !ok {
|
||||
return dto.AlertConfigTestResult{
|
||||
Success: false,
|
||||
Message: providers.ErrCustomWebhookUnsupported.Error(),
|
||||
}, nil
|
||||
}
|
||||
return tester.TestCustomWebhook(resolved)
|
||||
}
|
||||
|
||||
func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator string) error {
|
||||
var methodTypes []string
|
||||
if updateAlert.SendCount != 0 || strings.TrimSpace(updateAlert.Method) != "" {
|
||||
var err error
|
||||
methodTypes, err = a.validateAlertMethodReferences(updateAlert.Method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if updateAlert.SendCount != 0 {
|
||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.validateCommunityAlertMethod(updateAlert.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
upMap := make(map[string]interface{})
|
||||
var newStatus string
|
||||
|
||||
@@ -2,7 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
@@ -30,11 +29,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ResourceAlertInterval = 30
|
||||
CheckIntervalSec = 3
|
||||
LoadCheckIntervalMin = 5
|
||||
sshIPLoginWindow = 30 * time.Minute
|
||||
sslAutoRenewAlertSkipDays = 31
|
||||
ResourceAlertInterval = 30
|
||||
CheckIntervalSec = 3
|
||||
LoadCheckIntervalMin = 5
|
||||
)
|
||||
|
||||
type AlertTaskHelper struct {
|
||||
@@ -515,28 +512,10 @@ func loadPanelLogin(alert dto.AlertDTO) {
|
||||
}
|
||||
|
||||
func loadSSHLogin(alert dto.AlertDTO) {
|
||||
now := time.Now()
|
||||
failedWindow := time.Duration(alert.Cycle) * time.Minute
|
||||
loadWindow := failedWindow
|
||||
if loadWindow < sshIPLoginWindow {
|
||||
loadWindow = sshIPLoginWindow
|
||||
}
|
||||
location, err := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
count, isAlert, err := alertUtil.CountRecentFailedSSHLog(alert.Cycle, alert.Count)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to load timezone for ssh login logs: %v", err)
|
||||
location = time.Local
|
||||
global.LOG.Errorf("Failed to count recent failed ssh login logs: %v", err)
|
||||
}
|
||||
histories, err := loadSSHAlertHistories(defaultSSHLogDir, now.Add(-loadWindow), now, location)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to load ssh login logs: %v", err)
|
||||
}
|
||||
count, records := summarizeSSHLoginHistories(
|
||||
histories,
|
||||
now,
|
||||
failedWindow,
|
||||
strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n"),
|
||||
)
|
||||
isAlert := count >= int(alert.Count)
|
||||
if isAlert {
|
||||
params := []dto.Param{
|
||||
{
|
||||
@@ -552,6 +531,12 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
||||
}
|
||||
sendAlerts(alert, "sshLogin", strconv.Itoa(count), "sshLogin", params)
|
||||
}
|
||||
whitelist := strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n")
|
||||
records, err := alertUtil.FindRecentSuccessLoginNotInWhitelist(30, whitelist)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to check recent failed ip ssh login logs: %v", err)
|
||||
}
|
||||
records = filterSSHLoginEntriesNotInWhitelist(records, whitelist)
|
||||
if len(records) > 0 {
|
||||
quota := strings.Join(records, "\n")
|
||||
params := []dto.Param{
|
||||
@@ -580,6 +565,20 @@ func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string)
|
||||
return filtered
|
||||
}
|
||||
|
||||
func filterSSHLoginEntriesNotInWhitelist(records []string, whitelist []string) []string {
|
||||
filtered := make([]string, 0, len(records))
|
||||
for _, record := range records {
|
||||
ip := record
|
||||
if idx := strings.Index(record, "-"); idx >= 0 {
|
||||
ip = record[:idx]
|
||||
}
|
||||
if !isIPInWhitelist(ip, whitelist) {
|
||||
filtered = append(filtered, record)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func isIPInWhitelist(ip string, whitelist []string) bool {
|
||||
targetIP := net.ParseIP(strings.TrimSpace(ip))
|
||||
if targetIP == nil {
|
||||
@@ -696,10 +695,9 @@ func sendAlertsByConfigId(alert dto.AlertDTO, alertType, quota, quotaType string
|
||||
|
||||
func sendAlertsByLegacyMethod(alert dto.AlertDTO, alertType, quota, quotaType string, params []dto.Param, method string) {
|
||||
typeMap := map[string]string{
|
||||
"mail": constant.Email,
|
||||
constant.Bark: constant.Bark,
|
||||
constant.SMS: constant.SMS,
|
||||
constant.Custom: constant.Custom,
|
||||
"mail": constant.Email,
|
||||
constant.Bark: constant.Bark,
|
||||
constant.SMS: constant.SMS,
|
||||
}
|
||||
configType, ok := typeMap[method]
|
||||
if !ok {
|
||||
@@ -787,7 +785,7 @@ func doSendAlert(alert dto.AlertDTO, alertType, quota, quotaType string, params
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Custom:
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, methodStr)
|
||||
if !isValid {
|
||||
return
|
||||
@@ -800,31 +798,12 @@ func doSendAlert(alert dto.AlertDTO, alertType, quota, quotaType string, params
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
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)
|
||||
}
|
||||
alertErr := xpack.AlertProvider.CreateWebhookAlertLog(alertType, alert, create, quotaType, params, config, transport, agentInfo)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert webhook %s push faild, err: %v", alertType, methodStr, alertErr)
|
||||
return
|
||||
}
|
||||
if !queued {
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -865,7 +844,7 @@ func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]s
|
||||
daysDiff := int(math.Ceil(
|
||||
ssl.ExpireDate.Sub(currentDate).Hours() / 24,
|
||||
))
|
||||
if daysDiff > 0 && int(cycle) >= daysDiff && !shouldSuppressSSLExpiryAlert(ssl, daysDiff) {
|
||||
if daysDiff > 0 && int(cycle) >= daysDiff {
|
||||
daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], ssl.PrimaryDomain)
|
||||
projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate)
|
||||
}
|
||||
@@ -873,10 +852,6 @@ func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]s
|
||||
return daysDiffMap, projectMap
|
||||
}
|
||||
|
||||
func shouldSuppressSSLExpiryAlert(ssl model.WebsiteSSL, remainingDays int) bool {
|
||||
return ssl.AutoRenew && remainingDays < sslAutoRenewAlertSkipDays
|
||||
}
|
||||
|
||||
func calculateWebsiteExpiryDays(websites []model.Website, cycle uint) (map[int][]string, map[uint][]time.Time) {
|
||||
currentDate := time.Now()
|
||||
daysDiffMap := make(map[int][]string)
|
||||
@@ -1122,55 +1097,3 @@ func calculateMinutesDifference(newDate time.Time) int {
|
||||
minutesDifference := int(now.Sub(newDate).Minutes())
|
||||
return minutesDifference
|
||||
}
|
||||
|
||||
func loadSSHAlertHistories(
|
||||
baseDir string,
|
||||
startTime, endTime time.Time,
|
||||
location *time.Location,
|
||||
) ([]dto.SSHHistory, error) {
|
||||
fileList, err := listSSHLogFiles(baseDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
histories []dto.SSHHistory
|
||||
loadErr error
|
||||
)
|
||||
for _, file := range fileList {
|
||||
items, err := loadSSHHistoriesFromFile(file.Name, "", "", startTime, endTime, file.Year, location)
|
||||
if err != nil {
|
||||
loadErr = errors.Join(loadErr, fmt.Errorf("load SSH log file %s: %w", file.Name, err))
|
||||
continue
|
||||
}
|
||||
histories = append(histories, items...)
|
||||
}
|
||||
return histories, loadErr
|
||||
}
|
||||
|
||||
func summarizeSSHLoginHistories(
|
||||
histories []dto.SSHHistory,
|
||||
now time.Time,
|
||||
failedWindow time.Duration,
|
||||
whitelist []string,
|
||||
) (int, []string) {
|
||||
failedStartTime := now.Add(-failedWindow)
|
||||
successStartTime := now.Add(-sshIPLoginWindow)
|
||||
failedCount := 0
|
||||
var abnormalLogins []string
|
||||
|
||||
for _, item := range histories {
|
||||
switch item.Status {
|
||||
case constant.StatusFailed:
|
||||
if isSSHLogWithinTimeRange(item.Date, failedStartTime, now) {
|
||||
failedCount++
|
||||
}
|
||||
case constant.StatusSuccess:
|
||||
if !isSSHLogWithinTimeRange(item.Date, successStartTime, now) || isIPInWhitelist(item.Address, whitelist) {
|
||||
continue
|
||||
}
|
||||
abnormalLogins = append(abnormalLogins, fmt.Sprintf("%s-%s", item.Address, item.Date.Format(constant.DateTimeLayout)))
|
||||
}
|
||||
}
|
||||
return failedCount, abnormalLogins
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func (s *AlertSender) sendByConfig(config model.AlertConfig, quota string, param
|
||||
} else {
|
||||
s.sendBarkWithConfig(config, quota, params)
|
||||
}
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Custom:
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
if isResource {
|
||||
s.sendResourceWebhookWithConfig(config, quota, params)
|
||||
} else {
|
||||
@@ -86,7 +86,7 @@ func (s *AlertSender) sendByConfig(config model.AlertConfig, quota string, param
|
||||
|
||||
func (s *AlertSender) sendByLegacyMethod(method string, quota string, params []dto.Param, isResource bool) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
typeMap := map[string]string{"mail": constant.Email, constant.Bark: constant.Bark, constant.SMS: constant.SMS, constant.Custom: constant.Custom}
|
||||
typeMap := map[string]string{"mail": constant.Email, constant.Bark: constant.Bark, constant.SMS: constant.SMS}
|
||||
configType := method
|
||||
if mapped, ok := typeMap[method]; ok {
|
||||
configType = mapped
|
||||
@@ -308,31 +308,12 @@ func (s *AlertSender) sendWebhookWithConfig(config model.AlertConfig, quota stri
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
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)
|
||||
}
|
||||
err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
||||
return
|
||||
}
|
||||
if !queued {
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
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) {
|
||||
@@ -353,31 +334,11 @@ func (s *AlertSender) sendResourceWebhookWithConfig(config model.AlertConfig, qu
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
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 := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo); err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
||||
return
|
||||
}
|
||||
if !queued {
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method string) {
|
||||
|
||||
+53
-55
@@ -48,7 +48,7 @@ type IAppService interface {
|
||||
PageApp(ctx *gin.Context, req request.AppSearch) (*response.AppRes, error)
|
||||
GetAppTags(ctx *gin.Context) ([]response.TagDTO, error)
|
||||
GetApp(ctx *gin.Context, key string) (*response.AppDTO, error)
|
||||
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
|
||||
GetAppDetail(appId uint, version, appType string, readOnly ...bool) (response.AppDetailDTO, error)
|
||||
Install(req request.AppInstallCreate, executeScript bool) (*model.AppInstall, error)
|
||||
SyncAppListFromRemote(taskID string) error
|
||||
GetAppUpdate() (*response.AppUpdateRes, error)
|
||||
@@ -223,9 +223,6 @@ func (a AppService) GetAppDetailByKey(appKey, version string) (response.AppDetai
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
if err = checkVllmVersionAccess(app.Key, version); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
appDetail, err := appDetailRepo.GetFirst(appDetailRepo.WithAppId(app.ID), appDetailRepo.WithVersion(version))
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
@@ -234,7 +231,7 @@ func (a AppService) GetAppDetailByKey(appKey, version string) (response.AppDetai
|
||||
return appDetailDTO, nil
|
||||
}
|
||||
|
||||
func (a AppService) GetAppDetail(appID uint, version, appType string) (response.AppDetailDTO, error) {
|
||||
func (a AppService) GetAppDetail(appID uint, version, appType string, readOnly ...bool) (response.AppDetailDTO, error) {
|
||||
var (
|
||||
appDetailDTO response.AppDetailDTO
|
||||
opts []repo.DBOption
|
||||
@@ -244,56 +241,58 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
app, err := appRepo.GetFirst(repo.WithByID(detail.AppId))
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
if err = checkVllmVersionAccess(app.Key, detail.Version); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
appDetailDTO.AppDetail = detail
|
||||
appDetailDTO.Enable = true
|
||||
readOnlyMode := isDemoReadOnly(readOnly...)
|
||||
|
||||
if appType == "runtime" {
|
||||
app, err := appRepo.GetFirst(repo.WithByID(appID))
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
|
||||
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
|
||||
if !fileOp.Stat(versionPath) || detail.Update {
|
||||
versionExists := fileOp.Stat(versionPath)
|
||||
if (!versionExists || detail.Update) && !readOnlyMode {
|
||||
if err = downloadApp(app, detail, nil, nil); err != nil && !fileOp.Stat(versionPath) {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
versionExists = fileOp.Stat(versionPath)
|
||||
}
|
||||
switch app.Type {
|
||||
case constant.RuntimePHP:
|
||||
paramsPath := filepath.Join(versionPath, "data.yml")
|
||||
if !fileOp.Stat(paramsPath) {
|
||||
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", paramsPath, nil)
|
||||
}
|
||||
param, err := fileOp.GetContent(paramsPath)
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
paramMap := make(map[string]interface{})
|
||||
if err = yaml.Unmarshal(param, ¶mMap); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
appDetailDTO.Params = paramMap["additionalProperties"]
|
||||
composePath := filepath.Join(versionPath, "docker-compose.yml")
|
||||
if !fileOp.Stat(composePath) {
|
||||
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", composePath, nil)
|
||||
}
|
||||
compose, err := fileOp.GetContent(composePath)
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
composeMap := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal(compose, &composeMap); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
if service, ok := composeMap["services"]; ok {
|
||||
servicesMap := service.(map[string]interface{})
|
||||
for k := range servicesMap {
|
||||
appDetailDTO.Image = k
|
||||
if versionExists {
|
||||
switch app.Type {
|
||||
case constant.RuntimePHP:
|
||||
paramsPath := filepath.Join(versionPath, "data.yml")
|
||||
if !fileOp.Stat(paramsPath) {
|
||||
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", paramsPath, nil)
|
||||
}
|
||||
param, err := fileOp.GetContent(paramsPath)
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
paramMap := make(map[string]interface{})
|
||||
if err = yaml.Unmarshal(param, ¶mMap); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
appDetailDTO.Params = paramMap["additionalProperties"]
|
||||
composePath := filepath.Join(versionPath, "docker-compose.yml")
|
||||
if !fileOp.Stat(composePath) {
|
||||
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", composePath, nil)
|
||||
}
|
||||
compose, err := fileOp.GetContent(composePath)
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
composeMap := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal(compose, &composeMap); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
if service, ok := composeMap["services"]; ok {
|
||||
servicesMap := service.(map[string]interface{})
|
||||
for k := range servicesMap {
|
||||
appDetailDTO.Image = k
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,7 +304,7 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
||||
appDetailDTO.Params = paramMap
|
||||
}
|
||||
|
||||
if appDetailDTO.DockerCompose == "" {
|
||||
if appDetailDTO.DockerCompose == "" && !readOnlyMode {
|
||||
filename := filepath.Base(appDetailDTO.DownloadUrl)
|
||||
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(appDetailDTO.DownloadUrl, filename), "docker-compose.yml")
|
||||
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||
@@ -325,6 +324,10 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
||||
|
||||
appDetailDTO.HostMode = isHostModel(appDetailDTO.DockerCompose)
|
||||
|
||||
app, err := appRepo.GetFirst(repo.WithByID(detail.AppId))
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
if err := checkLimit(app); err != nil {
|
||||
appDetailDTO.Enable = false
|
||||
}
|
||||
@@ -376,9 +379,6 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = checkVllmVersionAccess(app.Key, appDetail.Version); err != nil {
|
||||
return
|
||||
}
|
||||
if DatabaseKeys[app.Key] > 0 {
|
||||
if existDatabases, _ := databaseRepo.GetList(repo.WithByName(req.Name)); len(existDatabases) > 0 {
|
||||
err = buserr.New("ErrRemoteExist")
|
||||
@@ -488,17 +488,15 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
index++
|
||||
}
|
||||
newServiceName := strings.ToLower(appInstall.Name)
|
||||
if app.Limit == 0 && newServiceName != serviceName && len(servicesMap) == 1 && !req.KeepServiceName {
|
||||
if app.Limit == 0 && newServiceName != serviceName && len(servicesMap) == 1 {
|
||||
servicesMap[newServiceName] = servicesMap[serviceName]
|
||||
delete(servicesMap, serviceName)
|
||||
serviceName = newServiceName
|
||||
}
|
||||
appInstall.ServiceName = serviceName
|
||||
|
||||
if !req.SkipComposeCommonConfig {
|
||||
if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
||||
return
|
||||
}
|
||||
if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
||||
return
|
||||
}
|
||||
var (
|
||||
composeByte []byte
|
||||
@@ -566,7 +564,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
return err
|
||||
}
|
||||
}
|
||||
if executeScript || req.UseLifecycleScripts {
|
||||
if executeScript {
|
||||
if err = runScript(t, appInstall, "init"); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -579,7 +577,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = upApp(t, appInstall, req.PullImage, req.UseLifecycleScripts); err != nil {
|
||||
if err = upApp(t, appInstall, req.PullImage); err != nil {
|
||||
return err
|
||||
}
|
||||
updateToolApp(appInstall)
|
||||
|
||||
@@ -13,7 +13,7 @@ type AppIgnoreUpgradeService struct {
|
||||
}
|
||||
|
||||
type IAppIgnoreUpgradeService interface {
|
||||
List() ([]response.AppIgnoreUpgradeDTO, error)
|
||||
List(readOnly ...bool) ([]response.AppIgnoreUpgradeDTO, error)
|
||||
CreateAppIgnore(req request.AppIgnoreUpgradeReq) error
|
||||
Delete(req request.ReqWithID) error
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func NewIAppIgnoreUpgradeService() IAppIgnoreUpgradeService {
|
||||
return AppIgnoreUpgradeService{}
|
||||
}
|
||||
|
||||
func (a AppIgnoreUpgradeService) List() ([]response.AppIgnoreUpgradeDTO, error) {
|
||||
func (a AppIgnoreUpgradeService) List(readOnly ...bool) ([]response.AppIgnoreUpgradeDTO, error) {
|
||||
var res []response.AppIgnoreUpgradeDTO
|
||||
ignores, err := appIgnoreUpgradeRepo.List()
|
||||
if err != nil {
|
||||
@@ -37,14 +37,18 @@ func (a AppIgnoreUpgradeService) List() ([]response.AppIgnoreUpgradeDTO, error)
|
||||
}
|
||||
app, err := appRepo.GetFirst(repo.WithByID(ignore.AppID))
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||
}
|
||||
continue
|
||||
}
|
||||
dto.Name = app.Name
|
||||
if ignore.Scope == "version" {
|
||||
appDetail, err := appDetailRepo.GetFirst(repo.WithByID(ignore.AppDetailID))
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||
}
|
||||
continue
|
||||
}
|
||||
dto.Version = appDetail.Version
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -14,14 +13,12 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
@@ -45,21 +42,21 @@ type IAppInstallService interface {
|
||||
Page(req request.AppInstalledSearch) (int64, []response.AppInstallDTO, error)
|
||||
CheckExist(req request.AppInstalledInfo) (*response.AppInstalledCheck, error)
|
||||
LoadPort(req dto.OperationWithNameAndType) (int64, error)
|
||||
LoadConnInfo(req dto.OperationWithNameAndType) (response.DatabaseConn, error)
|
||||
LoadConnInfo(req dto.OperationWithNameAndType, readOnly ...bool) (response.DatabaseConn, error)
|
||||
SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstallDTO, error)
|
||||
Operate(req request.AppInstalledOperate) error
|
||||
Update(req request.AppInstalledUpdate) error
|
||||
SyncAll(systemInit bool) error
|
||||
GetServices(key string) ([]response.AppService, error)
|
||||
GetUpdateVersions(req request.AppUpdateVersion) ([]dto.AppVersion, error)
|
||||
GetParams(id uint) (*response.AppConfig, error)
|
||||
GetParams(id uint, readOnly ...bool) (*response.AppConfig, error)
|
||||
ChangeAppPort(req request.PortUpdate) error
|
||||
GetDefaultConfigByKey(key, name string) (string, error)
|
||||
DeleteCheck(installId uint) ([]dto.AppResource, error)
|
||||
DeleteCheck(installId uint, readOnly ...bool) ([]dto.AppResource, error)
|
||||
|
||||
UpdateAppConfig(req request.AppConfigUpdate) error
|
||||
GetInstallList() ([]dto.AppInstallInfo, error)
|
||||
GetAppInstallInfo(appInstallID uint) (*response.AppInstallInfo, error)
|
||||
GetAppInstallInfo(appInstallID uint, readOnly ...bool) (*response.AppInstallInfo, error)
|
||||
UpdateSort(req request.AppInstallSort) error
|
||||
}
|
||||
|
||||
@@ -126,7 +123,7 @@ func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []respo
|
||||
}
|
||||
}
|
||||
|
||||
installDTOs, _ := handleInstalled(installs, req.Update, req.Sync, req.CheckUpdate)
|
||||
installDTOs, _ := handleInstalled(installs, req.Update, req.Sync && !req.ReadOnly, req.CheckUpdate, req.ReadOnly)
|
||||
if req.Update {
|
||||
total = int64(len(installDTOs))
|
||||
}
|
||||
@@ -154,8 +151,10 @@ func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.
|
||||
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
|
||||
return res, nil
|
||||
}
|
||||
if err = syncAppInstallStatus(&appInstall, false); err != nil {
|
||||
return nil, err
|
||||
if !req.ReadOnly {
|
||||
if err = syncAppInstallStatus(&appInstall, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
res.ContainerName = appInstall.ContainerName
|
||||
@@ -185,7 +184,7 @@ func (a *AppInstallService) LoadPort(req dto.OperationWithNameAndType) (int64, e
|
||||
return app.Port, nil
|
||||
}
|
||||
|
||||
func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType) (response.DatabaseConn, error) {
|
||||
func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType, readOnly ...bool) (response.DatabaseConn, error) {
|
||||
var data response.DatabaseConn
|
||||
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
|
||||
if err != nil {
|
||||
@@ -194,6 +193,9 @@ func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType) (resp
|
||||
data.Status = app.Status
|
||||
data.Username = app.UserName
|
||||
data.Password = app.Password
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
data.Password = ""
|
||||
}
|
||||
data.ServiceName = app.ServiceName
|
||||
data.Port = app.Port
|
||||
data.ContainerName = app.ContainerName
|
||||
@@ -243,7 +245,7 @@ func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]
|
||||
}
|
||||
}
|
||||
|
||||
return handleInstalled(installs, false, true, false)
|
||||
return handleInstalled(installs, false, !req.ReadOnly, false, req.ReadOnly)
|
||||
}
|
||||
|
||||
func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
@@ -255,9 +257,6 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
return buserr.New("ErrInstallDirNotFound")
|
||||
}
|
||||
dockerComposePath := install.GetComposePath()
|
||||
if req.UseLifecycleScripts && (req.Operate == constant.Start || req.Operate == constant.Stop || req.Operate == constant.Restart) {
|
||||
return operateAppWithLifecycleScripts(install, req, nil)
|
||||
}
|
||||
switch req.Operate {
|
||||
case constant.Rebuild:
|
||||
return rebuildApp(install)
|
||||
@@ -281,13 +280,12 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
return syncAppInstallStatus(&install, false)
|
||||
case constant.Delete:
|
||||
deleteReq := request.AppInstallDelete{
|
||||
Install: install,
|
||||
DeleteBackup: req.DeleteBackup,
|
||||
ForceDelete: req.ForceDelete,
|
||||
DeleteDB: req.DeleteDB,
|
||||
DeleteImage: req.DeleteImage,
|
||||
TaskID: req.TaskID,
|
||||
UseLifecycleScripts: req.UseLifecycleScripts,
|
||||
Install: install,
|
||||
DeleteBackup: req.DeleteBackup,
|
||||
ForceDelete: req.ForceDelete,
|
||||
DeleteDB: req.DeleteDB,
|
||||
DeleteImage: req.DeleteImage,
|
||||
TaskID: req.TaskID,
|
||||
}
|
||||
if err = deleteAppInstall(deleteReq); err != nil && !req.ForceDelete {
|
||||
return err
|
||||
@@ -319,70 +317,6 @@ 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 {
|
||||
installed, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
|
||||
if err != nil {
|
||||
@@ -445,10 +379,8 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !req.SkipComposeCommonConfig {
|
||||
if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
||||
return err
|
||||
}
|
||||
composeByte, err := yaml.Marshal(composeMap)
|
||||
if err != nil {
|
||||
@@ -481,7 +413,7 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backupEnvMaps := maps.Clone(oldEnvMaps)
|
||||
backupEnvMaps := oldEnvMaps
|
||||
handleMap(req.Params, oldEnvMaps)
|
||||
paramByte, err := json.Marshal(oldEnvMaps)
|
||||
if err != nil {
|
||||
@@ -493,32 +425,13 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(installed.DockerCompose), constant.DirPerm)
|
||||
restoreConfig := func(operationErr error) {
|
||||
if err := rebuildApp(installed); err != nil {
|
||||
_ = env.Write(backupEnvMaps, envPath)
|
||||
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(backupDockerCompose), constant.DirPerm)
|
||||
failed := oldInstalled
|
||||
failed.Status = constant.StatusUpErr
|
||||
failed.Message = operationErr.Error()
|
||||
_ = appInstallRepo.Save(context.Background(), &failed)
|
||||
}
|
||||
if req.UseLifecycleScripts {
|
||||
err = operateAppWithLifecycleScripts(installed, request.AppInstalledOperate{
|
||||
InstallId: installed.ID,
|
||||
Operate: constant.Restart,
|
||||
TaskID: req.TaskID,
|
||||
UseLifecycleScripts: true,
|
||||
}, restoreConfig)
|
||||
} else {
|
||||
err = rebuildApp(installed)
|
||||
}
|
||||
if err != nil {
|
||||
restoreConfig(err)
|
||||
return err
|
||||
}
|
||||
if !req.UseLifecycleScripts {
|
||||
installed.Status = constant.StatusRunning
|
||||
_ = appInstallRepo.Save(context.Background(), &installed)
|
||||
}
|
||||
installed.Status = constant.StatusRunning
|
||||
_ = appInstallRepo.Save(context.Background(), &installed)
|
||||
|
||||
proxyChanged := hasAppInstallProxyPassChanged(&oldInstalled, &installed)
|
||||
currentProxy, currentProxyErr := getAppInstallProxyPass(&installed)
|
||||
@@ -675,9 +588,6 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
|
||||
return versions, err
|
||||
}
|
||||
for _, detail := range details {
|
||||
if !canAccessVllmVersion(app.Key, detail.Version) {
|
||||
continue
|
||||
}
|
||||
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(detail.ID), appIgnoreUpgradeRepo.WithScope("version"))
|
||||
if len(ignores) > 0 {
|
||||
continue
|
||||
@@ -759,7 +669,7 @@ func (a *AppInstallService) ChangeAppPort(req request.PortUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AppInstallService) DeleteCheck(installID uint) ([]dto.AppResource, error) {
|
||||
func (a *AppInstallService) DeleteCheck(installID uint, readOnly ...bool) ([]dto.AppResource, error) {
|
||||
var res []dto.AppResource
|
||||
appInstall, err := appInstallRepo.GetFirst(repo.WithByID(installID))
|
||||
if err != nil {
|
||||
@@ -780,7 +690,7 @@ func (a *AppInstallService) DeleteCheck(installID uint) ([]dto.AppResource, erro
|
||||
Type: "app",
|
||||
Name: linkInstall.Name,
|
||||
})
|
||||
} else {
|
||||
} else if !isDemoReadOnly(readOnly...) {
|
||||
_ = appInstallResourceRepo.DeleteBy(context.Background(), appInstallResourceRepo.WithAppInstallId(resource.AppInstallId))
|
||||
}
|
||||
}
|
||||
@@ -818,7 +728,7 @@ func (a *AppInstallService) GetDefaultConfigByKey(key, name string) (string, err
|
||||
return string(contentByte), nil
|
||||
}
|
||||
|
||||
func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
func (a *AppInstallService) GetParams(id uint, readOnly ...bool) (*response.AppConfig, error) {
|
||||
var (
|
||||
params []response.AppParam
|
||||
appForm dto.AppForm
|
||||
@@ -913,8 +823,14 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
}
|
||||
}
|
||||
|
||||
readOnlyMode := isDemoReadOnly(readOnly...)
|
||||
if readOnlyMode {
|
||||
redactSensitiveAppParams(params)
|
||||
}
|
||||
config := getAppCommonConfig(envs)
|
||||
config.DockerCompose = install.DockerCompose
|
||||
if !readOnlyMode {
|
||||
config.DockerCompose = install.DockerCompose
|
||||
}
|
||||
res.Params = params
|
||||
if config.ContainerName == "" {
|
||||
config.ContainerName = install.ContainerName
|
||||
@@ -924,16 +840,16 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
res.RestartPolicy = getRestartPolicy(install.DockerCompose)
|
||||
res.WebUI = install.WebUI
|
||||
res.Type = install.App.Type
|
||||
if rawCompose, err := getUpgradeCompose(install, detail); err == nil {
|
||||
res.RawCompose = rawCompose
|
||||
if !readOnlyMode {
|
||||
if rawCompose, err := getUpgradeCompose(install, detail); err == nil {
|
||||
res.RawCompose = rawCompose
|
||||
}
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func syncAppInstallStatus(appInstall *model.AppInstall, force bool) error {
|
||||
switch appInstall.Status {
|
||||
case constant.StatusInstalling, constant.StatusRebuilding, constant.StatusUpgrading, constant.StatusUninstalling,
|
||||
constant.StatusStarting, constant.StatusRestarting, constant.StatusWaiting:
|
||||
if appInstall.Status == constant.StatusInstalling || appInstall.Status == constant.StatusRebuilding || appInstall.Status == constant.StatusUpgrading || appInstall.Status == constant.StatusUninstalling {
|
||||
return nil
|
||||
}
|
||||
cli, err := docker.NewClient()
|
||||
@@ -1055,20 +971,25 @@ func updateInstallInfoInDB(appKey, appName, param string, value interface{}) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AppInstallService) GetAppInstallInfo(installID uint) (*response.AppInstallInfo, error) {
|
||||
func (a *AppInstallService) GetAppInstallInfo(installID uint, readOnly ...bool) (*response.AppInstallInfo, error) {
|
||||
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(installID))
|
||||
if appInstall.ID == 0 {
|
||||
return &response.AppInstallInfo{
|
||||
Status: constant.StatusDeleted,
|
||||
}, nil
|
||||
}
|
||||
_ = syncAppInstallStatus(&appInstall, false)
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
_ = syncAppInstallStatus(&appInstall, false)
|
||||
}
|
||||
appInstall, _ = appInstallRepo.GetFirst(repo.WithByID(installID))
|
||||
var envMap map[string]interface{}
|
||||
err := json.Unmarshal([]byte(appInstall.Env), &envMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
redactSensitiveValues(envMap)
|
||||
}
|
||||
res := &response.AppInstallInfo{
|
||||
ID: appInstall.ID,
|
||||
Name: appInstall.Name,
|
||||
|
||||
@@ -40,13 +40,14 @@ const (
|
||||
appUpgradeDown
|
||||
appUpgradeMutated
|
||||
appUpgradeStarted
|
||||
appUpgradeReady
|
||||
appUpgradeCommitted
|
||||
)
|
||||
|
||||
var appUpgradeLocks sync.Map
|
||||
|
||||
const composeServiceLabel = "com.docker.compose.service"
|
||||
|
||||
var appUpgradeLocks sync.Map
|
||||
|
||||
type appUpgradeSnapshot interface {
|
||||
Restore() error
|
||||
Cleanup()
|
||||
@@ -106,9 +107,6 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = checkVllmVersionAccess(install.App.Key, detail.Version); err != nil {
|
||||
return err
|
||||
}
|
||||
if install.App.Key == vllmAppKeyForUpgrade && !isVllmUpgradeVersionAllowed(install.Version, detail.Version, loadVllmImageFromEnv(install.Env)) {
|
||||
return errors.New("vLLM can only upgrade within the same image type")
|
||||
}
|
||||
@@ -438,14 +436,15 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
|
||||
t.LogSuccess(logStr)
|
||||
u.phase = appUpgradeStarted
|
||||
|
||||
containerNames, discoverErr := discoverUpgradeContainerNames(u.candidate, u.envContent)
|
||||
if discoverErr != nil {
|
||||
t.Logf("WARNING: discover upgraded application containers failed: %v", discoverErr)
|
||||
} else if len(containerNames) > 0 {
|
||||
u.candidate.ContainerName = strings.Join(containerNames, ",")
|
||||
} else {
|
||||
t.Log("WARNING: no containers found for the upgraded application")
|
||||
t.LogStart(i18n.GetMsgByKey("UpgradeWaitReady"))
|
||||
containerNames, err := waitAppContainersReady(context.Background(), u.candidate)
|
||||
if err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("UpgradeWaitReady"), err)
|
||||
return err
|
||||
}
|
||||
t.LogSuccess(i18n.GetMsgByKey("UpgradeWaitReady"))
|
||||
u.phase = appUpgradeReady
|
||||
u.candidate.ContainerName = strings.Join(containerNames, ",")
|
||||
u.candidate.Status = constant.StatusRunning
|
||||
u.candidate.Message = ""
|
||||
|
||||
@@ -460,21 +459,9 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
// Upgrades deliberately keep the user's nginx.conf, so corrected gzip
|
||||
// defaults shipped with a new version would never reach existing
|
||||
// installations. Rewrite only an untouched factory configuration, and
|
||||
// never fail the upgrade over it.
|
||||
if gzipErr := upgradeStockNginxGzipConfig(u.candidate); gzipErr != nil {
|
||||
t.Logf("WARNING: update stock gzip configuration failed, keeping the current one: %v", gzipErr)
|
||||
}
|
||||
} else if err = appInstallRepo.Save(context.Background(), &u.candidate); err != nil {
|
||||
return err
|
||||
}
|
||||
if discoverErr == nil && len(containerNames) > 0 {
|
||||
if syncErr := syncAppInstallStatus(&u.candidate, true); syncErr != nil {
|
||||
t.Logf("WARNING: sync upgraded application status failed: %v", syncErr)
|
||||
}
|
||||
}
|
||||
u.phase = appUpgradeCommitted
|
||||
u.deleteOldImages(t)
|
||||
return nil
|
||||
@@ -594,6 +581,9 @@ func (u *appUpgradeContext) rollback(t *task.Task) (rollbackErr error) {
|
||||
}
|
||||
|
||||
func (u *appUpgradeContext) finishRollback() error {
|
||||
if _, err := waitAppContainersReady(context.Background(), u.original); err != nil {
|
||||
return err
|
||||
}
|
||||
restored := u.original
|
||||
if err := appInstallRepo.Save(context.Background(), &restored); err != nil {
|
||||
return err
|
||||
@@ -725,20 +715,6 @@ func renderUpgradeEnv(install *model.AppInstall, original []byte) ([]byte, error
|
||||
return nil, err
|
||||
}
|
||||
handleMap(envs, params)
|
||||
if install.App.Key == "openlist" {
|
||||
// The upgrade script updates this too late for the pre-pull phase.
|
||||
image := "openlistteam/openlist:v" + strings.TrimPrefix(install.Version, "v")
|
||||
if preInstalled := params["PRE_INSTALLED"]; preInstalled != "" {
|
||||
image += "-" + preInstalled
|
||||
}
|
||||
params["OPENLIST_IMAGE"] = image
|
||||
envs["OPENLIST_IMAGE"] = image
|
||||
content, err := json.Marshal(envs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
install.Env = string(content)
|
||||
}
|
||||
if install.App.Key == constant.AppOpenresty {
|
||||
for _, key := range []string{"CONTAINER_PACKAGE_URL", "RESTY_ADD_PACKAGE_BUILDDEPS", "RESTY_CONFIG_OPTIONS_MORE"} {
|
||||
if value, ok := originalEnv[key]; ok {
|
||||
@@ -881,7 +857,28 @@ func (s *upgradeFileSnapshot) Cleanup() {
|
||||
}
|
||||
}
|
||||
|
||||
func discoverUpgradeContainerNames(install model.AppInstall, envContent []byte) ([]string, error) {
|
||||
type appContainerReadinessClient interface {
|
||||
ContainerList(context.Context, container.ListOptions) ([]container.Summary, error)
|
||||
ContainerInspect(context.Context, string) (container.InspectResponse, error)
|
||||
}
|
||||
|
||||
func waitAppContainersReady(ctx context.Context, install model.AppInstall) ([]string, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
return waitAppContainersReadyWithClient(ctx, client, install)
|
||||
}
|
||||
|
||||
func waitAppContainersReadyWithClient(ctx context.Context, client appContainerReadinessClient, install model.AppInstall) ([]string, error) {
|
||||
envContent, err := os.ReadFile(install.GetEnvPath())
|
||||
if err != nil {
|
||||
envContent, err = renderUpgradeEnv(&install, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
project, err := docker.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), envContent, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -895,24 +892,36 @@ func discoverUpgradeContainerNames(install model.AppInstall, envContent []byte)
|
||||
if len(expectedServices) == 0 {
|
||||
return strings.Split(install.ContainerName, ","), nil
|
||||
}
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
options := container.ListOptions{
|
||||
All: true,
|
||||
Filters: filters.NewArgs(
|
||||
filters.Arg("label", composeWorkdirLabel+"="+install.GetPath()),
|
||||
),
|
||||
}
|
||||
defer client.Close()
|
||||
containers, err := client.ContainerList(context.Background(), container.ListOptions{
|
||||
All: true,
|
||||
Filters: filters.NewArgs(filters.Arg("label", composeWorkdirLabel+"="+install.GetPath())),
|
||||
})
|
||||
containers, err := client.ContainerList(ctx, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
foundServices := make(map[string]bool, len(expectedServices))
|
||||
containerNames := make([]string, 0, len(containers))
|
||||
for _, item := range containers {
|
||||
if _, ok := expectedServices[item.Labels[composeServiceLabel]]; ok && len(item.Names) > 0 {
|
||||
serviceName := item.Labels[composeServiceLabel]
|
||||
if _, ok := expectedServices[serviceName]; !ok {
|
||||
continue
|
||||
}
|
||||
if err = waitContainerReady(ctx, client, item.ID); err != nil {
|
||||
return nil, fmt.Errorf("container %s is not ready: %w", serviceName, err)
|
||||
}
|
||||
foundServices[serviceName] = true
|
||||
if len(item.Names) > 0 {
|
||||
containerNames = append(containerNames, strings.TrimPrefix(item.Names[0], "/"))
|
||||
}
|
||||
}
|
||||
for serviceName := range expectedServices {
|
||||
if !foundServices[serviceName] {
|
||||
return nil, fmt.Errorf("container for service %s was not created", serviceName)
|
||||
}
|
||||
}
|
||||
sort.Strings(containerNames)
|
||||
return containerNames, nil
|
||||
}
|
||||
|
||||
@@ -53,6 +53,62 @@ var (
|
||||
Delete DatabaseOp = "delete"
|
||||
)
|
||||
|
||||
func isDemoReadOnly(readOnly ...bool) bool {
|
||||
return global.CONF.Base.IsDemo || len(readOnly) > 0 && readOnly[0]
|
||||
}
|
||||
|
||||
func normalizeConfigKey(key string) string {
|
||||
return strings.ToLower(strings.NewReplacer("_", "", "-", "", ".", "", " ", "").Replace(key))
|
||||
}
|
||||
|
||||
func isSensitiveConfigKey(key string) bool {
|
||||
normalized := normalizeConfigKey(key)
|
||||
for _, marker := range []string{"password", "passwd", "passphrase", "secret", "token", "credential", "privatekey", "authorization"} {
|
||||
if strings.Contains(normalized, marker) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return normalized == "key" || strings.HasSuffix(normalized, "key")
|
||||
}
|
||||
|
||||
func redactSensitiveValues(values map[string]interface{}) {
|
||||
redactSensitiveData(values)
|
||||
}
|
||||
|
||||
func redactSensitiveData(value interface{}) {
|
||||
switch data := value.(type) {
|
||||
case map[string]interface{}:
|
||||
for key, item := range data {
|
||||
if isSensitiveConfigKey(key) {
|
||||
data[key] = ""
|
||||
} else {
|
||||
redactSensitiveData(item)
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for _, item := range data {
|
||||
redactSensitiveData(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func redactSensitiveAppParams(params []response.AppParam) {
|
||||
for i := range params {
|
||||
if strings.EqualFold(params[i].Type, "password") || isSensitiveConfigKey(params[i].Key) {
|
||||
params[i].Value = ""
|
||||
params[i].ShowValue = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func redactSensitiveEnvironments(environments []request.Environment) {
|
||||
for i := range environments {
|
||||
if isSensitiveConfigKey(environments[i].Key) {
|
||||
environments[i].Value = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkPort(key string, params map[string]interface{}) (int, error) {
|
||||
port, ok := params[key]
|
||||
if ok {
|
||||
@@ -352,33 +408,15 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
||||
if dir != nil {
|
||||
logStr := i18n.GetMsgByKey("Stop") + i18n.GetMsgByKey("App")
|
||||
t.Log(logStr)
|
||||
cleanupFailed := false
|
||||
|
||||
if deleteReq.UseLifecycleScripts {
|
||||
if scriptErr := runScript(t, &install, "uninstall"); scriptErr != nil {
|
||||
cleanupFailed = true
|
||||
if !deleteReq.ForceDelete {
|
||||
return scriptErr
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out, downErr := compose.Down(install.GetComposePath())
|
||||
if downErr != nil {
|
||||
cleanupFailed = true
|
||||
if !deleteReq.ForceDelete {
|
||||
return handleErr(install, downErr, out)
|
||||
}
|
||||
}
|
||||
if scriptErr := runScript(t, &install, "uninstall"); scriptErr != nil {
|
||||
cleanupFailed = true
|
||||
if !deleteReq.ForceDelete {
|
||||
_, _ = compose.Up(install.GetComposePath())
|
||||
return scriptErr
|
||||
}
|
||||
}
|
||||
out, err := compose.Down(install.GetComposePath())
|
||||
if err != nil && !deleteReq.ForceDelete {
|
||||
return handleErr(install, err, out)
|
||||
}
|
||||
if !cleanupFailed {
|
||||
t.LogSuccess(logStr)
|
||||
t.LogSuccess(logStr)
|
||||
if err = runScript(t, &install, "uninstall"); err != nil {
|
||||
_, _ = compose.Up(install.GetComposePath())
|
||||
return err
|
||||
}
|
||||
if deleteReq.DeleteImage {
|
||||
content, err := op.GetContent(install.GetEnvPath())
|
||||
@@ -478,9 +516,8 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
||||
}
|
||||
uninstallTask.AddSubTask(task.GetTaskName(install.Name, task.TaskUninstall, task.TaskScopeApp), uninstall, nil)
|
||||
go func() {
|
||||
if err := uninstallTask.Execute(); err != nil {
|
||||
if err := uninstallTask.Execute(); err != nil && !deleteReq.ForceDelete {
|
||||
install.Status = constant.StatusError
|
||||
install.Message = err.Error()
|
||||
_ = appInstallRepo.Save(context.Background(), &install)
|
||||
}
|
||||
}()
|
||||
@@ -1018,12 +1055,6 @@ func runScript(task *task.Task, appInstall *model.AppInstall, operate string) er
|
||||
scriptPath = path.Join(workDir, "scripts", "upgrade.sh")
|
||||
case "uninstall":
|
||||
scriptPath = path.Join(workDir, "scripts", "uninstall.sh")
|
||||
case "start":
|
||||
scriptPath = path.Join(workDir, "scripts", "start.sh")
|
||||
case "stop":
|
||||
scriptPath = path.Join(workDir, "scripts", "stop.sh")
|
||||
case "restart":
|
||||
scriptPath = path.Join(workDir, "scripts", "restart.sh")
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(scriptPath) {
|
||||
@@ -1033,11 +1064,7 @@ func runScript(task *task.Task, appInstall *model.AppInstall, operate string) er
|
||||
logStr := i18n.GetWithName("ExecShell", operate)
|
||||
task.LogStart(logStr)
|
||||
|
||||
timeout := 10 * time.Minute
|
||||
if operate == "start" || operate == "restart" {
|
||||
timeout = time.Hour
|
||||
}
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(timeout), cmd.WithWorkDir(workDir), cmd.WithTask(*task))
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute), cmd.WithWorkDir(workDir))
|
||||
if err := cmdMgr.Run("bash", scriptPath); err != nil {
|
||||
task.LogFailedWithErr(logStr, err)
|
||||
return err
|
||||
@@ -1072,15 +1099,12 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func upApp(task *task.Task, appInstall *model.AppInstall, pullImages, useLifecycleScripts bool) error {
|
||||
func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) error {
|
||||
upProject := func(appInstall *model.AppInstall) (err error) {
|
||||
var (
|
||||
out string
|
||||
errMsg string
|
||||
)
|
||||
if useLifecycleScripts {
|
||||
return runScript(task, appInstall, "start")
|
||||
}
|
||||
if pullImages && appInstall.App.Type != "php" {
|
||||
envByte, err := files.NewFileOp().GetContent(appInstall.GetEnvPath())
|
||||
if err != nil {
|
||||
@@ -1407,8 +1431,7 @@ func handleErr(install model.AppInstall, err error, out string) error {
|
||||
|
||||
func doNotNeedSync(installed model.AppInstall) bool {
|
||||
return installed.Status == constant.StatusInstalling || installed.Status == constant.StatusRebuilding || installed.Status == constant.StatusUpgrading ||
|
||||
installed.Status == constant.StatusSyncing || installed.Status == constant.StatusUninstalling || installed.Status == constant.StatusInstallErr ||
|
||||
installed.Status == constant.StatusStarting || installed.Status == constant.StatusRestarting || installed.Status == constant.StatusWaiting
|
||||
installed.Status == constant.StatusSyncing || installed.Status == constant.StatusUninstalling || installed.Status == constant.StatusInstallErr
|
||||
}
|
||||
|
||||
func synAppInstall(containers map[string]container.Summary, appInstall *model.AppInstall, force bool) {
|
||||
@@ -1420,7 +1443,9 @@ func synAppInstall(containers map[string]container.Summary, appInstall *model.Ap
|
||||
}
|
||||
appInstall.Status = constant.StatusError
|
||||
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
if !global.CONF.Base.IsDemo {
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
}
|
||||
return
|
||||
}
|
||||
notFoundNames := make([]string, 0)
|
||||
@@ -1479,10 +1504,12 @@ func synAppInstall(containers map[string]container.Summary, appInstall *model.Ap
|
||||
appInstall.Message = msg
|
||||
appInstall.Status = constant.StatusUnHealthy
|
||||
}
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
if !global.CONF.Base.IsDemo {
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
}
|
||||
}
|
||||
|
||||
func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpdate bool) ([]response.AppInstallDTO, error) {
|
||||
func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpdate, readOnly bool) ([]response.AppInstallDTO, error) {
|
||||
var (
|
||||
res []response.AppInstallDTO
|
||||
containersMap map[string]container.Summary
|
||||
@@ -1513,6 +1540,9 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
||||
resourceKeys := getAppInstallResourceKeys(installed.ID)
|
||||
envMap := make(map[string]interface{})
|
||||
_ = json.Unmarshal([]byte(installed.Env), &envMap)
|
||||
if isDemoReadOnly(readOnly) {
|
||||
redactSensitiveValues(envMap)
|
||||
}
|
||||
installDTO := response.AppInstallDTO{
|
||||
ID: installed.ID,
|
||||
Name: installed.Name,
|
||||
@@ -1557,7 +1587,9 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
||||
continue
|
||||
}
|
||||
|
||||
installDTO.DockerCompose = installed.DockerCompose
|
||||
if !isDemoReadOnly(readOnly) {
|
||||
installDTO.DockerCompose = installed.DockerCompose
|
||||
}
|
||||
installDTO.IsEdit = isEditCompose(installed)
|
||||
|
||||
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
|
||||
@@ -2262,9 +2294,6 @@ func getAppVersions(key string, details []model.AppDetail) []string {
|
||||
hasLatest := false
|
||||
latestVersion := ""
|
||||
for _, detail := range details {
|
||||
if !canAccessVllmVersion(key, detail.Version) {
|
||||
continue
|
||||
}
|
||||
if key != "mssql" && strings.Contains(detail.Version, "latest") {
|
||||
hasLatest = true
|
||||
latestVersion = detail.Version
|
||||
|
||||
@@ -33,7 +33,7 @@ type IBackupService interface {
|
||||
CheckUsed(name string, isPublic bool) error
|
||||
|
||||
LoadBackupOptions() ([]dto.BackupOption, error)
|
||||
SearchWithPage(search dto.SearchPageWithType) (int64, interface{}, error)
|
||||
SearchWithPage(search dto.SearchPageWithType, readOnly ...bool) (int64, interface{}, error)
|
||||
Create(backupDto dto.BackupOperate) error
|
||||
CheckConn(req dto.BackupOperate) dto.BackupCheckRes
|
||||
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
|
||||
@@ -80,7 +80,7 @@ func (u *BackupService) GetLocalDir() (string, error) {
|
||||
return account.BackupPath, nil
|
||||
}
|
||||
|
||||
func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, interface{}, error) {
|
||||
func (u *BackupService) SearchWithPage(req dto.SearchPageWithType, readOnly ...bool) (int64, interface{}, error) {
|
||||
options := []repo.DBOption{repo.WithOrderDesc("created_at")}
|
||||
if len(req.Type) != 0 {
|
||||
options = append(options, repo.WithByType(req.Type))
|
||||
@@ -128,11 +128,36 @@ func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, inter
|
||||
itemVars, _ := json.Marshal(varMap)
|
||||
item.Vars = string(itemVars)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.AccessKey = ""
|
||||
item.Credential = ""
|
||||
item.Vars = sanitizeBackupVars(item.Vars)
|
||||
}
|
||||
data = append(data, item)
|
||||
}
|
||||
return count, data, nil
|
||||
}
|
||||
|
||||
func sanitizeBackupVars(vars string) string {
|
||||
if vars == "" {
|
||||
return vars
|
||||
}
|
||||
var values map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(vars), &values); err != nil {
|
||||
return ""
|
||||
}
|
||||
for key := range values {
|
||||
if isSensitiveConfigKey(key) || normalizeConfigKey(key) == "code" {
|
||||
delete(values, key)
|
||||
}
|
||||
}
|
||||
data, err := json.Marshal(values)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func (u *BackupService) CheckConn(req dto.BackupOperate) dto.BackupCheckRes {
|
||||
var res dto.BackupCheckRes
|
||||
var backup model.BackupAccount
|
||||
|
||||
@@ -582,10 +582,6 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
||||
if config.Image == "" {
|
||||
return fmt.Errorf("container image not found in backup file")
|
||||
}
|
||||
networkConf, extraNetworks := buildContainerRecoverNetworkConfig(recoverCtx.inspectInfo.NetworkSettings, hostConfig)
|
||||
if err := normalizeContainerEndpointSettings(ctx, recoverCtx.client, networkConf, extraNetworks); err != nil {
|
||||
return err
|
||||
}
|
||||
if !checkImageExist(recoverCtx.client, config.Image) {
|
||||
if err := pullImages(taskItem, recoverCtx.client, config.Image); err != nil {
|
||||
return err
|
||||
@@ -600,7 +596,7 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
||||
return err
|
||||
}
|
||||
|
||||
createRes, err := createContainerWithNetworks(ctx, recoverCtx.client, config, hostConfig, networkConf, extraNetworks, recoverCtx.targetName)
|
||||
createRes, err := createContainerWithOldNetworks(ctx, recoverCtx.client, config, hostConfig, recoverCtx.inspectInfo.NetworkSettings, recoverCtx.targetName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -608,7 +604,7 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeContainerEndpointSettings(ctx context.Context, cli *client.Client, primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) error {
|
||||
func normalizeContainerEndpointSettings(ctx context.Context, cli *client.Client, primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) {
|
||||
if cli.NewVersionError(ctx, "1.44", "specify mac-address per network") != nil {
|
||||
removeEndpointMacAddresses(primary, extras)
|
||||
}
|
||||
@@ -623,14 +619,11 @@ func normalizeContainerEndpointSettings(ctx context.Context, cli *client.Client,
|
||||
}
|
||||
info, err := cli.NetworkInspect(ctx, netName, network.InspectOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("inspect network %s failed: %w", netName, err)
|
||||
}
|
||||
if err := validateContainerEndpointStaticIP(netName, info, endpoint); err != nil {
|
||||
return err
|
||||
continue
|
||||
}
|
||||
removeUnsupportedEndpointStaticIP(netName, info, endpoint)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeEndpointMacAddresses(primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) {
|
||||
@@ -648,28 +641,24 @@ func removeEndpointMacAddresses(primary *network.NetworkingConfig, extras map[st
|
||||
}
|
||||
}
|
||||
|
||||
func validateContainerEndpointStaticIP(netName string, info network.Inspect, endpoint *network.EndpointSettings) error {
|
||||
func removeUnsupportedEndpointStaticIP(netName string, info network.Inspect, endpoint *network.EndpointSettings) {
|
||||
if endpoint == nil || endpoint.IPAMConfig == nil {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
ipam := endpoint.IPAMConfig
|
||||
if err := ipam.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid IP configuration for network %s: %w", netName, err)
|
||||
}
|
||||
if ipam.IPv4Address == "" && ipam.IPv6Address == "" {
|
||||
return nil
|
||||
}
|
||||
if netName == "host" || netName == "none" || isDefaultBridgeNetwork(netName, info) {
|
||||
return fmt.Errorf("network %s does not support static IP configuration", netName)
|
||||
if isDefaultBridgeNetwork(netName, info) {
|
||||
endpoint.IPAMConfig = nil
|
||||
return
|
||||
}
|
||||
|
||||
if ipam.IPv4Address != "" && !networkSupportsStaticIP(info, ipam.IPv4Address, false) {
|
||||
return fmt.Errorf("static IPv4 address %s is not in a configured subnet of network %s", ipam.IPv4Address, netName)
|
||||
if endpoint.IPAMConfig.IPv4Address != "" && !networkSupportsStaticIP(info, endpoint.IPAMConfig.IPv4Address, false) {
|
||||
endpoint.IPAMConfig.IPv4Address = ""
|
||||
}
|
||||
if ipam.IPv6Address != "" && !networkSupportsStaticIP(info, ipam.IPv6Address, true) {
|
||||
return fmt.Errorf("static IPv6 address %s is not in a configured subnet of network %s", ipam.IPv6Address, netName)
|
||||
if endpoint.IPAMConfig.IPv6Address != "" && !networkSupportsStaticIP(info, endpoint.IPAMConfig.IPv6Address, true) {
|
||||
endpoint.IPAMConfig.IPv6Address = ""
|
||||
}
|
||||
if endpoint.IPAMConfig.IPv4Address == "" && endpoint.IPAMConfig.IPv6Address == "" && len(endpoint.IPAMConfig.LinkLocalIPs) == 0 {
|
||||
endpoint.IPAMConfig = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isDefaultBridgeNetwork(netName string, info network.Inspect) bool {
|
||||
@@ -684,7 +673,6 @@ func networkSupportsStaticIP(info network.Inspect, ip string, isIPv6 bool) bool
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
addr = addr.Unmap()
|
||||
if addr.Is6() != isIPv6 {
|
||||
return false
|
||||
}
|
||||
@@ -825,6 +813,11 @@ func buildContainerRecoverNetworkConfig(networkSettings *container.NetworkSettin
|
||||
IPv6Address: endpoint.IPAMConfig.IPv6Address,
|
||||
LinkLocalIPs: append([]string(nil), endpoint.IPAMConfig.LinkLocalIPs...),
|
||||
}
|
||||
} else if name != "bridge" && (endpoint.IPAddress != "" || endpoint.GlobalIPv6Address != "") {
|
||||
endpointSetting.IPAMConfig = &network.EndpointIPAMConfig{
|
||||
IPv4Address: endpoint.IPAddress,
|
||||
IPv6Address: endpoint.GlobalIPv6Address,
|
||||
}
|
||||
}
|
||||
if name == primaryName {
|
||||
config.EndpointsConfig[name] = endpointSetting
|
||||
@@ -838,6 +831,39 @@ func buildContainerRecoverNetworkConfig(networkSettings *container.NetworkSettin
|
||||
return config, extraNetworks
|
||||
}
|
||||
|
||||
const unsupportedUserSpecifiedIPAddress = "user specified IP address is supported only when connecting to networks with user configured subnets"
|
||||
|
||||
func clearUnsupportedDynamicEndpointIPAM(err error, endpoints map[string]*network.EndpointSettings, networkSettings *container.NetworkSettings) bool {
|
||||
if err == nil || !strings.Contains(err.Error(), unsupportedUserSpecifiedIPAddress) {
|
||||
return false
|
||||
}
|
||||
for name, endpoint := range endpoints {
|
||||
if !isDynamicContainerNetwork(networkSettings, name) || endpoint == nil || endpoint.IPAMConfig == nil {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(err.Error(), "network "+name+":") {
|
||||
endpoint.IPAMConfig = nil
|
||||
return true
|
||||
}
|
||||
}
|
||||
cleared := false
|
||||
for name, endpoint := range endpoints {
|
||||
if isDynamicContainerNetwork(networkSettings, name) && endpoint != nil && endpoint.IPAMConfig != nil {
|
||||
endpoint.IPAMConfig = nil
|
||||
cleared = true
|
||||
}
|
||||
}
|
||||
return cleared
|
||||
}
|
||||
|
||||
func isDynamicContainerNetwork(networkSettings *container.NetworkSettings, name string) bool {
|
||||
if networkSettings == nil || name == "bridge" {
|
||||
return false
|
||||
}
|
||||
endpoint := networkSettings.Networks[name]
|
||||
return endpoint != nil && endpoint.IPAMConfig == nil && (endpoint.IPAddress != "" || endpoint.GlobalIPv6Address != "")
|
||||
}
|
||||
|
||||
func cloneContainerConfig(config *container.Config) *container.Config {
|
||||
if config == nil {
|
||||
return &container.Config{}
|
||||
|
||||
@@ -33,7 +33,7 @@ type ClamService struct {
|
||||
}
|
||||
|
||||
type IClamService interface {
|
||||
LoadBaseInfo() (dto.ClamBaseInfo, error)
|
||||
LoadBaseInfo(readOnly bool) (dto.ClamBaseInfo, error)
|
||||
Operate(operate string) error
|
||||
SearchWithPage(search dto.SearchClamWithPage) (int64, interface{}, error)
|
||||
Create(req dto.ClamCreate, operator string) error
|
||||
@@ -53,7 +53,7 @@ func NewIClamService() IClamService {
|
||||
return &ClamService{}
|
||||
}
|
||||
|
||||
func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
func (c *ClamService) LoadBaseInfo(readOnly bool) (dto.ClamBaseInfo, error) {
|
||||
var baseInfo dto.ClamBaseInfo
|
||||
baseInfo.Version = "-"
|
||||
baseInfo.FreshVersion = "-"
|
||||
@@ -96,7 +96,7 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if !readOnly && !global.CONF.Base.IsDemo {
|
||||
_ = clam.CheckWithStopAll(false, clamRepo)
|
||||
}
|
||||
if baseInfo.FreshIsActive {
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
type ComposeTemplateService struct{}
|
||||
|
||||
type IComposeTemplateService interface {
|
||||
List() ([]dto.ComposeTemplateInfo, error)
|
||||
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
||||
List(readOnly ...bool) ([]dto.ComposeTemplateInfo, error)
|
||||
SearchWithPage(search dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error)
|
||||
Create(req dto.ComposeTemplateCreate) error
|
||||
Update(id uint, upMap map[string]interface{}) error
|
||||
Batch(req dto.ComposeTemplateBatch) error
|
||||
@@ -23,7 +23,7 @@ func NewIComposeTemplateService() IComposeTemplateService {
|
||||
return &ComposeTemplateService{}
|
||||
}
|
||||
|
||||
func (u *ComposeTemplateService) List() ([]dto.ComposeTemplateInfo, error) {
|
||||
func (u *ComposeTemplateService) List(readOnly ...bool) ([]dto.ComposeTemplateInfo, error) {
|
||||
composes, err := composeRepo.List()
|
||||
if err != nil {
|
||||
return nil, buserr.New("ErrRecordNotFound")
|
||||
@@ -34,12 +34,15 @@ func (u *ComposeTemplateService) List() ([]dto.ComposeTemplateInfo, error) {
|
||||
if err := copier.Copy(&item, &compose); err != nil {
|
||||
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Content = ""
|
||||
}
|
||||
dtoLists = append(dtoLists, item)
|
||||
}
|
||||
return dtoLists, err
|
||||
}
|
||||
|
||||
func (u *ComposeTemplateService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
func (u *ComposeTemplateService) SearchWithPage(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error) {
|
||||
total, composes, err := composeRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info))
|
||||
var dtoComposeTemplates []dto.ComposeTemplateInfo
|
||||
for _, compose := range composes {
|
||||
@@ -47,6 +50,9 @@ func (u *ComposeTemplateService) SearchWithPage(req dto.SearchWithPage) (int64,
|
||||
if err := copier.Copy(&item, &compose); err != nil {
|
||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Content = ""
|
||||
}
|
||||
dtoComposeTemplates = append(dtoComposeTemplates, item)
|
||||
}
|
||||
return total, dtoComposeTemplates, err
|
||||
|
||||
+137
-63
@@ -16,7 +16,6 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -25,7 +24,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
@@ -67,7 +65,7 @@ type IContainerService interface {
|
||||
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
ListVolume() ([]dto.Options, error)
|
||||
|
||||
PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
PageCompose(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error)
|
||||
LoadComposeEnv(name string) (string, error)
|
||||
CreateCompose(req dto.ComposeCreate) error
|
||||
ComposeOperation(req dto.ComposeOperation) error
|
||||
@@ -79,9 +77,9 @@ type IContainerService interface {
|
||||
ContainerCreate(req dto.ContainerOperate, inThread bool) error
|
||||
ContainerUpdate(req dto.ContainerOperate) error
|
||||
ContainerUpgrade(req dto.ContainerUpgrade) error
|
||||
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
||||
ContainerInfo(req dto.OperationWithName, readOnly ...bool) (*dto.ContainerOperate, error)
|
||||
ContainerListStats() ([]dto.ContainerListStats, error)
|
||||
ContainerItemStats(ctx context.Context, req dto.OperationWithName) (dto.ContainerItemStats, error)
|
||||
ContainerItemStats(req dto.OperationWithName) (dto.ContainerItemStats, error)
|
||||
LoadResourceLimit() (*dto.ResourceLimit, error)
|
||||
ContainerRename(req dto.ContainerRename) error
|
||||
ContainerCommit(req dto.ContainerCommit) error
|
||||
@@ -248,15 +246,15 @@ func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
func (u *ContainerService) ContainerItemStats(ctx context.Context, req dto.OperationWithName) (dto.ContainerItemStats, error) {
|
||||
func (u *ContainerService) ContainerItemStats(req dto.OperationWithName) (dto.ContainerItemStats, error) {
|
||||
var data dto.ContainerItemStats
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
defer client.Close()
|
||||
if req.Name != "system" {
|
||||
containerInfo, _, err := client.ContainerInspectWithRaw(ctx, req.Name, true)
|
||||
defer client.Close()
|
||||
containerInfo, _, err := client.ContainerInspectWithRaw(context.Background(), req.Name, true)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
@@ -265,7 +263,7 @@ func (u *ContainerService) ContainerItemStats(ctx context.Context, req dto.Opera
|
||||
return data, nil
|
||||
}
|
||||
|
||||
usage, err := client.DiskUsage(ctx, types.DiskUsageOptions{})
|
||||
usage, err := client.DiskUsage(context.Background(), types.DiskUsageOptions{})
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
@@ -536,9 +534,7 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := normalizeContainerEndpointSettings(ctx, client, networkConf, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
normalizeContainerEndpointSettings(ctx, client, networkConf, nil)
|
||||
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||
if err != nil {
|
||||
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
|
||||
@@ -572,7 +568,7 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
||||
return taskItem.Execute()
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) {
|
||||
func (u *ContainerService) ContainerInfo(req dto.OperationWithName, readOnly ...bool) (*dto.ContainerOperate, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -616,6 +612,9 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
||||
data.Tty = oldContainer.Config.Tty
|
||||
data.Entrypoint = oldContainer.Config.Entrypoint
|
||||
data.Env = oldContainer.Config.Env
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
data.Env = nil
|
||||
}
|
||||
data.CPUShares = oldContainer.HostConfig.CPUShares
|
||||
for key, val := range oldContainer.Config.Labels {
|
||||
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
|
||||
@@ -648,9 +647,14 @@ func loadContainerNetworkInfo(name string, endpoint *network.EndpointSettings) d
|
||||
if endpoint.IPAMConfig != nil {
|
||||
item.LinkLocalIPs = append([]string(nil), endpoint.IPAMConfig.LinkLocalIPs...)
|
||||
}
|
||||
if name != "bridge" && endpoint.IPAMConfig != nil {
|
||||
item.Ipv4 = endpoint.IPAMConfig.IPv4Address
|
||||
item.Ipv6 = endpoint.IPAMConfig.IPv6Address
|
||||
if name != "bridge" {
|
||||
if endpoint.IPAMConfig != nil {
|
||||
item.Ipv4 = endpoint.IPAMConfig.IPv4Address
|
||||
item.Ipv6 = endpoint.IPAMConfig.IPv6Address
|
||||
} else {
|
||||
item.Ipv4 = endpoint.IPAddress
|
||||
item.Ipv6 = endpoint.GlobalIPv6Address
|
||||
}
|
||||
}
|
||||
return item
|
||||
}
|
||||
@@ -1677,44 +1681,32 @@ func checkImageLike(client *client.Client, imageName string) bool {
|
||||
|
||||
func pullImages(task *task.Task, client *client.Client, imageName string) error {
|
||||
dockerCli := docker.NewClientWithExist(client)
|
||||
repos, err := imageRepoRepo.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imageRepo := selectImageRepo(imageName, repos)
|
||||
if imageRepo == nil || !imageRepo.Auth {
|
||||
return dockerCli.PullImageWithProcess(task, imageName)
|
||||
}
|
||||
|
||||
options := image.PullOptions{}
|
||||
authConfig := registry.AuthConfig{
|
||||
Username: imageRepo.Username,
|
||||
Password: imageRepo.Password,
|
||||
repos, _ := imageRepoRepo.List()
|
||||
if len(repos) != 0 {
|
||||
for _, repo := range repos {
|
||||
if strings.HasPrefix(imageName, repo.DownloadUrl) && repo.Auth {
|
||||
authConfig := registry.AuthConfig{
|
||||
Username: repo.Username,
|
||||
Password: repo.Password,
|
||||
}
|
||||
encodedJSON, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
|
||||
options.RegistryAuth = authStr
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hasAuth, authStr := loadAuthInfo(imageName)
|
||||
if hasAuth {
|
||||
options.RegistryAuth = authStr
|
||||
}
|
||||
}
|
||||
encodedJSON, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.RegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON)
|
||||
return dockerCli.PullImageWithProcessAndOptions(task, imageName, options)
|
||||
}
|
||||
|
||||
func selectImageRepo(imageName string, repos []model.ImageRepo) *model.ImageRepo {
|
||||
var selected *model.ImageRepo
|
||||
selectedURLLength := 0
|
||||
for i := range repos {
|
||||
downloadURL := strings.TrimRight(strings.TrimSpace(repos[i].DownloadUrl), "/")
|
||||
if downloadURL == "" || !strings.HasPrefix(imageName, downloadURL+"/") {
|
||||
continue
|
||||
}
|
||||
if len(downloadURL) > selectedURLLength {
|
||||
selected = &repos[i]
|
||||
selectedURLLength = len(downloadURL)
|
||||
}
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
||||
func loadCpuAndMem(client *client.Client, containerItem string) dto.ContainerListStats {
|
||||
data := dto.ContainerListStats{
|
||||
ContainerID: containerItem,
|
||||
@@ -1769,10 +1761,7 @@ func checkPortStats(ports []dto.PortHelper, checkInUse bool) (nat.PortMap, error
|
||||
}
|
||||
for i := 0; i <= hostEnd-hostStart; i++ {
|
||||
bindItem := nat.PortBinding{HostPort: strconv.Itoa(hostStart + i), HostIP: port.HostIP}
|
||||
portKey := nat.Port(fmt.Sprintf("%d/%s", containerStart+i, port.Protocol))
|
||||
if !slices.Contains(portMap[portKey], bindItem) {
|
||||
portMap[portKey] = append(portMap[portKey], bindItem)
|
||||
}
|
||||
portMap[nat.Port(fmt.Sprintf("%d/%s", containerStart+i, port.Protocol))] = []nat.PortBinding{bindItem}
|
||||
}
|
||||
for i := hostStart; i <= hostEnd; i++ {
|
||||
if checkInUse && common.ScanPortWithIP(port.HostIP, i) {
|
||||
@@ -1790,10 +1779,7 @@ func checkPortStats(ports []dto.PortHelper, checkInUse bool) (nat.PortMap, error
|
||||
return portMap, buserr.WithDetail("ErrPortInUsed", portItem, nil)
|
||||
}
|
||||
bindItem := nat.PortBinding{HostPort: strconv.Itoa(portItem), HostIP: port.HostIP}
|
||||
portKey := nat.Port(fmt.Sprintf("%s/%s", port.ContainerPort, port.Protocol))
|
||||
if !slices.Contains(portMap[portKey], bindItem) {
|
||||
portMap[portKey] = append(portMap[portKey], bindItem)
|
||||
}
|
||||
portMap[nat.Port(fmt.Sprintf("%s/%s", port.ContainerPort, port.Protocol))] = []nat.PortBinding{bindItem}
|
||||
}
|
||||
}
|
||||
return portMap, nil
|
||||
@@ -1939,7 +1925,90 @@ func loadPortByInspect(id string, client *client.Client) ([]container.Port, erro
|
||||
return itemPorts, nil
|
||||
}
|
||||
func transPortToStr(ports []container.Port) []string {
|
||||
return docker.SimplifyPorts(ports)
|
||||
var (
|
||||
ipv4Ports []container.Port
|
||||
ipv6Ports []container.Port
|
||||
)
|
||||
for _, port := range ports {
|
||||
if strings.Contains(port.IP, ":") {
|
||||
ipv6Ports = append(ipv6Ports, port)
|
||||
} else {
|
||||
ipv4Ports = append(ipv4Ports, port)
|
||||
}
|
||||
}
|
||||
list1 := simplifyPort(ipv4Ports)
|
||||
list2 := simplifyPort(ipv6Ports)
|
||||
return append(list1, list2...)
|
||||
}
|
||||
func simplifyPort(ports []container.Port) []string {
|
||||
var datas []string
|
||||
if len(ports) == 0 {
|
||||
return datas
|
||||
}
|
||||
if len(ports) == 1 {
|
||||
ip := ""
|
||||
if len(ports[0].IP) != 0 {
|
||||
ip = ports[0].IP + ":"
|
||||
}
|
||||
itemPortStr := fmt.Sprintf("%s%v/%s", ip, ports[0].PrivatePort, ports[0].Type)
|
||||
if ports[0].PublicPort != 0 {
|
||||
itemPortStr = fmt.Sprintf("%s%v->%v/%s", ip, ports[0].PublicPort, ports[0].PrivatePort, ports[0].Type)
|
||||
}
|
||||
datas = append(datas, itemPortStr)
|
||||
return datas
|
||||
}
|
||||
|
||||
sort.Slice(ports, func(i, j int) bool {
|
||||
return ports[i].PrivatePort < ports[j].PrivatePort
|
||||
})
|
||||
start := ports[0]
|
||||
|
||||
for i := 1; i < len(ports); i++ {
|
||||
if ports[i].PrivatePort != ports[i-1].PrivatePort+1 || ports[i].IP != ports[i-1].IP || ports[i].PublicPort != ports[i-1].PublicPort+1 || ports[i].Type != ports[i-1].Type {
|
||||
if ports[i-1].PrivatePort == start.PrivatePort {
|
||||
itemPortStr := fmt.Sprintf("%s:%v/%s", start.IP, start.PrivatePort, start.Type)
|
||||
if start.PublicPort != 0 {
|
||||
itemPortStr = fmt.Sprintf("%s:%v->%v/%s", start.IP, start.PublicPort, start.PrivatePort, start.Type)
|
||||
}
|
||||
if len(start.IP) == 0 {
|
||||
itemPortStr = strings.TrimPrefix(itemPortStr, ":")
|
||||
}
|
||||
datas = append(datas, itemPortStr)
|
||||
} else {
|
||||
itemPortStr := fmt.Sprintf("%s:%v-%v/%s", start.IP, start.PrivatePort, ports[i-1].PrivatePort, start.Type)
|
||||
if start.PublicPort != 0 {
|
||||
itemPortStr = fmt.Sprintf("%s:%v-%v->%v-%v/%s", start.IP, start.PublicPort, ports[i-1].PublicPort, start.PrivatePort, ports[i-1].PrivatePort, start.Type)
|
||||
}
|
||||
if len(start.IP) == 0 {
|
||||
itemPortStr = strings.TrimPrefix(itemPortStr, ":")
|
||||
}
|
||||
datas = append(datas, itemPortStr)
|
||||
}
|
||||
start = ports[i]
|
||||
}
|
||||
if i == len(ports)-1 {
|
||||
if ports[i].PrivatePort == start.PrivatePort {
|
||||
itemPortStr := fmt.Sprintf("%s:%v/%s", start.IP, start.PrivatePort, start.Type)
|
||||
if start.PublicPort != 0 {
|
||||
itemPortStr = fmt.Sprintf("%s:%v->%v/%s", start.IP, start.PublicPort, start.PrivatePort, start.Type)
|
||||
}
|
||||
if len(start.IP) == 0 {
|
||||
itemPortStr = strings.TrimPrefix(itemPortStr, ":")
|
||||
}
|
||||
datas = append(datas, itemPortStr)
|
||||
} else {
|
||||
itemPortStr := fmt.Sprintf("%s:%v-%v/%s", start.IP, start.PrivatePort, ports[i].PrivatePort, start.Type)
|
||||
if start.PublicPort != 0 {
|
||||
itemPortStr = fmt.Sprintf("%s:%v-%v->%v-%v/%s", start.IP, start.PublicPort, ports[i].PublicPort, start.PrivatePort, ports[i].PrivatePort, start.Type)
|
||||
}
|
||||
if len(start.IP) == 0 {
|
||||
itemPortStr = strings.TrimPrefix(itemPortStr, ":")
|
||||
}
|
||||
datas = append(datas, itemPortStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
return datas
|
||||
}
|
||||
|
||||
func loadComposeCount(client *client.Client) int {
|
||||
@@ -1972,7 +2041,7 @@ func loadComposeCount(client *client.Client) int {
|
||||
}
|
||||
func loadContainerPortForInfo(itemPorts []container.Port) []dto.PortHelper {
|
||||
var exposedPorts []dto.PortHelper
|
||||
seenPorts := make(map[dto.PortHelper]struct{})
|
||||
samePortMap := make(map[string]dto.PortHelper)
|
||||
ports := transPortToStr(itemPorts)
|
||||
for _, item := range ports {
|
||||
itemStr := strings.Split(item, "->")
|
||||
@@ -1993,11 +2062,16 @@ func loadContainerPortForInfo(itemPorts []container.Port) []dto.PortHelper {
|
||||
}
|
||||
itemPort.ContainerPort = itemContainer[0]
|
||||
itemPort.Protocol = itemContainer[1]
|
||||
if _, exists := seenPorts[itemPort]; exists {
|
||||
continue
|
||||
keyItem := fmt.Sprintf("%s->%s/%s", itemPort.HostPort, itemPort.ContainerPort, itemPort.Protocol)
|
||||
if val, ok := samePortMap[keyItem]; ok {
|
||||
val.HostIP = ""
|
||||
samePortMap[keyItem] = val
|
||||
} else {
|
||||
samePortMap[keyItem] = itemPort
|
||||
}
|
||||
seenPorts[itemPort] = struct{}{}
|
||||
exposedPorts = append(exposedPorts, itemPort)
|
||||
}
|
||||
for _, val := range samePortMap {
|
||||
exposedPorts = append(exposedPorts, val)
|
||||
}
|
||||
return exposedPorts
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"gopkg.in/yaml.v3"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const composeProjectLabel = "com.docker.compose.project"
|
||||
@@ -36,7 +35,7 @@ const composeConfigLabel = "com.docker.compose.project.config_files"
|
||||
const composeWorkdirLabel = "com.docker.compose.project.working_dir"
|
||||
const composeCreatedBy = "createdBy"
|
||||
|
||||
func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
func (u *ContainerService) PageCompose(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error) {
|
||||
var (
|
||||
records []dto.ComposeInfo
|
||||
BackDatas []dto.ComposeInfo
|
||||
@@ -188,7 +187,10 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
}
|
||||
BackDatas = records[start:end]
|
||||
}
|
||||
listItem := loadEnv(BackDatas)
|
||||
listItem := BackDatas
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
listItem = loadEnv(BackDatas)
|
||||
}
|
||||
return int64(total), listItem, nil
|
||||
}
|
||||
|
||||
@@ -253,10 +255,6 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
|
||||
return err
|
||||
}
|
||||
req.Name = projectName
|
||||
recordName := strings.ToLower(req.Name)
|
||||
if err := saveComposeRecord(recordName, req.Path); err != nil {
|
||||
return fmt.Errorf("save compose record failed, err: %v", err)
|
||||
}
|
||||
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeCompose, req.TaskID, 1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new task for image build failed, err: %v", err)
|
||||
@@ -265,7 +263,18 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("ComposeCreate"), func(t *task.Task) error {
|
||||
err := compose.UpWithTask(req.Path, t, req.ForcePull, req.Name)
|
||||
t.LogWithStatus(i18n.GetMsgByKey("ComposeCreate"), err)
|
||||
return err
|
||||
if err != nil {
|
||||
_, _ = compose.Down(req.Path, req.Name)
|
||||
return err
|
||||
}
|
||||
recordName := strings.ToLower(req.Name)
|
||||
record, _ := composeRepo.GetRecord(repo.WithByName(recordName))
|
||||
if record.ID == 0 {
|
||||
_ = composeRepo.CreateRecord(&model.Compose{Name: recordName, Path: req.Path})
|
||||
} else {
|
||||
_ = composeRepo.UpdateRecord(recordName, map[string]interface{}{"path": req.Path})
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
_ = taskItem.Execute()
|
||||
}()
|
||||
@@ -273,17 +282,6 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveComposeRecord(name, composePath string) error {
|
||||
record, err := composeRepo.GetRecord(repo.WithByName(name))
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
if record.ID == 0 {
|
||||
return composeRepo.CreateRecord(&model.Compose{Name: name, Path: composePath})
|
||||
}
|
||||
return composeRepo.UpdateRecord(name, map[string]interface{}{"path": composePath})
|
||||
}
|
||||
|
||||
func checkComposeRecordName(name string) error {
|
||||
composeItem, _ := composeRepo.GetRecord(repo.WithByName(name))
|
||||
if composeItem.ID != 0 && len(composeItem.Path) != 0 {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -20,7 +18,6 @@ import (
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
@@ -67,13 +64,13 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := normalizeContainerEndpointSettings(ctx, client, networkConf, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
normalizeContainerEndpointSettings(ctx, client, networkConf, nil)
|
||||
|
||||
cleanupErr, err := switchContainer(ctx, client, req.Name, oldContainer, func() (container.CreateResponse, error) {
|
||||
return client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||
}, config.Tty, t)
|
||||
return createContainerWithDynamicIPFallback(func() (container.CreateResponse, error) {
|
||||
return client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||
}, networkConf.EndpointsConfig, oldContainer.NetworkSettings)
|
||||
}, newContainerSwitchTaskLogger(t))
|
||||
if err != nil {
|
||||
return fmt.Errorf("update container failed, err: %v", err)
|
||||
}
|
||||
@@ -138,15 +135,9 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
|
||||
config.Image = req.Image
|
||||
hostConf := cloneContainerHostConfig(oldContainer.HostConfig)
|
||||
preserveContainerVolumeMounts(hostConf, oldContainer.Mounts)
|
||||
networkConf, extraNetworks := buildContainerRecoverNetworkConfig(oldContainer.NetworkSettings, hostConf)
|
||||
if err := normalizeContainerEndpointSettings(ctx, client, networkConf, extraNetworks); err != nil {
|
||||
upgradeErr := fmt.Errorf("prepare networks for container %s failed: %w", item, err)
|
||||
upgradeErrors = append(upgradeErrors, upgradeErr)
|
||||
return upgradeErr
|
||||
}
|
||||
cleanupErr, err := switchContainer(ctx, client, item, oldContainer, func() (container.CreateResponse, error) {
|
||||
return createContainerWithNetworks(ctx, client, config, hostConf, networkConf, extraNetworks, item)
|
||||
}, config.Tty, t)
|
||||
return createContainerWithOldNetworks(ctx, client, config, hostConf, oldContainer.NetworkSettings, item)
|
||||
}, newContainerSwitchTaskLogger(t))
|
||||
if err != nil {
|
||||
upgradeErr := fmt.Errorf("upgrade container %s failed: %w", item, err)
|
||||
upgradeErrors = append(upgradeErrors, upgradeErr)
|
||||
@@ -175,7 +166,6 @@ type containerSwitchClient interface {
|
||||
ContainerStart(context.Context, string, container.StartOptions) error
|
||||
ContainerRemove(context.Context, string, container.RemoveOptions) error
|
||||
ContainerInspect(context.Context, string) (container.InspectResponse, error)
|
||||
ContainerLogs(context.Context, string, container.LogsOptions) (io.ReadCloser, error)
|
||||
NetworkConnect(context.Context, string, string, *network.EndpointSettings) error
|
||||
NetworkDisconnect(context.Context, string, string, bool) error
|
||||
}
|
||||
@@ -248,18 +238,22 @@ func (l *containerOperationMutex) lock(names ...string) func() {
|
||||
}
|
||||
|
||||
type containerNetworkAttachment struct {
|
||||
name string
|
||||
endpoint *network.EndpointSettings
|
||||
name string
|
||||
endpoint *network.EndpointSettings
|
||||
isDynamic bool
|
||||
}
|
||||
|
||||
type containerSwitchLogger interface {
|
||||
LogWithStatus(string, error)
|
||||
Log(string)
|
||||
type containerSwitchLogFunc func(messageKey, containerName string, err error)
|
||||
|
||||
func newContainerSwitchTaskLogger(t *task.Task) containerSwitchLogFunc {
|
||||
return func(messageKey, containerName string, err error) {
|
||||
t.LogWithStatus(i18n.GetWithName(messageKey, containerName), err)
|
||||
}
|
||||
}
|
||||
|
||||
func logContainerSwitchStep(logger containerSwitchLogger, messageKey, containerName string, err error) {
|
||||
func logContainerSwitchStep(logger containerSwitchLogFunc, messageKey, containerName string, err error) {
|
||||
if logger != nil {
|
||||
logger.LogWithStatus(i18n.GetWithName(messageKey, containerName), err)
|
||||
logger(messageKey, containerName, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,8 +264,7 @@ func switchContainer(
|
||||
name string,
|
||||
oldContainer container.InspectResponse,
|
||||
createNew func() (container.CreateResponse, error),
|
||||
tty bool,
|
||||
logger containerSwitchLogger,
|
||||
logger containerSwitchLogFunc,
|
||||
) (cleanupErr error, err error) {
|
||||
if oldContainer.ID == "" {
|
||||
return nil, fmt.Errorf("original container ID is empty")
|
||||
@@ -321,7 +314,6 @@ func switchContainer(
|
||||
}
|
||||
if err := cli.ContainerStart(ctx, created.ID, container.StartOptions{}); err != nil {
|
||||
logContainerSwitchStep(logger, "ContainerStartReplacement", name, err)
|
||||
logContainerStartupLogs(ctx, cli, created.ID, name, tty, logger)
|
||||
rollbackErr := restoreOriginalContainer(ctx, cli, oldContainer.ID, name, wasRunning, created.ID, disconnectedNetworks, logger)
|
||||
return nil, errors.Join(fmt.Errorf("start new container failed: %w", err), rollbackErr)
|
||||
}
|
||||
@@ -329,7 +321,6 @@ func switchContainer(
|
||||
if wasRunning {
|
||||
if err := waitContainerReady(ctx, cli, created.ID); err != nil {
|
||||
logContainerSwitchStep(logger, "ContainerWaitReplacement", name, err)
|
||||
logContainerStartupLogs(ctx, cli, created.ID, name, tty, logger)
|
||||
rollbackErr := restoreOriginalContainer(ctx, cli, oldContainer.ID, name, wasRunning, created.ID, disconnectedNetworks, logger)
|
||||
return nil, errors.Join(fmt.Errorf("new container readiness check failed: %w", err), rollbackErr)
|
||||
}
|
||||
@@ -346,48 +337,8 @@ const (
|
||||
containerStartPollInterval = time.Second
|
||||
containerHealthCheckMinWait = 30 * time.Second
|
||||
containerHealthCheckMaxWait = 10 * time.Minute
|
||||
containerDiagnosticLogTail = "200"
|
||||
)
|
||||
|
||||
func logContainerStartupLogs(ctx context.Context, cli containerSwitchClient, containerID, name string, tty bool, logger containerSwitchLogger) {
|
||||
if logger == nil {
|
||||
return
|
||||
}
|
||||
logger.Log(fmt.Sprintf("========== %s ==========", i18n.GetWithName("ContainerStartupDiagnostic", name)))
|
||||
diagnosticCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
reader, err := cli.ContainerLogs(diagnosticCtx, containerID, container.LogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Timestamps: true,
|
||||
Tail: containerDiagnosticLogTail,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Log(i18n.GetWithNameAndErr("ContainerDiagnosticLogsFailed", name, err))
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
var output bytes.Buffer
|
||||
if tty {
|
||||
_, err = io.Copy(&output, reader)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(&output, &output, reader)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Log(i18n.GetWithNameAndErr("ContainerDiagnosticLogsFailed", name, err))
|
||||
return
|
||||
}
|
||||
logs := strings.TrimSpace(output.String())
|
||||
logger.Log(fmt.Sprintf("---------- %s ----------", i18n.GetMsgByKey("ContainerRecentLogs")))
|
||||
if logs == "" {
|
||||
logger.Log(i18n.GetMsgByKey("ContainerDiagnosticLogsEmpty"))
|
||||
return
|
||||
}
|
||||
logger.Log(logs)
|
||||
}
|
||||
|
||||
func waitContainerReady(ctx context.Context, cli containerInspectClient, containerID string) error {
|
||||
info, err := cli.ContainerInspect(ctx, containerID)
|
||||
if err != nil {
|
||||
@@ -587,13 +538,13 @@ func disconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitc
|
||||
endpoints := make(map[string]*network.EndpointSettings, len(extras)+1)
|
||||
if primary != nil {
|
||||
for name, endpoint := range primary.EndpointsConfig {
|
||||
if name != "bridge" && endpoint != nil {
|
||||
if name != "bridge" && endpoint != nil && endpoint.IPAMConfig != nil {
|
||||
endpoints[name] = endpoint
|
||||
}
|
||||
}
|
||||
}
|
||||
for name, endpoint := range extras {
|
||||
if name != "bridge" && endpoint != nil {
|
||||
if name != "bridge" && endpoint != nil && endpoint.IPAMConfig != nil {
|
||||
endpoints[name] = endpoint
|
||||
}
|
||||
}
|
||||
@@ -609,8 +560,9 @@ func disconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitc
|
||||
return disconnected, fmt.Errorf("disconnect original container from network %s failed: %w", name, err)
|
||||
}
|
||||
disconnected = append(disconnected, containerNetworkAttachment{
|
||||
name: name,
|
||||
endpoint: endpoints[name],
|
||||
name: name,
|
||||
endpoint: endpoints[name],
|
||||
isDynamic: isDynamicContainerNetwork(oldContainer.NetworkSettings, name),
|
||||
})
|
||||
}
|
||||
return disconnected, nil
|
||||
@@ -620,6 +572,10 @@ func reconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitch
|
||||
var reconnectErr error
|
||||
for _, attachment := range attachments {
|
||||
err := cli.NetworkConnect(ctx, attachment.name, containerID, attachment.endpoint)
|
||||
if err != nil && attachment.isDynamic && strings.Contains(err.Error(), unsupportedUserSpecifiedIPAddress) {
|
||||
attachment.endpoint.IPAMConfig = nil
|
||||
err = cli.NetworkConnect(ctx, attachment.name, containerID, attachment.endpoint)
|
||||
}
|
||||
if err != nil {
|
||||
reconnectErr = errors.Join(reconnectErr, fmt.Errorf("reconnect original container to network %s failed: %w", attachment.name, err))
|
||||
}
|
||||
@@ -627,7 +583,7 @@ func reconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitch
|
||||
return reconnectErr
|
||||
}
|
||||
|
||||
func restoreOriginalContainer(ctx context.Context, cli containerSwitchClient, oldContainerID, originalName string, wasRunning bool, newContainer string, disconnectedNetworks []containerNetworkAttachment, logger containerSwitchLogger) error {
|
||||
func restoreOriginalContainer(ctx context.Context, cli containerSwitchClient, oldContainerID, originalName string, wasRunning bool, newContainer string, disconnectedNetworks []containerNetworkAttachment, logger containerSwitchLogFunc) error {
|
||||
var rollbackErr error
|
||||
backupName := containerSwitchBackupName(oldContainerID)
|
||||
if newContainer != "" {
|
||||
@@ -652,7 +608,7 @@ func restoreOriginalContainer(ctx context.Context, cli containerSwitchClient, ol
|
||||
reconnectErr := reconnectOriginalContainerNetworks(ctx, cli, oldContainerID, disconnectedNetworks)
|
||||
logContainerSwitchStep(logger, "ContainerRollbackReconnectOld", currentName, reconnectErr)
|
||||
rollbackErr = errors.Join(rollbackErr, reconnectErr)
|
||||
if wasRunning && reconnectErr == nil {
|
||||
if wasRunning {
|
||||
restartErr := restartOriginalContainer(ctx, cli, oldContainerID)
|
||||
logContainerSwitchStep(logger, "ContainerRollbackRestartOld", currentName, restartErr)
|
||||
rollbackErr = errors.Join(rollbackErr, restartErr)
|
||||
@@ -660,8 +616,17 @@ func restoreOriginalContainer(ctx context.Context, cli containerSwitchClient, ol
|
||||
return rollbackErr
|
||||
}
|
||||
|
||||
func createContainerWithNetworks(ctx context.Context, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkConf *network.NetworkingConfig, extraNetworks map[string]*network.EndpointSettings, name string) (container.CreateResponse, error) {
|
||||
created, err := client.ContainerCreate(ctx, config, hostConf, networkConf, nil, name)
|
||||
func createContainerWithOldNetworks(ctx context.Context, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkSettings *container.NetworkSettings, name string) (container.CreateResponse, error) {
|
||||
networkConf, extraNetworks := buildContainerRecoverNetworkConfig(networkSettings, hostConf)
|
||||
normalizeContainerEndpointSettings(ctx, client, networkConf, extraNetworks)
|
||||
var primaryEndpoints map[string]*network.EndpointSettings
|
||||
if networkConf != nil {
|
||||
primaryEndpoints = networkConf.EndpointsConfig
|
||||
}
|
||||
|
||||
created, err := createContainerWithDynamicIPFallback(func() (container.CreateResponse, error) {
|
||||
return client.ContainerCreate(ctx, config, hostConf, networkConf, nil, name)
|
||||
}, primaryEndpoints, networkSettings)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
@@ -673,6 +638,9 @@ func createContainerWithNetworks(ctx context.Context, client *client.Client, con
|
||||
sort.Strings(extraNames)
|
||||
for _, item := range extraNames {
|
||||
err := client.NetworkConnect(ctx, item, created.ID, extraNetworks[item])
|
||||
if clearUnsupportedDynamicEndpointIPAM(err, map[string]*network.EndpointSettings{item: extraNetworks[item]}, networkSettings) {
|
||||
err = client.NetworkConnect(ctx, item, created.ID, extraNetworks[item])
|
||||
}
|
||||
if err != nil {
|
||||
_ = client.ContainerRemove(ctx, created.ID, container.RemoveOptions{Force: true})
|
||||
return created, err
|
||||
@@ -680,3 +648,16 @@ func createContainerWithNetworks(ctx context.Context, client *client.Client, con
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
type CronjobService struct{}
|
||||
|
||||
type ICronjobService interface {
|
||||
SearchWithPage(search dto.PageCronjob) (int64, interface{}, error)
|
||||
SearchWithPage(search dto.PageCronjob, readOnly ...bool) (int64, interface{}, error)
|
||||
SearchRecords(search dto.SearchRecord) (int64, interface{}, error)
|
||||
Create(cronjobDto dto.CronjobOperate, operator string) error
|
||||
LoadNextHandle(spec string) ([]string, error)
|
||||
@@ -50,7 +50,7 @@ func NewICronjobService() ICronjobService {
|
||||
return &CronjobService{}
|
||||
}
|
||||
|
||||
func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interface{}, error) {
|
||||
func (u *CronjobService) SearchWithPage(search dto.PageCronjob, readOnly ...bool) (int64, interface{}, error) {
|
||||
total, cronjobs, err := cronjobRepo.Page(search.Page,
|
||||
search.PageSize,
|
||||
repo.WithByGroups(search.GroupIDs),
|
||||
@@ -83,6 +83,9 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
|
||||
if cronjob.Type == "snapshot" && len(cronjob.SnapshotRule) != 0 {
|
||||
_ = json.Unmarshal([]byte(cronjob.SnapshotRule), &item.SnapshotRule)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Secret = ""
|
||||
}
|
||||
dtoCronjobs = append(dtoCronjobs, item)
|
||||
}
|
||||
return total, dtoCronjobs, err
|
||||
|
||||
@@ -18,7 +18,8 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
||||
@@ -243,7 +244,8 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
|
||||
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
|
||||
|
||||
currentInfo.DiskData = loadDiskInfo()
|
||||
currentInfo.GPUData, currentInfo.NPUData, currentInfo.XPUData = loadAcceleratorInfo()
|
||||
currentInfo.GPUData = loadGPUInfo()
|
||||
currentInfo.XPUData = loadXpuInfo()
|
||||
|
||||
if ioOption == "all" {
|
||||
diskInfo, _ := disk.IOCounters()
|
||||
@@ -464,14 +466,12 @@ func loadDiskInfo() []dto.DiskInfo {
|
||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||
)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load disk info with df -hT -P failed, err: %v", err)
|
||||
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
|
||||
stdout, err = cmdMgr2.RunPipe(
|
||||
cmd.PipeCommand{Name: "df", Args: []string{"-lhT", "-P"}},
|
||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||
)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load disk info with df -lhT -P failed, err: %v", err)
|
||||
return datas
|
||||
}
|
||||
}
|
||||
@@ -569,64 +569,32 @@ func loadDiskInfo() []dto.DiskInfo {
|
||||
return datas
|
||||
}
|
||||
|
||||
func loadAcceleratorInfo() ([]dto.GPUInfo, []dto.NPUInfo, []dto.XPUInfo) {
|
||||
ok, client := accelerator.New()
|
||||
if !ok {
|
||||
return nil, nil, nil
|
||||
}
|
||||
snapshot, err := client.Collect(context.Background())
|
||||
if err != nil || len(snapshot.Devices) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
if warning := snapshot.Warning(); warning != nil {
|
||||
global.LOG.Warnf("load accelerator dashboard data partially failed, err: %v", warning)
|
||||
}
|
||||
|
||||
var (
|
||||
gpuData []dto.GPUInfo
|
||||
npuData []dto.NPUInfo
|
||||
xpuData []dto.XPUInfo
|
||||
)
|
||||
for _, device := range snapshot.Devices {
|
||||
switch device.Kind {
|
||||
case accelerator.KindGPU:
|
||||
if device.GPU == nil {
|
||||
continue
|
||||
}
|
||||
var dataItem dto.GPUInfo
|
||||
if err := copier.Copy(&dataItem, device.GPU); err != nil {
|
||||
continue
|
||||
}
|
||||
dataItem.PowerUsage = dataItem.PowerDraw + " / " + 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,
|
||||
})
|
||||
func loadGPUInfo() []dto.GPUInfo {
|
||||
ok, client := gpu.New()
|
||||
var list []interface{}
|
||||
if ok {
|
||||
info, err := client.LoadGpuInfo()
|
||||
if err != nil || len(info.GPUs) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, item := range info.GPUs {
|
||||
list = append(list, item)
|
||||
}
|
||||
}
|
||||
return gpuData, npuData, xpuData
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
var data []dto.GPUInfo
|
||||
for _, gpu := range list {
|
||||
var dataItem dto.GPUInfo
|
||||
if err := copier.Copy(&dataItem, &gpu); err != nil {
|
||||
continue
|
||||
}
|
||||
dataItem.PowerUsage = dataItem.PowerDraw + " / " + dataItem.MaxPowerLimit
|
||||
dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal
|
||||
data = append(data, dataItem)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
type AppLauncher struct {
|
||||
@@ -642,6 +610,32 @@ func ArryContains(arr []string, element string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func loadXpuInfo() []dto.XPUInfo {
|
||||
var list []interface{}
|
||||
ok, xpuClient := xpu.New()
|
||||
if ok {
|
||||
xpus, err := xpuClient.LoadDashData()
|
||||
if err != nil || len(xpus) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, item := range xpus {
|
||||
list = append(list, item)
|
||||
}
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
var data []dto.XPUInfo
|
||||
for _, gpu := range list {
|
||||
var dataItem dto.XPUInfo
|
||||
if err := copier.Copy(&dataItem, &gpu); err != nil {
|
||||
continue
|
||||
}
|
||||
data = append(data, dataItem)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func loadOutboundIP() string {
|
||||
conn, err := network.Dial("udp", "8.8.8.8:80")
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ import (
|
||||
type DatabaseService struct{}
|
||||
|
||||
type IDatabaseService interface {
|
||||
Get(name string) (dto.DatabaseInfo, error)
|
||||
SearchWithPage(search dto.DatabaseSearch) (int64, interface{}, error)
|
||||
Get(name string, readOnly ...bool) (dto.DatabaseInfo, error)
|
||||
SearchWithPage(search dto.DatabaseSearch, readOnly ...bool) (int64, interface{}, error)
|
||||
CheckDatabase(req dto.DatabaseCreate) bool
|
||||
Create(req dto.DatabaseCreate) error
|
||||
Update(req dto.DatabaseUpdate) error
|
||||
@@ -39,7 +39,7 @@ func NewIDatabaseService() IDatabaseService {
|
||||
return &DatabaseService{}
|
||||
}
|
||||
|
||||
func (u *DatabaseService) SearchWithPage(search dto.DatabaseSearch) (int64, interface{}, error) {
|
||||
func (u *DatabaseService) SearchWithPage(search dto.DatabaseSearch, readOnly ...bool) (int64, interface{}, error) {
|
||||
total, dbs, err := databaseRepo.Page(search.Page, search.PageSize,
|
||||
databaseRepo.WithTypeList(search.Type),
|
||||
repo.WithByLikeName(search.Info),
|
||||
@@ -52,12 +52,16 @@ func (u *DatabaseService) SearchWithPage(search dto.DatabaseSearch) (int64, inte
|
||||
if err := copier.Copy(&item, &db); err != nil {
|
||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Password = ""
|
||||
item.ClientKey = ""
|
||||
}
|
||||
datas = append(datas, item)
|
||||
}
|
||||
return total, datas, err
|
||||
}
|
||||
|
||||
func (u *DatabaseService) Get(name string) (dto.DatabaseInfo, error) {
|
||||
func (u *DatabaseService) Get(name string, readOnly ...bool) (dto.DatabaseInfo, error) {
|
||||
var data dto.DatabaseInfo
|
||||
remote, err := databaseRepo.Get(repo.WithByName(name))
|
||||
if err != nil {
|
||||
@@ -66,6 +70,10 @@ func (u *DatabaseService) Get(name string) (dto.DatabaseInfo, error) {
|
||||
if err := copier.Copy(&data, &remote); err != nil {
|
||||
return data, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
data.Password = ""
|
||||
data.ClientKey = ""
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
type MongodbService struct{}
|
||||
|
||||
type IMongodbService interface {
|
||||
SearchWithPage(search dto.MongodbDBSearch) (int64, interface{}, error)
|
||||
SearchWithPage(search dto.MongodbDBSearch, readOnly ...bool) (int64, interface{}, error)
|
||||
Create(ctx context.Context, req dto.MongodbDBCreate) (*model.DatabaseMongodb, error)
|
||||
LoadFromRemote(req dto.MongodbLoadDB) error
|
||||
UpdateDescription(req dto.UpdateDescription) error
|
||||
@@ -40,7 +40,7 @@ func NewIMongodbService() IMongodbService {
|
||||
return &MongodbService{}
|
||||
}
|
||||
|
||||
func (u *MongodbService) SearchWithPage(search dto.MongodbDBSearch) (int64, interface{}, error) {
|
||||
func (u *MongodbService) SearchWithPage(search dto.MongodbDBSearch, readOnly ...bool) (int64, interface{}, error) {
|
||||
total, mongodbs, err := mongodbRepo.Page(
|
||||
search.Page,
|
||||
search.PageSize,
|
||||
@@ -54,6 +54,9 @@ func (u *MongodbService) SearchWithPage(search dto.MongodbDBSearch) (int64, inte
|
||||
if err := copier.Copy(&item, &mongodb); err != nil {
|
||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Password = ""
|
||||
}
|
||||
dtoMongodbs = append(dtoMongodbs, item)
|
||||
}
|
||||
return total, dtoMongodbs, err
|
||||
|
||||
@@ -46,7 +46,7 @@ type IMysqlService interface {
|
||||
DeleteCheck(req dto.MysqlDBDeleteCheck) ([]dto.DBResource, error)
|
||||
Delete(ctx context.Context, req dto.MysqlDBDelete) error
|
||||
|
||||
ListUsers(req dto.MysqlUserSearch) ([]dto.MysqlUser, error)
|
||||
ListUsers(req dto.MysqlUserSearch, readOnly ...bool) ([]dto.MysqlUser, error)
|
||||
ListGrants(req dto.MysqlUserSearch) ([]dto.MysqlGrant, error)
|
||||
ListGrantSummary(req dto.MysqlGrantSummarySearch) (map[string][]dto.MysqlUser, error)
|
||||
CreateUser(req dto.MysqlUserCreate) error
|
||||
@@ -506,7 +506,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
|
||||
return &createItem, nil
|
||||
}
|
||||
|
||||
func (u *MysqlService) ListUsers(req dto.MysqlUserSearch) ([]dto.MysqlUser, error) {
|
||||
func (u *MysqlService) ListUsers(req dto.MysqlUserSearch, readOnly ...bool) ([]dto.MysqlUser, error) {
|
||||
dbType, err := resolveDatabaseUserType(req.Database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -520,10 +520,14 @@ func (u *MysqlService) ListUsers(req dto.MysqlUserSearch) ([]dto.MysqlUser, erro
|
||||
if isMysqlSystemUser(user.Username) {
|
||||
continue
|
||||
}
|
||||
password := user.Password
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
password = ""
|
||||
}
|
||||
res = append(res, dto.MysqlUser{
|
||||
Username: user.Username,
|
||||
Host: user.Host,
|
||||
Password: user.Password,
|
||||
Password: password,
|
||||
Description: user.Description,
|
||||
IsDelete: user.IsDelete,
|
||||
})
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
type PostgresqlService struct{}
|
||||
|
||||
type IPostgresqlService interface {
|
||||
SearchWithPage(search dto.PostgresqlDBSearch) (int64, interface{}, error)
|
||||
SearchWithPage(search dto.PostgresqlDBSearch, readOnly ...bool) (int64, interface{}, error)
|
||||
ListDBOption() ([]dto.PostgresqlOption, error)
|
||||
BindUser(req dto.PostgresqlBindUser) error
|
||||
Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error)
|
||||
@@ -42,7 +42,7 @@ func NewIPostgresqlService() IPostgresqlService {
|
||||
return &PostgresqlService{}
|
||||
}
|
||||
|
||||
func (u *PostgresqlService) SearchWithPage(search dto.PostgresqlDBSearch) (int64, interface{}, error) {
|
||||
func (u *PostgresqlService) SearchWithPage(search dto.PostgresqlDBSearch, readOnly ...bool) (int64, interface{}, error) {
|
||||
total, postgresqls, err := postgresqlRepo.Page(search.Page, search.PageSize,
|
||||
postgresqlRepo.WithByPostgresqlName(search.Database),
|
||||
repo.WithByLikeName(search.Info),
|
||||
@@ -54,6 +54,9 @@ func (u *PostgresqlService) SearchWithPage(search dto.PostgresqlDBSearch) (int64
|
||||
if err := copier.Copy(&item, &pg); err != nil {
|
||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Password = ""
|
||||
}
|
||||
dtoPostgresqls = append(dtoPostgresqls, item)
|
||||
}
|
||||
return total, dtoPostgresqls, err
|
||||
|
||||
+1
-120
@@ -2,7 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -15,20 +14,14 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
|
||||
dockerfirewall "github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
|
||||
)
|
||||
|
||||
const dockerNftablesMinVersion = "29.0.0"
|
||||
|
||||
type DockerService struct{}
|
||||
|
||||
type IDockerService interface {
|
||||
UpdateConf(req dto.SettingUpdate, withRestart bool) error
|
||||
UpdateFirewallBackend(backend string) error
|
||||
UpdateLogOption(req dto.LogOption) error
|
||||
UpdateIpv6Option(req dto.Ipv6Option) error
|
||||
UpdateConfByFile(info dto.DaemonJsonUpdateByFile) error
|
||||
@@ -37,115 +30,6 @@ type IDockerService interface {
|
||||
OperateDocker(req dto.DockerOperation) error
|
||||
}
|
||||
|
||||
func loadDockerEngineVersion(ctx context.Context) string {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err == nil {
|
||||
defer client.Close()
|
||||
if version, versionErr := client.ServerVersion(ctx); versionErr == nil && version.Version != "" {
|
||||
return version.Version
|
||||
}
|
||||
}
|
||||
if !cmd.Which("dockerd") {
|
||||
return ""
|
||||
}
|
||||
stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("dockerd", "--version")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(stdout)
|
||||
}
|
||||
|
||||
func dockerNftablesSupported(version string) bool {
|
||||
return version != "" && common.CompareAppVersion(version, dockerNftablesMinVersion)
|
||||
}
|
||||
|
||||
func applyDockerFirewallBackendConfig(daemonMap map[string]interface{}, backend, version string) error {
|
||||
switch backend {
|
||||
case constant.FirewallProviderNftables:
|
||||
if !dockerNftablesSupported(version) {
|
||||
return fmt.Errorf("Docker Engine %s or later is required for the nftables firewall backend", dockerNftablesMinVersion)
|
||||
}
|
||||
daemonMap["experimental"] = true
|
||||
daemonMap["firewall-backend"] = constant.FirewallProviderNftables
|
||||
case constant.FirewallProviderIptables:
|
||||
if dockerNftablesSupported(version) {
|
||||
daemonMap["firewall-backend"] = constant.FirewallProviderIptables
|
||||
} else {
|
||||
delete(daemonMap, "firewall-backend")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported Docker firewall backend %q", backend)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *DockerService) UpdateFirewallBackend(backend string) error {
|
||||
version := loadDockerEngineVersion(context.Background())
|
||||
if backend == constant.FirewallProviderNftables && !dockerNftablesSupported(version) {
|
||||
return fmt.Errorf("Docker Engine %s or later is required for the nftables firewall backend", dockerNftablesMinVersion)
|
||||
}
|
||||
if backend == constant.FirewallProviderNftables {
|
||||
if err := dockerfirewall.CheckIPv4Forwarding(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
original, readErr := os.ReadFile(constant.DaemonJsonPath)
|
||||
existed := readErr == nil
|
||||
if readErr != nil && !os.IsNotExist(readErr) {
|
||||
return readErr
|
||||
}
|
||||
daemonMap := make(map[string]interface{})
|
||||
if len(bytes.TrimSpace(original)) > 0 {
|
||||
if err := json.Unmarshal(original, &daemonMap); err != nil {
|
||||
return fmt.Errorf("failed to parse Docker configuration: %w", err)
|
||||
}
|
||||
}
|
||||
if err := applyDockerFirewallBackendConfig(daemonMap, backend, version); err != nil {
|
||||
return err
|
||||
}
|
||||
updated, err := json.MarshalIndent(daemonMap, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existed && bytes.Equal(bytes.TrimSpace(original), bytes.TrimSpace(updated)) {
|
||||
return nil
|
||||
}
|
||||
if err := os.MkdirAll(path.Dir(constant.DaemonJsonPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(constant.DaemonJsonPath, updated, 0640); err != nil {
|
||||
return err
|
||||
}
|
||||
restore := func() error {
|
||||
if existed {
|
||||
return os.WriteFile(constant.DaemonJsonPath, original, 0640)
|
||||
}
|
||||
err := os.Remove(constant.DaemonJsonPath)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
if restoreErr := restore(); restoreErr != nil {
|
||||
return fmt.Errorf("%v; failed to restore Docker configuration: %w", err, restoreErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := controller.HandleRestart("docker"); err != nil {
|
||||
cause := fmt.Errorf("failed to restart Docker: %w", err)
|
||||
if restoreErr := restore(); restoreErr != nil {
|
||||
return fmt.Errorf("%v; failed to restore Docker configuration: %w", cause, restoreErr)
|
||||
}
|
||||
if restoreRestartErr := controller.HandleRestart("docker"); restoreRestartErr != nil {
|
||||
return fmt.Errorf("%v; the previous configuration was restored but Docker could not be restarted: %w", cause, restoreRestartErr)
|
||||
}
|
||||
return cause
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewIDockerService() IDockerService {
|
||||
return &DockerService{}
|
||||
}
|
||||
@@ -283,10 +167,7 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate, withRestart bool) erro
|
||||
delete(daemonMap, "ipv6")
|
||||
delete(daemonMap, "fixed-cidr-v6")
|
||||
delete(daemonMap, "ip6tables")
|
||||
backend, _ := settingRepo.GetValueByKey(constant.FirewallDockerBackendKey)
|
||||
if !strings.EqualFold(strings.TrimSpace(backend), constant.FirewallProviderNftables) {
|
||||
delete(daemonMap, "experimental")
|
||||
}
|
||||
delete(daemonMap, "experimental")
|
||||
}
|
||||
case "LogOption":
|
||||
if req.Value == "disable" {
|
||||
|
||||
@@ -37,9 +37,8 @@ var (
|
||||
clamRepo = repo.NewIClamRepo()
|
||||
monitorRepo = repo.NewIMonitorRepo()
|
||||
|
||||
settingRepo = repo.NewISettingRepo()
|
||||
forwardingRuleRepo = repo.NewIForwardingRuleRepo()
|
||||
backupRepo = repo.NewIBackupRepo()
|
||||
settingRepo = repo.NewISettingRepo()
|
||||
backupRepo = repo.NewIBackupRepo()
|
||||
|
||||
websiteRepo = repo.NewIWebsiteRepo()
|
||||
websiteDomainRepo = repo.NewIWebsiteDomainRepo()
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/toolbox"
|
||||
)
|
||||
|
||||
@@ -108,7 +109,7 @@ func (u *Fail2BanService) UpdateConf(req dto.Fail2BanUpdate) error {
|
||||
if req.Value == "firewallcmd-ipset" {
|
||||
itemName = "firewalld"
|
||||
}
|
||||
client, err := NewSelectedSystemFirewallClient()
|
||||
client, err := firewall.NewFirewallClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+20
-39
@@ -87,7 +87,7 @@ type IFileService interface {
|
||||
ConvertLog(req dto.PageInfo) (int64, []response.FileConvertLog, error)
|
||||
BatchGetRemarks(req request.FileRemarkBatch) map[string]string
|
||||
SetRemark(req request.FileRemarkUpdate) error
|
||||
AISearch(req request.FileAISearch) (*response.FileAISearchResult, error)
|
||||
AISearch(req request.FileAISearch, readOnly ...bool) (*response.FileAISearchResult, error)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -159,10 +159,6 @@ func (f *FileService) SearchUploadWithPage(req request.SearchUploadWithPage) (in
|
||||
})
|
||||
}
|
||||
|
||||
sort.SliceStable(files, func(i, j int) bool {
|
||||
return files[i].CreatedAt > files[j].CreatedAt
|
||||
})
|
||||
|
||||
total, start, end := len(files), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
backData = make([]response.UploadInfo, 0)
|
||||
@@ -439,15 +435,13 @@ func (f *FileService) Compress(c request.FileCompress) error {
|
||||
if err := preflightCompressTool(files.CompressType(c.Type)); err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
taskItem, err := task.NewTask(c.Name, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
taskItem.AddSubTask(taskName, func(t *task.Task) error {
|
||||
logFileTaskSources(t, c.Files)
|
||||
t.Log(i18n.GetMsgWithMap("FileTaskFormat", map[string]interface{}{"format": strconv.Quote(c.Type)}))
|
||||
taskItem.AddSubTask(c.Name, func(t *task.Task) error {
|
||||
t.LogStart(c.Name)
|
||||
compressType := files.CompressType(c.Type)
|
||||
dstFile := filepath.Join(c.Dst, c.Name)
|
||||
success := false
|
||||
@@ -518,15 +512,13 @@ func (f *FileService) DeCompress(c request.FileDeCompress) error {
|
||||
if err := preflightDecompressTool(files.CompressType(c.Type)); err != nil {
|
||||
return err
|
||||
}
|
||||
taskName := i18n.GetMsgWithMap("FileTaskDecompress", map[string]interface{}{"dst": strconv.Quote(c.Dst)})
|
||||
taskItem, err := task.NewTask(taskName, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||
taskItem, err := task.NewTask(c.Path, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
taskItem.AddSubTask(taskName, func(t *task.Task) error {
|
||||
logFileTaskSources(t, []string{c.Path})
|
||||
t.Log(i18n.GetMsgWithMap("FileTaskFormat", map[string]interface{}{"format": strconv.Quote(c.Type)}))
|
||||
taskItem.AddSubTask(c.Path, func(t *task.Task) error {
|
||||
t.LogStart(c.Path)
|
||||
dstExisted := fo.Stat(c.Dst)
|
||||
parentDir := filepath.Dir(c.Dst)
|
||||
if !fo.Stat(parentDir) {
|
||||
@@ -900,7 +892,6 @@ func (f *FileService) Wget(w request.FileWget) (string, error) {
|
||||
key := "file-wget-" + common.GetUuid()
|
||||
options := files.DownloadOptions{
|
||||
IgnoreCertificate: w.IgnoreCertificate,
|
||||
UseServerFilename: w.UseServerFilename,
|
||||
}
|
||||
if w.UseProxy {
|
||||
systemProxy, err := NewISettingService().GetSystemProxy()
|
||||
@@ -918,12 +909,6 @@ func (f *FileService) Wget(w request.FileWget) (string, error) {
|
||||
return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key, options)
|
||||
}
|
||||
|
||||
func logFileTaskSources(t *task.Task, sources []string) {
|
||||
for _, source := range sources {
|
||||
t.Log(i18n.GetMsgWithMap("FileTaskSource", map[string]interface{}{"path": strconv.Quote(source)}))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FileService) MvFile(m request.FileMove) error {
|
||||
fo := files.NewFileOp()
|
||||
if err := validateFileMove(fo, m); err != nil {
|
||||
@@ -935,24 +920,15 @@ func (f *FileService) MvFile(m request.FileMove) error {
|
||||
if !fileTransferLocks.Acquire(m.TaskID, getFileTransferPaths(m)) {
|
||||
return buserr.New("TaskIsExecuting")
|
||||
}
|
||||
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)
|
||||
taskItem, err := task.NewTask(m.NewPath, task.TaskExec, task.TaskScopeTask, m.TaskID, 1)
|
||||
if err != nil {
|
||||
fileTransferLocks.Release(m.TaskID)
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
defer fileTransferLocks.Release(m.TaskID)
|
||||
taskItem.AddSubTaskWithOps(taskName, func(t *task.Task) error {
|
||||
logFileTaskSources(t, m.OldPaths)
|
||||
logFileTaskSources(t, m.CoverPaths)
|
||||
if m.Name != "" {
|
||||
t.Log(i18n.GetMsgWithMap("FileTaskRename", map[string]interface{}{"name": strconv.Quote(m.Name)}))
|
||||
}
|
||||
taskItem.AddSubTaskWithOps(m.NewPath, func(t *task.Task) error {
|
||||
t.LogStart(m.NewPath)
|
||||
err := f.moveFileWithContext(t.TaskCtx, m)
|
||||
if err != nil && t.TaskCtx.Err() != nil {
|
||||
return t.TaskCtx.Err()
|
||||
@@ -1584,7 +1560,7 @@ func (f *FileService) ConvertLog(req dto.PageInfo) (total int64, data []response
|
||||
return total, data, nil
|
||||
}
|
||||
|
||||
func (f *FileService) AISearch(req request.FileAISearch) (*response.FileAISearchResult, error) {
|
||||
func (f *FileService) AISearch(req request.FileAISearch, readOnly ...bool) (*response.FileAISearchResult, error) {
|
||||
root := filepath.Clean(strings.TrimSpace(req.Path))
|
||||
if root == "" {
|
||||
return nil, buserr.WithDetail("ErrInvalidParams", "path is required", nil)
|
||||
@@ -1637,10 +1613,15 @@ func (f *FileService) AISearch(req request.FileAISearch) (*response.FileAISearch
|
||||
return nil, buserr.WithDetail("ErrFileAISearchBadPattern", err.Error(), nil)
|
||||
}
|
||||
|
||||
cfg, timeout, err := terminalai.LoadFileAIRuntimeConfig()
|
||||
aiEnabled := err == nil
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, err
|
||||
var cfg terminalai.GeneratorConfig
|
||||
var timeout time.Duration
|
||||
aiEnabled := false
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
cfg, timeout, err = terminalai.LoadFileAIRuntimeConfig()
|
||||
aiEnabled = err == nil
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
items, truncated, err := files.CollectDirInventory(root, containSub, maxItems)
|
||||
|
||||
@@ -37,14 +37,14 @@ var fileShareCodeRegexp = regexp.MustCompile(`^[A-Za-z0-9]{10,16}$`)
|
||||
|
||||
type IFileShareService interface {
|
||||
Create(req request.FileShareCreate) (*response.FileShareInfo, error)
|
||||
Page(req dto.PageInfo) (int64, []response.FileShareInfo, error)
|
||||
GetByPath(path string) (*response.FileShareInfo, error)
|
||||
GetByCode(code string) (*response.FileShareInfo, error)
|
||||
GetPublicByCode(code string) (*response.FileSharePublicInfo, error)
|
||||
Page(req dto.PageInfo, readOnly ...bool) (int64, []response.FileShareInfo, error)
|
||||
GetByPath(path string, readOnly ...bool) (*response.FileShareInfo, error)
|
||||
GetByCode(code string, readOnly ...bool) (*response.FileShareInfo, error)
|
||||
GetPublicByCode(code string, readOnly ...bool) (*response.FileSharePublicInfo, error)
|
||||
DeleteByPath(path string) error
|
||||
SharePathCodeMap() (map[string]string, error)
|
||||
Check(code, password string) error
|
||||
PrepareDownload(code, password string) (filePath, fileName string, err error)
|
||||
Check(code, password string, readOnly ...bool) error
|
||||
PrepareDownload(code, password string, readOnly ...bool) (filePath, fileName string, err error)
|
||||
}
|
||||
|
||||
func NewIFileShareService() IFileShareService {
|
||||
@@ -212,14 +212,14 @@ func (s *FileShareService) Create(req request.FileShareCreate) (*response.FileSh
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) Page(req dto.PageInfo) (int64, []response.FileShareInfo, error) {
|
||||
func (s *FileShareService) Page(req dto.PageInfo, readOnly ...bool) (int64, []response.FileShareInfo, error) {
|
||||
items, err := fileShareRepo.All()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
result := make([]response.FileShareInfo, 0, len(items))
|
||||
for _, item := range items {
|
||||
if err := s.pruneInvalidShare(item); err != nil {
|
||||
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
||||
@@ -239,7 +239,7 @@ func (s *FileShareService) Page(req dto.PageInfo) (int64, []response.FileShareIn
|
||||
return int64(total), result[start:end], nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) GetByPath(path string) (*response.FileShareInfo, error) {
|
||||
func (s *FileShareService) GetByPath(path string, readOnly ...bool) (*response.FileShareInfo, error) {
|
||||
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByPath(strings.TrimSpace(path)))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -247,7 +247,7 @@ func (s *FileShareService) GetByPath(path string) (*response.FileShareInfo, erro
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if err := s.pruneInvalidShare(item); err != nil {
|
||||
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
||||
@@ -258,7 +258,7 @@ func (s *FileShareService) GetByPath(path string) (*response.FileShareInfo, erro
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) GetByCode(code string) (*response.FileShareInfo, error) {
|
||||
func (s *FileShareService) GetByCode(code string, readOnly ...bool) (*response.FileShareInfo, error) {
|
||||
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByCode(strings.TrimSpace(code)))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -266,7 +266,7 @@ func (s *FileShareService) GetByCode(code string) (*response.FileShareInfo, erro
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if err := s.pruneInvalidShare(item); err != nil {
|
||||
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
||||
@@ -276,7 +276,7 @@ func (s *FileShareService) GetByCode(code string) (*response.FileShareInfo, erro
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) GetPublicByCode(code string) (*response.FileSharePublicInfo, error) {
|
||||
func (s *FileShareService) GetPublicByCode(code string, readOnly ...bool) (*response.FileSharePublicInfo, error) {
|
||||
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByCode(strings.TrimSpace(code)))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -284,7 +284,7 @@ func (s *FileShareService) GetPublicByCode(code string) (*response.FileSharePubl
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if err := s.pruneInvalidShare(item); err != nil {
|
||||
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
||||
@@ -324,32 +324,38 @@ func (s *FileShareService) SharePathCodeMap() (map[string]string, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) Check(code, password string) error {
|
||||
_, err := s.check(code, password)
|
||||
func (s *FileShareService) Check(code, password string, readOnly ...bool) error {
|
||||
_, err := s.check(code, password, readOnly...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *FileShareService) PrepareDownload(code, password string) (string, string, error) {
|
||||
item, err := s.check(code, password)
|
||||
func (s *FileShareService) PrepareDownload(code, password string, readOnly ...bool) (string, string, error) {
|
||||
item, err := s.check(code, password, readOnly...)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return item.Path, item.FileName, nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) pruneInvalidShare(item model.FileShare) error {
|
||||
func (s *FileShareService) pruneInvalidShare(item model.FileShare, readOnly ...bool) error {
|
||||
now := time.Now().Unix()
|
||||
if item.ExpiresUnix > 0 && now > item.ExpiresUnix {
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
return nil
|
||||
}
|
||||
return fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||
}
|
||||
info, err := os.Stat(item.Path)
|
||||
if err != nil || info.IsDir() {
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
return nil
|
||||
}
|
||||
return fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FileShareService) check(code, password string) (*model.FileShare, error) {
|
||||
func (s *FileShareService) check(code, password string, readOnly ...bool) (*model.FileShare, error) {
|
||||
code = strings.TrimSpace(code)
|
||||
password = strings.TrimSpace(password)
|
||||
if code == "" {
|
||||
@@ -366,7 +372,9 @@ func (s *FileShareService) check(code, password string) (*model.FileShare, error
|
||||
|
||||
now := time.Now().Unix()
|
||||
if item.ExpiresUnix > 0 && now > item.ExpiresUnix {
|
||||
_ = fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
_ = fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||
}
|
||||
return nil, buserr.New("ErrFileShareExpired")
|
||||
}
|
||||
if item.PasswordHash != "" {
|
||||
@@ -377,7 +385,9 @@ func (s *FileShareService) check(code, password string) (*model.FileShare, error
|
||||
|
||||
info, err := os.Stat(item.Path)
|
||||
if err != nil || info.IsDir() {
|
||||
_ = fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||
if !isDemoReadOnly(readOnly...) {
|
||||
_ = fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||
}
|
||||
return nil, buserr.New("ErrFileSharePath")
|
||||
}
|
||||
|
||||
|
||||
+603
-1663
File diff suppressed because it is too large
Load Diff
@@ -1,414 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
dockerfirewall "github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
dockerGuardComposeProjectLabel = "com.docker.compose.project"
|
||||
dockerGuardComposeCreatedBy = "createdBy"
|
||||
dockerTrafficPathForward = "forward"
|
||||
dockerTrafficPathInput = "input"
|
||||
dockerTrafficPathUnknown = "unknown"
|
||||
dockerManagementContainerGuard = "container_guard"
|
||||
dockerManagementHostFirewall = "host_firewall"
|
||||
dockerManagementNeedsDiagnosis = "needs_diagnosis"
|
||||
dockerReasonNATInspectFailed = "nat_inspect_failed"
|
||||
dockerReasonNATChainUnreachable = "nat_chain_unreachable"
|
||||
dockerReasonProxyInspectFailed = "proxy_inspect_failed"
|
||||
dockerReasonNoMatchingPath = "no_matching_path"
|
||||
)
|
||||
|
||||
type DockerPortGuardService struct {
|
||||
policies repo.IDockerPortGuardRepo
|
||||
runtime dockerfirewall.Runtime
|
||||
runtimeForBackend func(string) dockerfirewall.Runtime
|
||||
client func() (*client.Client, error)
|
||||
version func(string) string
|
||||
}
|
||||
|
||||
var dockerPortGuardServiceMu sync.Mutex
|
||||
|
||||
type IDockerPortGuardService interface {
|
||||
LoadOverview(context.Context) (dto.DockerPortGuardList, error)
|
||||
LoadPublishedPorts(context.Context) ([]dto.DockerPortGuardContainer, error)
|
||||
Operate(context.Context, dto.DockerPortGuardOperation) error
|
||||
QueueInitialization(dto.DockerPortGuardOperation) (dto.FilterChainOperationResponse, error)
|
||||
DeletePolicies(dto.DockerPortGuardPolicyBatchDelete) (dto.FilterChainOperationResponse, error)
|
||||
UpsertPolicies(dto.DockerPortGuardPolicyBatch) (dto.FilterChainOperationResponse, error)
|
||||
Reconcile(context.Context) error
|
||||
}
|
||||
|
||||
func (s *DockerPortGuardService) LoadOverview(ctx context.Context) (dto.DockerPortGuardList, error) {
|
||||
policies, err := s.policies.ListManaged(ctx)
|
||||
if err != nil {
|
||||
return dto.DockerPortGuardList{}, err
|
||||
}
|
||||
unavailable := func() dto.DockerPortGuardList {
|
||||
backend := selectedDockerFirewallBackend("")
|
||||
base := s.runtimeStatus(s.guardRuntime(backend), backend)
|
||||
base.Message = i18n.Get("ErrDockerFailed")
|
||||
return dto.DockerPortGuardList{Base: base, Containers: []dto.DockerPortGuardContainer{}, OrphanPolicies: dockerGuardPolicyEndpoints(policies)}
|
||||
}
|
||||
cli, err := s.client()
|
||||
if err != nil {
|
||||
return unavailable(), nil
|
||||
}
|
||||
defer cli.Close()
|
||||
info, err := cli.Info(ctx)
|
||||
if err != nil {
|
||||
return unavailable(), nil
|
||||
}
|
||||
detectedBackend := dockerFirewallBackend(info)
|
||||
backend := selectedDockerFirewallBackend(detectedBackend)
|
||||
base := s.runtimeStatus(s.guardRuntime(backend), backend)
|
||||
endpoints, err := discoverDockerEndpoints(ctx, cli, true)
|
||||
if err != nil {
|
||||
return dto.DockerPortGuardList{}, err
|
||||
}
|
||||
annotateDockerEndpointManagement(endpoints, detectedBackend)
|
||||
endpoints, orphanPolicies := matchDockerGuardPolicies(base, policies, endpoints)
|
||||
sort.Slice(endpoints, func(i, j int) bool {
|
||||
return fmt.Sprintf("%s|%s|%d|%s", endpoints[i].Family, endpoints[i].HostIP, endpoints[i].HostPort, endpoints[i].Protocol) < fmt.Sprintf("%s|%s|%d|%s", endpoints[j].Family, endpoints[j].HostIP, endpoints[j].HostPort, endpoints[j].Protocol)
|
||||
})
|
||||
sort.Slice(orphanPolicies, func(i, j int) bool {
|
||||
return fmt.Sprintf("%s|%s|%d|%s", orphanPolicies[i].Family, orphanPolicies[i].HostIP, orphanPolicies[i].HostPort, orphanPolicies[i].Protocol) < fmt.Sprintf("%s|%s|%d|%s", orphanPolicies[j].Family, orphanPolicies[j].HostIP, orphanPolicies[j].HostPort, orphanPolicies[j].Protocol)
|
||||
})
|
||||
return dto.DockerPortGuardList{Base: base, Containers: groupDockerGuardContainers(endpoints), OrphanPolicies: orphanPolicies}, nil
|
||||
}
|
||||
|
||||
func (s *DockerPortGuardService) LoadPublishedPorts(ctx context.Context) ([]dto.DockerPortGuardContainer, error) {
|
||||
cli, err := s.client()
|
||||
if err != nil {
|
||||
return nil, buserr.WithDetail("ErrDockerFailed", err.Error(), err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
if socketPath, local := strings.CutPrefix(cli.DaemonHost(), "unix://"); local {
|
||||
if _, statErr := os.Stat(socketPath); errors.Is(statErr, os.ErrNotExist) {
|
||||
return []dto.DockerPortGuardContainer{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
endpoints, err := discoverDockerEndpoints(ctx, cli, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backend := selectedDockerFirewallBackend("")
|
||||
if info, infoErr := cli.Info(ctx); infoErr == nil {
|
||||
backend = dockerFirewallBackend(info)
|
||||
}
|
||||
annotateDockerEndpointManagement(endpoints, backend)
|
||||
return groupDockerGuardContainers(endpoints), nil
|
||||
}
|
||||
|
||||
func (s *DockerPortGuardService) Operate(ctx context.Context, request dto.DockerPortGuardOperation) error {
|
||||
dockerPortGuardServiceMu.Lock()
|
||||
defer dockerPortGuardServiceMu.Unlock()
|
||||
switch request.Operation {
|
||||
case "initialize":
|
||||
runtime, backend, err := s.runtimeForDocker(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
policies, err := s.runtimePolicies(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inventory, err := runtime.ListPolicies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := runtime.Initialize(policies, inventory); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.UpdateOrCreate(constant.FirewallDockerBackendKey, backend); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.UpdateOrCreate(constant.FirewallDockerPortGuardStatusKey, constant.StatusEnable); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case "bind":
|
||||
runtime, _, err := s.runtimeForDocker(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := runtime.Bind(); err != nil {
|
||||
return err
|
||||
}
|
||||
return settingRepo.UpdateOrCreate(constant.FirewallDockerPortGuardStatusKey, constant.StatusEnable)
|
||||
case "unbind":
|
||||
var err error
|
||||
if s.runtime != nil {
|
||||
err = s.runtime.Unbind()
|
||||
} else {
|
||||
err = errors.Join(dockerfirewall.NewIptables().Unbind(), dockerfirewall.NewNftables().Unbind())
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return settingRepo.UpdateOrCreate(constant.FirewallDockerPortGuardStatusKey, constant.StatusDisable)
|
||||
default:
|
||||
return fmt.Errorf("unsupported Docker port guard operation: %s", request.Operation)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerPortGuardService) QueueInitialization(request dto.DockerPortGuardOperation) (dto.FilterChainOperationResponse, error) {
|
||||
if request.Operation != "initialize" {
|
||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("only Docker port guard initialization can be queued")
|
||||
}
|
||||
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeFirewall, 0); err != nil {
|
||||
return dto.FilterChainOperationResponse{}, err
|
||||
}
|
||||
taskItem, err := task.NewTask(firewallTaskName(task.TaskExec, firewallTaskDocker, ""), task.TaskExec, task.TaskScopeFirewall, request.TaskID, 0)
|
||||
if err != nil {
|
||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("create Docker port guard initialization task: %w", err)
|
||||
}
|
||||
var runtime dockerfirewall.Runtime
|
||||
var backend string
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("FirewallInspectDockerGuardStep"), func(t *task.Task) error {
|
||||
var err error
|
||||
runtime, backend, err = s.runtimeForDocker(t.TaskCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Logf("backend=%s", backend)
|
||||
return nil
|
||||
}, nil)
|
||||
taskItem.AddSubTask(i18n.GetWithName("FirewallInitializeDockerGuardStep", "Docker"), func(t *task.Task) error {
|
||||
dockerPortGuardServiceMu.Lock()
|
||||
defer dockerPortGuardServiceMu.Unlock()
|
||||
policies, err := s.runtimePolicies(t.TaskCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Logf("backend=%s", backend)
|
||||
inventory, err := runtime.ListPolicies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return runtime.Initialize(policies, inventory)
|
||||
}, nil)
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("FirewallPersistDockerGuardStep"), func(t *task.Task) error {
|
||||
if err := settingRepo.UpdateOrCreate(constant.FirewallDockerBackendKey, backend); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.UpdateOrCreate(constant.FirewallDockerPortGuardStatusKey, constant.StatusEnable); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
if err := repo.NewITaskRepo().Save(context.Background(), taskItem.Task); err != nil {
|
||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("save Docker port guard initialization task: %w", err)
|
||||
}
|
||||
go func() { _ = taskItem.Execute() }()
|
||||
return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil
|
||||
}
|
||||
|
||||
func (s *DockerPortGuardService) DeletePolicies(request dto.DockerPortGuardPolicyBatchDelete) (dto.FilterChainOperationResponse, error) {
|
||||
uuids, err := normalizeDockerFirewallUUIDs(request.UUIDs)
|
||||
if err != nil {
|
||||
return dto.FilterChainOperationResponse{}, err
|
||||
}
|
||||
labels := make([]string, len(uuids))
|
||||
for i, id := range uuids {
|
||||
labels[i] = fmt.Sprintf("[%d/%d] %s", i+1, len(uuids), id)
|
||||
}
|
||||
return queueFirewallRuleTask(firewallTaskDocker, task.TaskDelete, labels, func(ctx context.Context) error {
|
||||
dockerPortGuardServiceMu.Lock()
|
||||
defer dockerPortGuardServiceMu.Unlock()
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.policies.DeleteBatch(ctx, uuids); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.reconcileLocked(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *DockerPortGuardService) UpsertPolicies(request dto.DockerPortGuardPolicyBatch) (dto.FilterChainOperationResponse, error) {
|
||||
if len(request.Policies) > filter.MaxAtomicExpansion {
|
||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("create or import at most %d rules per batch (after expansion)", filter.MaxAtomicExpansion)
|
||||
}
|
||||
labels := make([]string, len(request.Policies))
|
||||
policies := make([]model.DockerPortGuardPolicy, 0, len(request.Policies))
|
||||
endpoints := make([]dto.DockerPortGuardEndpointIdentity, 0, len(request.Policies))
|
||||
count := 0
|
||||
for i, policy := range request.Policies {
|
||||
labels[i] = fmt.Sprintf("[%d/%d] %s %s %s:%d %s", i+1, len(request.Policies), policy.Family, policy.Protocol, policy.HostIP, policy.HostPort, policy.Mode)
|
||||
normalized, err := normalizeDockerFirewallPolicy(dockerfirewall.Policy{
|
||||
Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort,
|
||||
Protocol: policy.Protocol, Mode: policy.Mode, Sources: policy.Sources,
|
||||
})
|
||||
if err != nil {
|
||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("%s: %w", labels[i], err)
|
||||
}
|
||||
if normalized.Mode == dockerfirewall.ModeAll {
|
||||
count++
|
||||
} else {
|
||||
count += len(normalized.Sources)
|
||||
if normalized.Mode == dockerfirewall.ModeAllow {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > filter.MaxAtomicExpansion {
|
||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("create or import at most %d rules per batch (after expansion)", filter.MaxAtomicExpansion)
|
||||
}
|
||||
encoded, err := json.Marshal(normalized.Sources)
|
||||
if err != nil {
|
||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("%s: %w", labels[i], err)
|
||||
}
|
||||
policies = append(policies, model.DockerPortGuardPolicy{
|
||||
UUID: uuid.NewString(), Family: normalized.Family, HostIP: normalized.HostIP,
|
||||
HostPort: normalized.HostPort, Protocol: normalized.Protocol, Mode: normalized.Mode,
|
||||
Sources: string(encoded), Description: strings.TrimSpace(policy.Description),
|
||||
})
|
||||
endpoints = append(endpoints, dto.DockerPortGuardEndpointIdentity{
|
||||
Family: normalized.Family, HostIP: normalized.HostIP, HostPort: normalized.HostPort, Protocol: normalized.Protocol,
|
||||
})
|
||||
}
|
||||
return queueFirewallRuleTask(firewallTaskDocker, task.TaskUpdate, labels, func(ctx context.Context) error {
|
||||
dockerPortGuardServiceMu.Lock()
|
||||
defer dockerPortGuardServiceMu.Unlock()
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.rejectHostInputDockerGuardEndpoints(ctx, endpoints); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.policies.UpsertBatch(ctx, policies); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.reconcileLocked(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func NewIDockerPortGuardService() IDockerPortGuardService {
|
||||
return newDockerPortGuardService()
|
||||
}
|
||||
|
||||
func (s *DockerPortGuardService) runtimeStatus(runtime dockerfirewall.Runtime, backend string) dto.DockerPortGuardBase {
|
||||
ipv4 := runtime.Status(dockerfirewall.FamilyIPv4)
|
||||
ipv6 := runtime.Status(dockerfirewall.FamilyIPv6)
|
||||
version := "-"
|
||||
if s.version != nil {
|
||||
version = s.version(backend)
|
||||
}
|
||||
name := "iptables-docker"
|
||||
if strings.EqualFold(strings.TrimSpace(backend), constant.FirewallProviderNftables) {
|
||||
name = "nftables-docker"
|
||||
}
|
||||
return dto.DockerPortGuardBase{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Backend: backend,
|
||||
IsExist: ipv4.Reason != dockerfirewall.ReasonCommandMissing || ipv6.Reason != dockerfirewall.ReasonCommandMissing,
|
||||
Initialized: ipv4.Initialized || ipv6.Initialized,
|
||||
Bound: ipv4.Bound || ipv6.Bound,
|
||||
IPv4: dto.DockerPortGuardFamilyStatus{State: ipv4.State, Reason: ipv4.Reason, Initialized: ipv4.Initialized, Bound: ipv4.Bound, Effective: ipv4.Effective},
|
||||
IPv6: dto.DockerPortGuardFamilyStatus{State: ipv6.State, Reason: ipv6.Reason, Initialized: ipv6.Initialized, Bound: ipv6.Bound, Effective: ipv6.Effective},
|
||||
}
|
||||
}
|
||||
|
||||
func dockerGuardPolicyEndpoints(policies []model.DockerPortGuardPolicy) []dto.DockerPortGuardEndpoint {
|
||||
endpoints := make([]dto.DockerPortGuardEndpoint, 0, len(policies))
|
||||
for _, policy := range policies {
|
||||
sources := []string{}
|
||||
_ = json.Unmarshal([]byte(policy.Sources), &sources)
|
||||
endpoints = append(endpoints, dto.DockerPortGuardEndpoint{
|
||||
Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort, Protocol: policy.Protocol,
|
||||
PolicyUUID: policy.UUID, Mode: policy.Mode, Sources: sources,
|
||||
Description: policy.Description, TrafficPath: dockerTrafficPathUnknown,
|
||||
ManagementTarget: dockerManagementNeedsDiagnosis, ManagementReason: dockerReasonNoMatchingPath,
|
||||
})
|
||||
}
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func matchDockerGuardPolicies(base dto.DockerPortGuardBase, policies []model.DockerPortGuardPolicy, endpoints []dto.DockerPortGuardEndpoint) ([]dto.DockerPortGuardEndpoint, []dto.DockerPortGuardEndpoint) {
|
||||
byEndpoint := make(map[string]model.DockerPortGuardPolicy, len(policies))
|
||||
for _, policy := range policies {
|
||||
byEndpoint[fmt.Sprintf("%s|%s|%d|%s", policy.Family, policy.HostIP, policy.HostPort, policy.Protocol)] = policy
|
||||
}
|
||||
for i := range endpoints {
|
||||
key := fmt.Sprintf("%s|%s|%d|%s", endpoints[i].Family, endpoints[i].HostIP, endpoints[i].HostPort, endpoints[i].Protocol)
|
||||
policy, ok := byEndpoint[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
sources := []string{}
|
||||
_ = json.Unmarshal([]byte(policy.Sources), &sources)
|
||||
endpoints[i].PolicyUUID, endpoints[i].Mode, endpoints[i].Sources = policy.UUID, policy.Mode, sources
|
||||
endpoints[i].Description = policy.Description
|
||||
endpoints[i].Effective = endpoints[i].ManagementTarget == dockerManagementContainerGuard &&
|
||||
((policy.Family == dockerfirewall.FamilyIPv4 && base.IPv4.Effective) || (policy.Family == dockerfirewall.FamilyIPv6 && base.IPv6.Effective))
|
||||
delete(byEndpoint, key)
|
||||
}
|
||||
orphanPolicies := make([]dto.DockerPortGuardEndpoint, 0, len(byEndpoint))
|
||||
for _, policy := range byEndpoint {
|
||||
sources := []string{}
|
||||
_ = json.Unmarshal([]byte(policy.Sources), &sources)
|
||||
orphanPolicies = append(orphanPolicies, dto.DockerPortGuardEndpoint{
|
||||
Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort, Protocol: policy.Protocol,
|
||||
PolicyUUID: policy.UUID, Mode: policy.Mode, Sources: sources, Description: policy.Description,
|
||||
TrafficPath: dockerTrafficPathUnknown, ManagementTarget: dockerManagementNeedsDiagnosis,
|
||||
ManagementReason: dockerReasonNoMatchingPath,
|
||||
})
|
||||
}
|
||||
return endpoints, orphanPolicies
|
||||
}
|
||||
|
||||
func (s *DockerPortGuardService) rejectHostInputDockerGuardEndpoints(ctx context.Context, requested []dto.DockerPortGuardEndpointIdentity) error {
|
||||
if s.client == nil || len(requested) == 0 {
|
||||
return nil
|
||||
}
|
||||
cli, err := s.client()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer cli.Close()
|
||||
info, err := cli.Info(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
endpoints, err := discoverDockerEndpoints(ctx, cli, true)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
annotateDockerEndpointManagement(endpoints, dockerFirewallBackend(info))
|
||||
targets := make(map[string]string, len(endpoints))
|
||||
for _, endpoint := range endpoints {
|
||||
targets[fmt.Sprintf("%s|%s|%d|%s", endpoint.Family, endpoint.HostIP, endpoint.HostPort, endpoint.Protocol)] = endpoint.ManagementTarget
|
||||
}
|
||||
for _, endpoint := range requested {
|
||||
target := targets[fmt.Sprintf("%s|%s|%d|%s", endpoint.Family, endpoint.HostIP, endpoint.HostPort, endpoint.Protocol)]
|
||||
if target == dockerManagementHostFirewall {
|
||||
return buserr.WithDetail("ErrInvalidParams", "endpoint traffic is handled by the host input firewall", nil)
|
||||
}
|
||||
if target == dockerManagementNeedsDiagnosis {
|
||||
return buserr.WithDetail("ErrInvalidParams", "endpoint traffic management target requires diagnosis", nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
||||
)
|
||||
|
||||
const fail2BanRestoreWithFirewallMarker = "/run/1panel_fail2ban_restore_with_firewall"
|
||||
|
||||
type firewallFail2BanState struct {
|
||||
markerPath string
|
||||
isExist func(string) bool
|
||||
isActive func(string) bool
|
||||
start func(string) error
|
||||
}
|
||||
|
||||
func newFirewallFail2BanState() *firewallFail2BanState {
|
||||
return &firewallFail2BanState{
|
||||
markerPath: fail2BanRestoreWithFirewallMarker,
|
||||
isExist: func(serviceName string) bool {
|
||||
exists, err := controller.CheckExist(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Warnf("check %s installation before stopping the firewall failed: %v", serviceName, err)
|
||||
}
|
||||
return exists
|
||||
},
|
||||
isActive: func(serviceName string) bool {
|
||||
active, err := controller.CheckActive(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Warnf("check %s status before stopping the firewall failed: %v", serviceName, err)
|
||||
}
|
||||
return active
|
||||
},
|
||||
start: controller.HandleStart,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *firewallFail2BanState) rememberBeforeFirewallStop() error {
|
||||
if !s.isExist("fail2ban.service") {
|
||||
return nil
|
||||
}
|
||||
if !s.isActive("fail2ban.service") {
|
||||
return nil
|
||||
}
|
||||
return s.markForRestore()
|
||||
}
|
||||
|
||||
func (s *firewallFail2BanState) markForRestore() error {
|
||||
if err := os.WriteFile(s.markerPath, nil, 0600); err != nil {
|
||||
return fmt.Errorf("mark Fail2Ban for restoration with the firewall: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *firewallFail2BanState) restoreAfterFirewallStart() error {
|
||||
_, err := os.Stat(s.markerPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("load Fail2Ban restore marker after starting the firewall: %w", err)
|
||||
}
|
||||
|
||||
if err := s.start("fail2ban.service"); err != nil {
|
||||
return fmt.Errorf("restore Fail2Ban after starting the firewall: %w", err)
|
||||
}
|
||||
return s.clearRestoreMarker()
|
||||
}
|
||||
|
||||
func (s *firewallFail2BanState) clearRestoreMarker() error {
|
||||
if err := os.Remove(s.markerPath); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("clear Fail2Ban firewall restore status: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,484 +1,175 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
dockerfirewall "github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
|
||||
"gorm.io/gorm"
|
||||
fireClient "github.com/1Panel-dev/1Panel/agent/utils/firewall/client"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/client/iptables"
|
||||
)
|
||||
|
||||
type IFirewallSettingService interface {
|
||||
CreatePortWhitelist(context.Context, dto.FirewallPortWhitelistCreate) error
|
||||
UpdatePortWhitelist(context.Context, dto.FirewallPortWhitelistUpdate) error
|
||||
DeletePortWhitelist(context.Context, dto.FirewallPortWhitelistDelete) error
|
||||
Load(context.Context) (dto.FirewallSettings, error)
|
||||
Operate(context.Context, dto.FirewallBackendOperation) error
|
||||
type firewallPortWhitelist struct {
|
||||
Port string
|
||||
Protocol string
|
||||
}
|
||||
|
||||
type FirewallSettingService struct{}
|
||||
|
||||
var firewallWhitelistMu sync.Mutex
|
||||
|
||||
func (s *FirewallSettingService) CreatePortWhitelist(ctx context.Context, request dto.FirewallPortWhitelistCreate) error {
|
||||
firewallWhitelistMu.Lock()
|
||||
defer firewallWhitelistMu.Unlock()
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
current, err := loadPortWhitelistSetting(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
current = append(current, request.Rule)
|
||||
current, err = firewall.ValidatePortWhitelist(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := json.Marshal(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tx.Where("key = ?", constant.FirewallPortWhiteList).Assign(map[string]interface{}{"value": string(value)}).FirstOrCreate(&model.Setting{Key: constant.FirewallPortWhiteList}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) UpdatePortWhitelist(ctx context.Context, request dto.FirewallPortWhitelistUpdate) error {
|
||||
firewallWhitelistMu.Lock()
|
||||
defer firewallWhitelistMu.Unlock()
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
current, err := loadPortWhitelistSetting(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
index, err := findPortWhitelistRule(current, request.OldRule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
current[index] = request.Rule
|
||||
current, err = firewall.ValidatePortWhitelist(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := json.Marshal(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tx.Where("key = ?", constant.FirewallPortWhiteList).Assign(map[string]interface{}{"value": string(value)}).FirstOrCreate(&model.Setting{Key: constant.FirewallPortWhiteList}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) DeletePortWhitelist(ctx context.Context, request dto.FirewallPortWhitelistDelete) error {
|
||||
if request.Rule == nil {
|
||||
return fmt.Errorf("select one firewall port whitelist rule to delete")
|
||||
}
|
||||
firewallWhitelistMu.Lock()
|
||||
defer firewallWhitelistMu.Unlock()
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
current, err := loadPortWhitelistSetting(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
index, err := findPortWhitelistRule(current, *request.Rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
current = slices.Delete(current, index, index+1)
|
||||
current, err = firewall.ValidatePortWhitelist(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := json.Marshal(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tx.Where("key = ?", constant.FirewallPortWhiteList).Assign(map[string]interface{}{"value": string(value)}).FirstOrCreate(&model.Setting{Key: constant.FirewallPortWhiteList}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings, error) {
|
||||
result := dto.FirewallSettings{PingStatus: firewall.LoadPingStatus()}
|
||||
|
||||
installed := make(map[string]bool)
|
||||
for _, name := range lifecycle.InstalledProviders() {
|
||||
installed[name] = true
|
||||
}
|
||||
systemBackend, _ := settingRepo.GetValueByKey(constant.FirewallSystemBackendKey)
|
||||
result.System.Selected = strings.TrimSpace(systemBackend)
|
||||
if result.System.Selected == "" {
|
||||
if client, err := lifecycle.NewClient(""); err == nil {
|
||||
result.System.Selected = client.Name()
|
||||
}
|
||||
}
|
||||
result.System.Current = result.System.Selected
|
||||
for _, name := range []string{
|
||||
constant.FirewallProviderFirewalld,
|
||||
constant.FirewallProviderUFW,
|
||||
constant.FirewallProviderIptables,
|
||||
constant.FirewallProviderNftables,
|
||||
} {
|
||||
option := dto.FirewallBackendOption{Name: name, Installed: installed[name], Supported: true}
|
||||
if option.Installed && name == result.System.Selected {
|
||||
client, err := lifecycle.NewClient(name)
|
||||
if err != nil {
|
||||
option.Message = err.Error()
|
||||
} else if name == constant.FirewallProviderIptables || name == constant.FirewallProviderNftables {
|
||||
overview, err := loadSystemFirewallOverview(name, "base")
|
||||
if err != nil {
|
||||
option.Message = err.Error()
|
||||
}
|
||||
option.Initialized, option.Bound = overview.IsInit, overview.IsBind
|
||||
option.IPv4, option.IPv6 = overview.IPv4, overview.IPv6
|
||||
} else if option.Active, err = client.Status(); err != nil {
|
||||
option.Message = err.Error()
|
||||
}
|
||||
}
|
||||
if name == result.System.Selected && name == constant.FirewallProviderIptables {
|
||||
if commands, err := lifecycle.ResolveIptablesCommands(); err == nil {
|
||||
option.Implementation = commands.IPv4
|
||||
}
|
||||
}
|
||||
result.System.Options = append(result.System.Options, option)
|
||||
}
|
||||
|
||||
forwardingBackend, _ := settingRepo.GetValueByKey(constant.FirewallForwardingBackendKey)
|
||||
result.Forwarding.Selected = strings.TrimSpace(forwardingBackend)
|
||||
if result.Forwarding.Selected == "" {
|
||||
result.Forwarding.Selected = constant.FirewallProviderIptables
|
||||
}
|
||||
result.Forwarding.Current = result.Forwarding.Selected
|
||||
for _, name := range []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables} {
|
||||
option := dto.FirewallBackendOption{Name: name, Installed: installed[name], Supported: true}
|
||||
if option.Installed && name == result.Forwarding.Selected {
|
||||
manager, err := newForwardingAdapterFor(name)
|
||||
if err != nil {
|
||||
option.Message = err.Error()
|
||||
} else {
|
||||
status, statusErr := loadForwardingFirewallOverview(manager)
|
||||
option.IPv4, option.IPv6 = status.IPv4, status.IPv6
|
||||
if statusErr != nil {
|
||||
option.Message = statusErr.Error()
|
||||
} else {
|
||||
option.Initialized, option.Bound = status.IsInit, status.IsBind
|
||||
}
|
||||
if name == constant.FirewallProviderIptables && !option.IPv6.Available {
|
||||
if commands, err := lifecycle.ResolveIptablesCommands(); err == nil && !commands.IPv6Available() {
|
||||
option.IPv6.Reason = dockerfirewall.ReasonCommandMissing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if name == result.Forwarding.Selected && name == constant.FirewallProviderIptables {
|
||||
if commands, err := lifecycle.ResolveIptablesCommands(); err == nil {
|
||||
option.Implementation = commands.IPv4
|
||||
}
|
||||
}
|
||||
result.Forwarding.Options = append(result.Forwarding.Options, option)
|
||||
}
|
||||
|
||||
dockerInstalled := cmd.Which("docker")
|
||||
dockerVersion := ""
|
||||
if dockerInstalled {
|
||||
dockerVersion = loadDockerEngineVersion(ctx)
|
||||
}
|
||||
dockerBackend, _ := settingRepo.GetValueByKey(constant.FirewallDockerBackendKey)
|
||||
dockerBackend = strings.ToLower(strings.TrimSpace(dockerBackend))
|
||||
if dockerBackend == constant.FirewallProviderIptables || dockerBackend == constant.FirewallProviderNftables {
|
||||
result.Docker.Selected = dockerBackend
|
||||
}
|
||||
result.Docker.Current = result.Docker.Selected
|
||||
for _, name := range []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables} {
|
||||
option := dto.FirewallBackendOption{
|
||||
Name: name, Installed: installed[name], Supported: dockerInstalled,
|
||||
Active: dockerInstalled && installed[name] && result.Docker.Selected == name,
|
||||
}
|
||||
if name == constant.FirewallProviderNftables && dockerInstalled && !dockerNftablesSupported(dockerVersion) {
|
||||
option.Supported = false
|
||||
option.SupportReason = "docker_version_unsupported"
|
||||
option.Active = false
|
||||
}
|
||||
if option.Active {
|
||||
guard := newDockerFirewallRuntime(name)
|
||||
ipv4, ipv6 := guard.Status(dockerfirewall.FamilyIPv4), guard.Status(dockerfirewall.FamilyIPv6)
|
||||
option.Initialized = ipv4.Initialized || ipv6.Initialized
|
||||
option.Bound = ipv4.Bound || ipv6.Bound
|
||||
option.IPv4.Initialized, option.IPv4.Bound = ipv4.Initialized, ipv4.Bound
|
||||
option.IPv6.Initialized, option.IPv6.Bound = ipv6.Initialized, ipv6.Bound
|
||||
option.IPv4.Available = ipv4.Reason != dockerfirewall.ReasonCommandMissing
|
||||
option.IPv6.Available = ipv6.Reason != dockerfirewall.ReasonCommandMissing
|
||||
option.IPv4.Reason, option.IPv6.Reason = ipv4.Reason, ipv6.Reason
|
||||
}
|
||||
result.Docker.Options = append(result.Docker.Options, option)
|
||||
}
|
||||
var err error
|
||||
result.PortWhitelist, err = loadPortWhitelistSetting(global.DB.WithContext(ctx))
|
||||
func loadConfiguredFirewallPortWhiteList() ([]firewallPortWhitelist, error) {
|
||||
value, err := settingRepo.GetValueByKey(constant.FirewallPortWhiteList)
|
||||
if err != nil {
|
||||
return result, err
|
||||
value = constant.FirewallPortWhiteListValue
|
||||
if err := settingRepo.UpdateOrCreate(constant.FirewallPortWhiteList, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
result.PanelPort = LoadPanelPort()
|
||||
sshPort, sshErr := loadSSHWhitelistPortFrom(sshPath)
|
||||
if sshErr != nil {
|
||||
global.LOG.Warnf("load SSH port for firewall settings: %v", sshErr)
|
||||
} else {
|
||||
result.SSHPort = sshPort
|
||||
}
|
||||
return result, err
|
||||
return parseFirewallPortWhiteList(value)
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) Operate(ctx context.Context, request dto.FirewallBackendOperation) error {
|
||||
if err := lockFirewallLifecycleIdle(); err != nil {
|
||||
return err
|
||||
func loadFirewallPortWhiteList() ([]firewallPortWhitelist, error) {
|
||||
portWhiteList, err := loadConfiguredFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer firewallLifecycleTaskMu.Unlock()
|
||||
if request.Subsystem != "system" && request.Backend != constant.FirewallProviderIptables && request.Backend != constant.FirewallProviderNftables {
|
||||
return fmt.Errorf("%s only supports iptables or nftables", request.Subsystem)
|
||||
}
|
||||
if request.Subsystem == "system" && (request.Backend != constant.FirewallProviderIptables && request.Backend != constant.FirewallProviderNftables) && request.Operation != "select" {
|
||||
return fmt.Errorf("%s does not support initialization or cleanup", request.Backend)
|
||||
}
|
||||
switch request.Subsystem {
|
||||
case "system":
|
||||
if err := s.operateSystem(request); err != nil {
|
||||
return err
|
||||
}
|
||||
if request.Operation == "initialize" {
|
||||
service := newFirewallService()
|
||||
rulesErr := service.restoreStoredFirewallRules(ctx, filter.Provider(request.Backend), nil)
|
||||
whitelistErr := service.SyncPortWhitelist(ctx)
|
||||
return errors.Join(rulesErr, whitelistErr)
|
||||
}
|
||||
return nil
|
||||
case "forwarding":
|
||||
return s.operateForwarding(request)
|
||||
case "docker":
|
||||
return s.operateDocker(ctx, request)
|
||||
default:
|
||||
return fmt.Errorf("unsupported firewall subsystem %q", request.Subsystem)
|
||||
requiredPorts, err := loadRequiredFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return normalizeFirewallPortWhiteList(append(portWhiteList, requiredPorts...)), nil
|
||||
}
|
||||
|
||||
func NewIFirewallSettingService() IFirewallSettingService {
|
||||
return &FirewallSettingService{}
|
||||
func loadRequiredFirewallPortWhiteList() ([]firewallPortWhitelist, error) {
|
||||
panelPort := LoadPanelPort()
|
||||
if panelPort == "" {
|
||||
return nil, fmt.Errorf("find 1panel service port failed")
|
||||
}
|
||||
return normalizeFirewallPortWhiteList([]firewallPortWhitelist{
|
||||
{Port: panelPort, Protocol: "tcp"},
|
||||
{Port: loadSSHPort(), Protocol: "tcp"},
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperation) error {
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
if _, err := lifecycle.NewClient(request.Backend); err != nil {
|
||||
func parseFirewallPortWhiteList(value string) ([]firewallPortWhitelist, error) {
|
||||
items := strings.FieldsFunc(value, func(r rune) bool {
|
||||
return r == ',' || r == '\n' || r == ';' || r == ' '
|
||||
})
|
||||
ports := make([]firewallPortWhitelist, 0, len(items))
|
||||
exists := make(map[string]struct{})
|
||||
for _, item := range items {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
port, protocol, ok := strings.Cut(item, "/")
|
||||
if !ok {
|
||||
protocol = "tcp"
|
||||
}
|
||||
port = strings.TrimSpace(port)
|
||||
protocol = strings.ToLower(strings.TrimSpace(protocol))
|
||||
if protocol != "tcp" && protocol != "udp" {
|
||||
return nil, fmt.Errorf("invalid firewall port whitelist protocol: %s", item)
|
||||
}
|
||||
portNum, err := strconv.Atoi(port)
|
||||
if err != nil || portNum < 1 || portNum > 65535 {
|
||||
return nil, fmt.Errorf("invalid firewall port whitelist: %s", item)
|
||||
}
|
||||
key := fmt.Sprintf("%d/%s", portNum, protocol)
|
||||
if _, ok := exists[key]; ok {
|
||||
continue
|
||||
}
|
||||
exists[key] = struct{}{}
|
||||
ports = append(ports, firewallPortWhitelist{Port: strconv.Itoa(portNum), Protocol: protocol})
|
||||
}
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
func normalizeFirewallPortWhiteList(portWhiteList []firewallPortWhitelist) []firewallPortWhitelist {
|
||||
ports := make([]firewallPortWhitelist, 0, len(portWhiteList))
|
||||
exists := make(map[string]struct{})
|
||||
for _, item := range portWhiteList {
|
||||
if item.Port == "" {
|
||||
continue
|
||||
}
|
||||
key := fmt.Sprintf("%s/%s", item.Port, item.Protocol)
|
||||
if _, ok := exists[key]; ok {
|
||||
continue
|
||||
}
|
||||
exists[key] = struct{}{}
|
||||
ports = append(ports, item)
|
||||
}
|
||||
return ports
|
||||
}
|
||||
|
||||
func syncFirewallPortWhiteListAfterUpdate(oldValue string) error {
|
||||
client, err := firewall.NewFirewallClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if request.Operation == "cleanup" {
|
||||
return cleanupSystemBackend(request.Backend)
|
||||
}
|
||||
previous, _ := settingRepo.GetValueByKey(constant.FirewallSystemBackendKey)
|
||||
if previous == "" {
|
||||
if client, err := lifecycle.NewClient(""); err == nil {
|
||||
previous = client.Name()
|
||||
}
|
||||
}
|
||||
if request.Operation == "select" && previous != "" && previous != request.Backend {
|
||||
initialized, err := systemFirewallBackendInitialized(previous)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if initialized {
|
||||
return buserr.WithMap("ErrFirewallBackendCleanupRequired", map[string]interface{}{"current": previous, "target": request.Backend}, nil)
|
||||
}
|
||||
}
|
||||
if err := settingRepo.UpdateOrCreate(constant.FirewallSystemBackendKey, request.Backend); err != nil {
|
||||
return err
|
||||
}
|
||||
rollback := func(err error) error {
|
||||
if err == nil {
|
||||
if client.Name() == "iptables" {
|
||||
isInit, _ := iptables.LoadInitStatus("iptables", "base")
|
||||
if !isInit {
|
||||
return nil
|
||||
}
|
||||
_ = settingRepo.UpdateOrCreate(constant.FirewallSystemBackendKey, previous)
|
||||
return err
|
||||
oldPortWhiteList, err := parseFirewallPortWhiteList(oldValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return syncIptablesFirewallPortWhiteList(true, oldPortWhiteList)
|
||||
}
|
||||
if request.Operation == "select" {
|
||||
|
||||
isActive, _ := client.Status()
|
||||
if !isActive {
|
||||
return nil
|
||||
}
|
||||
initErr := newFirewallService().operateFilterChainBaseLocked(request.Backend, dto.FilterChainOperation{
|
||||
Name: constant.FirewallBasicChain, Operate: string(firewall.BaseOperationInit),
|
||||
})
|
||||
if initErr != nil {
|
||||
return rollback(initErr)
|
||||
}
|
||||
return settingRepo.UpdateOrCreate(constant.FirewallFilterInitializedKey, constant.StatusEnable)
|
||||
}
|
||||
|
||||
func systemFirewallBackendInitialized(backend string) (bool, error) {
|
||||
client, err := lifecycle.NewClient(backend)
|
||||
if err != nil {
|
||||
if errors.Is(err, lifecycle.ErrNotInstalled) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if backend == constant.FirewallProviderIptables || backend == constant.FirewallProviderNftables {
|
||||
for _, family := range []string{constant.FirewallFamilyIPv4, constant.FirewallFamilyIPv6} {
|
||||
initialized, _, err := loadSystemFirewallFamilyStatus(backend, family)
|
||||
if family == constant.FirewallFamilyIPv6 && errors.Is(err, filter.ErrFamilyUnavailable) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if initialized {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return client.Status()
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) operateForwarding(request dto.FirewallBackendOperation) error {
|
||||
manager, err := newForwardingAdapterFor(request.Backend)
|
||||
portWhiteList, err := loadFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if request.Operation == "cleanup" {
|
||||
if err := manager.Cleanup(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.UpdateOrCreate(constant.FirewallForwardingInitializedKey, constant.StatusDisable); err != nil {
|
||||
return err
|
||||
}
|
||||
recordForwardingSyncError(nil)
|
||||
return nil
|
||||
}
|
||||
previous, _ := settingRepo.GetValueByKey(constant.FirewallForwardingBackendKey)
|
||||
if request.Operation == "select" {
|
||||
current := previous
|
||||
if current == "" {
|
||||
detected, err := newForwardingAdapter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
current = detected.Name()
|
||||
}
|
||||
initialized, err := forwardingBackendInitialized(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if current != request.Backend && initialized {
|
||||
return buserr.WithMap("ErrFirewallBackendCleanupRequired", map[string]interface{}{"current": current, "target": request.Backend}, nil)
|
||||
}
|
||||
}
|
||||
if err := settingRepo.UpdateOrCreate(constant.FirewallForwardingBackendKey, request.Backend); err != nil {
|
||||
return err
|
||||
}
|
||||
if request.Operation == "initialize" {
|
||||
return newForwardingService().Enable()
|
||||
}
|
||||
recordForwardingSyncError(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func forwardingBackendInitialized(backend string) (bool, error) {
|
||||
manager, err := newForwardingAdapterFor(backend)
|
||||
oldPortWhiteList, err := parseFirewallPortWhiteList(oldValue)
|
||||
if err != nil {
|
||||
if errors.Is(err, lifecycle.ErrNotInstalled) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
for _, family := range []string{constant.FirewallFamilyIPv4, constant.FirewallFamilyIPv6} {
|
||||
initialized, _, err := manager.FamilyStatus(family)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if initialized {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) operateDocker(ctx context.Context, request dto.FirewallBackendOperation) error {
|
||||
guard := newDockerFirewallRuntime(request.Backend)
|
||||
if request.Operation == "cleanup" {
|
||||
if err := guard.Cleanup(); err != nil {
|
||||
return err
|
||||
}
|
||||
return settingRepo.UpdateOrCreate(constant.FirewallDockerPortGuardStatusKey, constant.StatusDisable)
|
||||
}
|
||||
previous, _ := settingRepo.GetValueByKey(constant.FirewallDockerBackendKey)
|
||||
if request.Operation == "select" {
|
||||
current := previous
|
||||
if current == "" {
|
||||
current = constant.FirewallProviderNftables
|
||||
if request.Backend == constant.FirewallProviderNftables {
|
||||
current = constant.FirewallProviderIptables
|
||||
}
|
||||
}
|
||||
initialized, err := dockerGuardBackendInitialized(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if current != request.Backend && initialized {
|
||||
return buserr.WithMap("ErrFirewallBackendCleanupRequired", map[string]interface{}{"current": current, "target": request.Backend}, nil)
|
||||
}
|
||||
}
|
||||
if err := settingRepo.UpdateOrCreate(constant.FirewallDockerBackendKey, request.Backend); err != nil {
|
||||
return err
|
||||
}
|
||||
if request.Operation == "select" {
|
||||
if err := (&DockerService{}).UpdateFirewallBackend(request.Backend); err != nil {
|
||||
_ = settingRepo.UpdateOrCreate(constant.FirewallDockerBackendKey, previous)
|
||||
return err
|
||||
}
|
||||
requiredPorts, err := loadRequiredFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if request.Operation == "initialize" {
|
||||
if err := newDockerPortGuardService().Operate(ctx, dto.DockerPortGuardOperation{Operation: "initialize"}); err != nil {
|
||||
_ = settingRepo.UpdateOrCreate(constant.FirewallDockerBackendKey, previous)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
oldPortWhiteList = normalizeFirewallPortWhiteList(append(oldPortWhiteList, requiredPorts...))
|
||||
return syncFirewallClientPortWhiteList(client, oldPortWhiteList, portWhiteList)
|
||||
}
|
||||
|
||||
func dockerGuardBackendInitialized(backend string) (bool, error) {
|
||||
guard := newDockerFirewallRuntime(backend)
|
||||
for _, family := range []string{dockerfirewall.FamilyIPv4, dockerfirewall.FamilyIPv6} {
|
||||
initialized, err := guard.Initialized(family)
|
||||
if err != nil {
|
||||
return false, err
|
||||
func syncFirewallClientPortWhiteList(client firewall.FilterClient, oldPortWhiteList, portWhiteList []firewallPortWhitelist) error {
|
||||
oldPorts := firewallPortWhiteListMap(oldPortWhiteList)
|
||||
newPorts := firewallPortWhiteListMap(portWhiteList)
|
||||
for _, item := range oldPortWhiteList {
|
||||
key := firewallPortWhiteListKey(item)
|
||||
if _, ok := newPorts[key]; ok {
|
||||
continue
|
||||
}
|
||||
if initialized {
|
||||
return true, nil
|
||||
if err := client.Port(fireClient.FireInfo{Port: item.Port, Protocol: item.Protocol, Strategy: "accept"}, "remove"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
for _, item := range portWhiteList {
|
||||
key := firewallPortWhiteListKey(item)
|
||||
if _, ok := oldPorts[key]; ok {
|
||||
continue
|
||||
}
|
||||
if err := client.Port(fireClient.FireInfo{Port: item.Port, Protocol: item.Protocol, Strategy: "accept"}, "add"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return client.Reload()
|
||||
}
|
||||
|
||||
func firewallPortWhiteListMap(portWhiteList []firewallPortWhitelist) map[string]struct{} {
|
||||
ports := make(map[string]struct{})
|
||||
for _, item := range portWhiteList {
|
||||
ports[firewallPortWhiteListKey(item)] = struct{}{}
|
||||
}
|
||||
return ports
|
||||
}
|
||||
|
||||
func firewallPortWhiteListKey(item firewallPortWhitelist) string {
|
||||
return item.Port + "/" + item.Protocol
|
||||
}
|
||||
|
||||
@@ -1,396 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"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"
|
||||
dockerfirewall "github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
|
||||
firewallsync "github.com/1Panel-dev/1Panel/agent/utils/firewall/sync"
|
||||
)
|
||||
|
||||
var (
|
||||
firewallRuleSyncTaskMu sync.Mutex
|
||||
firewallRuleSyncTaskID string
|
||||
)
|
||||
|
||||
type firewallDatabaseSyncAdapter interface {
|
||||
previewRuleSync(context.Context, dto.FirewallRuleSyncRequest) (dto.FirewallRuleSyncPreview, error)
|
||||
syncRules(context.Context, dto.FirewallRuleSyncRequest) (dto.FirewallRuleSyncResult, error)
|
||||
}
|
||||
|
||||
func (s *FirewallService) SyncPortWhitelist(ctx context.Context) error {
|
||||
firewallWhitelistMu.Lock()
|
||||
defer firewallWhitelistMu.Unlock()
|
||||
|
||||
ports, err := loadFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
provider, err := s.selectedProvider(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := s.syncPortWhitelist(ctx, provider, ports); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.removeTransferredSystemPortRules(ctx, provider, ports)
|
||||
}
|
||||
|
||||
func (s *FirewallService) PreviewRuleSync(ctx context.Context, clientIP string, request dto.FirewallRuleSyncRequest) (dto.FirewallRuleSyncPreview, error) {
|
||||
switch strings.TrimSpace(request.Subsystem) {
|
||||
case "forwarding":
|
||||
service := s.forwardingSync
|
||||
if service == nil {
|
||||
service = newForwardingService()
|
||||
}
|
||||
return service.previewRuleSync(ctx, request)
|
||||
case "docker":
|
||||
service := s.dockerSync
|
||||
if service == nil {
|
||||
service = newDockerPortGuardService()
|
||||
}
|
||||
return service.previewRuleSync(ctx, request)
|
||||
}
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
_, rules, _, err := s.loadFirewallSyncRules(ctx, request)
|
||||
preview := dto.FirewallRuleSyncPreview{Subsystem: "system", TargetProvider: request.TargetProvider, Items: make([]dto.FirewallRuleSyncItem, 0, len(rules))}
|
||||
for _, rule := range rules {
|
||||
preview.Add(rule.FirewallRuleSyncItem)
|
||||
}
|
||||
return preview, err
|
||||
}
|
||||
|
||||
func (s *FirewallService) SyncRules(ctx context.Context, clientIP string, request dto.FirewallRuleSyncRequest) (dto.FirewallRuleSyncResult, error) {
|
||||
switch strings.TrimSpace(request.Subsystem) {
|
||||
case "forwarding":
|
||||
service := s.forwardingSync
|
||||
if service == nil {
|
||||
service = newForwardingService()
|
||||
}
|
||||
return service.syncRules(ctx, request)
|
||||
case "docker":
|
||||
service := s.dockerSync
|
||||
if service == nil {
|
||||
service = newDockerPortGuardService()
|
||||
}
|
||||
return service.syncRules(ctx, request)
|
||||
default:
|
||||
return s.syncSystemRules(ctx, clientIP, request)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FirewallService) CurrentRuleSyncTask() (dto.FirewallRuleSyncTask, error) {
|
||||
firewallRuleSyncTaskMu.Lock()
|
||||
defer firewallRuleSyncTaskMu.Unlock()
|
||||
return currentFirewallRuleSyncTaskLocked()
|
||||
}
|
||||
|
||||
func (s *DockerPortGuardService) Reconcile(ctx context.Context) error {
|
||||
dockerPortGuardServiceMu.Lock()
|
||||
defer dockerPortGuardServiceMu.Unlock()
|
||||
return s.reconcileLocked(ctx)
|
||||
}
|
||||
|
||||
func (s *ForwardingService) Restore(ctx context.Context) error {
|
||||
forwardingMutationMu.Lock()
|
||||
defer forwardingMutationMu.Unlock()
|
||||
enabled, err := s.forwardingEnabled()
|
||||
if err != nil || !enabled {
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
manager, err := s.clientFactory()
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
stored, err := s.rules.List(ctx)
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
if err := s.initializeForwarding(manager); err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
err = manager.ReplaceRules(forwardingRulesFromModels(stored))
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ForwardingService) previewRuleSync(ctx context.Context, request dto.FirewallRuleSyncRequest) (dto.FirewallRuleSyncPreview, error) {
|
||||
targetProvider, err := databaseRuleSyncTarget(request, "forwarding")
|
||||
if err != nil {
|
||||
return dto.FirewallRuleSyncPreview{}, err
|
||||
}
|
||||
target, candidates, targetRules, _, err := s.loadRuleSyncCandidates(ctx, targetProvider)
|
||||
if err != nil {
|
||||
return dto.FirewallRuleSyncPreview{}, err
|
||||
}
|
||||
return forwardingSyncPreview(filter.Provider(target.Name()), candidates, targetRules), nil
|
||||
}
|
||||
|
||||
func (s *ForwardingService) syncRules(ctx context.Context, request dto.FirewallRuleSyncRequest) (dto.FirewallRuleSyncResult, error) {
|
||||
forwardingMutationMu.Lock()
|
||||
defer forwardingMutationMu.Unlock()
|
||||
|
||||
targetProvider, err := databaseRuleSyncTarget(request, "forwarding")
|
||||
if err != nil {
|
||||
return dto.FirewallRuleSyncResult{}, err
|
||||
}
|
||||
target, candidates, targetRules, targetInitialized, err := s.loadRuleSyncCandidates(ctx, targetProvider)
|
||||
if err != nil {
|
||||
return dto.FirewallRuleSyncResult{}, err
|
||||
}
|
||||
preview := forwardingSyncPreview(filter.Provider(target.Name()), candidates, targetRules)
|
||||
desired := make([]forwarding.Rule, 0, len(candidates))
|
||||
for _, candidate := range candidates {
|
||||
if candidate.err == nil {
|
||||
desired = append(desired, candidate.rule)
|
||||
}
|
||||
}
|
||||
if preview.Blocked > 0 {
|
||||
return firewallSyncResult(preview, nil, false), nil
|
||||
}
|
||||
if len(desired) == 0 && !targetInitialized {
|
||||
return firewallSyncResult(preview, nil, true), nil
|
||||
}
|
||||
reconcileErr := func() error {
|
||||
if len(desired) > 0 {
|
||||
if err := s.persistForwardingEnabled(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.initializeForwarding(target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := target.ReplaceRules(desired); err != nil {
|
||||
return err
|
||||
}
|
||||
return verifyForwardingRuleSync(target, desired)
|
||||
}()
|
||||
result := firewallSyncResult(preview, reconcileErr, true)
|
||||
recordForwardingSyncError(reconcileErr)
|
||||
if reconcileErr != nil {
|
||||
if preview.Ready == 0 {
|
||||
return result, reconcileErr
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *DockerPortGuardService) previewRuleSync(ctx context.Context, request dto.FirewallRuleSyncRequest) (dto.FirewallRuleSyncPreview, error) {
|
||||
target, policies, runtime, err := s.loadRuleSyncCandidates(ctx, request)
|
||||
if err != nil {
|
||||
return dto.FirewallRuleSyncPreview{}, err
|
||||
}
|
||||
targetInventory, err := runtime.ListPolicies()
|
||||
if err != nil {
|
||||
return dto.FirewallRuleSyncPreview{}, err
|
||||
}
|
||||
return dockerSyncPreview(filter.Provider(target), policies, targetInventory), nil
|
||||
}
|
||||
|
||||
func (s *DockerPortGuardService) syncRules(ctx context.Context, request dto.FirewallRuleSyncRequest) (dto.FirewallRuleSyncResult, error) {
|
||||
dockerPortGuardServiceMu.Lock()
|
||||
defer dockerPortGuardServiceMu.Unlock()
|
||||
|
||||
target, policies, targetRuntime, err := s.loadRuleSyncCandidates(ctx, request)
|
||||
if err != nil {
|
||||
return dto.FirewallRuleSyncResult{}, err
|
||||
}
|
||||
runtimePolicies := make([]dockerfirewall.Policy, 0, len(policies))
|
||||
for _, policy := range policies {
|
||||
sources := []string{}
|
||||
_ = json.Unmarshal([]byte(policy.Sources), &sources)
|
||||
runtimePolicies = append(runtimePolicies, dockerfirewall.Policy{
|
||||
UUID: policy.UUID, Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort,
|
||||
Protocol: policy.Protocol, Mode: policy.Mode, Sources: sources,
|
||||
})
|
||||
}
|
||||
targetInventory, err := targetRuntime.ListPolicies()
|
||||
if err != nil {
|
||||
return dto.FirewallRuleSyncResult{}, err
|
||||
}
|
||||
preview := dockerSyncPreview(filter.Provider(target), policies, targetInventory)
|
||||
for _, item := range preview.Items {
|
||||
if item.Status == firewallsync.StatusBlocked && item.ReasonCode != firewallsync.ReasonReadOnlyRule {
|
||||
return firewallSyncResult(preview, nil, false), nil
|
||||
}
|
||||
}
|
||||
if preview.Ready == 0 && preview.Removed == 0 {
|
||||
return firewallSyncResult(preview, nil, false), nil
|
||||
}
|
||||
reconcileErr := func() error {
|
||||
if err := reconcileDockerFirewall(target, runtimePolicies, targetRuntime, targetInventory); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := verifyDockerFirewall(targetRuntime, runtimePolicies, targetInventory.ReadOnly); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(policies) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := settingRepo.UpdateOrCreate(constant.FirewallDockerBackendKey, target); err != nil {
|
||||
return err
|
||||
}
|
||||
return settingRepo.UpdateOrCreate(constant.FirewallDockerPortGuardStatusKey, constant.StatusEnable)
|
||||
}()
|
||||
result := firewallSyncResult(preview, reconcileErr, true)
|
||||
if reconcileErr != nil {
|
||||
return result, reconcileErr
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *FirewallService) syncSystemRules(ctx context.Context, clientIP string, request dto.FirewallRuleSyncRequest) (dto.FirewallRuleSyncResult, error) {
|
||||
subsystem := strings.TrimSpace(request.Subsystem)
|
||||
if subsystem == "" {
|
||||
subsystem = "system"
|
||||
}
|
||||
if err := lockFirewallLifecycleIdle(); err != nil {
|
||||
return dto.FirewallRuleSyncResult{}, err
|
||||
}
|
||||
defer firewallLifecycleTaskMu.Unlock()
|
||||
firewallRuleSyncTaskMu.Lock()
|
||||
defer firewallRuleSyncTaskMu.Unlock()
|
||||
|
||||
running, err := currentFirewallRuleSyncTaskLocked()
|
||||
if err != nil {
|
||||
return dto.FirewallRuleSyncResult{}, err
|
||||
}
|
||||
if running.Executing {
|
||||
return dto.FirewallRuleSyncResult{
|
||||
Subsystem: subsystem,
|
||||
TargetProvider: request.TargetProvider,
|
||||
TaskID: running.TaskID,
|
||||
Queued: true,
|
||||
}, nil
|
||||
}
|
||||
if subsystem != "system" {
|
||||
return dto.FirewallRuleSyncResult{}, fmt.Errorf("%w: firewall synchronization tasks are only available for the system firewall", filter.ErrInvalidRule)
|
||||
}
|
||||
taskItem, err := task.NewTask(firewallTaskName(task.TaskSync, firewallTaskHost, string(request.TargetProvider)), task.TaskSync, task.TaskScopeFirewall, "", 0)
|
||||
if err != nil {
|
||||
return dto.FirewallRuleSyncResult{}, fmt.Errorf("create firewall sync task: %w", err)
|
||||
}
|
||||
taskItem.AddSubTaskWithOps(i18n.GetWithName("FirewallSyncStep", string(request.TargetProvider)), func(t *task.Task) error {
|
||||
result, err := s.syncRules(t.TaskCtx, clientIP, request, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.Failed > 0 {
|
||||
return errors.New(i18n.GetMsgWithMap("FirewallSyncFailed", map[string]interface{}{"failed": result.Failed}))
|
||||
}
|
||||
return nil
|
||||
}, nil, 0, 0)
|
||||
|
||||
if err := repo.NewITaskRepo().Save(context.Background(), taskItem.Task); err != nil {
|
||||
taskItem.LogFailedWithErr(taskItem.Name, err)
|
||||
closeUnstartedFirewallTask(taskItem)
|
||||
return dto.FirewallRuleSyncResult{}, fmt.Errorf("save firewall sync task: %w", err)
|
||||
}
|
||||
firewallRuleSyncTaskID = taskItem.TaskID
|
||||
go func() {
|
||||
defer func() {
|
||||
firewallRuleSyncTaskMu.Lock()
|
||||
if firewallRuleSyncTaskID == taskItem.TaskID {
|
||||
firewallRuleSyncTaskID = ""
|
||||
}
|
||||
firewallRuleSyncTaskMu.Unlock()
|
||||
}()
|
||||
if err := taskItem.Execute(); err != nil && global.LOG != nil {
|
||||
global.LOG.Errorf("firewall sync task %s failed: %v", taskItem.TaskID, err)
|
||||
}
|
||||
}()
|
||||
return dto.FirewallRuleSyncResult{
|
||||
Subsystem: "system",
|
||||
TargetProvider: request.TargetProvider,
|
||||
TaskID: taskItem.TaskID,
|
||||
Queued: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func verifyForwardingRuleSync(target forwarding.Adapter, desired []forwarding.Rule) error {
|
||||
actual, err := target.List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("verify synchronized forwarding rules: %w", err)
|
||||
}
|
||||
actual, err = normalizeForwardingRuntimeRules(actual)
|
||||
if err != nil {
|
||||
return fmt.Errorf("verify synchronized forwarding rules: %w", err)
|
||||
}
|
||||
if !firewallRuleStatesEqual(actual, desired, func(rule forwarding.Rule) string { return rule.Identity() }) {
|
||||
return fmt.Errorf("verify synchronized forwarding rules: target rules do not match the database")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reconcileDockerFirewall(backend string, policies []dockerfirewall.Policy, runtime dockerfirewall.Runtime, inventory dockerfirewall.PolicyInventory) error {
|
||||
families := make(map[string]struct{}, len(policies))
|
||||
needsInitialize, needsBind := false, false
|
||||
for _, policy := range policies {
|
||||
families[policy.Family] = struct{}{}
|
||||
}
|
||||
if len(families) == 0 {
|
||||
initialized := false
|
||||
for _, family := range []string{dockerfirewall.FamilyIPv4, dockerfirewall.FamilyIPv6} {
|
||||
status := runtime.Status(family)
|
||||
if status.Reason == dockerfirewall.ReasonInspectFailed {
|
||||
return fmt.Errorf("inspect Docker firewall target %s for %s failed", backend, family)
|
||||
}
|
||||
initialized = initialized || status.Initialized
|
||||
}
|
||||
if initialized {
|
||||
return runtime.ReplacePolicies(nil, inventory)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for family := range families {
|
||||
status := runtime.Status(family)
|
||||
needsInitialize = needsInitialize || !status.Initialized
|
||||
needsBind = needsBind || !status.Bound || !status.Effective
|
||||
}
|
||||
var err error
|
||||
if needsInitialize {
|
||||
err = runtime.Initialize(policies, inventory)
|
||||
} else {
|
||||
if needsBind {
|
||||
err = runtime.Bind()
|
||||
}
|
||||
if err == nil {
|
||||
err = runtime.ReplacePolicies(policies, inventory)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for family := range families {
|
||||
if !runtime.Status(family).Effective {
|
||||
return fmt.Errorf("Docker firewall target %s is not effective for %s", backend, family)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReconcileDockerPortGuardBestEffort(ctx context.Context) {
|
||||
if err := ReconcileDockerPortGuard(ctx); err != nil {
|
||||
global.LOG.Warnf("reconcile Docker port guard failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,566 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
|
||||
)
|
||||
|
||||
const (
|
||||
forwardingSyncConverged = "converged"
|
||||
forwardingSyncMissing = "missing"
|
||||
forwardingSyncRuntimeOnly = "runtime_only"
|
||||
)
|
||||
|
||||
type IForwardingService interface {
|
||||
LoadBaseInfo() (dto.FirewallSubsystemStatus, error)
|
||||
SearchRules(request dto.ForwardRuleSearch) (int64, []dto.ForwardRule, error)
|
||||
OperateRules(dto.ForwardRuleOperate) (dto.FilterChainOperationResponse, error)
|
||||
Enable() error
|
||||
QueueInitialization(dto.FirewallInitializationTask) (dto.FilterChainOperationResponse, error)
|
||||
Restore(context.Context) error
|
||||
}
|
||||
|
||||
type ForwardingService struct {
|
||||
clientFactory func() (forwarding.Adapter, error)
|
||||
rules repo.IForwardingRuleRepo
|
||||
enabled func() (bool, error)
|
||||
persistBackend func(string) error
|
||||
markEnabled func() error
|
||||
}
|
||||
|
||||
var errForwardingBackendUnavailable = errors.New("no supported forwarding backend detected")
|
||||
|
||||
var forwardingMutationMu sync.Mutex
|
||||
|
||||
var (
|
||||
forwardingSyncStateMu sync.RWMutex
|
||||
forwardingLastSyncErr error
|
||||
)
|
||||
|
||||
func (s *ForwardingService) LoadBaseInfo() (dto.FirewallSubsystemStatus, error) {
|
||||
selected, _ := settingRepo.GetValueByKey(constant.FirewallForwardingBackendKey)
|
||||
selected = strings.TrimSpace(selected)
|
||||
if selected == "" {
|
||||
selected = constant.FirewallProviderIptables
|
||||
}
|
||||
baseInfo := dto.FirewallSubsystemStatus{
|
||||
Version: "-", Name: selected, Backend: selected, SyncError: lastForwardingSyncError(),
|
||||
}
|
||||
if selected == constant.FirewallProviderIptables || selected == constant.FirewallProviderNftables {
|
||||
baseInfo.Name += "-forward"
|
||||
}
|
||||
manager, err := s.clientFactory()
|
||||
if err != nil {
|
||||
if errors.Is(err, errForwardingBackendUnavailable) {
|
||||
baseInfo.Reason = constant.FirewallBackendNotInstalled
|
||||
return baseInfo, nil
|
||||
}
|
||||
return baseInfo, err
|
||||
}
|
||||
client, err := lifecycle.NewClient(manager.Name())
|
||||
if err != nil {
|
||||
return baseInfo, err
|
||||
}
|
||||
version, versionErr := client.Version()
|
||||
status, statusErr := loadForwardingFirewallOverview(manager)
|
||||
if err := errors.Join(versionErr, statusErr); err != nil {
|
||||
return baseInfo, err
|
||||
}
|
||||
baseInfo.IsExist = true
|
||||
baseInfo.Name, baseInfo.Backend = manager.Name(), manager.Name()
|
||||
if baseInfo.Backend == constant.FirewallProviderIptables || baseInfo.Backend == constant.FirewallProviderNftables {
|
||||
baseInfo.Name += "-forward"
|
||||
}
|
||||
baseInfo.Version = version
|
||||
baseInfo.PingStatus = firewall.LoadPingStatus()
|
||||
baseInfo.IsInit, baseInfo.IsBind = status.IsInit, status.IsBind
|
||||
baseInfo.IPv4, baseInfo.IPv6 = status.IPv4, status.IPv6
|
||||
for _, family := range []struct {
|
||||
command string
|
||||
status *dto.FirewallBackendFamilyStatus
|
||||
}{
|
||||
{"iptables", &baseInfo.IPv4},
|
||||
{"ip6tables", &baseInfo.IPv6},
|
||||
} {
|
||||
policy, err := loadForwardPolicy(family.command)
|
||||
if err != nil {
|
||||
global.LOG.Warnf("inspect %s FORWARD policy: %v", family.command, err)
|
||||
continue
|
||||
}
|
||||
family.status.ForwardPolicy = policy
|
||||
}
|
||||
return baseInfo, nil
|
||||
}
|
||||
|
||||
func (s *ForwardingService) SearchRules(request dto.ForwardRuleSearch) (int64, []dto.ForwardRule, error) {
|
||||
if request.Strategy != "" {
|
||||
return 0, nil, nil
|
||||
}
|
||||
stored, err := s.rules.List(context.Background())
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
manager, err := s.clientFactory()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
runtime, err := manager.List()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
inventory, err := mergeForwardingInventory(stored, runtime)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
keyword := strings.ToLower(strings.TrimSpace(request.Info))
|
||||
filtered := inventory[:0]
|
||||
for _, item := range inventory {
|
||||
if keyword == "" || forwardingRuleMatchesKeyword(item, keyword) {
|
||||
filtered = append(filtered, item)
|
||||
}
|
||||
}
|
||||
inventory = filtered
|
||||
total := len(inventory)
|
||||
start, end := (request.Page-1)*request.PageSize, request.Page*request.PageSize
|
||||
if request.All {
|
||||
start, end = 0, total
|
||||
}
|
||||
if start > total {
|
||||
return int64(total), make([]dto.ForwardRule, 0), nil
|
||||
}
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
pageRules := inventory[start:end]
|
||||
var items []dto.ForwardRule
|
||||
if pageRules != nil {
|
||||
items = make([]dto.ForwardRule, 0, len(pageRules))
|
||||
}
|
||||
for index, item := range pageRules {
|
||||
items = append(items, dto.ForwardRule{
|
||||
ID: item.ID,
|
||||
Num: strconv.Itoa(start + index + 1),
|
||||
Family: item.Rule.Family,
|
||||
Protocol: item.Rule.Protocol,
|
||||
Port: item.Rule.Port,
|
||||
TargetIP: item.Rule.TargetIP,
|
||||
TargetPort: item.Rule.TargetPort,
|
||||
Interface: item.Rule.Interface,
|
||||
IsDesired: item.IsDesired,
|
||||
IsRuntime: item.IsRuntime,
|
||||
SyncStatus: item.SyncStatus(),
|
||||
})
|
||||
}
|
||||
return int64(total), items, nil
|
||||
}
|
||||
|
||||
func (s *ForwardingService) OperateRules(request dto.ForwardRuleOperate) (dto.FilterChainOperationResponse, error) {
|
||||
count := 0
|
||||
for _, rule := range request.Rules {
|
||||
if rule.Operation == "add" {
|
||||
count += strings.Count(rule.Protocol, "/") + 1
|
||||
}
|
||||
if count > filter.MaxAtomicExpansion {
|
||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("create or import at most %d rules per batch (after expansion)", filter.MaxAtomicExpansion)
|
||||
}
|
||||
}
|
||||
operation := task.TaskCreate
|
||||
for _, rule := range request.Rules {
|
||||
if rule.Operation != "add" {
|
||||
operation = task.TaskUpdate
|
||||
}
|
||||
}
|
||||
if forwardingOperationsOnlyRemove(request.Rules) {
|
||||
operation = task.TaskDelete
|
||||
}
|
||||
taskItem, err := task.NewTask(firewallTaskName(operation, firewallTaskForwarding, ""), operation, task.TaskScopeFirewall, "", 0)
|
||||
if err != nil {
|
||||
return dto.FilterChainOperationResponse{}, err
|
||||
}
|
||||
taskItem.AddSubTaskWithOps(taskItem.Name, func(t *task.Task) error {
|
||||
return s.operateRules(t.TaskCtx, request, t)
|
||||
}, nil, 0, 0)
|
||||
if err := taskRepo.Save(context.Background(), taskItem.Task); err != nil {
|
||||
taskItem.LogFailedWithErr(taskItem.Name, err)
|
||||
closeUnstartedFirewallTask(taskItem)
|
||||
return dto.FilterChainOperationResponse{}, err
|
||||
}
|
||||
go func() { _ = taskItem.Execute() }()
|
||||
return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil
|
||||
}
|
||||
|
||||
func (s *ForwardingService) Enable() error {
|
||||
forwardingMutationMu.Lock()
|
||||
defer forwardingMutationMu.Unlock()
|
||||
manager, err := s.clientFactory()
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
if err := s.persistForwardingEnabled(); err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
if err := s.initializeForwarding(manager); err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
rules, err := s.rules.List(context.Background())
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
err = manager.ReplaceRules(forwardingRulesFromModels(rules))
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ForwardingService) QueueInitialization(request dto.FirewallInitializationTask) (dto.FilterChainOperationResponse, error) {
|
||||
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeFirewall, 0); err != nil {
|
||||
return dto.FilterChainOperationResponse{}, err
|
||||
}
|
||||
taskItem, err := task.NewTask(firewallTaskName(task.TaskExec, firewallTaskForwarding, ""), task.TaskExec, task.TaskScopeFirewall, request.TaskID, 0)
|
||||
if err != nil {
|
||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("create forwarding initialization task: %w", err)
|
||||
}
|
||||
var manager forwarding.Adapter
|
||||
var backend string
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("FirewallEnableForwardingStep"), func(t *task.Task) error {
|
||||
forwardingMutationMu.Lock()
|
||||
defer forwardingMutationMu.Unlock()
|
||||
var err error
|
||||
manager, err = s.clientFactory()
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
backend = manager.Name()
|
||||
t.Logf("backend=%s", backend)
|
||||
if err := s.persistForwardingEnabled(); err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
if err := s.initializeForwarding(manager); err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("FirewallRestoreForwardingRulesStep"), func(t *task.Task) error {
|
||||
forwardingMutationMu.Lock()
|
||||
defer forwardingMutationMu.Unlock()
|
||||
rules, err := s.rules.List(t.TaskCtx)
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
err = manager.ReplaceRules(forwardingRulesFromModels(rules))
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}, nil)
|
||||
if err := repo.NewITaskRepo().Save(context.Background(), taskItem.Task); err != nil {
|
||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("save forwarding initialization task: %w", err)
|
||||
}
|
||||
go func() { _ = taskItem.Execute() }()
|
||||
return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil
|
||||
}
|
||||
|
||||
func NewIForwardingService() IForwardingService {
|
||||
return newForwardingService()
|
||||
}
|
||||
|
||||
func loadForwardPolicy(command string) (string, error) {
|
||||
if !cmd.Which(command) {
|
||||
command += "-nft"
|
||||
if !cmd.Which(command) {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
output, err := cmd.NewCommandMgr(cmd.WithTimeout(5*time.Second)).RunWithOptionalSudoAndStdout(command, "-t", "filter", "-w", "2", "-S", "FORWARD")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 3 && fields[0] == "-P" && fields[1] == "FORWARD" {
|
||||
if fields[2] != "ACCEPT" && fields[2] != "DROP" {
|
||||
return "", fmt.Errorf("unexpected FORWARD policy: %s", fields[2])
|
||||
}
|
||||
return fields[2], nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("FORWARD default policy was not found")
|
||||
}
|
||||
|
||||
func lastForwardingSyncError() string {
|
||||
forwardingSyncStateMu.RLock()
|
||||
defer forwardingSyncStateMu.RUnlock()
|
||||
if forwardingLastSyncErr == nil {
|
||||
return ""
|
||||
}
|
||||
return forwardingLastSyncErr.Error()
|
||||
}
|
||||
|
||||
func mergeForwardingInventory(stored []model.ForwardingRule, runtime []forwarding.Rule) ([]forwardingInventoryItem, error) {
|
||||
items := make([]forwardingInventoryItem, 0, len(stored)+len(runtime))
|
||||
byIdentity := make(map[string]int, len(stored)+len(runtime))
|
||||
for _, record := range stored {
|
||||
rule, err := forwarding.NormalizeRule(forwarding.Rule{
|
||||
Family: record.Family, Protocol: record.Protocol, Port: record.Port, TargetIP: record.TargetIP,
|
||||
TargetPort: record.TargetPort, Interface: record.Interface,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("normalize desired forwarding rule: %w", err)
|
||||
}
|
||||
key := rule.Identity()
|
||||
byIdentity[key] = len(items)
|
||||
items = append(items, forwardingInventoryItem{ID: record.ID, Rule: rule, IsDesired: true})
|
||||
}
|
||||
for _, observed := range runtime {
|
||||
rule, err := forwarding.NormalizeRule(observed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("normalize runtime forwarding rule: %w", err)
|
||||
}
|
||||
key := rule.Identity()
|
||||
if index, exists := byIdentity[key]; exists {
|
||||
items[index].IsRuntime = true
|
||||
continue
|
||||
}
|
||||
byIdentity[key] = len(items)
|
||||
items = append(items, forwardingInventoryItem{Rule: rule, IsRuntime: true})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func forwardingRuleMatchesKeyword(item forwardingInventoryItem, keyword string) bool {
|
||||
values := []string{
|
||||
item.Rule.Family, item.Rule.Protocol, item.Rule.Port, item.Rule.TargetIP,
|
||||
item.Rule.TargetPort, item.Rule.Interface, item.SyncStatus(),
|
||||
}
|
||||
for _, value := range values {
|
||||
if strings.Contains(strings.ToLower(value), keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *ForwardingService) operateRules(ctx context.Context, request dto.ForwardRuleOperate, t *task.Task) (resultErr error) {
|
||||
forwardingMutationMu.Lock()
|
||||
defer forwardingMutationMu.Unlock()
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
type operationBatch struct {
|
||||
operation forwarding.OperationType
|
||||
rules []forwarding.Rule
|
||||
}
|
||||
groups := make([]operationBatch, 0)
|
||||
for _, operation := range request.Rules {
|
||||
kind := forwarding.OperationType(operation.Operation)
|
||||
if kind != forwarding.OperationAdd && kind != forwarding.OperationRemove {
|
||||
return fmt.Errorf("unsupported forwarding operation %q", operation.Operation)
|
||||
}
|
||||
if len(groups) == 0 || groups[len(groups)-1].operation != kind {
|
||||
groups = append(groups, operationBatch{operation: kind})
|
||||
}
|
||||
for _, protocol := range strings.Split(operation.Protocol, "/") {
|
||||
rule, err := forwarding.NormalizeRule(forwarding.Rule{
|
||||
Family: operation.Family, Protocol: protocol, Port: operation.Port,
|
||||
TargetIP: operation.TargetIP, TargetPort: operation.TargetPort, Interface: operation.Interface,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
groups[len(groups)-1].rules = append(groups[len(groups)-1].rules, rule)
|
||||
}
|
||||
}
|
||||
stored, err := s.rules.List(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
byIdentity := make(map[string]model.ForwardingRule, len(stored))
|
||||
for index, rule := range forwardingRulesFromModels(stored) {
|
||||
normalized, err := forwarding.NormalizeRule(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
byIdentity[normalized.Identity()] = stored[index]
|
||||
}
|
||||
succeeded, failed, skipped := 0, 0, 0
|
||||
var nativeFailure error
|
||||
defer func() {
|
||||
recordForwardingSyncError(errors.Join(resultErr, nativeFailure))
|
||||
if t != nil {
|
||||
t.Log(i18n.GetMsgWithMap("FirewallRuleOperationResult", map[string]interface{}{"succeeded": succeeded, "failed": failed}))
|
||||
if skipped > 0 {
|
||||
t.Logf("%s: %d", i18n.GetMsgByKey("FirewallCreateRuleSkipped"), skipped)
|
||||
}
|
||||
}
|
||||
}()
|
||||
record := func(operation forwarding.OperationType, rule forwarding.Rule, status string, cause error) {
|
||||
label := fmt.Sprintf("%s %s %s %s -> %s:%s", operation, rule.Family, rule.Protocol, rule.Port, rule.TargetIP, rule.TargetPort)
|
||||
switch status {
|
||||
case "skipped":
|
||||
skipped++
|
||||
if t != nil {
|
||||
t.Logf("%s %s: %v", label, i18n.GetMsgByKey("FirewallCreateRuleSkipped"), cause)
|
||||
}
|
||||
case "failed":
|
||||
failed++
|
||||
if t != nil {
|
||||
t.LogFailedWithErr(label, cause)
|
||||
}
|
||||
default:
|
||||
succeeded++
|
||||
if t != nil {
|
||||
t.LogSuccess(label)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(request.Rules) == 2 && len(groups) == 2 && groups[0].operation == forwarding.OperationRemove && groups[1].operation == forwarding.OperationAdd {
|
||||
old := make(map[string]bool, len(groups[0].rules))
|
||||
for _, rule := range groups[0].rules {
|
||||
old[rule.Identity()] = true
|
||||
}
|
||||
unchanged := len(old) == len(groups[1].rules)
|
||||
duplicate := false
|
||||
for _, rule := range groups[1].rules {
|
||||
key := rule.Identity()
|
||||
unchanged = unchanged && old[key]
|
||||
if _, exists := byIdentity[key]; exists && !old[key] {
|
||||
duplicate = true
|
||||
}
|
||||
}
|
||||
if unchanged || duplicate {
|
||||
for _, group := range groups {
|
||||
for _, rule := range group.rules {
|
||||
record(group.operation, rule, "skipped", buserr.New("ErrRecordExist"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, rule := range groups[1].rules {
|
||||
if rule.Family != forwarding.FamilyIPv6 {
|
||||
continue
|
||||
}
|
||||
interfaces, err := forwarding.IPv6RAInterfaces(os.ReadFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check IPv6 Router Advertisement: %w", err)
|
||||
}
|
||||
if len(interfaces) > 0 {
|
||||
return fmt.Errorf("IPv6 forwarding blocked: interfaces %s may depend on RA/SLAAC with accept_ra=1; persist accept_ra=2 on interfaces that require RA before retrying", strings.Join(interfaces, ", "))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
var client forwarding.Adapter
|
||||
var failures []error
|
||||
for _, group := range groups {
|
||||
byFamily := make(map[string][]forwarding.Rule, 2)
|
||||
seen := make(map[string]bool, len(group.rules))
|
||||
for _, rule := range group.rules {
|
||||
key := rule.Identity()
|
||||
_, exists := byIdentity[key]
|
||||
if seen[key] || (group.operation == forwarding.OperationAdd && exists) {
|
||||
record(group.operation, rule, "skipped", buserr.New("ErrRecordExist"))
|
||||
continue
|
||||
}
|
||||
seen[key] = true
|
||||
byFamily[rule.Family] = append(byFamily[rule.Family], rule)
|
||||
}
|
||||
for _, family := range []string{forwarding.FamilyIPv4, forwarding.FamilyIPv6} {
|
||||
rules := byFamily[family]
|
||||
if len(rules) == 0 {
|
||||
continue
|
||||
}
|
||||
err := ctx.Err()
|
||||
if err == nil && client == nil {
|
||||
var enabled bool
|
||||
enabled, err = s.forwardingEnabled()
|
||||
if err == nil && !enabled {
|
||||
err = fmt.Errorf("%w: forwarding is not initialized", filter.ErrProviderUnavailable)
|
||||
}
|
||||
if err == nil {
|
||||
client, err = s.clientFactory()
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
if group.operation == forwarding.OperationAdd {
|
||||
err = client.CreateRules(ctx, rules)
|
||||
} else {
|
||||
err = client.DeleteRules(ctx, rules)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
nativeFailure = errors.Join(nativeFailure, err)
|
||||
if !request.ForceDelete || !forwardingOperationsOnlyRemove(request.Rules) || ctx.Err() != nil {
|
||||
failures = append(failures, err)
|
||||
for _, rule := range rules {
|
||||
record(group.operation, rule, "failed", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if t != nil {
|
||||
t.Logf("force delete database records: %v", err)
|
||||
}
|
||||
}
|
||||
for start := 0; start < len(rules); start += 500 {
|
||||
batch := rules[start:min(start+500, len(rules))]
|
||||
records := make([]model.ForwardingRule, 0, len(batch))
|
||||
ids := make([]uint, 0, len(batch))
|
||||
for _, rule := range batch {
|
||||
if group.operation == forwarding.OperationAdd {
|
||||
records = append(records, model.ForwardingRule{Family: rule.Family, Protocol: rule.Protocol, Port: rule.Port, TargetIP: rule.TargetIP, TargetPort: rule.TargetPort, Interface: rule.Interface})
|
||||
} else if stored, exists := byIdentity[rule.Identity()]; exists {
|
||||
ids = append(ids, stored.ID)
|
||||
}
|
||||
}
|
||||
if group.operation == forwarding.OperationAdd {
|
||||
err = s.rules.CreateBatch(context.WithoutCancel(ctx), records)
|
||||
} else {
|
||||
err = s.rules.DeleteBatch(context.WithoutCancel(ctx), ids)
|
||||
}
|
||||
if err != nil {
|
||||
failures = append(failures, err)
|
||||
}
|
||||
for index, rule := range batch {
|
||||
if err != nil {
|
||||
record(group.operation, rule, "failed", err)
|
||||
continue
|
||||
}
|
||||
if group.operation == forwarding.OperationAdd {
|
||||
byIdentity[rule.Identity()] = records[index]
|
||||
} else {
|
||||
delete(byIdentity, rule.Identity())
|
||||
}
|
||||
record(group.operation, rule, "succeeded", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
if group.operation == forwarding.OperationRemove && len(failures) > 0 {
|
||||
return errors.Join(failures...)
|
||||
}
|
||||
}
|
||||
return errors.Join(failures...)
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
forwardClient "github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
|
||||
)
|
||||
|
||||
type IForwardingService interface {
|
||||
LoadBaseInfo() (dto.FirewallBaseInfo, error)
|
||||
SearchWithPage(search dto.ForwardRuleSearch) (int64, interface{}, error)
|
||||
Operate(req dto.ForwardRuleOperate) error
|
||||
Enable() error
|
||||
Replay() error
|
||||
}
|
||||
|
||||
type ForwardingService struct {
|
||||
adapterFactory func() (forwardClient.Adapter, error)
|
||||
filterFactory func() (firewall.FilterClient, error)
|
||||
}
|
||||
|
||||
func NewIForwardingService() IForwardingService {
|
||||
return &ForwardingService{
|
||||
adapterFactory: newForwardingAdapter,
|
||||
filterFactory: firewall.NewFirewallClient,
|
||||
}
|
||||
}
|
||||
|
||||
func newForwardingAdapter() (forwardClient.Adapter, error) {
|
||||
client, err := firewall.NewFirewallClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return forwardClient.NewAdapter(client.Name())
|
||||
}
|
||||
|
||||
func (s *ForwardingService) LoadBaseInfo() (dto.FirewallBaseInfo, error) {
|
||||
baseInfo := dto.FirewallBaseInfo{Version: "-", Name: "-"}
|
||||
adapter, err := s.adapterFactory()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load forwarding failed, err: %v", err)
|
||||
return baseInfo, nil
|
||||
}
|
||||
filter, err := s.filterFactory()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load firewall status failed, err: %v", err)
|
||||
return baseInfo, nil
|
||||
}
|
||||
baseInfo.IsExist = true
|
||||
baseInfo.Name = adapter.Name()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
baseInfo.PingStatus = firewall.LoadPingStatus()
|
||||
baseInfo.Version, _ = filter.Version()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
baseInfo.IsActive, _ = filter.Status()
|
||||
baseInfo.IsInit, baseInfo.IsBind = adapter.InitStatus()
|
||||
}()
|
||||
wg.Wait()
|
||||
return baseInfo, nil
|
||||
}
|
||||
|
||||
func (s *ForwardingService) SearchWithPage(req dto.ForwardRuleSearch) (int64, interface{}, error) {
|
||||
adapter, err := s.adapterFactory()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
rules, err := adapter.List()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if req.Strategy != "" {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
var filtered []forwardClient.Rule
|
||||
for _, rule := range rules {
|
||||
if req.Info != "" && !strings.Contains(rule.Port, req.Info) &&
|
||||
!strings.Contains(rule.TargetPort, req.Info) && !strings.Contains(rule.TargetIP, req.Info) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, rule)
|
||||
}
|
||||
total := len(filtered)
|
||||
start, end := (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
return int64(total), make([]dto.ForwardRule, 0), nil
|
||||
}
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
pageRules := filtered[start:end]
|
||||
var items []dto.ForwardRule
|
||||
if pageRules != nil {
|
||||
items = make([]dto.ForwardRule, 0, len(pageRules))
|
||||
}
|
||||
for _, rule := range pageRules {
|
||||
items = append(items, dto.ForwardRule{
|
||||
Num: rule.Num,
|
||||
Protocol: rule.Protocol,
|
||||
Port: rule.Port,
|
||||
TargetIP: rule.TargetIP,
|
||||
TargetPort: rule.TargetPort,
|
||||
Interface: rule.Interface,
|
||||
})
|
||||
}
|
||||
return int64(total), items, nil
|
||||
}
|
||||
|
||||
func (s *ForwardingService) Operate(req dto.ForwardRuleOperate) error {
|
||||
adapter, err := s.adapterFactory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rules, _ := adapter.List()
|
||||
kept := rules[:0]
|
||||
for _, rule := range rules {
|
||||
shouldKeep := true
|
||||
for i := range req.Rules {
|
||||
reqRule := &req.Rules[i]
|
||||
if reqRule.TargetIP == "" {
|
||||
reqRule.TargetIP = "127.0.0.1"
|
||||
}
|
||||
if reqRule.Operation == "remove" && requestMatchesForwardRule(*reqRule, rule) {
|
||||
shouldKeep = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if shouldKeep {
|
||||
kept = append(kept, rule)
|
||||
}
|
||||
}
|
||||
|
||||
for _, rule := range kept {
|
||||
for _, reqRule := range req.Rules {
|
||||
if reqRule.Operation != "remove" && requestMatchesForwardRule(reqRule, rule) {
|
||||
return buserr.New("ErrRecordExist")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(req.Rules, func(i, j int) bool {
|
||||
if req.Rules[i].Operation == "remove" && req.Rules[j].Operation != "remove" {
|
||||
return true
|
||||
}
|
||||
if req.Rules[i].Operation != "remove" && req.Rules[j].Operation == "remove" {
|
||||
return false
|
||||
}
|
||||
n1, _ := strconv.Atoi(req.Rules[i].Num)
|
||||
n2, _ := strconv.Atoi(req.Rules[j].Num)
|
||||
return n1 > n2
|
||||
})
|
||||
|
||||
for _, rule := range req.Rules {
|
||||
for _, protocol := range strings.Split(rule.Protocol, "/") {
|
||||
targetIP := rule.TargetIP
|
||||
if targetIP == "" {
|
||||
targetIP = "127.0.0.1"
|
||||
}
|
||||
err := adapter.Operate(forwardClient.Rule{
|
||||
Num: rule.Num,
|
||||
Protocol: protocol,
|
||||
Port: rule.Port,
|
||||
TargetIP: targetIP,
|
||||
TargetPort: rule.TargetPort,
|
||||
Interface: rule.Interface,
|
||||
}, rule.Operation)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if req.ForceDelete {
|
||||
global.LOG.Error(err)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func requestMatchesForwardRule(req dto.ForwardRuleOperation, rule forwardClient.Rule) bool {
|
||||
for _, protocol := range strings.Split(req.Protocol, "/") {
|
||||
if req.Port == rule.Port && req.TargetPort == rule.TargetPort && req.TargetIP == rule.TargetIP &&
|
||||
protocol == rule.Protocol && req.Interface == rule.Interface {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *ForwardingService) Enable() error {
|
||||
adapter, err := s.adapterFactory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := adapter.Enable(); err != nil {
|
||||
return err
|
||||
}
|
||||
if adapter.Name() != "firewalld" {
|
||||
_ = settingRepo.Update("IptablesForwardStatus", constant.StatusEnable)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ForwardingService) Replay() error {
|
||||
adapter, err := s.adapterFactory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := adapter.Replay(); err != nil {
|
||||
return err
|
||||
}
|
||||
if adapter.Name() == "firewalld" {
|
||||
return nil
|
||||
}
|
||||
status, _ := settingRepo.GetValueByKey("IptablesForwardStatus")
|
||||
if status == constant.StatusEnable {
|
||||
return adapter.Enable()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
forwardClient "github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
type forwardingCall struct {
|
||||
rule forwardClient.Rule
|
||||
operation string
|
||||
}
|
||||
|
||||
type fakeForwardingAdapter struct {
|
||||
name string
|
||||
rules []forwardClient.Rule
|
||||
listErr error
|
||||
operateErr error
|
||||
calls []forwardingCall
|
||||
}
|
||||
|
||||
func (f *fakeForwardingAdapter) Name() string { return f.name }
|
||||
|
||||
func (f *fakeForwardingAdapter) List() ([]forwardClient.Rule, error) {
|
||||
return append([]forwardClient.Rule(nil), f.rules...), f.listErr
|
||||
}
|
||||
|
||||
func (f *fakeForwardingAdapter) Operate(rule forwardClient.Rule, operation string) error {
|
||||
f.calls = append(f.calls, forwardingCall{rule: rule, operation: operation})
|
||||
return f.operateErr
|
||||
}
|
||||
|
||||
func (f *fakeForwardingAdapter) Enable() error { return nil }
|
||||
func (f *fakeForwardingAdapter) InitStatus() (bool, bool) { return true, true }
|
||||
func (f *fakeForwardingAdapter) Replay() error { return nil }
|
||||
|
||||
func forwardingServiceWithAdapter(adapter forwardClient.Adapter) *ForwardingService {
|
||||
return &ForwardingService{
|
||||
adapterFactory: func() (forwardClient.Adapter, error) { return adapter, nil },
|
||||
filterFactory: firewall.NewFirewallClient,
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardingAndFilterInterfacesAreSeparated(t *testing.T) {
|
||||
filterType := reflect.TypeOf((*firewall.FilterClient)(nil)).Elem()
|
||||
for _, method := range []string{"ListForward", "PortForward", "EnableForward"} {
|
||||
if _, ok := filterType.MethodByName(method); ok {
|
||||
t.Fatalf("filter interface still exposes %s", method)
|
||||
}
|
||||
}
|
||||
firewallServiceType := reflect.TypeOf((*IFirewallService)(nil)).Elem()
|
||||
if _, ok := firewallServiceType.MethodByName("OperateForwardRule"); ok {
|
||||
t.Fatal("firewall service still owns forwarding writes")
|
||||
}
|
||||
forwardingServiceType := reflect.TypeOf((*IForwardingService)(nil)).Elem()
|
||||
for _, method := range []string{"LoadBaseInfo", "SearchWithPage", "Operate", "Enable", "Replay"} {
|
||||
if _, ok := forwardingServiceType.MethodByName(method); !ok {
|
||||
t.Fatalf("forwarding service missing %s", method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardingInitRequestContract(t *testing.T) {
|
||||
req := dto.IptablesOp{Name: "1PANEL_FORWARD", Operate: "init-forward"}
|
||||
if err := validator.New().Struct(req); err != nil {
|
||||
t.Fatalf("frontend forwarding initialization request must remain valid: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardingSearchPreservesAPIShapeAndPagination(t *testing.T) {
|
||||
adapter := &fakeForwardingAdapter{name: "iptables", rules: []forwardClient.Rule{
|
||||
{Num: "1", Protocol: "tcp", Port: "8080", TargetIP: "10.0.0.2", TargetPort: "80", Interface: "eth0"},
|
||||
{Num: "2", Protocol: "udp", Port: "5353", TargetIP: "127.0.0.1", TargetPort: "53"},
|
||||
}}
|
||||
service := forwardingServiceWithAdapter(adapter)
|
||||
total, value, err := service.SearchWithPage(dto.ForwardRuleSearch{PageInfo: dto.PageInfo{Page: 1, PageSize: 10}, Info: "10.0.0.2"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if total != 1 {
|
||||
t.Fatalf("got total %d want 1", total)
|
||||
}
|
||||
items, ok := value.([]dto.ForwardRule)
|
||||
if !ok || len(items) != 1 || items[0].Port != "8080" {
|
||||
t.Fatalf("unexpected items: %#v", value)
|
||||
}
|
||||
data, err := json.Marshal(items[0])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var fields map[string]interface{}
|
||||
if err := json.Unmarshal(data, &fields); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantFields := []string{"id", "chain", "family", "address", "port", "protocol", "strategy", "num", "targetIP", "targetPort", "interface", "usedStatus", "description"}
|
||||
for _, field := range wantFields {
|
||||
if _, ok := fields[field]; !ok {
|
||||
t.Fatalf("forward response dropped compatibility field %q: %s", field, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardingOperatePreservesDuplicateAndOrderingContracts(t *testing.T) {
|
||||
existing := &fakeForwardingAdapter{name: "ufw", rules: []forwardClient.Rule{
|
||||
{Protocol: "tcp", Port: "8080", TargetIP: "127.0.0.1", TargetPort: "80"},
|
||||
}}
|
||||
service := forwardingServiceWithAdapter(existing)
|
||||
err := service.Operate(dto.ForwardRuleOperate{Rules: []dto.ForwardRuleOperation{{
|
||||
Operation: "add", Protocol: "tcp", Port: "8080", TargetPort: "80",
|
||||
}}})
|
||||
if err == nil {
|
||||
t.Fatal("duplicate forwarding rule must be rejected")
|
||||
}
|
||||
if len(existing.calls) != 0 {
|
||||
t.Fatalf("duplicate check wrote forwarding state: %#v", existing.calls)
|
||||
}
|
||||
|
||||
adapter := &fakeForwardingAdapter{name: "iptables"}
|
||||
service = forwardingServiceWithAdapter(adapter)
|
||||
err = service.Operate(dto.ForwardRuleOperate{Rules: []dto.ForwardRuleOperation{
|
||||
{Operation: "add", Protocol: "tcp/udp", Port: "9000", TargetIP: "10.0.0.2", TargetPort: "90"},
|
||||
{Operation: "remove", Num: "1", Protocol: "tcp", Port: "8001", TargetIP: "10.0.0.2", TargetPort: "81"},
|
||||
{Operation: "remove", Num: "3", Protocol: "tcp", Port: "8003", TargetIP: "10.0.0.2", TargetPort: "83"},
|
||||
}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []forwardingCall{
|
||||
{operation: "remove", rule: forwardClient.Rule{Num: "3", Protocol: "tcp", Port: "8003", TargetIP: "10.0.0.2", TargetPort: "83"}},
|
||||
{operation: "remove", rule: forwardClient.Rule{Num: "1", Protocol: "tcp", Port: "8001", TargetIP: "10.0.0.2", TargetPort: "81"}},
|
||||
{operation: "add", rule: forwardClient.Rule{Protocol: "tcp", Port: "9000", TargetIP: "10.0.0.2", TargetPort: "90"}},
|
||||
{operation: "add", rule: forwardClient.Rule{Protocol: "udp", Port: "9000", TargetIP: "10.0.0.2", TargetPort: "90"}},
|
||||
}
|
||||
if !reflect.DeepEqual(adapter.calls, want) {
|
||||
t.Fatalf("operation order changed\ngot %#v\nwant %#v", adapter.calls, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardingSearchReturnsAdapterError(t *testing.T) {
|
||||
wantErr := errors.New("list failed")
|
||||
service := forwardingServiceWithAdapter(&fakeForwardingAdapter{name: "firewalld", listErr: wantErr})
|
||||
_, _, err := service.SearchWithPage(dto.ForwardRuleSearch{PageInfo: dto.PageInfo{Page: 1, PageSize: 20}})
|
||||
if !errors.Is(err, wantErr) {
|
||||
t.Fatalf("got %v want %v", err, wantErr)
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ type FtpService struct{}
|
||||
|
||||
type IFtpService interface {
|
||||
LoadBaseInfo() (dto.FtpBaseInfo, error)
|
||||
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
||||
SearchWithPage(search dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error)
|
||||
Operate(operation string) error
|
||||
Create(req dto.FtpCreate) (uint, error)
|
||||
CreateWebsite(req dto.FtpCreate) (uint, error)
|
||||
@@ -74,7 +74,7 @@ func (u *FtpService) Operate(operation string) error {
|
||||
return client.Operate(operation)
|
||||
}
|
||||
|
||||
func (f *FtpService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
func (f *FtpService) SearchWithPage(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error) {
|
||||
total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithLikeUser(req.Info), repo.WithOrderDesc("created_at"))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
@@ -85,7 +85,11 @@ func (f *FtpService) SearchWithPage(req dto.SearchWithPage) (int64, interface{},
|
||||
if err := copier.Copy(&item, &user); err != nil {
|
||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
item.Password, _ = encrypt.StringDecrypt(item.Password)
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Password = ""
|
||||
} else {
|
||||
item.Password, _ = encrypt.StringDecrypt(item.Password)
|
||||
}
|
||||
users = append(users, item)
|
||||
}
|
||||
return total, users, err
|
||||
|
||||
@@ -21,7 +21,7 @@ type IHostService interface {
|
||||
TestByInfo(req dto.HostConnTest) bool
|
||||
GetHostByID(id uint) (*dto.HostInfo, error)
|
||||
SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error)
|
||||
SearchWithPage(search dto.SearchPageWithGroup) (int64, interface{}, error)
|
||||
SearchWithPage(search dto.SearchPageWithGroup, readOnly ...bool) (int64, interface{}, error)
|
||||
Create(req dto.HostOperate) (*dto.HostInfo, error)
|
||||
Update(id uint, upMap map[string]interface{}) (*dto.HostInfo, error)
|
||||
Delete(id []uint) error
|
||||
@@ -111,7 +111,7 @@ func (u *HostService) TestLocalConn(id uint) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *HostService) SearchWithPage(req dto.SearchPageWithGroup) (int64, interface{}, error) {
|
||||
func (u *HostService) SearchWithPage(req dto.SearchPageWithGroup, readOnly ...bool) (int64, interface{}, error) {
|
||||
var options []repo.DBOption
|
||||
if len(req.Info) != 0 {
|
||||
options = append(options, hostRepo.WithByInfo(req.Info))
|
||||
@@ -155,6 +155,11 @@ func (u *HostService) SearchWithPage(req dto.SearchPageWithGroup) (int64, interf
|
||||
}
|
||||
}
|
||||
}
|
||||
if isDemoReadOnly(readOnly...) {
|
||||
item.Password = ""
|
||||
item.PrivateKey = ""
|
||||
item.PassPhrase = ""
|
||||
}
|
||||
dtoHosts = append(dtoHosts, item)
|
||||
}
|
||||
return total, dtoHosts, err
|
||||
|
||||
+71
-23
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
)
|
||||
|
||||
type ImageService struct{}
|
||||
@@ -277,37 +278,38 @@ func (u *ImageService) ImagePull(req dto.ImagePull) error {
|
||||
itemName := strings.ReplaceAll(path.Base(item), ":", "_")
|
||||
taskItem.AddSubTask(i18n.GetWithName("ImagePull", itemName), func(t *task.Task) error {
|
||||
taskItem.Logf("----------------- %s -----------------", itemName)
|
||||
if req.RepoID == 0 {
|
||||
pullErr := pullImages(taskItem, client, item)
|
||||
taskItem.LogWithStatus(i18n.GetMsgByKey("TaskPull"), pullErr)
|
||||
return pullErr
|
||||
}
|
||||
|
||||
options := image.PullOptions{}
|
||||
imageName := item
|
||||
repo, repoErr := imageRepoRepo.Get(repo.WithByID(req.RepoID))
|
||||
taskItem.LogWithStatus(i18n.GetMsgByKey("ImageRepoAuthFromDB"), repoErr)
|
||||
if repoErr != nil {
|
||||
return repoErr
|
||||
}
|
||||
if repo.Auth {
|
||||
authConfig := registry.AuthConfig{
|
||||
Username: repo.Username,
|
||||
Password: repo.Password,
|
||||
if req.RepoID == 0 {
|
||||
hasAuth, authStr := loadAuthInfo(item)
|
||||
if hasAuth {
|
||||
options.RegistryAuth = authStr
|
||||
}
|
||||
encodedJSON, err := json.Marshal(authConfig)
|
||||
} else {
|
||||
repo, err := imageRepoRepo.Get(repo.WithByID(req.RepoID))
|
||||
taskItem.LogWithStatus(i18n.GetMsgByKey("ImageRepoAuthFromDB"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
|
||||
options.RegistryAuth = authStr
|
||||
if 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
|
||||
}
|
||||
imageName = repo.DownloadUrl + "/" + item
|
||||
}
|
||||
imageName = repo.DownloadUrl + "/" + item
|
||||
dockerCli := docker.NewClientWithExist(client)
|
||||
pullErr := dockerCli.PullImageWithProcessAndOptions(taskItem, imageName, options)
|
||||
taskItem.LogWithStatus(i18n.GetMsgByKey("TaskPull"), pullErr)
|
||||
if pullErr != nil {
|
||||
return pullErr
|
||||
err = dockerCli.PullImageWithProcessAndOptions(taskItem, imageName, options)
|
||||
taskItem.LogWithStatus(i18n.GetMsgByKey("TaskPull"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
@@ -545,3 +547,49 @@ func checkUsed(imageID string, containers []container.Summary) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func loadAuthInfo(image string) (bool, string) {
|
||||
if !strings.Contains(image, "/") {
|
||||
return false, ""
|
||||
}
|
||||
homeDir := homedir.Get()
|
||||
confPath := path.Join(homeDir, ".docker/config.json")
|
||||
configFileBytes, err := os.ReadFile(confPath)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
var config dockerConfig
|
||||
if err = json.Unmarshal(configFileBytes, &config); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
var (
|
||||
user string
|
||||
passwd string
|
||||
)
|
||||
imagePrefix := strings.Split(image, "/")[0]
|
||||
if val, ok := config.Auths[imagePrefix]; ok {
|
||||
itemByte, _ := base64.StdEncoding.DecodeString(val.Auth)
|
||||
itemStr := string(itemByte)
|
||||
if strings.Contains(itemStr, ":") {
|
||||
user = strings.Split(itemStr, ":")[0]
|
||||
passwd = strings.Split(itemStr, ":")[1]
|
||||
}
|
||||
}
|
||||
authConfig := registry.AuthConfig{
|
||||
Username: user,
|
||||
Password: passwd,
|
||||
}
|
||||
encodedJSON, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
|
||||
return true, authStr
|
||||
}
|
||||
|
||||
type dockerConfig struct {
|
||||
Auths map[string]authConfig `json:"auths"`
|
||||
}
|
||||
type authConfig struct {
|
||||
Auth string `json:"auth"`
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user