mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-10 08:00:32 +00:00
Compare commits
54
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
200ddbfa28 | ||
|
|
f34418da6d | ||
|
|
7ff86c0ee3 | ||
|
|
5a58f44548 | ||
|
|
d27d6db1ea | ||
|
|
c75cb5d7ce | ||
|
|
2207147e7f | ||
|
|
d2bb3813d9 | ||
|
|
cac73e6d45 | ||
|
|
b5604eab11 | ||
|
|
b498882708 | ||
|
|
4712ceaf2c | ||
|
|
65243c68c4 | ||
|
|
381d71663c | ||
|
|
f7f4135193 | ||
|
|
256e79ca81 | ||
|
|
dbf6d9e5fe | ||
|
|
4861eb69cb | ||
|
|
fb8cf15537 | ||
|
|
c4a6791271 | ||
|
|
f58e147636 | ||
|
|
387e9fbeed | ||
|
|
4eb627bc79 | ||
|
|
850c86229c | ||
|
|
f984917a66 | ||
|
|
8588217fbf | ||
|
|
a2307c5f64 | ||
|
|
5923290de8 | ||
|
|
1c994fba4a | ||
|
|
415ab96aab | ||
|
|
19bb823b05 | ||
|
|
3a5371652e | ||
|
|
a267b4148a | ||
|
|
36a01eb60d | ||
|
|
65f6fdd045 | ||
|
|
75b60b32e4 | ||
|
|
6cb65e2290 | ||
|
|
0bad1b471f | ||
|
|
3814525edd | ||
|
|
8162dd1856 | ||
|
|
e833787020 | ||
|
|
673ffac516 | ||
|
|
e864610015 | ||
|
|
78402e1b7d | ||
|
|
782bc1e67c | ||
|
|
86e4ed6f64 | ||
|
|
ee8bac39af | ||
|
|
fe742b9f41 | ||
|
|
9a5bd9bcba | ||
|
|
b9c8e39560 | ||
|
|
6b20ff0b13 | ||
|
|
89bd32b6d4 | ||
|
|
005f240fb7 | ||
|
|
75da53e374 |
@@ -28,7 +28,7 @@ func (b *BaseApi) SearchContainer(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := containerService.Page(req)
|
total, list, err := containerService.Page(c.Request.Context(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -249,9 +249,10 @@ func (b *BaseApi) ListContainerByImage(c *gin.Context) {
|
|||||||
// @Success 200 {object} dto.ContainerStatus
|
// @Success 200 {object} dto.ContainerStatus
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
|
// @Param containersOnly query boolean false "Only count containers"
|
||||||
// @Router /containers/status [get]
|
// @Router /containers/status [get]
|
||||||
func (b *BaseApi) LoadContainerStatus(c *gin.Context) {
|
func (b *BaseApi) LoadContainerStatus(c *gin.Context) {
|
||||||
data, err := containerService.LoadStatus()
|
data, err := containerService.LoadStatus(c.Request.Context(), c.Query("containersOnly") == "true")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -415,9 +416,14 @@ func (b *BaseApi) LoadResourceLimit(c *gin.Context) {
|
|||||||
// @Success 200 {array} dto.ContainerListStats
|
// @Success 200 {array} dto.ContainerListStats
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
|
// @Param ids query string false "Comma-separated container IDs; omitted selects all containers"
|
||||||
// @Router /containers/list/stats [get]
|
// @Router /containers/list/stats [get]
|
||||||
func (b *BaseApi) ContainerListStats(c *gin.Context) {
|
func (b *BaseApi) ContainerListStats(c *gin.Context) {
|
||||||
data, err := containerService.ContainerListStats()
|
var ids []string
|
||||||
|
if _, supplied := c.Request.URL.Query()["ids"]; supplied {
|
||||||
|
ids = strings.FieldsFunc(c.Query("ids"), func(r rune) bool { return r == ',' })
|
||||||
|
}
|
||||||
|
data, err := containerService.ContainerListStats(c.Request.Context(), ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -964,3 +970,12 @@ func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
|
|||||||
|
|
||||||
containerService.StreamLogs(c, streamLog)
|
containerService.StreamLogs(c, streamLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) CleanNetworks(c *gin.Context) {
|
||||||
|
result, err := containerService.CleanNetworks()
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, result)
|
||||||
|
}
|
||||||
|
|||||||
@@ -86,17 +86,38 @@ func (b *BaseApi) CheckHasCli(c *gin.Context) {
|
|||||||
|
|
||||||
// @Tags Database Redis
|
// @Tags Database Redis
|
||||||
// @Summary Install redis-cli
|
// @Summary Install redis-cli
|
||||||
// @Success 200
|
// @Accept json
|
||||||
|
// @Param request body dto.RedisCliInstall true "request"
|
||||||
|
// @Success 200 {object} dto.RedisCliStatus
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /databases/redis/install/cli [post]
|
// @Router /databases/redis/install/cli [post]
|
||||||
func (b *BaseApi) InstallCli(c *gin.Context) {
|
func (b *BaseApi) InstallCli(c *gin.Context) {
|
||||||
if err := redisService.InstallCli(); err != nil {
|
var req dto.RedisCliInstall
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := redisService.InstallCli(req)
|
||||||
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
helper.Success(c)
|
// @Tags Database Redis
|
||||||
|
// @Summary Load redis-cli installation status
|
||||||
|
// @Success 200 {object} dto.RedisCliStatus
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /databases/redis/cli/status [get]
|
||||||
|
func (b *BaseApi) LoadRedisCliStatus(c *gin.Context) {
|
||||||
|
data, err := redisService.LoadCliStatus()
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Database Redis
|
// @Tags Database Redis
|
||||||
|
|||||||
@@ -2,14 +2,16 @@ package v2
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/service"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"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/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -60,7 +62,7 @@ func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
|
|||||||
// @Summary Operate firewall
|
// @Summary Operate firewall
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.FirewallLifecycleOperation true "request"
|
// @Param request body dto.FirewallLifecycleOperation true "request"
|
||||||
// @Success 200
|
// @Success 200 {object} dto.FirewallLifecycleOperationResponse
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/operate [post]
|
// @Router /hosts/firewall/operate [post]
|
||||||
@@ -71,12 +73,13 @@ func (b *BaseApi) OperateFirewall(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := firewallService.OperateFirewall(request); err != nil {
|
result, err := firewallService.QueueFirewallOperation(request)
|
||||||
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.Success(c)
|
helper.SuccessWithData(c, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
@@ -367,7 +370,8 @@ func (b *BaseApi) SyncFirewallRules(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Delete managed unified firewall v2 rules
|
// @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
|
// @Accept json
|
||||||
// @Param request body dto.FirewallRuleDelete true "request"
|
// @Param request body dto.FirewallRuleDelete true "request"
|
||||||
// @Success 200 {object} dto.FirewallRuleDeleteResponse
|
// @Success 200 {object} dto.FirewallRuleDeleteResponse
|
||||||
@@ -453,6 +457,8 @@ func normalizeFirewallRuleUUID(c *gin.Context, value *string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleFirewallRuleError(c *gin.Context, err error) {
|
func handleFirewallRuleError(c *gin.Context, err error) {
|
||||||
|
var businessErr buserr.BusinessError
|
||||||
|
isBusinessError := errors.As(err, &businessErr)
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, filter.ErrProtectedRule):
|
case errors.Is(err, filter.ErrProtectedRule):
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_LOCKOUT_RISK", "ErrInvalidParams", err)
|
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_LOCKOUT_RISK", "ErrInvalidParams", err)
|
||||||
@@ -460,7 +466,7 @@ func handleFirewallRuleError(c *gin.Context, err error) {
|
|||||||
helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_STALE", "ErrInvalidParams", err)
|
helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_STALE", "ErrInvalidParams", err)
|
||||||
case errors.Is(err, repo.ErrFirewallRuleRevisionConflict):
|
case errors.Is(err, repo.ErrFirewallRuleRevisionConflict):
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_REVISION_CONFLICT", "ErrInvalidParams", err)
|
helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_REVISION_CONFLICT", "ErrInvalidParams", err)
|
||||||
case errors.Is(err, filter.ErrManagedScopeChange):
|
case isBusinessError && businessErr.Msg == "ErrFirewallRuleScopeChange":
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_SCOPE_UNSUPPORTED", "ErrFirewallRuleScopeChange", err)
|
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_SCOPE_UNSUPPORTED", "ErrFirewallRuleScopeChange", err)
|
||||||
case errors.Is(err, filter.ErrUnsupportedScope), errors.Is(err, filter.ErrInvalidScope),
|
case errors.Is(err, filter.ErrUnsupportedScope), errors.Is(err, filter.ErrInvalidScope),
|
||||||
errors.Is(err, filter.ErrProviderUnavailable), errors.Is(err, filter.ErrAdapterUnavailable):
|
errors.Is(err, filter.ErrProviderUnavailable), errors.Is(err, filter.ErrAdapterUnavailable):
|
||||||
@@ -468,6 +474,9 @@ func handleFirewallRuleError(c *gin.Context, err error) {
|
|||||||
case errors.Is(err, filter.ErrInvalidRule), errors.Is(err, filter.ErrRuleOperation), errors.Is(err, filter.ErrRuleConflict),
|
case errors.Is(err, filter.ErrInvalidRule), errors.Is(err, filter.ErrRuleOperation), errors.Is(err, filter.ErrRuleConflict),
|
||||||
errors.Is(err, repo.ErrFirewallPersistenceInvalid):
|
errors.Is(err, repo.ErrFirewallPersistenceInvalid):
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_RULE_UNSUPPORTED", "ErrInvalidParams", err)
|
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):
|
case errors.Is(err, filter.ErrVerificationFailed):
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_VERIFY_FAILED", "ErrInternalServer", err)
|
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_VERIFY_FAILED", "ErrInternalServer", err)
|
||||||
default:
|
default:
|
||||||
@@ -491,26 +500,69 @@ func (b *BaseApi) LoadFirewallSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Queue firewall port whitelist update
|
// @Summary Create firewall port whitelist rules
|
||||||
// @Description Returns a taskID; configuration save and per-rule results are recorded in the task log.
|
// @Description Saves whitelist configuration only. Missing rules are added on startup, restart, initialization, or synchronization; existing rules are not removed.
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.FirewallPortWhitelistUpdate true "request"
|
// @Param request body dto.FirewallPortWhitelistCreate true "request"
|
||||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/settings/whitelist [post]
|
// @Router /hosts/firewall/settings/whitelist [post]
|
||||||
// @x-panel-log {"bodyKeys":["value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新防火墙端口白名单 [value]","formatEN":"update firewall port whitelist [value]"}
|
// @x-panel-log {"bodyKeys":["rule"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建防火墙端口白名单","formatEN":"create firewall port whitelist"}
|
||||||
|
func (b *BaseApi) CreateFirewallPortWhitelist(c *gin.Context) {
|
||||||
|
var request dto.FirewallPortWhitelistCreate
|
||||||
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := firewallSettingService.CreatePortWhitelist(c.Request.Context(), request); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @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) {
|
func (b *BaseApi) UpdateFirewallPortWhitelist(c *gin.Context) {
|
||||||
var request dto.FirewallPortWhitelistUpdate
|
var request dto.FirewallPortWhitelistUpdate
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result, err := firewallSettingService.QueuePortWhitelist(request.Value)
|
if err := firewallSettingService.UpdatePortWhitelist(c.Request.Context(), request); err != nil {
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
helper.SuccessWithData(c, result)
|
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
|
// @Tags Firewall
|
||||||
@@ -528,14 +580,10 @@ func (b *BaseApi) OperateFirewallBackend(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := firewallSettingService.Operate(c.Request.Context(), request); err != nil {
|
if err := firewallSettingService.Operate(c.Request.Context(), request); err != nil {
|
||||||
if errors.Is(err, service.ErrFirewallBackendCleanupRequired) {
|
var businessErr buserr.BusinessError
|
||||||
helper.ErrorWithBusinessCode(
|
if errors.As(err, &businessErr) && businessErr.Msg == "ErrFirewallBackendCleanupRequired" {
|
||||||
c,
|
c.JSON(http.StatusOK, dto.Response{Code: http.StatusConflict, ErrorCode: "FW_BACKEND_CLEANUP_REQUIRED", Message: err.Error()})
|
||||||
http.StatusConflict,
|
c.Abort()
|
||||||
"FW_BACKEND_CLEANUP_REQUIRED",
|
|
||||||
"ErrInvalidParams",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
@@ -664,19 +712,26 @@ func (b *BaseApi) UpsertDockerPortGuardPolicies(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleDockerPortGuardError(c *gin.Context, err error) {
|
func handleDockerPortGuardError(c *gin.Context, err error) {
|
||||||
if errors.Is(err, service.ErrDockerIptablesChainUnavailable) {
|
var businessErr buserr.BusinessError
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_IPTABLES_CHAIN_UNAVAILABLE", "ErrDockerIptablesChainUnavailable", err)
|
if errors.As(err, &businessErr) {
|
||||||
return
|
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, service.ErrDockerNftablesChainUnavailable) {
|
if errors.Is(err, docker.ErrUnavailable) {
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_NFTABLES_CHAIN_UNAVAILABLE", "ErrDockerNftablesChainUnavailable", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if errors.Is(err, service.ErrDockerGuardInvalid) {
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_DOCKER_GUARD_INVALID", "ErrInvalidParams", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if errors.Is(err, service.ErrDockerUnavailable) {
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_UNAVAILABLE", "ErrDockerFailed", err)
|
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_UNAVAILABLE", "ErrDockerFailed", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ func (b *BaseApi) GetNodePackageRunScript(c *gin.Context) {
|
|||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /runtimes/operate [post]
|
// @Router /runtimes/operate [post]
|
||||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"操作运行环境 [id]","formatEN":"Operate runtime [id]"}
|
// @x-panel-log {"bodyKeys":["ID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ID","isList":false,"db":"runtimes","output_column":"name","output_value":"name"}],"formatZH":"操作运行环境 [name]","formatEN":"Operate runtime [name]"}
|
||||||
func (b *BaseApi) OperateRuntime(c *gin.Context) {
|
func (b *BaseApi) OperateRuntime(c *gin.Context) {
|
||||||
var req request.RuntimeOperate
|
var req request.RuntimeOperate
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
// @Summary Ws local terminal
|
// @Summary Ws local terminal
|
||||||
// @Param command query string false "command"
|
// @Param command query string false "command"
|
||||||
// @Param session query string false "session id to reattach"
|
// @Param session query string false "session id to reattach"
|
||||||
|
// @Param terminalPersistent query boolean false "allow recovery after an unexpected disconnect"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
@@ -43,6 +44,7 @@ func (b *BaseApi) WsLocalTerminal(c *gin.Context) {
|
|||||||
// @Param command query string false "command"
|
// @Param command query string false "command"
|
||||||
// @Param session query string false "session id to reattach"
|
// @Param session query string false "session id to reattach"
|
||||||
// @Param title query string false "session title shown in the session list"
|
// @Param title query string false "session title shown in the session list"
|
||||||
|
// @Param terminalPersistent query boolean false "allow recovery after an unexpected disconnect"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
@@ -146,13 +148,14 @@ func (b *BaseApi) runSSHSession(c *gin.Context, kind string, connect func() (*ss
|
|||||||
hostID, _ = strconv.Atoi(c.DefaultQuery("id", "0"))
|
hostID, _ = strconv.Atoi(c.DefaultQuery("id", "0"))
|
||||||
}
|
}
|
||||||
opts := terminal.SessionOptions{
|
opts := terminal.SessionOptions{
|
||||||
Identity: identity,
|
Identity: identity,
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Title: sanitizeTerminalTitle(c.Query("title")),
|
Title: sanitizeTerminalTitle(c.Query("title")),
|
||||||
HostID: uint(max(hostID, 0)),
|
Persistent: c.Query("terminalPersistent") == "true",
|
||||||
Cols: cols,
|
HostID: uint(max(hostID, 0)),
|
||||||
Rows: rows,
|
Cols: cols,
|
||||||
InitCmd: command,
|
Rows: rows,
|
||||||
|
InitCmd: command,
|
||||||
}
|
}
|
||||||
err := terminal.Serve(wsConn, strings.TrimSpace(c.Query("session")), opts, func() (*gossh.Client, error) {
|
err := terminal.Serve(wsConn, strings.TrimSpace(c.Query("session")), opts, func() (*gossh.Client, error) {
|
||||||
client, err := connect()
|
client, err := connect()
|
||||||
@@ -240,6 +243,16 @@ func loadTerminalIdentity(c *gin.Context) (terminal.Identity, bool) {
|
|||||||
UserID: strings.TrimSpace(c.GetHeader(terminal.HeaderUserID)),
|
UserID: strings.TrimSpace(c.GetHeader(terminal.HeaderUserID)),
|
||||||
AuthSessionID: strings.TrimSpace(c.GetHeader(terminal.HeaderAuthSessionID)),
|
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()
|
return identity, identity.Valid()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *BaseApi) TerminalCapabilities(c *gin.Context) {
|
||||||
|
helper.SuccessWithData(c, gin.H{"apiKeyLeaseVersion": 1})
|
||||||
|
}
|
||||||
+16
-7
@@ -162,16 +162,25 @@ type AgentWebsiteBindReq struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AgentModelConfigUpdateReq struct {
|
type AgentModelConfigUpdateReq struct {
|
||||||
AgentID uint `json:"agentId" validate:"required"`
|
AgentID uint `json:"agentId" validate:"required"`
|
||||||
AccountID uint `json:"accountId" validate:"required"`
|
AccountID uint `json:"accountId" validate:"required"`
|
||||||
Model string `json:"model" validate:"required"`
|
Model string `json:"model" validate:"required"`
|
||||||
Fallbacks []string `json:"fallbacks"`
|
Fallbacks []string `json:"fallbacks"`
|
||||||
|
Metadata []AgentModelMetadata `json:"metadata" validate:"dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentModelConfig struct {
|
type AgentModelConfig struct {
|
||||||
AccountID uint `json:"accountId"`
|
AccountID uint `json:"accountId"`
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Fallbacks []string `json:"fallbacks"`
|
Fallbacks []string `json:"fallbacks"`
|
||||||
|
Metadata []AgentModelMetadata `json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AgentModelMetadata struct {
|
||||||
|
Model string `json:"model" validate:"required"`
|
||||||
|
InputMode string `json:"inputMode" validate:"required,oneof=auto text image"`
|
||||||
|
ContextWindow int `json:"contextWindow" validate:"min=0"`
|
||||||
|
MaxTokens int `json:"maxTokens" validate:"min=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentHermesChatSessionItem struct {
|
type AgentHermesChatSessionItem struct {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ type AlertBase struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PushAlert struct {
|
type PushAlert struct {
|
||||||
|
Result string `json:"result,omitempty"`
|
||||||
TaskName string `json:"taskName"`
|
TaskName string `json:"taskName"`
|
||||||
AlertType string `json:"alertType"`
|
AlertType string `json:"alertType"`
|
||||||
EntryID uint `json:"entryID"`
|
EntryID uint `json:"entryID"`
|
||||||
@@ -53,6 +54,7 @@ type AlertDTO struct {
|
|||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Project string `json:"project"`
|
Project string `json:"project"`
|
||||||
|
TaskName string `json:"taskName,omitempty"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
SendCount uint `json:"sendCount"`
|
SendCount uint `json:"sendCount"`
|
||||||
AdvancedParams string `json:"advancedParams"`
|
AdvancedParams string `json:"advancedParams"`
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
type NetworkCleanupReport struct {
|
||||||
|
Deleted []NetworkCleanupItem `json:"deleted"`
|
||||||
|
Skipped []NetworkCleanupItem `json:"skipped"`
|
||||||
|
Failed []NetworkCleanupItem `json:"failed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkCleanupItem struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkCleanupTask struct {
|
||||||
|
TaskID string `json:"taskID"`
|
||||||
|
}
|
||||||
@@ -51,9 +51,10 @@ type CronjobOperate struct {
|
|||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
Args string `json:"args"`
|
Args string `json:"args"`
|
||||||
|
|
||||||
AlertCount uint `json:"alertCount"`
|
AlertCount uint `json:"alertCount"`
|
||||||
AlertTitle string `json:"alertTitle"`
|
AlertTitle string `json:"alertTitle"`
|
||||||
AlertMethod string `json:"alertMethod"`
|
AlertMethod string `json:"alertMethod"`
|
||||||
|
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
|
||||||
|
|
||||||
CleanLogConfig
|
CleanLogConfig
|
||||||
}
|
}
|
||||||
@@ -126,7 +127,8 @@ type CronjobInfo struct {
|
|||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
Args string `json:"args"`
|
Args string `json:"args"`
|
||||||
|
|
||||||
AlertCount uint `json:"alertCount"`
|
AlertCount uint `json:"alertCount"`
|
||||||
|
AlertTriggerMode string `json:"alertTriggerMode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CronjobImport struct {
|
type CronjobImport struct {
|
||||||
@@ -169,9 +171,10 @@ type CronjobTrans struct {
|
|||||||
SourceAccounts []string `json:"sourceAccounts"`
|
SourceAccounts []string `json:"sourceAccounts"`
|
||||||
DownloadAccount string `json:"downloadAccount"`
|
DownloadAccount string `json:"downloadAccount"`
|
||||||
|
|
||||||
AlertCount uint `json:"alertCount"`
|
AlertCount uint `json:"alertCount"`
|
||||||
AlertTitle string `json:"alertTitle"`
|
AlertTitle string `json:"alertTitle"`
|
||||||
AlertMethod string `json:"alertMethod"`
|
AlertMethod string `json:"alertMethod"`
|
||||||
|
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
|
||||||
}
|
}
|
||||||
type TransHelper struct {
|
type TransHelper struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|||||||
@@ -22,6 +22,17 @@ type DBBaseInfo struct {
|
|||||||
Port int64 `json:"port"`
|
Port int64 `json:"port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RedisCliInstall struct {
|
||||||
|
TaskID string `json:"taskID" validate:"omitempty,uuid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedisCliStatus struct {
|
||||||
|
Installed bool `json:"installed"`
|
||||||
|
TaskID string `json:"taskID"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
ErrorMsg string `json:"errorMsg"`
|
||||||
|
}
|
||||||
|
|
||||||
// mysql
|
// mysql
|
||||||
type MysqlDBSearch struct {
|
type MysqlDBSearch struct {
|
||||||
PageInfo
|
PageInfo
|
||||||
|
|||||||
+45
-18
@@ -1,6 +1,7 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"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/filter"
|
||||||
firewallsync "github.com/1Panel-dev/1Panel/agent/utils/firewall/sync"
|
firewallsync "github.com/1Panel-dev/1Panel/agent/utils/firewall/sync"
|
||||||
)
|
)
|
||||||
@@ -18,6 +19,7 @@ type FirewallSubsystemStatus struct {
|
|||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
Reason string `json:"reason,omitempty"`
|
Reason string `json:"reason,omitempty"`
|
||||||
SyncError string `json:"syncError,omitempty"`
|
SyncError string `json:"syncError,omitempty"`
|
||||||
|
LifecycleTaskID string `json:"lifecycleTaskID,omitempty"`
|
||||||
IPv4 FirewallBackendFamilyStatus `json:"ipv4"`
|
IPv4 FirewallBackendFamilyStatus `json:"ipv4"`
|
||||||
IPv6 FirewallBackendFamilyStatus `json:"ipv6"`
|
IPv6 FirewallBackendFamilyStatus `json:"ipv6"`
|
||||||
}
|
}
|
||||||
@@ -27,6 +29,11 @@ type FirewallLifecycleOperation struct {
|
|||||||
WithDockerRestart bool `json:"withDockerRestart"`
|
WithDockerRestart bool `json:"withDockerRestart"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FirewallLifecycleOperationResponse struct {
|
||||||
|
TaskID string `json:"taskID,omitempty"`
|
||||||
|
Queued bool `json:"queued"`
|
||||||
|
}
|
||||||
|
|
||||||
type FirewallBackendOption struct {
|
type FirewallBackendOption struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Installed bool `json:"installed"`
|
Installed bool `json:"installed"`
|
||||||
@@ -42,10 +49,12 @@ type FirewallBackendOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FirewallBackendFamilyStatus struct {
|
type FirewallBackendFamilyStatus struct {
|
||||||
Available bool `json:"available"`
|
Available bool `json:"available"`
|
||||||
Initialized bool `json:"initialized"`
|
Initialized bool `json:"initialized"`
|
||||||
Bound bool `json:"bound"`
|
Bound bool `json:"bound"`
|
||||||
Reason string `json:"reason,omitempty"`
|
Reason string `json:"reason,omitempty"`
|
||||||
|
ForwardPolicy string `json:"forwardPolicy,omitempty"`
|
||||||
|
RAInterfaces []string `json:"raInterfaces,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallBackendGroup struct {
|
type FirewallBackendGroup struct {
|
||||||
@@ -55,15 +64,26 @@ type FirewallBackendGroup struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FirewallSettings struct {
|
type FirewallSettings struct {
|
||||||
System FirewallBackendGroup `json:"system"`
|
System FirewallBackendGroup `json:"system"`
|
||||||
Forwarding FirewallBackendGroup `json:"forwarding"`
|
Forwarding FirewallBackendGroup `json:"forwarding"`
|
||||||
Docker FirewallBackendGroup `json:"docker"`
|
Docker FirewallBackendGroup `json:"docker"`
|
||||||
PingStatus string `json:"pingStatus"`
|
PingStatus string `json:"pingStatus"`
|
||||||
PortWhitelist string `json:"portWhiteList"`
|
PortWhitelist []filter.PortWhitelist `json:"portWhiteList"`
|
||||||
|
PanelPort string `json:"panelPort"`
|
||||||
|
SSHPort string `json:"sshPort"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallPortWhitelistCreate struct {
|
||||||
|
Rule filter.PortWhitelist `json:"rule" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallPortWhitelistUpdate struct {
|
type FirewallPortWhitelistUpdate struct {
|
||||||
Value string `json:"value" validate:"required"`
|
OldRule filter.PortWhitelist `json:"oldRule" validate:"required"`
|
||||||
|
Rule filter.PortWhitelist `json:"rule" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallPortWhitelistDelete struct {
|
||||||
|
Rule *filter.PortWhitelist `json:"rule" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallBackendOperation struct {
|
type FirewallBackendOperation struct {
|
||||||
@@ -87,11 +107,7 @@ type FirewallInitializationTask struct {
|
|||||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallSystemPort struct {
|
type FirewallSystemPort = firewall.SystemPort
|
||||||
Family string
|
|
||||||
Port string
|
|
||||||
Protocol string
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleInventoryResponse struct {
|
type FirewallRuleInventoryResponse struct {
|
||||||
IPv4Range filter.PositionRange `json:"ipv4Range"`
|
IPv4Range filter.PositionRange `json:"ipv4Range"`
|
||||||
@@ -114,6 +130,7 @@ type FirewallRuleReset struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FirewallRuleInventory struct {
|
type FirewallRuleInventory struct {
|
||||||
|
Refresh bool `json:"refresh,omitempty"`
|
||||||
PageInfo
|
PageInfo
|
||||||
Scope filter.Scope `json:"scope,omitempty"`
|
Scope filter.Scope `json:"scope,omitempty"`
|
||||||
Scopes []filter.Scope `json:"scopes,omitempty" validate:"max=16"`
|
Scopes []filter.Scope `json:"scopes,omitempty" validate:"max=16"`
|
||||||
@@ -225,8 +242,10 @@ type DockerPortGuardOperation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FirewallRuleAdopt struct {
|
type FirewallRuleAdopt struct {
|
||||||
Scope filter.Scope `json:"scope" validate:"required"`
|
Scope filter.Scope `json:"scope" validate:"required"`
|
||||||
InstanceKey string `json:"instanceKey" validate:"required,max=128"`
|
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 {
|
type FirewallRuleCreateItem struct {
|
||||||
@@ -313,10 +332,18 @@ type FirewallRuleSyncFailure struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FirewallRuleDelete struct {
|
type FirewallRuleDelete struct {
|
||||||
UUIDs []string `json:"uuids" validate:"required,min=1,dive,required,max=64"`
|
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 {
|
type FirewallRuleDeleteResponse struct {
|
||||||
|
TaskID string `json:"taskID,omitempty"`
|
||||||
|
Queued bool `json:"queued,omitempty"`
|
||||||
Succeeded int `json:"succeeded"`
|
Succeeded int `json:"succeeded"`
|
||||||
Failed int `json:"failed"`
|
Failed int `json:"failed"`
|
||||||
Errors []FirewallRuleDeleteFailure `json:"errors,omitempty"`
|
Errors []FirewallRuleDeleteFailure `json:"errors,omitempty"`
|
||||||
|
|||||||
+44
-11
@@ -44,6 +44,8 @@ type MonitorGPUOptions struct {
|
|||||||
Options []string `json:"options"`
|
Options []string `json:"options"`
|
||||||
}
|
}
|
||||||
type GPUChartHide struct {
|
type GPUChartHide struct {
|
||||||
|
DeviceID string `json:"deviceID"`
|
||||||
|
Legacy bool `json:"legacy"`
|
||||||
ProductName string `json:"productName"`
|
ProductName string `json:"productName"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Process bool `json:"process"`
|
Process bool `json:"process"`
|
||||||
@@ -55,23 +57,54 @@ type GPUChartHide struct {
|
|||||||
Speed bool `json:"speed"`
|
Speed bool `json:"speed"`
|
||||||
}
|
}
|
||||||
type MonitorGPUSearch struct {
|
type MonitorGPUSearch struct {
|
||||||
|
Aggregation string `json:"aggregation" validate:"omitempty,oneof=avg max"`
|
||||||
|
DeviceID string `json:"deviceID"`
|
||||||
|
Legacy bool `json:"legacy"`
|
||||||
ProductName string `json:"productName"`
|
ProductName string `json:"productName"`
|
||||||
StartTime time.Time `json:"startTime"`
|
StartTime time.Time `json:"startTime"`
|
||||||
EndTime time.Time `json:"endTime"`
|
EndTime time.Time `json:"endTime"`
|
||||||
}
|
}
|
||||||
type MonitorGPUData struct {
|
type MonitorGPUData struct {
|
||||||
Date []time.Time `json:"date"`
|
MemoryActivity []*float64 `json:"memoryActivity"`
|
||||||
GPUValue []float64 `json:"gpuValue"`
|
EncoderUtil []*float64 `json:"encoderUtil"`
|
||||||
TemperatureValue []float64 `json:"temperatureValue"`
|
DecoderUtil []*float64 `json:"decoderUtil"`
|
||||||
PowerTotal []float64 `json:"powerTotal"`
|
JPEGUtil []*float64 `json:"jpegUtil"`
|
||||||
PowerUsed []float64 `json:"powerUsed"`
|
OFAUtil []*float64 `json:"ofaUtil"`
|
||||||
PowerPercent []float64 `json:"powerPercent"`
|
MediaUtil []*float64 `json:"mediaUtil"`
|
||||||
MemoryTotal []float64 `json:"memoryTotal"`
|
ComputeUtil []*float64 `json:"computeUtil"`
|
||||||
MemoryUsed []float64 `json:"memoryUsed"`
|
CopyUtil []*float64 `json:"copyUtil"`
|
||||||
MemoryPercent []float64 `json:"memoryPercent"`
|
HotspotTemperature []*float64 `json:"hotspotTemperature"`
|
||||||
SpeedValue []int `json:"speedValue"`
|
FanRPM []*float64 `json:"fanRPM"`
|
||||||
|
AICPUUtil []*float64 `json:"aiCPUUtil"`
|
||||||
|
CtrlCPUUtil []*float64 `json:"ctrlCPUUtil"`
|
||||||
|
DDRUsed []*float64 `json:"ddrUsed"`
|
||||||
|
DDRTotal []*float64 `json:"ddrTotal"`
|
||||||
|
HBMUsed []*float64 `json:"hbmUsed"`
|
||||||
|
HBMTotal []*float64 `json:"hbmTotal"`
|
||||||
|
DDRBandwidth []*float64 `json:"ddrBandwidth"`
|
||||||
|
HBMBandwidth []*float64 `json:"hbmBandwidth"`
|
||||||
|
MemoryBandwidth []*float64 `json:"memoryBandwidth"`
|
||||||
|
MediaFrequency []*float64 `json:"mediaFrequency"`
|
||||||
|
HugepagesUsed []*float64 `json:"hugepagesUsed"`
|
||||||
|
HugepagesTotal []*float64 `json:"hugepagesTotal"`
|
||||||
|
|
||||||
ProcessCount []int `json:"processCount"`
|
BucketSeconds int64 `json:"bucketSeconds"`
|
||||||
|
SampleCount int64 `json:"sampleCount"`
|
||||||
|
MemoryTemperatureValue []*float64 `json:"memoryTemperatureValue"`
|
||||||
|
FrequencyValue []*float64 `json:"frequencyValue"`
|
||||||
|
MemoryFrequencyValue []*float64 `json:"memoryFrequencyValue"`
|
||||||
|
Date []time.Time `json:"date"`
|
||||||
|
GPUValue []*float64 `json:"gpuValue"`
|
||||||
|
TemperatureValue []*float64 `json:"temperatureValue"`
|
||||||
|
PowerTotal []*float64 `json:"powerTotal"`
|
||||||
|
PowerUsed []*float64 `json:"powerUsed"`
|
||||||
|
PowerPercent []*float64 `json:"powerPercent"`
|
||||||
|
MemoryTotal []*float64 `json:"memoryTotal"`
|
||||||
|
MemoryUsed []*float64 `json:"memoryUsed"`
|
||||||
|
MemoryPercent []*float64 `json:"memoryPercent"`
|
||||||
|
SpeedValue []*float64 `json:"speedValue"`
|
||||||
|
|
||||||
|
ProcessCount []*float64 `json:"processCount"`
|
||||||
GPUProcesses [][]GPUProcess `json:"gpuProcesses"`
|
GPUProcesses [][]GPUProcess `json:"gpuProcesses"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ type FileWget struct {
|
|||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
IgnoreCertificate bool `json:"ignoreCertificate"`
|
IgnoreCertificate bool `json:"ignoreCertificate"`
|
||||||
UseProxy bool `json:"useProxy"`
|
UseProxy bool `json:"useProxy"`
|
||||||
|
UseServerFilename bool `json:"useServerFilename"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileMove struct {
|
type FileMove struct {
|
||||||
|
|||||||
+27
-198
@@ -1,222 +1,51 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
|
||||||
)
|
|
||||||
|
|
||||||
const FirewallRuleSequenceStep int64 = 1 << 32
|
|
||||||
|
|
||||||
type DockerPortGuardPolicy struct {
|
type DockerPortGuardPolicy struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
|
|
||||||
UUID string `gorm:"size:64;not null;uniqueIndex" json:"uuid"`
|
UUID string `gorm:"uniqueIndex" json:"uuid"`
|
||||||
ReadOnly bool `gorm:"not null;default:false;uniqueIndex:idx_docker_port_guard_endpoint" json:"-"`
|
ReadOnly bool `gorm:"default:false;uniqueIndex:idx_docker_port_guard_endpoint" json:"-"`
|
||||||
Family string `gorm:"size:16;not null;uniqueIndex:idx_docker_port_guard_endpoint" json:"family"`
|
Family string `gorm:"uniqueIndex:idx_docker_port_guard_endpoint" json:"family"`
|
||||||
HostIP string `gorm:"size:64;not null;uniqueIndex:idx_docker_port_guard_endpoint" json:"hostIP"`
|
HostIP string `gorm:"uniqueIndex:idx_docker_port_guard_endpoint" json:"hostIP"`
|
||||||
HostPort uint16 `gorm:"not null;uniqueIndex:idx_docker_port_guard_endpoint" json:"hostPort"`
|
HostPort uint16 `gorm:"uniqueIndex:idx_docker_port_guard_endpoint" json:"hostPort"`
|
||||||
Protocol string `gorm:"size:8;not null;uniqueIndex:idx_docker_port_guard_endpoint" json:"protocol"`
|
Protocol string `gorm:"uniqueIndex:idx_docker_port_guard_endpoint" json:"protocol"`
|
||||||
Mode string `gorm:"size:32;not null" json:"mode"`
|
Mode string `json:"mode"`
|
||||||
Sources string `gorm:"type:text" json:"-"`
|
Sources string `gorm:"type:text" json:"-"`
|
||||||
Description string `gorm:"type:text" json:"description"`
|
Description string `gorm:"type:text" json:"description"`
|
||||||
NativeAction string `gorm:"size:32;not null;default:''" json:"-"`
|
NativeAction string `gorm:"default:''" json:"-"`
|
||||||
NativeRules string `gorm:"type:text" json:"-"`
|
NativeRules string `gorm:"type:text" json:"-"`
|
||||||
Sequence int64 `gorm:"not null;default:0" json:"-"`
|
Sequence int64 `gorm:"default:0" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ForwardingRule struct {
|
type ForwardingRule struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
|
|
||||||
Family string `gorm:"size:16;not null;uniqueIndex:idx_forwarding_rule_identity" json:"family"`
|
Family string `gorm:"uniqueIndex:idx_forwarding_rule_identity" json:"family"`
|
||||||
Protocol string `gorm:"size:8;not null;uniqueIndex:idx_forwarding_rule_identity" json:"protocol"`
|
Protocol string `gorm:"uniqueIndex:idx_forwarding_rule_identity" json:"protocol"`
|
||||||
Port string `gorm:"size:32;not null;uniqueIndex:idx_forwarding_rule_identity" json:"port"`
|
Port string `gorm:"uniqueIndex:idx_forwarding_rule_identity" json:"port"`
|
||||||
TargetIP string `gorm:"size:64;not null;uniqueIndex:idx_forwarding_rule_identity" json:"targetIP"`
|
TargetIP string `gorm:"uniqueIndex:idx_forwarding_rule_identity" json:"targetIP"`
|
||||||
TargetPort string `gorm:"size:32;not null;uniqueIndex:idx_forwarding_rule_identity" json:"targetPort"`
|
TargetPort string `gorm:"uniqueIndex:idx_forwarding_rule_identity" json:"targetPort"`
|
||||||
Interface string `gorm:"size:32;not null;default:'';uniqueIndex:idx_forwarding_rule_identity" json:"interface"`
|
Interface string `gorm:"default:'';uniqueIndex:idx_forwarding_rule_identity" json:"interface"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallRule struct {
|
type FirewallRule struct {
|
||||||
UUID string `gorm:"size:64;primaryKey" json:"uuid"`
|
UUID string `gorm:"primaryKey" json:"uuid"`
|
||||||
Family string `gorm:"size:16;not null" json:"family"`
|
Family string `json:"family"`
|
||||||
|
|
||||||
Protocol string `gorm:"size:32;not null" json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
SourceAddress string `gorm:"size:255" json:"sourceAddress"`
|
SourceAddress string `json:"sourceAddress"`
|
||||||
SourcePort string `gorm:"size:64" json:"sourcePort"`
|
SourcePort string `json:"sourcePort"`
|
||||||
DestinationAddress string `gorm:"size:255" json:"destinationAddress"`
|
DestinationAddress string `json:"destinationAddress"`
|
||||||
DestinationPort string `gorm:"size:64" json:"destinationPort"`
|
DestinationPort string `json:"destinationPort"`
|
||||||
Interface string `gorm:"size:128" json:"interface"`
|
Interface string `json:"interface"`
|
||||||
ConnectionStates string `gorm:"type:text" json:"connectionStates"`
|
ConnectionStates string `gorm:"type:text" json:"connectionStates"`
|
||||||
Action string `gorm:"size:32;not null" json:"action"`
|
Action string `json:"action"`
|
||||||
Description string `gorm:"type:text" json:"description"`
|
Description string `gorm:"type:text" json:"description"`
|
||||||
CompatibilityError string `gorm:"type:text" json:"compatibilityError,omitempty"`
|
CompatibilityError string `gorm:"type:text" json:"compatibilityError,omitempty"`
|
||||||
Priority *int `json:"priority,omitempty"`
|
Priority *int `json:"priority,omitempty"`
|
||||||
Sequence *int64 `gorm:"index" json:"sequence,omitempty"`
|
Sequence *int64 `gorm:"index" json:"sequence,omitempty"`
|
||||||
|
|
||||||
Origin string `gorm:"size:32;not null" json:"origin"`
|
Origin string `json:"origin"`
|
||||||
Owner string `gorm:"size:320;not null" json:"owner"`
|
Owner string `json:"owner"`
|
||||||
Revision uint `gorm:"not null;default:1" json:"revision"`
|
Revision uint `gorm:"default:1" json:"revision"`
|
||||||
}
|
|
||||||
|
|
||||||
func FirewallRuleOwner(sourceKind, sourceID string) string {
|
|
||||||
sourceKind = strings.TrimSpace(sourceKind)
|
|
||||||
sourceID = strings.TrimSpace(sourceID)
|
|
||||||
if sourceID == "" {
|
|
||||||
return sourceKind
|
|
||||||
}
|
|
||||||
return sourceKind + ":" + sourceID
|
|
||||||
}
|
|
||||||
|
|
||||||
func FirewallRuleFromDomain(rule filter.FirewallRule) (FirewallRule, error) {
|
|
||||||
normalized, err := filter.NormalizeRule(rule)
|
|
||||||
if err != nil {
|
|
||||||
return FirewallRule{}, err
|
|
||||||
}
|
|
||||||
switch normalized.NativeKind {
|
|
||||||
case "", filter.NativeKindRule, filter.NativeKindZonePort, filter.NativeKindRichRule, filter.NativeKindUFWRule:
|
|
||||||
default:
|
|
||||||
return FirewallRule{}, fmt.Errorf("%w: native rule %q cannot be stored as a provider-neutral policy", filter.ErrUnsupportedScope, normalized.NativeKind)
|
|
||||||
}
|
|
||||||
record := FirewallRule{
|
|
||||||
Family: string(normalized.Scope.Family),
|
|
||||||
Protocol: normalized.Protocol,
|
|
||||||
SourceAddress: normalized.SourceAddress,
|
|
||||||
SourcePort: normalized.SourcePort,
|
|
||||||
DestinationAddress: normalized.DestinationAddress,
|
|
||||||
DestinationPort: normalized.DestinationPort,
|
|
||||||
Interface: normalized.Interface,
|
|
||||||
ConnectionStates: strings.Join(normalized.ConnectionStates, ","),
|
|
||||||
Action: string(normalized.Action),
|
|
||||||
Description: normalized.Description,
|
|
||||||
}
|
|
||||||
if normalized.Scope.Provider == filter.ProviderFirewalld {
|
|
||||||
record.Priority = normalized.Priority
|
|
||||||
}
|
|
||||||
return record, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rule FirewallRule) PolicyKey() string {
|
|
||||||
payload, _ := json.Marshal(struct {
|
|
||||||
Family string `json:"family"`
|
|
||||||
Protocol string `json:"protocol"`
|
|
||||||
SourceAddress string `json:"sourceAddress,omitempty"`
|
|
||||||
SourcePort string `json:"sourcePort,omitempty"`
|
|
||||||
DestinationAddress string `json:"destinationAddress,omitempty"`
|
|
||||||
DestinationPort string `json:"destinationPort,omitempty"`
|
|
||||||
Interface string `json:"interface,omitempty"`
|
|
||||||
ConnectionStates string `json:"connectionStates,omitempty"`
|
|
||||||
Action string `json:"action"`
|
|
||||||
}{
|
|
||||||
Family: rule.Family, Protocol: rule.Protocol,
|
|
||||||
SourceAddress: rule.SourceAddress, SourcePort: rule.SourcePort,
|
|
||||||
DestinationAddress: rule.DestinationAddress, DestinationPort: rule.DestinationPort,
|
|
||||||
Interface: rule.Interface, ConnectionStates: rule.ConnectionStates, Action: rule.Action,
|
|
||||||
})
|
|
||||||
sum := sha256.Sum256(payload)
|
|
||||||
return hex.EncodeToString(sum[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rule FirewallRule) RulesForProvider(provider filter.Provider) ([]filter.FirewallRule, error) {
|
|
||||||
if rule.CompatibilityError != "" {
|
|
||||||
return nil, fmt.Errorf("%w: %s", filter.ErrUnsupportedScope, rule.CompatibilityError)
|
|
||||||
}
|
|
||||||
connectionStates := make([]string, 0)
|
|
||||||
if rule.ConnectionStates != "" {
|
|
||||||
connectionStates = strings.Split(rule.ConnectionStates, ",")
|
|
||||||
}
|
|
||||||
base := filter.FirewallRule{
|
|
||||||
Protocol: rule.Protocol, SourceAddress: rule.SourceAddress, SourcePort: rule.SourcePort,
|
|
||||||
DestinationAddress: rule.DestinationAddress, DestinationPort: rule.DestinationPort,
|
|
||||||
Interface: rule.Interface, ConnectionStates: connectionStates,
|
|
||||||
Action: filter.Action(rule.Action), Description: rule.Description,
|
|
||||||
}
|
|
||||||
if provider != filter.ProviderUFW && strings.EqualFold(strings.TrimSpace(base.Protocol), "all") &&
|
|
||||||
strings.TrimSpace(base.SourcePort) == "" && strings.TrimSpace(base.DestinationPort) != "" {
|
|
||||||
base.Protocol = "tcp/udp"
|
|
||||||
}
|
|
||||||
if provider == filter.ProviderFirewalld {
|
|
||||||
base.Priority = rule.Priority
|
|
||||||
}
|
|
||||||
families := []filter.Family{filter.Family(rule.Family)}
|
|
||||||
if provider != filter.ProviderFirewalld && families[0] == filter.FamilyInet {
|
|
||||||
hasIPv4, hasIPv6 := ruleAddressFamilies(base)
|
|
||||||
switch {
|
|
||||||
case hasIPv4 && hasIPv6:
|
|
||||||
return nil, fmt.Errorf("%w: inet policy contains both IPv4 and IPv6 addresses", filter.ErrUnsupportedScope)
|
|
||||||
case hasIPv6 || strings.EqualFold(base.Protocol, "icmpv6"):
|
|
||||||
families = []filter.Family{filter.FamilyIPv6}
|
|
||||||
case hasIPv4:
|
|
||||||
families = []filter.Family{filter.FamilyIPv4}
|
|
||||||
default:
|
|
||||||
families = []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result := make([]filter.FirewallRule, 0, len(families))
|
|
||||||
for _, family := range families {
|
|
||||||
compiled := base
|
|
||||||
compiled.Scope = filter.Scope{Provider: provider, Family: family, Direction: filter.DirectionInput}
|
|
||||||
switch provider {
|
|
||||||
case filter.ProviderIptables, filter.ProviderNftables:
|
|
||||||
compiled.Scope.Table, compiled.Scope.Chain = "filter", filter.IptablesInputChain
|
|
||||||
case filter.ProviderFirewalld:
|
|
||||||
compiled.Scope.Zone = filter.FirewalldInputZone
|
|
||||||
case filter.ProviderUFW:
|
|
||||||
compiled.Scope.Chain = filter.UFWInputChain
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("%w: unsupported firewall provider %q", filter.ErrProviderUnavailable, provider)
|
|
||||||
}
|
|
||||||
expanded, err := filter.ExpandAtomicRules(compiled)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result = append(result, expanded...)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SortFirewallRules(rules []FirewallRule, provider filter.Provider) {
|
|
||||||
sort.SliceStable(rules, func(i, j int) bool {
|
|
||||||
left, right := rules[i], rules[j]
|
|
||||||
if provider == filter.ProviderFirewalld {
|
|
||||||
switch {
|
|
||||||
case left.Priority == nil && right.Priority != nil:
|
|
||||||
return false
|
|
||||||
case left.Priority != nil && right.Priority == nil:
|
|
||||||
return true
|
|
||||||
case left.Priority != nil && right.Priority != nil && *left.Priority != *right.Priority:
|
|
||||||
return *left.Priority < *right.Priority
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch {
|
|
||||||
case left.Sequence == nil && right.Sequence != nil:
|
|
||||||
return false
|
|
||||||
case left.Sequence != nil && right.Sequence == nil:
|
|
||||||
return true
|
|
||||||
case left.Sequence != nil && right.Sequence != nil && *left.Sequence != *right.Sequence:
|
|
||||||
return *left.Sequence < *right.Sequence
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return left.UUID < right.UUID
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func ruleAddressFamilies(rule filter.FirewallRule) (bool, bool) {
|
|
||||||
hasIPv4, hasIPv6 := false, false
|
|
||||||
for _, address := range []string{rule.SourceAddress, rule.DestinationAddress} {
|
|
||||||
address = strings.TrimSpace(address)
|
|
||||||
if address == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(address, ":") {
|
|
||||||
hasIPv6 = true
|
|
||||||
} else {
|
|
||||||
hasIPv4 = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasIPv4, hasIPv6
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,14 +33,45 @@ type MonitorNetwork struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MonitorGPU struct {
|
type MonitorGPU struct {
|
||||||
|
MemoryUtil *float64 `json:"memoryUtil"`
|
||||||
|
MemoryActivity *float64 `json:"memoryActivity"`
|
||||||
|
EncoderUtil *float64 `json:"encoderUtil"`
|
||||||
|
DecoderUtil *float64 `json:"decoderUtil"`
|
||||||
|
JPEGUtil *float64 `json:"jpegUtil"`
|
||||||
|
OFAUtil *float64 `json:"ofaUtil"`
|
||||||
|
MediaUtil *float64 `json:"mediaUtil"`
|
||||||
|
ComputeUtil *float64 `json:"computeUtil"`
|
||||||
|
CopyUtil *float64 `json:"copyUtil"`
|
||||||
|
HotspotTemperature *float64 `json:"hotspotTemperature"`
|
||||||
|
FanRPM *float64 `json:"fanRPM"`
|
||||||
|
AICPUUtil *float64 `json:"aiCPUUtil"`
|
||||||
|
CtrlCPUUtil *float64 `json:"ctrlCPUUtil"`
|
||||||
|
DDRUsed *float64 `json:"ddrUsed"`
|
||||||
|
DDRTotal *float64 `json:"ddrTotal"`
|
||||||
|
HBMUsed *float64 `json:"hbmUsed"`
|
||||||
|
HBMTotal *float64 `json:"hbmTotal"`
|
||||||
|
DDRBandwidth *float64 `json:"ddrBandwidth"`
|
||||||
|
HBMBandwidth *float64 `json:"hbmBandwidth"`
|
||||||
|
MemoryBandwidth *float64 `json:"memoryBandwidth"`
|
||||||
|
MediaFrequency *float64 `json:"mediaFrequency"`
|
||||||
|
HugepagesUsed *float64 `json:"hugepagesUsed"`
|
||||||
|
HugepagesTotal *float64 `json:"hugepagesTotal"`
|
||||||
|
|
||||||
|
MemoryTemperature *float64 `json:"memoryTemperature"`
|
||||||
|
DeviceID string `json:"deviceID"`
|
||||||
|
DeviceType string `json:"deviceType"`
|
||||||
|
ProcessStatus string `json:"processStatus"`
|
||||||
|
Frequency *float64 `json:"frequency"`
|
||||||
|
MemoryFrequency *float64 `json:"memoryFrequency"`
|
||||||
|
IntervalSeconds int `json:"intervalSeconds"`
|
||||||
BaseModel
|
BaseModel
|
||||||
ProductName string `json:"productName"`
|
ProductName string `json:"productName"`
|
||||||
GPUUtil float64 `json:"gpuUtil"`
|
GPUUtil *float64 `json:"gpuUtil"`
|
||||||
Temperature float64 `json:"temperature"`
|
Temperature *float64 `json:"temperature"`
|
||||||
PowerDraw float64 `json:"powerDraw"`
|
PowerDraw *float64 `json:"powerDraw"`
|
||||||
MaxPowerLimit float64 `json:"maxPowerLimit"`
|
MaxPowerLimit *float64 `json:"maxPowerLimit"`
|
||||||
MemUsed float64 `json:"memUsed"`
|
MemUsed *float64 `json:"memUsed"`
|
||||||
MemTotal float64 `json:"memTotal"`
|
MemTotal *float64 `json:"memTotal"`
|
||||||
FanSpeed int `json:"fanSpeed"`
|
FanSpeed *float64 `json:"fanSpeed"`
|
||||||
Processes string `json:"processes"`
|
Processes string `json:"processes"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,8 @@ import (
|
|||||||
|
|
||||||
type IDockerPortGuardRepo interface {
|
type IDockerPortGuardRepo interface {
|
||||||
ListManaged(context.Context) ([]model.DockerPortGuardPolicy, error)
|
ListManaged(context.Context) ([]model.DockerPortGuardPolicy, error)
|
||||||
ListRuntimeReadOnly(context.Context) ([]model.DockerPortGuardPolicy, error)
|
|
||||||
DeleteBatch(context.Context, []string) error
|
DeleteBatch(context.Context, []string) error
|
||||||
UpsertBatch(context.Context, []model.DockerPortGuardPolicy) error
|
UpsertBatch(context.Context, []model.DockerPortGuardPolicy) error
|
||||||
ReplaceRuntimeReadOnly(context.Context, []model.DockerPortGuardPolicy) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DockerPortGuardRepo struct{}
|
type DockerPortGuardRepo struct{}
|
||||||
@@ -30,15 +28,6 @@ func (r *DockerPortGuardRepo) ListManaged(ctx context.Context) ([]model.DockerPo
|
|||||||
return policies, err
|
return policies, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DockerPortGuardRepo) ListRuntimeReadOnly(ctx context.Context) ([]model.DockerPortGuardPolicy, error) {
|
|
||||||
var policies []model.DockerPortGuardPolicy
|
|
||||||
err := global.DB.WithContext(ctx).
|
|
||||||
Where("read_only = ?", true).
|
|
||||||
Order("family, sequence, host_ip, host_port, protocol").
|
|
||||||
Find(&policies).Error
|
|
||||||
return policies, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DockerPortGuardRepo) DeleteBatch(ctx context.Context, uuids []string) error {
|
func (r *DockerPortGuardRepo) DeleteBatch(ctx context.Context, uuids []string) error {
|
||||||
return global.DB.WithContext(ctx).
|
return global.DB.WithContext(ctx).
|
||||||
Where("read_only = ? AND uuid IN ?", false, uuids).
|
Where("read_only = ? AND uuid IN ?", false, uuids).
|
||||||
@@ -59,19 +48,3 @@ func (r *DockerPortGuardRepo) UpsertBatch(ctx context.Context, policies []model.
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DockerPortGuardRepo) ReplaceRuntimeReadOnly(ctx context.Context, policies []model.DockerPortGuardPolicy) error {
|
|
||||||
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
if err := tx.Where("read_only = ?", true).
|
|
||||||
Delete(&model.DockerPortGuardPolicy{}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(policies) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for i := range policies {
|
|
||||||
policies[i].ReadOnly = true
|
|
||||||
}
|
|
||||||
return tx.Create(&policies).Error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -23,18 +24,14 @@ type IFirewallRuleRepo interface {
|
|||||||
List(context.Context, ...DBOption) ([]model.FirewallRule, error)
|
List(context.Context, ...DBOption) ([]model.FirewallRule, error)
|
||||||
UpdateWithRevision(context.Context, string, uint, map[string]interface{}) error
|
UpdateWithRevision(context.Context, string, uint, map[string]interface{}) error
|
||||||
DeleteWithRevision(context.Context, string, uint) error
|
DeleteWithRevision(context.Context, string, uint) error
|
||||||
|
DeleteBatchWithRevision(context.Context, []model.FirewallRule) map[string]error
|
||||||
|
SaveResetOrder(context.Context, []model.FirewallRule) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallRuleRepo struct {
|
type FirewallRuleRepo struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithFirewallRuleSource(kind, id string) DBOption {
|
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
|
||||||
return db.Where("owner = ?", model.FirewallRuleOwner(kind, id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIFirewallRuleRepo() IFirewallRuleRepo {
|
func NewIFirewallRuleRepo() IFirewallRuleRepo {
|
||||||
return &FirewallRuleRepo{}
|
return &FirewallRuleRepo{}
|
||||||
}
|
}
|
||||||
@@ -93,6 +90,51 @@ func (r *FirewallRuleRepo) DeleteWithRevision(ctx context.Context, ruleUUID stri
|
|||||||
return nil
|
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 {
|
func (r *FirewallRuleRepo) dbFor(ctx context.Context) *gorm.DB {
|
||||||
return firewallDB(ctx, r.db)
|
return firewallDB(ctx, r.db)
|
||||||
}
|
}
|
||||||
@@ -133,18 +175,13 @@ func prepareFirewallRule(rule *model.FirewallRule) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sanitizeRuleUpdates(updates map[string]interface{}) map[string]interface{} {
|
func sanitizeRuleUpdates(updates map[string]interface{}) map[string]interface{} {
|
||||||
result := cloneUpdates(updates)
|
result := make(map[string]interface{}, len(updates)+1)
|
||||||
|
for key, value := range updates {
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
delete(result, "id")
|
delete(result, "id")
|
||||||
delete(result, "uuid")
|
delete(result, "uuid")
|
||||||
delete(result, "revision")
|
delete(result, "revision")
|
||||||
delete(result, "created_at")
|
delete(result, "created_at")
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneUpdates(updates map[string]interface{}) map[string]interface{} {
|
|
||||||
result := make(map[string]interface{}, len(updates)+1)
|
|
||||||
for key, value := range updates {
|
|
||||||
result[key] = value
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import (
|
|||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type IForwardingRuleRepo interface {
|
type IForwardingRuleRepo interface {
|
||||||
List(context.Context) ([]model.ForwardingRule, error)
|
List(context.Context) ([]model.ForwardingRule, error)
|
||||||
ReplaceAll(context.Context, []model.ForwardingRule) error
|
CreateBatch(context.Context, []model.ForwardingRule) error
|
||||||
|
DeleteBatch(context.Context, []uint) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ForwardingRuleRepo struct{}
|
type ForwardingRuleRepo struct{}
|
||||||
@@ -23,14 +23,16 @@ func (r *ForwardingRuleRepo) List(ctx context.Context) ([]model.ForwardingRule,
|
|||||||
return rules, err
|
return rules, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ForwardingRuleRepo) ReplaceAll(ctx context.Context, rules []model.ForwardingRule) error {
|
func (r *ForwardingRuleRepo) CreateBatch(ctx context.Context, rules []model.ForwardingRule) error {
|
||||||
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
if len(rules) == 0 {
|
||||||
if err := tx.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&model.ForwardingRule{}).Error; err != nil {
|
return nil
|
||||||
return err
|
}
|
||||||
}
|
return global.DB.WithContext(ctx).CreateInBatches(&rules, 500).Error
|
||||||
if len(rules) == 0 {
|
}
|
||||||
return nil
|
|
||||||
}
|
func (r *ForwardingRuleRepo) DeleteBatch(ctx context.Context, ids []uint) error {
|
||||||
return tx.Create(&rules).Error
|
if len(ids) == 0 {
|
||||||
})
|
return nil
|
||||||
|
}
|
||||||
|
return global.DB.WithContext(ctx).Where("id IN ?", ids).Delete(&model.ForwardingRule{}).Error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
@@ -10,9 +12,20 @@ import (
|
|||||||
|
|
||||||
type MonitorRepo struct{}
|
type MonitorRepo struct{}
|
||||||
|
|
||||||
|
type GPUHistoryPoint struct {
|
||||||
|
model.MonitorGPU
|
||||||
|
Bucket int64
|
||||||
|
PowerPercent *float64
|
||||||
|
MemoryPercent *float64
|
||||||
|
ProcessCount *float64
|
||||||
|
}
|
||||||
|
|
||||||
type IMonitorRepo interface {
|
type IMonitorRepo interface {
|
||||||
GetBase(opts ...DBOption) ([]model.MonitorBase, error)
|
GetBase(opts ...DBOption) ([]model.MonitorBase, error)
|
||||||
GetGPU(opts ...DBOption) ([]model.MonitorGPU, error)
|
GetGPU(opts ...DBOption) ([]model.MonitorGPU, error)
|
||||||
|
CountGPU(opts ...DBOption) (int64, error)
|
||||||
|
GetGPUHistory(start time.Time, bucketSeconds int64, aggregation string, opts ...DBOption) ([]GPUHistoryPoint, error)
|
||||||
|
GetGPUDevices() ([]model.MonitorGPU, error)
|
||||||
GetIO(opts ...DBOption) ([]model.MonitorIO, error)
|
GetIO(opts ...DBOption) ([]model.MonitorIO, error)
|
||||||
GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error)
|
GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error)
|
||||||
|
|
||||||
@@ -26,6 +39,7 @@ type IMonitorRepo interface {
|
|||||||
DelMonitorNet(timeForDelete time.Time) error
|
DelMonitorNet(timeForDelete time.Time) error
|
||||||
|
|
||||||
WithByProductName(name string) DBOption
|
WithByProductName(name string) DBOption
|
||||||
|
WithByGPUDevice(deviceID, name string, legacy bool) DBOption
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIMonitorRepo() IMonitorRepo {
|
func NewIMonitorRepo() IMonitorRepo {
|
||||||
@@ -102,3 +116,68 @@ func (s *MonitorRepo) WithByProductName(name string) DBOption {
|
|||||||
return g.Where("product_name = ?", name)
|
return g.Where("product_name = ?", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *MonitorRepo) GetGPUDevices() ([]model.MonitorGPU, error) {
|
||||||
|
var data []model.MonitorGPU
|
||||||
|
err := global.GPUMonitorDB.Model(&model.MonitorGPU{}).Select("device_id, product_name, device_type").Group("device_id, product_name, device_type").Order("product_name, device_id").Find(&data).Error
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *MonitorRepo) WithByGPUDevice(deviceID, name string, legacy bool) DBOption {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
if deviceID != "" {
|
||||||
|
return db.Where("device_id = ?", deviceID)
|
||||||
|
}
|
||||||
|
db = db.Where("product_name = ?", name)
|
||||||
|
if legacy {
|
||||||
|
db = db.Where("device_id IS NULL OR device_id = ''")
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *MonitorRepo) CountGPU(opts ...DBOption) (int64, error) {
|
||||||
|
db := global.GPUMonitorDB.Model(&model.MonitorGPU{})
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
var count int64
|
||||||
|
err := db.Count(&count).Error
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *MonitorRepo) GetGPUHistory(start time.Time, bucketSeconds int64, aggregation string, opts ...DBOption) ([]GPUHistoryPoint, error) {
|
||||||
|
db := global.GPUMonitorDB.Model(&model.MonitorGPU{})
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
expressions := []string{
|
||||||
|
"CASE WHEN max_power_limit > 0 THEN 100.0 * power_draw / max_power_limit END",
|
||||||
|
"CASE WHEN mem_total > 0 AND mem_used IS NOT NULL THEN 100.0 * mem_used / mem_total ELSE memory_util END",
|
||||||
|
"CASE WHEN (process_status = 'ok' OR process_status IS NULL OR process_status = '') AND json_valid(processes) THEN CASE WHEN json_type(processes) = 'array' THEN json_array_length(processes) END END",
|
||||||
|
}
|
||||||
|
aliases := []string{"power_percent", "memory_percent", "process_count"}
|
||||||
|
columns := []string{"*"}
|
||||||
|
if bucketSeconds > 0 {
|
||||||
|
operation := "AVG"
|
||||||
|
if aggregation == "max" {
|
||||||
|
operation = "MAX"
|
||||||
|
}
|
||||||
|
columns = []string{fmt.Sprintf("(CAST(strftime('%%s', created_at) AS INTEGER) - %d) / %d AS bucket", start.Unix(), bucketSeconds)}
|
||||||
|
for _, column := range []string{"memory_activity", "encoder_util", "decoder_util", "jpeg_util", "ofa_util", "media_util", "compute_util", "copy_util", "hotspot_temperature", "fan_rpm", "ai_cpu_util", "ctrl_cpu_util", "ddr_used", "ddr_total", "hbm_used", "hbm_total", "ddr_bandwidth", "hbm_bandwidth", "memory_bandwidth", "media_frequency", "hugepages_used", "hugepages_total", "gpu_util", "temperature", "memory_temperature", "power_draw", "max_power_limit", "mem_used", "mem_total", "frequency", "memory_frequency", "fan_speed"} {
|
||||||
|
columns = append(columns, operation+"("+column+") AS "+column)
|
||||||
|
}
|
||||||
|
for i := range expressions {
|
||||||
|
expressions[i] = operation + "(" + expressions[i] + ")"
|
||||||
|
}
|
||||||
|
db = db.Group("bucket").Order("bucket ASC")
|
||||||
|
} else {
|
||||||
|
db = db.Order("created_at ASC, id ASC")
|
||||||
|
}
|
||||||
|
for i, expression := range expressions {
|
||||||
|
columns = append(columns, expression+" AS "+aliases[i])
|
||||||
|
}
|
||||||
|
var data []GPUHistoryPoint
|
||||||
|
err := db.Select(strings.Join(columns, ", ")).Scan(&data).Error
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -936,6 +936,7 @@ func (a AgentService) GetModelConfig(req dto.AgentIDReq) (*dto.AgentModelConfig,
|
|||||||
AccountID: agent.AccountID,
|
AccountID: agent.AccountID,
|
||||||
Model: model,
|
Model: model,
|
||||||
Fallbacks: extractOpenclawFallbackModelIDs(conf, account, models, model),
|
Fallbacks: extractOpenclawFallbackModelIDs(conf, account, models, model),
|
||||||
|
Metadata: extractOpenclawModelMetadata(conf, account, models),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,7 +968,7 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
|||||||
if agent.AgentType != constant.AppOpenclaw {
|
if agent.AgentType != constant.AppOpenclaw {
|
||||||
return fmt.Errorf("%s does not support", agent.AgentType)
|
return fmt.Errorf("%s does not support", agent.AgentType)
|
||||||
}
|
}
|
||||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, req.Fallbacks); err != nil {
|
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, req.Fallbacks, req.Metadata); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1684,7 +1685,7 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fallbacks := extractOpenclawFallbackModelIDs(conf, account, accountModels, selectedAccountModel.ID)
|
fallbacks := extractOpenclawFallbackModelIDs(conf, account, accountModels, selectedAccountModel.ID)
|
||||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, fallbacks); err != nil {
|
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, fallbacks, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case constant.AppHermesAgent:
|
case constant.AppHermesAgent:
|
||||||
|
|||||||
@@ -1354,6 +1354,10 @@ func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID
|
|||||||
} else if slices.Contains(options, "--dangerously-force-unsafe-install") {
|
} else if slices.Contains(options, "--dangerously-force-unsafe-install") {
|
||||||
args = append(args, "--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", args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -737,9 +738,11 @@ type modelProvider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type modelEntry struct {
|
type modelEntry struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Input []string `json:"input,omitempty"`
|
Input []string `json:"input,omitempty"`
|
||||||
|
ContextWindow int `json:"contextWindow,omitempty"`
|
||||||
|
MaxTokens int `json:"maxTokens,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func requiresOpenclawProviderModels(provider string) bool {
|
func requiresOpenclawProviderModels(provider string) bool {
|
||||||
@@ -767,7 +770,7 @@ type browserConfig struct {
|
|||||||
DefaultProfile string `json:"defaultProfile"`
|
DefaultProfile string `json:"defaultProfile"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, token string, allowedOrigins []string, fallbacks []string) error {
|
func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, token string, allowedOrigins []string, fallbacks []string, metadata []dto.AgentModelMetadata) error {
|
||||||
if strings.TrimSpace(confDir) == "" {
|
if strings.TrimSpace(confDir) == "" {
|
||||||
return fmt.Errorf("config dir is required")
|
return fmt.Errorf("config dir is required")
|
||||||
}
|
}
|
||||||
@@ -852,6 +855,7 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
|||||||
}
|
}
|
||||||
conf = initial
|
conf = initial
|
||||||
} else {
|
} else {
|
||||||
|
preserveOpenclawModelMetadata(conf, cfg.Models)
|
||||||
if err := applyOpenclawModelsConfig(conf, cfg.Models); err != nil {
|
if err := applyOpenclawModelsConfig(conf, cfg.Models); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -906,6 +910,9 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
|||||||
if allowedOrigins != nil {
|
if allowedOrigins != nil {
|
||||||
setSecurityConfig(conf, dto.AgentSecurityConfig{AllowedOrigins: allowedOrigins})
|
setSecurityConfig(conf, dto.AgentSecurityConfig{AllowedOrigins: allowedOrigins})
|
||||||
}
|
}
|
||||||
|
if err := applyOpenclawModelMetadata(conf, account, metadata); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
|
if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -920,6 +927,144 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
|||||||
return writeAgentEnvMap(path.Join(confDir, ".env"), envMap, order)
|
return writeAgentEnvMap(path.Join(confDir, ".env"), envMap, order)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readOpenclawModelsConfig(conf map[string]interface{}) *modelsConfig {
|
||||||
|
raw, ok := conf["models"]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
payload, err := json.Marshal(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var models modelsConfig
|
||||||
|
if err := json.Unmarshal(payload, &models); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &models
|
||||||
|
}
|
||||||
|
|
||||||
|
func preserveOpenclawModelMetadata(conf map[string]interface{}, next *modelsConfig) {
|
||||||
|
current := readOpenclawModelsConfig(conf)
|
||||||
|
if current == nil || next == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for providerID, nextProvider := range next.Providers {
|
||||||
|
currentProvider, ok := current.Providers[providerID]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
byID := make(map[string]modelEntry, len(currentProvider.Models))
|
||||||
|
for _, entry := range currentProvider.Models {
|
||||||
|
byID[entry.ID] = entry
|
||||||
|
}
|
||||||
|
for index := range nextProvider.Models {
|
||||||
|
currentEntry, ok := byID[nextProvider.Models[index].ID]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nextProvider.Models[index].Input = currentEntry.Input
|
||||||
|
nextProvider.Models[index].ContextWindow = currentEntry.ContextWindow
|
||||||
|
nextProvider.Models[index].MaxTokens = currentEntry.MaxTokens
|
||||||
|
}
|
||||||
|
next.Providers[providerID] = nextProvider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractOpenclawModelMetadata(conf map[string]interface{}, account *model.AgentAccount, accountModels []dto.AgentAccountModel) []dto.AgentModelMetadata {
|
||||||
|
result := make([]dto.AgentModelMetadata, 0, len(accountModels))
|
||||||
|
configured := readOpenclawModelsConfig(conf)
|
||||||
|
for _, item := range accountModels {
|
||||||
|
_, inferred, providerID, _, err := buildOpenclawAccountModelConfig(account, item)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
metadata := dto.AgentModelMetadata{Model: item.ID, InputMode: "auto"}
|
||||||
|
if configured != nil {
|
||||||
|
for _, entry := range configured.Providers[providerID].Models {
|
||||||
|
if entry.ID != inferred.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
metadata.ContextWindow = entry.ContextWindow
|
||||||
|
metadata.MaxTokens = entry.MaxTokens
|
||||||
|
if len(entry.Input) > 0 && !slices.Equal(entry.Input, inferred.Input) {
|
||||||
|
if slices.Contains(entry.Input, "image") {
|
||||||
|
metadata.InputMode = "image"
|
||||||
|
} else {
|
||||||
|
metadata.InputMode = "text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = append(result, metadata)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyOpenclawModelMetadata(conf map[string]interface{}, account *model.AgentAccount, requested []dto.AgentModelMetadata) error {
|
||||||
|
if len(requested) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
configured := readOpenclawModelsConfig(conf)
|
||||||
|
if configured == nil {
|
||||||
|
return fmt.Errorf("model metadata is not supported for provider %s", account.Provider)
|
||||||
|
}
|
||||||
|
accountModels, err := loadAgentAccountModels(account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
available := make(map[string]dto.AgentAccountModel, len(accountModels))
|
||||||
|
for _, item := range accountModels {
|
||||||
|
available[item.ID] = item
|
||||||
|
}
|
||||||
|
seen := make(map[string]struct{}, len(requested))
|
||||||
|
for _, metadata := range requested {
|
||||||
|
item, ok := available[metadata.Model]
|
||||||
|
if !ok {
|
||||||
|
return buserr.New("ErrAgentModelNotInAccount")
|
||||||
|
}
|
||||||
|
if _, ok := seen[metadata.Model]; ok {
|
||||||
|
return fmt.Errorf("duplicate model metadata: %s", metadata.Model)
|
||||||
|
}
|
||||||
|
seen[metadata.Model] = struct{}{}
|
||||||
|
_, inferred, providerID, _, err := buildOpenclawAccountModelConfig(account, item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
provider := configured.Providers[providerID]
|
||||||
|
found := false
|
||||||
|
for index := range provider.Models {
|
||||||
|
if provider.Models[index].ID != inferred.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
provider.Models[index].ContextWindow = metadata.ContextWindow
|
||||||
|
provider.Models[index].MaxTokens = metadata.MaxTokens
|
||||||
|
switch metadata.InputMode {
|
||||||
|
case "auto":
|
||||||
|
provider.Models[index].Input = inferred.Input
|
||||||
|
case "text":
|
||||||
|
provider.Models[index].Input = []string{"text"}
|
||||||
|
case "image":
|
||||||
|
provider.Models[index].Input = []string{"text", "image"}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported model input mode: %s", metadata.InputMode)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return buserr.New("ErrAgentModelNotInAccount")
|
||||||
|
}
|
||||||
|
configured.Providers[providerID] = provider
|
||||||
|
}
|
||||||
|
modelsMap, err := structToMap(configured)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conf["models"] = modelsMap
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func resolveOpenclawFallbackModels(account *model.AgentAccount, primaryModel string, fallbackIDs []string) ([]string, error) {
|
func resolveOpenclawFallbackModels(account *model.AgentAccount, primaryModel string, fallbackIDs []string) ([]string, error) {
|
||||||
accountModels, err := loadAgentAccountModels(account)
|
accountModels, err := loadAgentAccountModels(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1041,7 +1186,7 @@ func prepareOpenclawInstallFiles(appInstall *model.AppInstall, account *model.Ag
|
|||||||
return fmt.Errorf("app install is required")
|
return fmt.Errorf("app install is required")
|
||||||
}
|
}
|
||||||
confDir := path.Join(appInstall.GetPath(), "data", "conf")
|
confDir := path.Join(appInstall.GetPath(), "data", "conf")
|
||||||
if err := writeOpenclawConfig(confDir, account, modelName, token, allowedOrigins, nil); err != nil {
|
if err := writeOpenclawConfig(confDir, account, modelName, token, allowedOrigins, nil, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dataDir := path.Join(appInstall.GetPath(), "data")
|
dataDir := path.Join(appInstall.GetPath(), "data")
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
|
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||||
alertconfig "github.com/1Panel-dev/1Panel/agent/utils/alert_config"
|
alertconfig "github.com/1Panel-dev/1Panel/agent/utils/alert_config"
|
||||||
alertwebhook "github.com/1Panel-dev/1Panel/agent/utils/alert_webhook"
|
alertwebhook "github.com/1Panel-dev/1Panel/agent/utils/alert_webhook"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||||
@@ -109,7 +110,38 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
|||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cronjobProjects := make(map[string]uint)
|
||||||
|
var cronjobIDs []uint
|
||||||
for _, item := range alerts {
|
for _, item := range alerts {
|
||||||
|
if alertUtil.GetCronJobType(item.Type) != "cronJob" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := cronjobProjects[item.Project]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id, parseErr := strconv.ParseUint(item.Project, 10, strconv.IntSize)
|
||||||
|
if parseErr != nil || id == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cronjobProjects[item.Project] = uint(id)
|
||||||
|
cronjobIDs = append(cronjobIDs, uint(id))
|
||||||
|
}
|
||||||
|
cronjobsByID := make(map[uint]model.Cronjob)
|
||||||
|
if len(cronjobIDs) > 0 {
|
||||||
|
cronjobs, err := cronjobRepo.List(repo.WithByIDs(cronjobIDs))
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
for _, cronjob := range cronjobs {
|
||||||
|
cronjobsByID[cronjob.ID] = cronjob
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range alerts {
|
||||||
|
var taskName string
|
||||||
|
if cronjob, exists := cronjobsByID[cronjobProjects[item.Project]]; exists && cronjob.Type == item.Type {
|
||||||
|
taskName = cronjob.Name
|
||||||
|
}
|
||||||
|
|
||||||
result = append(result, dto.AlertDTO{
|
result = append(result, dto.AlertDTO{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
@@ -119,6 +151,7 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
|||||||
Method: item.Method,
|
Method: item.Method,
|
||||||
Title: item.Title,
|
Title: item.Title,
|
||||||
Project: item.Project,
|
Project: item.Project,
|
||||||
|
TaskName: taskName,
|
||||||
Status: item.Status,
|
Status: item.Status,
|
||||||
SendCount: item.SendCount,
|
SendCount: item.SendCount,
|
||||||
AdvancedParams: item.AdvancedParams,
|
AdvancedParams: item.AdvancedParams,
|
||||||
@@ -190,6 +223,16 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
advanced, err := prepareCronJobAlertParams(create.Type, "", create.AdvancedParams)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
create.AdvancedParams = advanced
|
||||||
|
if create.Status != constant.AlertDisable {
|
||||||
|
if err := a.validateCronJobAlertChannels(create.Type, advanced, create.Method); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
alertInfo.Status = constant.AlertEnable
|
alertInfo.Status = constant.AlertEnable
|
||||||
if err := copier.Copy(&alertInfo, &create); err != nil {
|
if err := copier.Copy(&alertInfo, &create); err != nil {
|
||||||
return buserr.WithErr("ErrStructTransform", err)
|
return buserr.WithErr("ErrStructTransform", err)
|
||||||
@@ -207,11 +250,24 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) UpdateAlert(req dto.AlertUpdate, operator string) error {
|
func (a AlertService) UpdateAlert(req dto.AlertUpdate, operator string) error {
|
||||||
|
if alertUtil.GetCronJobType(req.Type) == "cronJob" {
|
||||||
|
previous, err := alertRepo.Get(repo.WithByID(req.ID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.AdvancedParams, err = prepareCronJobAlertParams(req.Type, previous.AdvancedParams, req.AdvancedParams)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
methodTypes, err := a.validateAlertMethodReferences(req.Method)
|
methodTypes, err := a.validateAlertMethodReferences(req.Method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if req.Status != constant.AlertDisable {
|
if req.Status != constant.AlertDisable {
|
||||||
|
if err := a.validateCronJobAlertChannels(req.Type, req.AdvancedParams, req.Method); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -278,6 +334,9 @@ func (a AlertService) UpdateStatus(id uint, status string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status == constant.AlertEnable {
|
if status == constant.AlertEnable {
|
||||||
|
if err := a.validateCronJobAlertChannels(alertInfo.Type, alertInfo.AdvancedParams, alertInfo.Method); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1021,6 +1080,23 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
|||||||
alertRepo.WithByType(updateAlert.Type),
|
alertRepo.WithByType(updateAlert.Type),
|
||||||
alertRepo.WithByProject(updateAlert.Project),
|
alertRepo.WithByProject(updateAlert.Project),
|
||||||
)
|
)
|
||||||
|
advanced, err := prepareCronJobAlertParams(updateAlert.Type, alertInfo.AdvancedParams, updateAlert.AdvancedParams)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
updateAlert.AdvancedParams = advanced
|
||||||
|
if alertUtil.GetCronJobType(updateAlert.Type) == "cronJob" {
|
||||||
|
upMap["advanced_params"] = advanced
|
||||||
|
}
|
||||||
|
if newStatus == constant.AlertEnable {
|
||||||
|
method := updateAlert.Method
|
||||||
|
if method == "" {
|
||||||
|
method = alertInfo.Method
|
||||||
|
}
|
||||||
|
if err := a.validateCronJobAlertChannels(updateAlert.Type, advanced, method); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if alertInfo.ID > 0 {
|
if alertInfo.ID > 0 {
|
||||||
shouldUpdate := false
|
shouldUpdate := false
|
||||||
@@ -1034,6 +1110,9 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
|||||||
if val, ok := upMap["method"]; ok && val != "" && val != alertInfo.Method {
|
if val, ok := upMap["method"]; ok && val != "" && val != alertInfo.Method {
|
||||||
shouldUpdate = true
|
shouldUpdate = true
|
||||||
}
|
}
|
||||||
|
if val, ok := upMap["advanced_params"]; ok && val != alertInfo.AdvancedParams {
|
||||||
|
shouldUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
if shouldUpdate {
|
if shouldUpdate {
|
||||||
if err := alertRepo.Update(
|
if err := alertRepo.Update(
|
||||||
@@ -1055,3 +1134,22 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prepareCronJobAlertParams(alertType, previous, incoming string) (string, error) {
|
||||||
|
if alertUtil.GetCronJobType(alertType) != "cronJob" {
|
||||||
|
return incoming, nil
|
||||||
|
}
|
||||||
|
return alertUtil.MergeCronJobAlertParams(previous, incoming)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AlertService) validateCronJobAlertChannels(alertType, advanced, method string) error {
|
||||||
|
if alertUtil.GetCronJobType(alertType) != "cronJob" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mode, err := alertUtil.CronJobAlertTriggerMode(advanced)
|
||||||
|
if err != nil || mode != alertUtil.CronJobAlertSuccess {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = a.validateAlertMethodReferences(method)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,10 +30,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ResourceAlertInterval = 30
|
ResourceAlertInterval = 30
|
||||||
CheckIntervalSec = 3
|
CheckIntervalSec = 3
|
||||||
LoadCheckIntervalMin = 5
|
LoadCheckIntervalMin = 5
|
||||||
sshIPLoginWindow = 30 * time.Minute
|
sshIPLoginWindow = 30 * time.Minute
|
||||||
|
sslAutoRenewAlertSkipDays = 31
|
||||||
)
|
)
|
||||||
|
|
||||||
type AlertTaskHelper struct {
|
type AlertTaskHelper struct {
|
||||||
@@ -529,11 +530,15 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
global.LOG.Errorf("Failed to load ssh login logs: %v", err)
|
global.LOG.Errorf("Failed to load ssh login logs: %v", err)
|
||||||
}
|
}
|
||||||
|
interfaceAddrs, err := net.InterfaceAddrs()
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Warnf("Failed to load local IP addresses for ssh login alert: %v", err)
|
||||||
|
}
|
||||||
count, records := summarizeSSHLoginHistories(
|
count, records := summarizeSSHLoginHistories(
|
||||||
histories,
|
histories,
|
||||||
now,
|
now,
|
||||||
failedWindow,
|
failedWindow,
|
||||||
strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n"),
|
sshSuccessLoginWhitelist(alert.AdvancedParams, interfaceAddrs),
|
||||||
)
|
)
|
||||||
isAlert := count >= int(alert.Count)
|
isAlert := count >= int(alert.Count)
|
||||||
if isAlert {
|
if isAlert {
|
||||||
@@ -569,6 +574,19 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sshSuccessLoginWhitelist(configured string, interfaceAddrs []net.Addr) []string {
|
||||||
|
whitelist := strings.Split(strings.TrimSpace(configured), "\n")
|
||||||
|
whitelist = append(whitelist, "127.0.0.0/8", "::1")
|
||||||
|
for _, addr := range interfaceAddrs {
|
||||||
|
ipNet, ok := addr.(*net.IPNet)
|
||||||
|
if !ok || ipNet.IP == nil || ipNet.IP.IsUnspecified() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
whitelist = append(whitelist, ipNet.IP.String())
|
||||||
|
}
|
||||||
|
return whitelist
|
||||||
|
}
|
||||||
|
|
||||||
func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string) []model.LoginLog {
|
func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string) []model.LoginLog {
|
||||||
filtered := make([]model.LoginLog, 0, len(records))
|
filtered := make([]model.LoginLog, 0, len(records))
|
||||||
for _, record := range records {
|
for _, record := range records {
|
||||||
@@ -864,7 +882,7 @@ func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]s
|
|||||||
daysDiff := int(math.Ceil(
|
daysDiff := int(math.Ceil(
|
||||||
ssl.ExpireDate.Sub(currentDate).Hours() / 24,
|
ssl.ExpireDate.Sub(currentDate).Hours() / 24,
|
||||||
))
|
))
|
||||||
if daysDiff > 0 && int(cycle) >= daysDiff {
|
if daysDiff > 0 && int(cycle) >= daysDiff && !shouldSuppressSSLExpiryAlert(ssl, daysDiff) {
|
||||||
daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], ssl.PrimaryDomain)
|
daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], ssl.PrimaryDomain)
|
||||||
projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate)
|
projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate)
|
||||||
}
|
}
|
||||||
@@ -872,6 +890,10 @@ func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]s
|
|||||||
return daysDiffMap, projectMap
|
return daysDiffMap, projectMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func shouldSuppressSSLExpiryAlert(ssl model.WebsiteSSL, remainingDays int) bool {
|
||||||
|
return ssl.AutoRenew && remainingDays < sslAutoRenewAlertSkipDays
|
||||||
|
}
|
||||||
|
|
||||||
func calculateWebsiteExpiryDays(websites []model.Website, cycle uint) (map[int][]string, map[uint][]time.Time) {
|
func calculateWebsiteExpiryDays(websites []model.Website, cycle uint) (map[int][]string, map[uint][]time.Time) {
|
||||||
currentDate := time.Now()
|
currentDate := time.Now()
|
||||||
daysDiffMap := make(map[int][]string)
|
daysDiffMap := make(map[int][]string)
|
||||||
|
|||||||
@@ -40,14 +40,13 @@ const (
|
|||||||
appUpgradeDown
|
appUpgradeDown
|
||||||
appUpgradeMutated
|
appUpgradeMutated
|
||||||
appUpgradeStarted
|
appUpgradeStarted
|
||||||
appUpgradeReady
|
|
||||||
appUpgradeCommitted
|
appUpgradeCommitted
|
||||||
)
|
)
|
||||||
|
|
||||||
const composeServiceLabel = "com.docker.compose.service"
|
|
||||||
|
|
||||||
var appUpgradeLocks sync.Map
|
var appUpgradeLocks sync.Map
|
||||||
|
|
||||||
|
const composeServiceLabel = "com.docker.compose.service"
|
||||||
|
|
||||||
type appUpgradeSnapshot interface {
|
type appUpgradeSnapshot interface {
|
||||||
Restore() error
|
Restore() error
|
||||||
Cleanup()
|
Cleanup()
|
||||||
@@ -439,15 +438,14 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
|
|||||||
t.LogSuccess(logStr)
|
t.LogSuccess(logStr)
|
||||||
u.phase = appUpgradeStarted
|
u.phase = appUpgradeStarted
|
||||||
|
|
||||||
t.LogStart(i18n.GetMsgByKey("UpgradeWaitReady"))
|
containerNames, discoverErr := discoverUpgradeContainerNames(u.candidate, u.envContent)
|
||||||
containerNames, err := waitAppContainersReady(context.Background(), u.candidate)
|
if discoverErr != nil {
|
||||||
if err != nil {
|
t.Logf("WARNING: discover upgraded application containers failed: %v", discoverErr)
|
||||||
t.LogFailedWithErr(i18n.GetMsgByKey("UpgradeWaitReady"), err)
|
} else if len(containerNames) > 0 {
|
||||||
return err
|
u.candidate.ContainerName = strings.Join(containerNames, ",")
|
||||||
|
} else {
|
||||||
|
t.Log("WARNING: no containers found for the upgraded application")
|
||||||
}
|
}
|
||||||
t.LogSuccess(i18n.GetMsgByKey("UpgradeWaitReady"))
|
|
||||||
u.phase = appUpgradeReady
|
|
||||||
u.candidate.ContainerName = strings.Join(containerNames, ",")
|
|
||||||
u.candidate.Status = constant.StatusRunning
|
u.candidate.Status = constant.StatusRunning
|
||||||
u.candidate.Message = ""
|
u.candidate.Message = ""
|
||||||
|
|
||||||
@@ -472,6 +470,11 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
|
|||||||
} else if err = appInstallRepo.Save(context.Background(), &u.candidate); err != nil {
|
} else if err = appInstallRepo.Save(context.Background(), &u.candidate); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if discoverErr == nil && len(containerNames) > 0 {
|
||||||
|
if syncErr := syncAppInstallStatus(&u.candidate, true); syncErr != nil {
|
||||||
|
t.Logf("WARNING: sync upgraded application status failed: %v", syncErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
u.phase = appUpgradeCommitted
|
u.phase = appUpgradeCommitted
|
||||||
u.deleteOldImages(t)
|
u.deleteOldImages(t)
|
||||||
return nil
|
return nil
|
||||||
@@ -591,9 +594,6 @@ func (u *appUpgradeContext) rollback(t *task.Task) (rollbackErr error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *appUpgradeContext) finishRollback() error {
|
func (u *appUpgradeContext) finishRollback() error {
|
||||||
if _, err := waitAppContainersReady(context.Background(), u.original); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
restored := u.original
|
restored := u.original
|
||||||
if err := appInstallRepo.Save(context.Background(), &restored); err != nil {
|
if err := appInstallRepo.Save(context.Background(), &restored); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -881,28 +881,7 @@ func (s *upgradeFileSnapshot) Cleanup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type appContainerReadinessClient interface {
|
func discoverUpgradeContainerNames(install model.AppInstall, envContent []byte) ([]string, error) {
|
||||||
ContainerList(context.Context, container.ListOptions) ([]container.Summary, error)
|
|
||||||
ContainerInspect(context.Context, string) (container.InspectResponse, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitAppContainersReady(ctx context.Context, install model.AppInstall) ([]string, error) {
|
|
||||||
client, err := docker.NewDockerClient()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
return waitAppContainersReadyWithClient(ctx, client, install)
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitAppContainersReadyWithClient(ctx context.Context, client appContainerReadinessClient, install model.AppInstall) ([]string, error) {
|
|
||||||
envContent, err := os.ReadFile(install.GetEnvPath())
|
|
||||||
if err != nil {
|
|
||||||
envContent, err = renderUpgradeEnv(&install, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
project, err := docker.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), envContent, false)
|
project, err := docker.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), envContent, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -916,36 +895,24 @@ func waitAppContainersReadyWithClient(ctx context.Context, client appContainerRe
|
|||||||
if len(expectedServices) == 0 {
|
if len(expectedServices) == 0 {
|
||||||
return strings.Split(install.ContainerName, ","), nil
|
return strings.Split(install.ContainerName, ","), nil
|
||||||
}
|
}
|
||||||
options := container.ListOptions{
|
client, err := docker.NewDockerClient()
|
||||||
All: true,
|
if err != nil {
|
||||||
Filters: filters.NewArgs(
|
return nil, err
|
||||||
filters.Arg("label", composeWorkdirLabel+"="+install.GetPath()),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
containers, err := client.ContainerList(ctx, options)
|
defer client.Close()
|
||||||
|
containers, err := client.ContainerList(context.Background(), container.ListOptions{
|
||||||
|
All: true,
|
||||||
|
Filters: filters.NewArgs(filters.Arg("label", composeWorkdirLabel+"="+install.GetPath())),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
foundServices := make(map[string]bool, len(expectedServices))
|
|
||||||
containerNames := make([]string, 0, len(containers))
|
containerNames := make([]string, 0, len(containers))
|
||||||
for _, item := range containers {
|
for _, item := range containers {
|
||||||
serviceName := item.Labels[composeServiceLabel]
|
if _, ok := expectedServices[item.Labels[composeServiceLabel]]; ok && len(item.Names) > 0 {
|
||||||
if _, ok := expectedServices[serviceName]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err = waitContainerReady(ctx, client, item.ID); err != nil {
|
|
||||||
return nil, fmt.Errorf("container %s is not ready: %w", serviceName, err)
|
|
||||||
}
|
|
||||||
foundServices[serviceName] = true
|
|
||||||
if len(item.Names) > 0 {
|
|
||||||
containerNames = append(containerNames, strings.TrimPrefix(item.Names[0], "/"))
|
containerNames = append(containerNames, strings.TrimPrefix(item.Names[0], "/"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for serviceName := range expectedServices {
|
|
||||||
if !foundServices[serviceName] {
|
|
||||||
return nil, fmt.Errorf("container for service %s was not created", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(containerNames)
|
sort.Strings(containerNames)
|
||||||
return containerNames, nil
|
return containerNames, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -352,22 +352,34 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
|||||||
if dir != nil {
|
if dir != nil {
|
||||||
logStr := i18n.GetMsgByKey("Stop") + i18n.GetMsgByKey("App")
|
logStr := i18n.GetMsgByKey("Stop") + i18n.GetMsgByKey("App")
|
||||||
t.Log(logStr)
|
t.Log(logStr)
|
||||||
|
cleanupFailed := false
|
||||||
|
|
||||||
if deleteReq.UseLifecycleScripts {
|
if deleteReq.UseLifecycleScripts {
|
||||||
if err = runScript(t, &install, "uninstall"); err != nil {
|
if scriptErr := runScript(t, &install, "uninstall"); scriptErr != nil {
|
||||||
return err
|
cleanupFailed = true
|
||||||
|
if !deleteReq.ForceDelete {
|
||||||
|
return scriptErr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
out, err := compose.Down(install.GetComposePath())
|
out, downErr := compose.Down(install.GetComposePath())
|
||||||
if err != nil && !deleteReq.ForceDelete {
|
if downErr != nil {
|
||||||
return handleErr(install, err, out)
|
cleanupFailed = true
|
||||||
|
if !deleteReq.ForceDelete {
|
||||||
|
return handleErr(install, downErr, out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err = runScript(t, &install, "uninstall"); err != nil {
|
if scriptErr := runScript(t, &install, "uninstall"); scriptErr != nil {
|
||||||
_, _ = compose.Up(install.GetComposePath())
|
cleanupFailed = true
|
||||||
return err
|
if !deleteReq.ForceDelete {
|
||||||
|
_, _ = compose.Up(install.GetComposePath())
|
||||||
|
return scriptErr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.LogSuccess(logStr)
|
if !cleanupFailed {
|
||||||
|
t.LogSuccess(logStr)
|
||||||
|
}
|
||||||
if deleteReq.DeleteImage {
|
if deleteReq.DeleteImage {
|
||||||
content, err := op.GetContent(install.GetEnvPath())
|
content, err := op.GetContent(install.GetEnvPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -466,8 +478,9 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
|||||||
}
|
}
|
||||||
uninstallTask.AddSubTask(task.GetTaskName(install.Name, task.TaskUninstall, task.TaskScopeApp), uninstall, nil)
|
uninstallTask.AddSubTask(task.GetTaskName(install.Name, task.TaskUninstall, task.TaskScopeApp), uninstall, nil)
|
||||||
go func() {
|
go func() {
|
||||||
if err := uninstallTask.Execute(); err != nil && !deleteReq.ForceDelete {
|
if err := uninstallTask.Execute(); err != nil {
|
||||||
install.Status = constant.StatusError
|
install.Status = constant.StatusError
|
||||||
|
install.Message = err.Error()
|
||||||
_ = appInstallRepo.Save(context.Background(), &install)
|
_ = appInstallRepo.Save(context.Background(), &install)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ type ContainerService struct{}
|
|||||||
var containerLogAnsiRegex = regexp.MustCompile("\x1b\\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>")
|
var containerLogAnsiRegex = regexp.MustCompile("\x1b\\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>")
|
||||||
|
|
||||||
type IContainerService interface {
|
type IContainerService interface {
|
||||||
Page(req dto.PageContainer) (int64, interface{}, error)
|
Page(ctx context.Context, req dto.PageContainer) (int64, interface{}, error)
|
||||||
List() []dto.ContainerOptions
|
List() []dto.ContainerOptions
|
||||||
ListByImage(imageName string) []dto.ContainerOptions
|
ListByImage(imageName string) []dto.ContainerOptions
|
||||||
LoadStatus() (dto.ContainerStatus, error)
|
LoadStatus(ctx context.Context, containersOnly bool) (dto.ContainerStatus, error)
|
||||||
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
|
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
|
||||||
ListNetwork() ([]dto.Options, error)
|
ListNetwork() ([]dto.Options, error)
|
||||||
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
||||||
@@ -80,7 +80,7 @@ type IContainerService interface {
|
|||||||
ContainerUpdate(req dto.ContainerOperate) error
|
ContainerUpdate(req dto.ContainerOperate) error
|
||||||
ContainerUpgrade(req dto.ContainerUpgrade) error
|
ContainerUpgrade(req dto.ContainerUpgrade) error
|
||||||
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
||||||
ContainerListStats() ([]dto.ContainerListStats, error)
|
ContainerListStats(ctx context.Context, ids []string) ([]dto.ContainerListStats, error)
|
||||||
ContainerItemStats(ctx context.Context, req dto.OperationWithName) (dto.ContainerItemStats, error)
|
ContainerItemStats(ctx context.Context, req dto.OperationWithName) (dto.ContainerItemStats, error)
|
||||||
LoadResourceLimit() (*dto.ResourceLimit, error)
|
LoadResourceLimit() (*dto.ResourceLimit, error)
|
||||||
ContainerRename(req dto.ContainerRename) error
|
ContainerRename(req dto.ContainerRename) error
|
||||||
@@ -92,6 +92,7 @@ type IContainerService interface {
|
|||||||
|
|
||||||
Inspect(req dto.InspectReq) (string, error)
|
Inspect(req dto.InspectReq) (string, error)
|
||||||
DeleteNetwork(req dto.BatchDelete) error
|
DeleteNetwork(req dto.BatchDelete) error
|
||||||
|
CleanNetworks() (*dto.NetworkCleanupTask, error)
|
||||||
CreateNetwork(req dto.NetworkCreate) error
|
CreateNetwork(req dto.NetworkCreate) error
|
||||||
DeleteVolume(req dto.BatchDelete) error
|
DeleteVolume(req dto.BatchDelete) error
|
||||||
CreateVolume(req dto.VolumeCreate) error
|
CreateVolume(req dto.VolumeCreate) error
|
||||||
@@ -112,7 +113,9 @@ func NewIContainerService() IContainerService {
|
|||||||
return &ContainerService{}
|
return &ContainerService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
|
func (u *ContainerService) Page(ctx context.Context, req dto.PageContainer) (int64, interface{}, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
@@ -123,7 +126,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
|||||||
options.Filters = filters.NewArgs()
|
options.Filters = filters.NewArgs()
|
||||||
options.Filters.Add("label", req.Filters)
|
options.Filters.Add("label", req.Filters)
|
||||||
}
|
}
|
||||||
containers, err := client.ContainerList(context.Background(), options)
|
containers, err := client.ContainerList(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
@@ -206,27 +209,32 @@ func (u *ContainerService) ListByImage(imageName string) []dto.ContainerOptions
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
|
func (u *ContainerService) LoadStatus(ctx context.Context, containersOnly bool) (dto.ContainerStatus, error) {
|
||||||
var data dto.ContainerStatus
|
var data dto.ContainerStatus
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
c := context.Background()
|
c, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
images, _ := client.ImageList(c, image.ListOptions{All: true})
|
if !containersOnly {
|
||||||
data.ImageCount = len(images)
|
images, _ := client.ImageList(c, image.ListOptions{All: true})
|
||||||
repo, _ := imageRepoRepo.List()
|
data.ImageCount = len(images)
|
||||||
data.RepoCount = len(repo)
|
repo, _ := imageRepoRepo.List()
|
||||||
templates, _ := composeRepo.List()
|
data.RepoCount = len(repo)
|
||||||
data.ComposeTemplateCount = len(templates)
|
templates, _ := composeRepo.List()
|
||||||
networks, _ := client.NetworkList(c, network.ListOptions{})
|
data.ComposeTemplateCount = len(templates)
|
||||||
data.NetworkCount = len(networks)
|
networks, _ := client.NetworkList(c, network.ListOptions{})
|
||||||
volumes, _ := client.VolumeList(c, volume.ListOptions{})
|
data.NetworkCount = len(networks)
|
||||||
data.VolumeCount = len(volumes.Volumes)
|
volumes, _ := client.VolumeList(c, volume.ListOptions{})
|
||||||
data.ComposeCount = loadComposeCount(client)
|
data.VolumeCount = len(volumes.Volumes)
|
||||||
containers, _ := client.ContainerList(c, container.ListOptions{All: true})
|
data.ComposeCount = loadComposeCount(c, client)
|
||||||
|
}
|
||||||
|
containers, err := client.ContainerList(c, container.ListOptions{All: true})
|
||||||
|
if err != nil {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
data.ContainerCount = len(containers)
|
data.ContainerCount = len(containers)
|
||||||
for _, item := range containers {
|
for _, item := range containers {
|
||||||
switch item.State {
|
switch item.State {
|
||||||
@@ -292,27 +300,67 @@ func (u *ContainerService) ContainerItemStats(ctx context.Context, req dto.Opera
|
|||||||
}
|
}
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) {
|
func (u *ContainerService) ContainerListStats(ctx context.Context, ids []string) ([]dto.ContainerListStats, error) {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
list, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
options := container.ListOptions{All: true}
|
||||||
|
if ids != nil {
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return []dto.ContainerListStats{}, nil
|
||||||
|
}
|
||||||
|
options.Filters = filters.NewArgs()
|
||||||
|
for _, id := range ids {
|
||||||
|
options.Filters.Add("id", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list, err := client.ContainerList(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return collectContainerStats(ctx, list, func(ctx context.Context, id string) dto.ContainerListStats {
|
||||||
|
return loadCpuAndMem(ctx, client, id)
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A fixed worker pool bounds Docker stats requests, including for legacy callers
|
||||||
|
// that request all containers. Stopped containers do not need a stats sample.
|
||||||
|
func collectContainerStats(ctx context.Context, list []container.Summary, load func(context.Context, string) dto.ContainerListStats) []dto.ContainerListStats {
|
||||||
datas := make([]dto.ContainerListStats, len(list))
|
datas := make([]dto.ContainerListStats, len(list))
|
||||||
var wg sync.WaitGroup
|
for i, item := range list {
|
||||||
wg.Add(len(list))
|
datas[i].ContainerID = item.ID
|
||||||
for i := 0; i < len(list); i++ {
|
|
||||||
go func(index int, item container.Summary) {
|
|
||||||
datas[index] = loadCpuAndMem(client, item.ID)
|
|
||||||
wg.Done()
|
|
||||||
}(i, list[i])
|
|
||||||
}
|
}
|
||||||
|
jobs := make(chan int)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for worker := 0; worker < min(8, len(list)); worker++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for index := range jobs {
|
||||||
|
if ctx.Err() != nil || list[index].State != "running" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sampleCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||||
|
datas[index] = load(sampleCtx, list[index].ID)
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
dispatch:
|
||||||
|
for index := range list {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
break dispatch
|
||||||
|
case jobs <- index:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(jobs)
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
return datas, nil
|
return datas
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||||
@@ -484,6 +532,10 @@ func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bool) error {
|
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bool) error {
|
||||||
|
return u.containerCreate(req, inThread, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) containerCreate(req dto.ContainerOperate, inThread bool, taskName string) error {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -497,7 +549,10 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
|||||||
return buserr.New("ErrContainerName")
|
return buserr.New("ErrContainerName")
|
||||||
}
|
}
|
||||||
|
|
||||||
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
if taskName == "" {
|
||||||
|
taskName = task.GetTaskName(req.Name, task.TaskCreate, task.TaskScopeContainer)
|
||||||
|
}
|
||||||
|
taskItem, err := task.NewTask(taskName, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
unlock()
|
unlock()
|
||||||
_ = client.Close()
|
_ = client.Close()
|
||||||
@@ -558,6 +613,11 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
if inThread {
|
if inThread {
|
||||||
|
if err := taskItem.Prepare(); err != nil {
|
||||||
|
unlock()
|
||||||
|
_ = client.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
defer unlock()
|
defer unlock()
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
@@ -1715,11 +1775,11 @@ func selectImageRepo(imageName string, repos []model.ImageRepo) *model.ImageRepo
|
|||||||
return selected
|
return selected
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadCpuAndMem(client *client.Client, containerItem string) dto.ContainerListStats {
|
func loadCpuAndMem(ctx context.Context, client *client.Client, containerItem string) dto.ContainerListStats {
|
||||||
data := dto.ContainerListStats{
|
data := dto.ContainerListStats{
|
||||||
ContainerID: containerItem,
|
ContainerID: containerItem,
|
||||||
}
|
}
|
||||||
res, err := client.ContainerStats(context.Background(), containerItem, false)
|
res, err := client.ContainerStats(ctx, containerItem, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
@@ -1942,11 +2002,11 @@ func transPortToStr(ports []container.Port) []string {
|
|||||||
return docker.SimplifyPorts(ports)
|
return docker.SimplifyPorts(ports)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadComposeCount(client *client.Client) int {
|
func loadComposeCount(ctx context.Context, client *client.Client) int {
|
||||||
options := container.ListOptions{All: true}
|
options := container.ListOptions{All: true}
|
||||||
options.Filters = filters.NewArgs()
|
options.Filters = filters.NewArgs()
|
||||||
options.Filters.Add("label", composeProjectLabel)
|
options.Filters.Add("label", composeProjectLabel)
|
||||||
list, err := client.ContainerList(context.Background(), options)
|
list, err := client.ContainerList(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||||
|
)
|
||||||
|
|
||||||
|
var networkCleanupMu sync.Mutex
|
||||||
|
|
||||||
|
func (u *ContainerService) CleanNetworks() (*dto.NetworkCleanupTask, error) {
|
||||||
|
taskItem, err := task.NewTaskWithOps(i18n.GetMsgByKey("Network"), task.TaskClean, task.TaskScopeContainer, "", 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error {
|
||||||
|
networkCleanupMu.Lock()
|
||||||
|
defer networkCleanupMu.Unlock()
|
||||||
|
if err := t.TaskCtx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cli, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
return executeNetworkCleanup(t, cli)
|
||||||
|
}, nil)
|
||||||
|
go func() {
|
||||||
|
if err := taskItem.Execute(); err != nil {
|
||||||
|
global.LOG.Errorf("network cleanup task %s failed: %v", taskItem.TaskID, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return &dto.NetworkCleanupTask{TaskID: taskItem.TaskID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeNetworkCleanup(t *task.Task, cli docker.NetworkCleanupClient) error {
|
||||||
|
t.Log(i18n.GetMsgByKey("PruneStart"))
|
||||||
|
report, err := docker.CleanUnusedNetworks(t.TaskCtx, cli, func(status string, item dto.NetworkCleanupItem) {
|
||||||
|
key := "NetworkCleanupDeleted"
|
||||||
|
if status == "skipped" || status == "failed" {
|
||||||
|
key = map[string]string{
|
||||||
|
"protected": "NetworkCleanupProtected",
|
||||||
|
"container_connected": "NetworkCleanupConnected",
|
||||||
|
"network_in_use": "NetworkCleanupConnected",
|
||||||
|
"unsupported_network": "NetworkCleanupUnsupported",
|
||||||
|
"already_removed": "NetworkCleanupGone",
|
||||||
|
"inspect_failed": "NetworkCleanupInspectFailed",
|
||||||
|
"remove_failed": "NetworkCleanupRemoveFailed",
|
||||||
|
}[item.Reason]
|
||||||
|
}
|
||||||
|
t.Log(i18n.GetMsgWithMap(key, map[string]interface{}{"name": item.Name, "id": item.ID}))
|
||||||
|
})
|
||||||
|
if report != nil {
|
||||||
|
t.Log(i18n.GetMsgWithMap("NetworkCleanupSummary", map[string]interface{}{
|
||||||
|
"deleted": len(report.Deleted), "skipped": len(report.Skipped), "failed": len(report.Failed),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(report.Failed) > 0 {
|
||||||
|
return fmt.Errorf("%s", i18n.GetMsgByKey("NetworkCleanupPartialFailure"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
|
"github.com/glebarez/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type networkTaskClient struct{ fail bool }
|
||||||
|
|
||||||
|
func (f networkTaskClient) NetworkList(context.Context, network.ListOptions) ([]network.Inspect, error) {
|
||||||
|
return []network.Inspect{{ID: "reserved", Name: "1panel-network", Scope: "local"}, {ID: "connected", Name: "busy", Scope: "local"}, {ID: "unused", Name: "free", Scope: "local"}, {ID: "race", Name: "race", Scope: "local"}}, nil
|
||||||
|
}
|
||||||
|
func (f networkTaskClient) ContainerList(context.Context, container.ListOptions) ([]container.Summary, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (f networkTaskClient) NetworkInspect(_ context.Context, id string, _ network.InspectOptions) (network.Inspect, error) {
|
||||||
|
n := network.Inspect{}
|
||||||
|
if id == "connected" {
|
||||||
|
n.Containers = map[string]network.EndpointResource{"container-id": {}}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
func (f networkTaskClient) NetworkRemove(_ context.Context, id string) error {
|
||||||
|
if id == "race" {
|
||||||
|
return errdefs.Conflict(errors.New("has active endpoints"))
|
||||||
|
}
|
||||||
|
if id != "unused" {
|
||||||
|
return errors.New("unexpected removal")
|
||||||
|
}
|
||||||
|
if f.fail {
|
||||||
|
return errors.New("remove failed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkCleanupTaskPersistsLogsAndStatus(t *testing.T) {
|
||||||
|
oldDB, oldTaskDB, oldDir, oldI18n := global.DB, global.TaskDB, global.Dir, global.I18n
|
||||||
|
t.Cleanup(func() { global.DB = oldDB; global.TaskDB = oldTaskDB; global.Dir = oldDir; global.I18n = oldI18n })
|
||||||
|
dir := t.TempDir()
|
||||||
|
db, err := gorm.Open(sqlite.Open(filepath.Join(dir, "tasks.db")), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sqlDB, err := db.DB()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer sqlDB.Close()
|
||||||
|
if err := db.AutoMigrate(&model.Task{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
global.DB = nil
|
||||||
|
global.TaskDB = db
|
||||||
|
global.Dir.TaskDir = dir
|
||||||
|
i18n.Init()
|
||||||
|
for _, fail := range []bool{false, true} {
|
||||||
|
name := "success"
|
||||||
|
wantStatus := constant.StatusSuccess
|
||||||
|
if fail {
|
||||||
|
name = "partial failure"
|
||||||
|
wantStatus = constant.StatusFailed
|
||||||
|
}
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
item, err := task.NewTaskWithOps("Network", task.TaskClean, task.TaskScopeContainer, "", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
item.AddSubTask("Clean", func(t *task.Task) error { return executeNetworkCleanup(t, networkTaskClient{fail: fail}) }, nil)
|
||||||
|
err = item.Execute()
|
||||||
|
if (err != nil) != fail {
|
||||||
|
t.Fatalf("unexpected execution error: %v", err)
|
||||||
|
}
|
||||||
|
var saved model.Task
|
||||||
|
if err := db.First(&saved, "id = ?", item.TaskID).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if saved.Status != wantStatus {
|
||||||
|
t.Fatalf("status %s, want %s", saved.Status, wantStatus)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(saved.LogFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, want := range []string{"[busy] (connected): containers connected", "[race] (race): containers connected", "[1panel-network] (reserved): reserved network", "Network cleanup finished:", "[TASK-END]"} {
|
||||||
|
if !strings.Contains(string(content), want) {
|
||||||
|
t.Fatalf("missing %q in log: %s", want, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
want := "Deleted network [free]"
|
||||||
|
if fail {
|
||||||
|
want = "Failed to remove network [free]"
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(content), want) {
|
||||||
|
t.Fatalf("missing %q", want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -75,6 +76,7 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
|
|||||||
EntryID: cronjob.ID,
|
EntryID: cronjob.ID,
|
||||||
}
|
}
|
||||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
|
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
|
||||||
|
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||||
if alertInfo.SendCount != 0 {
|
if alertInfo.SendCount != 0 {
|
||||||
item.AlertCount = alertInfo.SendCount
|
item.AlertCount = alertInfo.SendCount
|
||||||
} else {
|
} else {
|
||||||
@@ -98,9 +100,11 @@ func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, err
|
|||||||
AlertType: cronjob.Type,
|
AlertType: cronjob.Type,
|
||||||
EntryID: cronjob.ID,
|
EntryID: cronjob.ID,
|
||||||
}
|
}
|
||||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
|
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))))
|
||||||
item.AlertMethod = alertInfo.Method
|
item.AlertMethod = alertInfo.Method
|
||||||
if alertInfo.SendCount != 0 {
|
item.AlertTitle = alertInfo.Title
|
||||||
|
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||||
|
if alertInfo.Status == constant.AlertEnable {
|
||||||
item.AlertCount = alertInfo.SendCount
|
item.AlertCount = alertInfo.SendCount
|
||||||
} else {
|
} else {
|
||||||
item.AlertCount = 0
|
item.AlertCount = 0
|
||||||
@@ -195,11 +199,12 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID)
|
item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID)
|
||||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))), repo.WithByStatus(constant.AlertEnable))
|
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))))
|
||||||
if alertInfo.SendCount != 0 {
|
item.AlertTitle = alertInfo.Title
|
||||||
|
item.AlertMethod = alertInfo.Method
|
||||||
|
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||||
|
if alertInfo.Status == constant.AlertEnable {
|
||||||
item.AlertCount = alertInfo.SendCount
|
item.AlertCount = alertInfo.SendCount
|
||||||
item.AlertTitle = alertInfo.Title
|
|
||||||
item.AlertMethod = alertInfo.Method
|
|
||||||
} else {
|
} else {
|
||||||
item.AlertCount = 0
|
item.AlertCount = 0
|
||||||
}
|
}
|
||||||
@@ -213,6 +218,17 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
||||||
|
for _, item := range req {
|
||||||
|
advanced, err := cronJobAlertAdvancedParams(item.AlertTriggerMode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if item.AlertCount != 0 {
|
||||||
|
if err := (AlertService{}).validateCronJobAlertChannels(item.Type, advanced, item.AlertMethod); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, item := range req {
|
for _, item := range req {
|
||||||
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
|
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
|
||||||
if cronjobItem.ID != 0 {
|
if cronjobItem.ID != 0 {
|
||||||
@@ -395,17 +411,27 @@ func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
|||||||
} else {
|
} else {
|
||||||
cronjob.Status = constant.StatusDisable
|
cronjob.Status = constant.StatusDisable
|
||||||
}
|
}
|
||||||
_ = cronjobRepo.Create(&cronjob)
|
if err := cronjobRepo.Create(&cronjob); err != nil {
|
||||||
if item.AlertCount != 0 && item.AlertTitle != "" && item.AlertMethod != "" {
|
return err
|
||||||
|
}
|
||||||
|
if item.AlertTitle != "" && item.AlertMethod != "" {
|
||||||
|
advanced, _ := cronJobAlertAdvancedParams(item.AlertTriggerMode)
|
||||||
|
status := constant.AlertEnable
|
||||||
|
if item.AlertCount == 0 {
|
||||||
|
status = constant.AlertDisable
|
||||||
|
}
|
||||||
createAlert := dto.AlertCreate{
|
createAlert := dto.AlertCreate{
|
||||||
Title: item.AlertTitle,
|
Title: item.AlertTitle,
|
||||||
SendCount: item.AlertCount,
|
SendCount: item.AlertCount,
|
||||||
Method: item.AlertMethod,
|
Method: item.AlertMethod,
|
||||||
Type: cronjob.Type,
|
Type: cronjob.Type,
|
||||||
Project: strconv.Itoa(int(cronjob.ID)),
|
Project: strconv.Itoa(int(cronjob.ID)),
|
||||||
Status: constant.AlertEnable,
|
Status: status,
|
||||||
|
AdvancedParams: advanced,
|
||||||
|
}
|
||||||
|
if err := NewIAlertService().CreateAlert(createAlert, operator); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
_ = NewIAlertService().CreateAlert(createAlert, operator)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -562,6 +588,15 @@ func (u *CronjobService) HandleOnce(id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
|
func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
|
||||||
|
advanced, err := cronJobAlertAdvancedParams(req.AlertTriggerMode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if req.AlertCount != 0 {
|
||||||
|
if err := (AlertService{}).validateCronJobAlertChannels(req.Type, advanced, req.AlertMethod); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name))
|
cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name))
|
||||||
if cronjob.ID != 0 {
|
if cronjob.ID != 0 {
|
||||||
return buserr.New("ErrRecordExist")
|
return buserr.New("ErrRecordExist")
|
||||||
@@ -603,12 +638,13 @@ func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
|
|||||||
}
|
}
|
||||||
if req.AlertCount != 0 && req.AlertTitle != "" && req.AlertMethod != "" {
|
if req.AlertCount != 0 && req.AlertTitle != "" && req.AlertMethod != "" {
|
||||||
createAlert := dto.AlertCreate{
|
createAlert := dto.AlertCreate{
|
||||||
Title: req.AlertTitle,
|
Title: req.AlertTitle,
|
||||||
SendCount: req.AlertCount,
|
SendCount: req.AlertCount,
|
||||||
Method: req.AlertMethod,
|
Method: req.AlertMethod,
|
||||||
Type: cronjob.Type,
|
Type: cronjob.Type,
|
||||||
Project: strconv.Itoa(int(cronjob.ID)),
|
Project: strconv.Itoa(int(cronjob.ID)),
|
||||||
Status: constant.AlertEnable,
|
Status: constant.AlertEnable,
|
||||||
|
AdvancedParams: advanced,
|
||||||
}
|
}
|
||||||
err := NewIAlertService().CreateAlert(createAlert, operator)
|
err := NewIAlertService().CreateAlert(createAlert, operator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -682,6 +718,10 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string) error {
|
func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string) error {
|
||||||
|
advanced, err := cronJobAlertAdvancedParams(req.AlertTriggerMode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
var cronjob model.Cronjob
|
var cronjob model.Cronjob
|
||||||
if err := copier.Copy(&cronjob, &req); err != nil {
|
if err := copier.Copy(&cronjob, &req); err != nil {
|
||||||
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
@@ -697,6 +737,20 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return buserr.New("ErrRecordNotFound")
|
return buserr.New("ErrRecordNotFound")
|
||||||
}
|
}
|
||||||
|
if req.AlertCount != 0 {
|
||||||
|
previous, _ := alertRepo.Get(alertRepo.WithByType(cronModel.Type), alertRepo.WithByProject(strconv.Itoa(int(id))))
|
||||||
|
merged, err := prepareCronJobAlertParams(cronModel.Type, previous.AdvancedParams, advanced)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
method := req.AlertMethod
|
||||||
|
if method == "" {
|
||||||
|
method = previous.Method
|
||||||
|
}
|
||||||
|
if err := (AlertService{}).validateCronJobAlertChannels(cronModel.Type, merged, method); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
upMap := make(map[string]interface{})
|
upMap := make(map[string]interface{})
|
||||||
cronjob.EntryIDs = cronModel.EntryIDs
|
cronjob.EntryIDs = cronModel.EntryIDs
|
||||||
cronjob.Type = cronModel.Type
|
cronjob.Type = cronModel.Type
|
||||||
@@ -753,11 +807,12 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
updateAlert := dto.AlertCreate{
|
updateAlert := dto.AlertCreate{
|
||||||
Title: req.AlertTitle,
|
Title: req.AlertTitle,
|
||||||
SendCount: req.AlertCount,
|
SendCount: req.AlertCount,
|
||||||
Method: req.AlertMethod,
|
Method: req.AlertMethod,
|
||||||
Type: cronjob.Type,
|
Type: cronjob.Type,
|
||||||
Project: strconv.Itoa(int(cronModel.ID)),
|
Project: strconv.Itoa(int(cronModel.ID)),
|
||||||
|
AdvancedParams: advanced,
|
||||||
}
|
}
|
||||||
err = NewIAlertService().ExternalUpdateAlert(updateAlert, operator)
|
err = NewIAlertService().ExternalUpdateAlert(updateAlert, operator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -766,6 +821,17 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cronJobAlertAdvancedParams(mode string) (string, error) {
|
||||||
|
if mode == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(map[string]string{"alertTriggerMode": mode})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return alertUtil.MergeCronJobAlertParams("", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
func (u *CronjobService) UpdateStatus(id uint, status string) error {
|
func (u *CronjobService) UpdateStatus(id uint, status string) error {
|
||||||
cronjob, _ := cronjobRepo.Get(repo.WithByID(id))
|
cronjob, _ := cronjobRepo.Get(repo.WithByID(id))
|
||||||
if cronjob.ID == 0 {
|
if cronjob.ID == 0 {
|
||||||
|
|||||||
@@ -412,6 +412,7 @@ func addSkipTask(source string, taskItem *task.Task) {
|
|||||||
taskItem.Log(i18n.GetMsgByKey("NoSuchResource"))
|
taskItem.Log(i18n.GetMsgByKey("NoSuchResource"))
|
||||||
return nil
|
return nil
|
||||||
}, nil)
|
}, nil)
|
||||||
|
taskItem.SubTasks[len(taskItem.SubTasks)-1].StepAlias = cronJobSkippedStep
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -23,6 +24,7 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
|
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ntp"
|
"github.com/1Panel-dev/1Panel/agent/utils/ntp"
|
||||||
@@ -56,10 +58,11 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
|||||||
_ = taskRepo.Save(context.Background(), taskItem.Task)
|
_ = taskRepo.Save(context.Background(), taskItem.Task)
|
||||||
}
|
}
|
||||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||||
handleCronJobAlert(cronjob)
|
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
||||||
|
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, nil))
|
||||||
}()
|
}()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -70,19 +73,20 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
|||||||
record.TaskID = ""
|
record.TaskID = ""
|
||||||
}
|
}
|
||||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||||
handleCronJobAlert(cronjob)
|
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
if err := taskItem.Execute(); err != nil {
|
if err := taskItem.Execute(); err != nil {
|
||||||
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
storedTask, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||||
if len(taskItem.ID) == 0 {
|
if len(storedTask.ID) == 0 {
|
||||||
record.TaskID = ""
|
record.TaskID = ""
|
||||||
}
|
}
|
||||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||||
handleCronJobAlert(cronjob)
|
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||||
} else {
|
} else {
|
||||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
||||||
|
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, nil))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -482,8 +486,33 @@ func hasBackup(cronjobType string) bool {
|
|||||||
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log" || cronjobType == "cutWebsiteLog"
|
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log" || cronjobType == "cutWebsiteLog"
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCronJobAlert(cronjob *model.Cronjob) {
|
const cronJobSkippedStep = "cronjob-skipped"
|
||||||
|
|
||||||
|
func cronJobAlertResult(taskItem *task.Task, err error) string {
|
||||||
|
if errors.Is(err, context.Canceled) || taskItem.Task.Status == constant.StatusCanceled ||
|
||||||
|
(taskItem.TaskCtx != nil && taskItem.TaskCtx.Err() != nil) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return alertUtil.CronJobAlertFailed
|
||||||
|
}
|
||||||
|
if taskItem.Task.Status != constant.StatusSuccess {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for _, subTask := range taskItem.SubTasks {
|
||||||
|
if subTask.StepAlias != cronJobSkippedStep {
|
||||||
|
return alertUtil.CronJobAlertSuccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCronJobAlert(cronjob *model.Cronjob, result string) {
|
||||||
|
if result == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
pushAlert := dto.PushAlert{
|
pushAlert := dto.PushAlert{
|
||||||
|
Result: result,
|
||||||
TaskName: cronjob.Name,
|
TaskName: cronjob.Name,
|
||||||
AlertType: cronjob.Type,
|
AlertType: cronjob.Type,
|
||||||
EntryID: cronjob.ID,
|
EntryID: cronjob.ID,
|
||||||
|
|||||||
@@ -464,12 +464,14 @@ func loadDiskInfo() []dto.DiskInfo {
|
|||||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
global.LOG.Errorf("load disk info with df -hT -P failed, err: %v", err)
|
||||||
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
|
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
|
||||||
stdout, err = cmdMgr2.RunPipe(
|
stdout, err = cmdMgr2.RunPipe(
|
||||||
cmd.PipeCommand{Name: "df", Args: []string{"-lhT", "-P"}},
|
cmd.PipeCommand{Name: "df", Args: []string{"-lhT", "-P"}},
|
||||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
global.LOG.Errorf("load disk info with df -lhT -P failed, err: %v", err)
|
||||||
return datas
|
return datas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -586,6 +588,9 @@ func loadAcceleratorInfo() ([]dto.GPUInfo, []dto.NPUInfo, []dto.XPUInfo) {
|
|||||||
xpuData []dto.XPUInfo
|
xpuData []dto.XPUInfo
|
||||||
)
|
)
|
||||||
for _, device := range snapshot.Devices {
|
for _, device := range snapshot.Devices {
|
||||||
|
if device.ParentID != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
switch device.Kind {
|
switch device.Kind {
|
||||||
case accelerator.KindGPU:
|
case accelerator.KindGPU:
|
||||||
if device.GPU == nil {
|
if device.GPU == nil {
|
||||||
@@ -595,7 +600,11 @@ func loadAcceleratorInfo() ([]dto.GPUInfo, []dto.NPUInfo, []dto.XPUInfo) {
|
|||||||
if err := copier.Copy(&dataItem, device.GPU); err != nil {
|
if err := copier.Copy(&dataItem, device.GPU); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dataItem.PowerUsage = dataItem.PowerDraw + " / " + dataItem.MaxPowerLimit
|
dataItem.MaxPowerLimit = device.GPU.PowerLimit
|
||||||
|
dataItem.PowerUsage = dataItem.PowerDraw
|
||||||
|
if dataItem.MaxPowerLimit != "" {
|
||||||
|
dataItem.PowerUsage += " / " + dataItem.MaxPowerLimit
|
||||||
|
}
|
||||||
dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal
|
dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal
|
||||||
gpuData = append(gpuData, dataItem)
|
gpuData = append(gpuData, dataItem)
|
||||||
case accelerator.KindNPU:
|
case accelerator.KindNPU:
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
@@ -23,6 +29,11 @@ import (
|
|||||||
|
|
||||||
type RedisService struct{}
|
type RedisService struct{}
|
||||||
|
|
||||||
|
const redisCliTaskName = "RedisCliEnable"
|
||||||
|
|
||||||
|
// The CLI container is shared by all remote Redis databases on this node.
|
||||||
|
var redisCliInstallMutex sync.Mutex
|
||||||
|
|
||||||
type IRedisService interface {
|
type IRedisService interface {
|
||||||
UpdateConf(req dto.RedisConfUpdate) error
|
UpdateConf(req dto.RedisConfUpdate) error
|
||||||
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
|
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
|
||||||
@@ -33,7 +44,8 @@ type IRedisService interface {
|
|||||||
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
|
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
|
||||||
|
|
||||||
CheckHasCli() bool
|
CheckHasCli() bool
|
||||||
InstallCli() error
|
InstallCli(req dto.RedisCliInstall) (*dto.RedisCliStatus, error)
|
||||||
|
LoadCliStatus() (*dto.RedisCliStatus, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIRedisService() IRedisService {
|
func NewIRedisService() IRedisService {
|
||||||
@@ -71,20 +83,62 @@ func (u *RedisService) CheckHasCli() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, item := range containerLists {
|
for _, item := range containerLists {
|
||||||
if strings.ReplaceAll(item.Names[0], "/", "") == "1Panel-redis-cli-tools" {
|
if len(item.Names) > 0 && strings.TrimPrefix(item.Names[0], "/") == "1Panel-redis-cli-tools" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *RedisService) InstallCli() error {
|
func (u *RedisService) LoadCliStatus() (*dto.RedisCliStatus, error) {
|
||||||
|
result := &dto.RedisCliStatus{}
|
||||||
|
latest, err := taskRepo.GetFirst(repo.WithByName(redisCliTaskName), repo.WithByType(task.TaskScopeContainer), repo.WithOrderDesc("created_at"))
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
result.Installed = u.CheckHasCli()
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result.TaskID = latest.ID
|
||||||
|
result.Status = latest.Status
|
||||||
|
result.ErrorMsg = latest.ErrorMsg
|
||||||
|
result.Installed = u.CheckHasCli()
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RedisService) InstallCli(req dto.RedisCliInstall) (*dto.RedisCliStatus, error) {
|
||||||
|
if !redisCliInstallMutex.TryLock() {
|
||||||
|
return nil, buserr.New("TaskIsExecuting")
|
||||||
|
}
|
||||||
|
defer redisCliInstallMutex.Unlock()
|
||||||
|
status, err := u.LoadCliStatus()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if status.Status == constant.StatusExecuting || status.Installed {
|
||||||
|
return status, nil
|
||||||
|
}
|
||||||
|
if req.TaskID == "" {
|
||||||
|
req.TaskID = uuid.NewString()
|
||||||
|
}
|
||||||
|
// Never reuse an existing task ID: doing so would truncate its log.
|
||||||
|
if _, err := taskRepo.GetFirst(taskRepo.WithByID(req.TaskID)); !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, buserr.New("TaskIsExecuting")
|
||||||
|
}
|
||||||
item := dto.ContainerOperate{
|
item := dto.ContainerOperate{
|
||||||
|
TaskID: req.TaskID,
|
||||||
Name: "1Panel-redis-cli-tools",
|
Name: "1Panel-redis-cli-tools",
|
||||||
Image: "redis:7.4.4",
|
Image: "redis:7.4.4",
|
||||||
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
|
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
|
||||||
}
|
}
|
||||||
return NewIContainerService().ContainerCreate(item, false)
|
if err := (&ContainerService{}).containerCreate(item, true, redisCliTaskName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &dto.RedisCliStatus{TaskID: req.TaskID, Status: constant.StatusExecuting}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||||
|
|
||||||
|
dockerfirewall "github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dockerNftablesMinVersion = "29.0.0"
|
const dockerNftablesMinVersion = "29.0.0"
|
||||||
@@ -82,6 +84,11 @@ func (u *DockerService) UpdateFirewallBackend(backend string) error {
|
|||||||
if backend == constant.FirewallProviderNftables && !dockerNftablesSupported(version) {
|
if backend == constant.FirewallProviderNftables && !dockerNftablesSupported(version) {
|
||||||
return fmt.Errorf("Docker Engine %s or later is required for the nftables firewall backend", dockerNftablesMinVersion)
|
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)
|
original, readErr := os.ReadFile(constant.DaemonJsonPath)
|
||||||
existed := readErr == nil
|
existed := readErr == nil
|
||||||
@@ -276,7 +283,8 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate, withRestart bool) erro
|
|||||||
delete(daemonMap, "ipv6")
|
delete(daemonMap, "ipv6")
|
||||||
delete(daemonMap, "fixed-cidr-v6")
|
delete(daemonMap, "fixed-cidr-v6")
|
||||||
delete(daemonMap, "ip6tables")
|
delete(daemonMap, "ip6tables")
|
||||||
if configuredDockerFirewallBackend() != constant.FirewallProviderNftables {
|
backend, _ := settingRepo.GetValueByKey(constant.FirewallDockerBackendKey)
|
||||||
|
if !strings.EqualFold(strings.TrimSpace(backend), constant.FirewallProviderNftables) {
|
||||||
delete(daemonMap, "experimental")
|
delete(daemonMap, "experimental")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,9 @@ var (
|
|||||||
clamRepo = repo.NewIClamRepo()
|
clamRepo = repo.NewIClamRepo()
|
||||||
monitorRepo = repo.NewIMonitorRepo()
|
monitorRepo = repo.NewIMonitorRepo()
|
||||||
|
|
||||||
settingRepo = repo.NewISettingRepo()
|
settingRepo = repo.NewISettingRepo()
|
||||||
backupRepo = repo.NewIBackupRepo()
|
forwardingRuleRepo = repo.NewIForwardingRuleRepo()
|
||||||
|
backupRepo = repo.NewIBackupRepo()
|
||||||
|
|
||||||
websiteRepo = repo.NewIWebsiteRepo()
|
websiteRepo = repo.NewIWebsiteRepo()
|
||||||
websiteDomainRepo = repo.NewIWebsiteDomainRepo()
|
websiteDomainRepo = repo.NewIWebsiteDomainRepo()
|
||||||
|
|||||||
@@ -439,13 +439,15 @@ func (f *FileService) Compress(c request.FileCompress) error {
|
|||||||
if err := preflightCompressTool(files.CompressType(c.Type)); err != nil {
|
if err := preflightCompressTool(files.CompressType(c.Type)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
taskItem, err := task.NewTask(c.Name, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
taskName := i18n.GetMsgWithMap("FileTaskCompress", map[string]interface{}{"dst": strconv.Quote(filepath.Join(c.Dst, c.Name))})
|
||||||
|
taskItem, err := task.NewTask(taskName, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
taskItem.AddSubTask(c.Name, func(t *task.Task) error {
|
taskItem.AddSubTask(taskName, func(t *task.Task) error {
|
||||||
t.LogStart(c.Name)
|
logFileTaskSources(t, c.Files)
|
||||||
|
t.Log(i18n.GetMsgWithMap("FileTaskFormat", map[string]interface{}{"format": strconv.Quote(c.Type)}))
|
||||||
compressType := files.CompressType(c.Type)
|
compressType := files.CompressType(c.Type)
|
||||||
dstFile := filepath.Join(c.Dst, c.Name)
|
dstFile := filepath.Join(c.Dst, c.Name)
|
||||||
success := false
|
success := false
|
||||||
@@ -516,13 +518,15 @@ func (f *FileService) DeCompress(c request.FileDeCompress) error {
|
|||||||
if err := preflightDecompressTool(files.CompressType(c.Type)); err != nil {
|
if err := preflightDecompressTool(files.CompressType(c.Type)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
taskItem, err := task.NewTask(c.Path, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
taskName := i18n.GetMsgWithMap("FileTaskDecompress", map[string]interface{}{"dst": strconv.Quote(c.Dst)})
|
||||||
|
taskItem, err := task.NewTask(taskName, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
taskItem.AddSubTask(c.Path, func(t *task.Task) error {
|
taskItem.AddSubTask(taskName, func(t *task.Task) error {
|
||||||
t.LogStart(c.Path)
|
logFileTaskSources(t, []string{c.Path})
|
||||||
|
t.Log(i18n.GetMsgWithMap("FileTaskFormat", map[string]interface{}{"format": strconv.Quote(c.Type)}))
|
||||||
dstExisted := fo.Stat(c.Dst)
|
dstExisted := fo.Stat(c.Dst)
|
||||||
parentDir := filepath.Dir(c.Dst)
|
parentDir := filepath.Dir(c.Dst)
|
||||||
if !fo.Stat(parentDir) {
|
if !fo.Stat(parentDir) {
|
||||||
@@ -896,6 +900,7 @@ func (f *FileService) Wget(w request.FileWget) (string, error) {
|
|||||||
key := "file-wget-" + common.GetUuid()
|
key := "file-wget-" + common.GetUuid()
|
||||||
options := files.DownloadOptions{
|
options := files.DownloadOptions{
|
||||||
IgnoreCertificate: w.IgnoreCertificate,
|
IgnoreCertificate: w.IgnoreCertificate,
|
||||||
|
UseServerFilename: w.UseServerFilename,
|
||||||
}
|
}
|
||||||
if w.UseProxy {
|
if w.UseProxy {
|
||||||
systemProxy, err := NewISettingService().GetSystemProxy()
|
systemProxy, err := NewISettingService().GetSystemProxy()
|
||||||
@@ -913,6 +918,12 @@ func (f *FileService) Wget(w request.FileWget) (string, error) {
|
|||||||
return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key, options)
|
return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func logFileTaskSources(t *task.Task, sources []string) {
|
||||||
|
for _, source := range sources {
|
||||||
|
t.Log(i18n.GetMsgWithMap("FileTaskSource", map[string]interface{}{"path": strconv.Quote(source)}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FileService) MvFile(m request.FileMove) error {
|
func (f *FileService) MvFile(m request.FileMove) error {
|
||||||
fo := files.NewFileOp()
|
fo := files.NewFileOp()
|
||||||
if err := validateFileMove(fo, m); err != nil {
|
if err := validateFileMove(fo, m); err != nil {
|
||||||
@@ -924,15 +935,24 @@ func (f *FileService) MvFile(m request.FileMove) error {
|
|||||||
if !fileTransferLocks.Acquire(m.TaskID, getFileTransferPaths(m)) {
|
if !fileTransferLocks.Acquire(m.TaskID, getFileTransferPaths(m)) {
|
||||||
return buserr.New("TaskIsExecuting")
|
return buserr.New("TaskIsExecuting")
|
||||||
}
|
}
|
||||||
taskItem, err := task.NewTask(m.NewPath, task.TaskExec, task.TaskScopeTask, m.TaskID, 1)
|
nameKey := "FileTaskCopy"
|
||||||
|
if m.Type == "cut" {
|
||||||
|
nameKey = "FileTaskMove"
|
||||||
|
}
|
||||||
|
taskName := i18n.GetMsgWithMap(nameKey, map[string]interface{}{"dst": strconv.Quote(m.NewPath)})
|
||||||
|
taskItem, err := task.NewTask(taskName, task.TaskExec, task.TaskScopeTask, m.TaskID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fileTransferLocks.Release(m.TaskID)
|
fileTransferLocks.Release(m.TaskID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
defer fileTransferLocks.Release(m.TaskID)
|
defer fileTransferLocks.Release(m.TaskID)
|
||||||
taskItem.AddSubTaskWithOps(m.NewPath, func(t *task.Task) error {
|
taskItem.AddSubTaskWithOps(taskName, func(t *task.Task) error {
|
||||||
t.LogStart(m.NewPath)
|
logFileTaskSources(t, m.OldPaths)
|
||||||
|
logFileTaskSources(t, m.CoverPaths)
|
||||||
|
if m.Name != "" {
|
||||||
|
t.Log(i18n.GetMsgWithMap("FileTaskRename", map[string]interface{}{"name": strconv.Quote(m.Name)}))
|
||||||
|
}
|
||||||
err := f.moveFileWithContext(t.TaskCtx, m)
|
err := f.moveFileWithContext(t.TaskCtx, m)
|
||||||
if err != nil && t.TaskCtx.Err() != nil {
|
if err != nil && t.TaskCtx.Err() != nil {
|
||||||
return t.TaskCtx.Err()
|
return t.TaskCtx.Err()
|
||||||
|
|||||||
+1061
-2288
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,144 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
|
||||||
"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/lifecycle"
|
|
||||||
)
|
|
||||||
|
|
||||||
type panelPortWhitelistKey struct{}
|
|
||||||
|
|
||||||
func (s *FirewallService) UpdatePanelPort(ctx context.Context, oldPort, port uint) error {
|
|
||||||
if oldPort == 0 || oldPort > 65535 || port == 0 || port > 65535 {
|
|
||||||
return fmt.Errorf("invalid panel port transition %d -> %d", oldPort, port)
|
|
||||||
}
|
|
||||||
if LoadPanelPort() != strconv.Itoa(int(oldPort)) {
|
|
||||||
return fmt.Errorf("panel port changed before firewall update")
|
|
||||||
}
|
|
||||||
if oldPort == port {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
client, err := s.baseClient()
|
|
||||||
if err != nil {
|
|
||||||
if configuredSystemFirewallBackend() == "" && len(lifecycle.InstalledProviders()) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
provider := client.Name()
|
|
||||||
active, err := client.Status()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !supportsManagedFilterChains(provider) && !active {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
required, err := loadRequiredFirewallPorts(strconv.Itoa(int(port)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
managedChains := supportsManagedFilterChains(provider)
|
|
||||||
if managedChains {
|
|
||||||
firewallRuleMutationMu.Lock()
|
|
||||||
defer firewallRuleMutationMu.Unlock()
|
|
||||||
}
|
|
||||||
configured, err := loadConfiguredFirewallPortWhiteList()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
protected := firewall.NormalizePortWhitelist(append(configured, required...))
|
|
||||||
if managedChains {
|
|
||||||
prepared := append([]firewall.PortWhitelist{{Port: strconv.Itoa(int(oldPort)), Protocol: "tcp"}}, required...)
|
|
||||||
if err := syncPanelRequiredPorts(provider, prepared); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := syncPanelRequiredPorts(provider, required); err != nil {
|
|
||||||
warnPanelPortCleanupFailure(oldPort, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
warnPanelPortCleanupFailure(oldPort, s.cleanupPanelPortLocked(ctx, provider, oldPort, protected))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ports := systemPorts([]firewall.PortWhitelist{{Port: strconv.Itoa(int(port)), Protocol: "tcp"}})
|
|
||||||
for _, port := range ports {
|
|
||||||
if err := s.ensureSystemPort(ctx, port); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
firewallRuleMutationMu.Lock()
|
|
||||||
defer firewallRuleMutationMu.Unlock()
|
|
||||||
warnPanelPortCleanupFailure(oldPort, s.cleanupPanelPortLocked(ctx, provider, oldPort, protected))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanupPanelPortLocked removes the old system-owned policy as well as any
|
|
||||||
// remaining managed runtime rule. The caller must hold firewallRuleMutationMu.
|
|
||||||
func (s *FirewallService) cleanupPanelPortLocked(ctx context.Context, provider string, oldPort uint, protected []firewall.PortWhitelist) error {
|
|
||||||
ctx = context.WithValue(ctx, panelPortWhitelistKey{}, protected)
|
|
||||||
ports := systemPorts([]firewall.PortWhitelist{{Port: strconv.Itoa(int(oldPort)), Protocol: "tcp"}})
|
|
||||||
if provider == constant.FirewallProviderUFW {
|
|
||||||
for _, port := range ports {
|
|
||||||
port.Protocol = "all"
|
|
||||||
ports = append(ports, port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Include family-neutral records left by firewalld or older versions, even
|
|
||||||
// when the selected backend has since changed.
|
|
||||||
ports = append(ports, dto.FirewallSystemPort{Port: strconv.Itoa(int(oldPort)), Protocol: "tcp"})
|
|
||||||
var cleanupErrors []error
|
|
||||||
for _, port := range ports {
|
|
||||||
if panelPortStillRequired(port, protected) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
records, err := s.systemPortRecords(ctx, port)
|
|
||||||
if err != nil {
|
|
||||||
cleanupErrors = append(cleanupErrors, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, record := range records {
|
|
||||||
if err := s.deleteRule(ctx, record.UUID, true); err != nil && !errors.Is(err, filter.ErrProtectedRule) {
|
|
||||||
cleanupErrors = append(cleanupErrors, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.Join(cleanupErrors...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func warnPanelPortCleanupFailure(port uint, err error) {
|
|
||||||
if err != nil && global.LOG != nil {
|
|
||||||
global.LOG.Warnf("clean up old panel firewall port %d failed: %v", port, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func panelPortStillRequired(port dto.FirewallSystemPort, protected []firewall.PortWhitelist) bool {
|
|
||||||
rule := systemPortRule(filter.ProviderUFW, port)
|
|
||||||
for _, required := range protected {
|
|
||||||
if port.Family != "" && required.Family != "" && port.Family != required.Family {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
other := rule
|
|
||||||
other.Protocol, other.DestinationPort = required.Protocol, required.Port
|
|
||||||
if filter.RulesOverlap(rule, other) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func panelRuleStillRequired(rule filter.FirewallRule, protected []firewall.PortWhitelist) bool {
|
|
||||||
family := string(rule.Scope.Family)
|
|
||||||
if rule.Scope.Family == filter.FamilyInet {
|
|
||||||
family = ""
|
|
||||||
}
|
|
||||||
return panelPortStillRequired(dto.FirewallSystemPort{
|
|
||||||
Family: family, Port: rule.DestinationPort, Protocol: rule.Protocol,
|
|
||||||
}, protected)
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"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/global"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
firewallTaskHost = "FirewallTaskHost"
|
|
||||||
firewallTaskForwarding = "FirewallTaskForwarding"
|
|
||||||
firewallTaskDocker = "FirewallTaskDocker"
|
|
||||||
)
|
|
||||||
|
|
||||||
func firewallTaskName(operation, subsystem, backend string) string {
|
|
||||||
name := i18n.GetMsgByKey(subsystem)
|
|
||||||
if backend != "" {
|
|
||||||
name += " · " + backend
|
|
||||||
}
|
|
||||||
key := "FirewallRule" + operation
|
|
||||||
if operation == task.TaskExec {
|
|
||||||
key = "FirewallTaskInitialize"
|
|
||||||
}
|
|
||||||
return i18n.GetMsgWithMap(key, map[string]interface{}{"name": name})
|
|
||||||
}
|
|
||||||
|
|
||||||
func queueFirewallRuleTask(subsystem, operation string, labels []string, apply func(context.Context) error) (dto.FilterChainOperationResponse, error) {
|
|
||||||
taskItem, err := task.NewTask(firewallTaskName(operation, subsystem, ""), operation, task.TaskScopeFirewall, "", 0)
|
|
||||||
if err != nil {
|
|
||||||
return dto.FilterChainOperationResponse{}, err
|
|
||||||
}
|
|
||||||
taskItem.AddSubTaskWithOps(taskItem.Name, func(t *task.Task) error {
|
|
||||||
t.Logf("rules=%d", len(labels))
|
|
||||||
err := t.TaskCtx.Err()
|
|
||||||
if err == nil {
|
|
||||||
err = apply(t.TaskCtx)
|
|
||||||
}
|
|
||||||
succeeded, failed := 0, 0
|
|
||||||
for _, label := range labels {
|
|
||||||
if err != nil {
|
|
||||||
failed++
|
|
||||||
t.LogFailedWithErr(label, err)
|
|
||||||
} else {
|
|
||||||
succeeded++
|
|
||||||
t.LogSuccess(label)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Log(i18n.GetMsgWithMap("FirewallRuleOperationResult", map[string]interface{}{
|
|
||||||
"succeeded": succeeded, "failed": failed,
|
|
||||||
}))
|
|
||||||
return err
|
|
||||||
}, nil, 0, 0)
|
|
||||||
if err := repo.NewITaskRepo().Save(context.Background(), taskItem.Task); err != nil {
|
|
||||||
taskItem.LogFailedWithErr(taskItem.Name, err)
|
|
||||||
closeUnstartedFirewallTask(taskItem)
|
|
||||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("save firewall rule task: %w", err)
|
|
||||||
}
|
|
||||||
go func() { _ = taskItem.Execute() }()
|
|
||||||
return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func closeUnstartedFirewallTask(t *task.Task) {
|
|
||||||
if cancel, ok := global.LoadTaskCancel(t.TaskID); ok {
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
global.RemoveTaskCancel(t.TaskID)
|
|
||||||
if closer, ok := t.Logger.Out.(io.Closer); ok {
|
|
||||||
_ = closer.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
|
|
||||||
)
|
|
||||||
|
|
||||||
func selectedDockerFirewallBackend(fallback string) string {
|
|
||||||
selected := configuredDockerFirewallBackend()
|
|
||||||
if selected == constant.FirewallProviderIptables || selected == constant.FirewallProviderNftables {
|
|
||||||
return selected
|
|
||||||
}
|
|
||||||
fallback = strings.ToLower(strings.TrimSpace(fallback))
|
|
||||||
if fallback == constant.FirewallProviderNftables {
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
return constant.FirewallProviderIptables
|
|
||||||
}
|
|
||||||
|
|
||||||
func configuredDockerFirewallBackend() string {
|
|
||||||
if global.DB == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
selected, _ := settingRepo.GetValueByKey(constant.FirewallDockerBackendKey)
|
|
||||||
selected = strings.ToLower(strings.TrimSpace(selected))
|
|
||||||
if selected == constant.FirewallProviderIptables || selected == constant.FirewallProviderNftables {
|
|
||||||
return selected
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func selectedSystemFirewallClient() (lifecycle.Client, error) {
|
|
||||||
if provider := configuredSystemFirewallBackend(); provider != "" {
|
|
||||||
return lifecycle.NewClientFor(provider)
|
|
||||||
}
|
|
||||||
client, err := lifecycle.NewClient()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_ = settingRepo.UpdateOrCreate(constant.FirewallSystemBackendKey, client.Name())
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func configuredSystemFirewallBackend() string {
|
|
||||||
if global.DB == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
provider, _ := settingRepo.GetValueByKey(constant.FirewallSystemBackendKey)
|
|
||||||
return strings.TrimSpace(provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSelectedSystemFirewallClient() (lifecycle.Client, error) {
|
|
||||||
return selectedSystemFirewallClient()
|
|
||||||
}
|
|
||||||
|
|
||||||
func selectedSystemFirewallProvider() (string, error) {
|
|
||||||
client, err := selectedSystemFirewallClient()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return client.Name(), nil
|
|
||||||
}
|
|
||||||
@@ -5,240 +5,138 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
|
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/filter"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/iptables_helper"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/nftables_helper"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/ping"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IFirewallSettingService interface {
|
type IFirewallSettingService interface {
|
||||||
QueuePortWhitelist(value string) (dto.FilterChainOperationResponse, error)
|
CreatePortWhitelist(context.Context, dto.FirewallPortWhitelistCreate) error
|
||||||
|
UpdatePortWhitelist(context.Context, dto.FirewallPortWhitelistUpdate) error
|
||||||
|
DeletePortWhitelist(context.Context, dto.FirewallPortWhitelistDelete) error
|
||||||
Load(context.Context) (dto.FirewallSettings, error)
|
Load(context.Context) (dto.FirewallSettings, error)
|
||||||
Operate(context.Context, dto.FirewallBackendOperation) error
|
Operate(context.Context, dto.FirewallBackendOperation) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallSettingService struct{}
|
type FirewallSettingService struct{}
|
||||||
|
|
||||||
var firewallWhitelistTaskMu sync.Mutex
|
var firewallWhitelistMu sync.Mutex
|
||||||
|
|
||||||
var ErrFirewallBackendCleanupRequired = errors.New("firewall backend cleanup required")
|
func (s *FirewallSettingService) CreatePortWhitelist(ctx context.Context, request dto.FirewallPortWhitelistCreate) error {
|
||||||
|
firewallWhitelistMu.Lock()
|
||||||
func firewallBackendCleanupRequired(current, target string) error {
|
defer firewallWhitelistMu.Unlock()
|
||||||
return fmt.Errorf(
|
|
||||||
"%w: current backend %s still contains 1Panel runtime rules; clean it up before switching to %s",
|
|
||||||
ErrFirewallBackendCleanupRequired,
|
|
||||||
current,
|
|
||||||
target,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIFirewallSettingService() IFirewallSettingService {
|
|
||||||
return &FirewallSettingService{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FirewallSettingService) QueuePortWhitelist(value string) (dto.FilterChainOperationResponse, error) {
|
|
||||||
return s.queuePortWhitelist(value, newFirewallService())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FirewallSettingService) queuePortWhitelist(value string, firewallService *FirewallService) (dto.FilterChainOperationResponse, error) {
|
|
||||||
firewallWhitelistTaskMu.Lock()
|
|
||||||
defer firewallWhitelistTaskMu.Unlock()
|
|
||||||
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeFirewall, 0); err != nil {
|
|
||||||
return dto.FilterChainOperationResponse{}, err
|
|
||||||
}
|
|
||||||
taskItem, err := task.NewTask(i18n.GetMsgByKey("FirewallWhitelistTask"), task.TaskUpdate, task.TaskScopeFirewall, "", 0)
|
|
||||||
if err != nil {
|
|
||||||
return dto.FilterChainOperationResponse{}, err
|
|
||||||
}
|
|
||||||
taskItem.AddSubTaskWithOps(taskItem.Name, func(t *task.Task) error {
|
|
||||||
succeeded, failed := 0, 0
|
|
||||||
err := s.applyPortWhitelist(t.TaskCtx, value, firewallService, func(status, label string, err error) {
|
|
||||||
switch status {
|
|
||||||
case "applied":
|
|
||||||
succeeded++
|
|
||||||
t.LogSuccess(label)
|
|
||||||
case "failed":
|
|
||||||
failed++
|
|
||||||
t.LogFailedWithErr(label, err)
|
|
||||||
default:
|
|
||||||
t.Log(i18n.GetWithName(status, label))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
t.Log(i18n.GetMsgWithMap("FirewallRuleOperationResult", map[string]interface{}{
|
|
||||||
"succeeded": succeeded, "failed": failed,
|
|
||||||
}))
|
|
||||||
return err
|
|
||||||
}, nil, 0, 0)
|
|
||||||
if err := repo.NewITaskRepo().Save(context.Background(), taskItem.Task); err != nil {
|
|
||||||
closeUnstartedFirewallTask(taskItem)
|
|
||||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("save firewall whitelist task: %w", err)
|
|
||||||
}
|
|
||||||
go func() { _ = taskItem.Execute() }()
|
|
||||||
return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type whitelistReporter func(status, label string, err error)
|
|
||||||
|
|
||||||
func (s *FirewallSettingService) applyPortWhitelist(ctx context.Context, value string, firewallService *FirewallService, report whitelistReporter) error {
|
|
||||||
firewallRuleMutationMu.Lock()
|
firewallRuleMutationMu.Lock()
|
||||||
defer firewallRuleMutationMu.Unlock()
|
defer firewallRuleMutationMu.Unlock()
|
||||||
if err := ctx.Err(); err != nil {
|
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
return err
|
current, err := loadPortWhitelistSetting(tx)
|
||||||
}
|
|
||||||
ports, err := firewall.ParsePortWhitelist(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
required, err := firewallService.requiredPorts()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
removed, err := s.savePortWhitelist(ctx, ports, required, firewallService)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
report("FirewallWhitelistSaved", "", nil)
|
|
||||||
for _, port := range systemPorts(removed) {
|
|
||||||
report("FirewallWhitelistReleased", whitelistPortLabel(port), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, panelPortWhitelistKey{}, required)
|
|
||||||
provider, providerErr := firewallService.selectedProvider(ctx)
|
|
||||||
ready := s.portWhitelistReadiness(provider, providerErr, firewallService)
|
|
||||||
return syncPortWhitelist(ctx, ports, required, ready, firewallService.ensureSystemPortLocked, report)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FirewallSettingService) savePortWhitelist(ctx context.Context, ports, required []firewall.PortWhitelist, firewallService *FirewallService) ([]firewall.PortWhitelist, error) {
|
|
||||||
var removed []firewall.PortWhitelist
|
|
||||||
err := global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
var setting model.Setting
|
|
||||||
err := tx.Where("key = ?", constant.FirewallPortWhiteList).First(&setting).Error
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
setting.Value = constant.FirewallPortWhiteListValue
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
previous, err := firewall.ParsePortWhitelist(setting.Value)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
removed = excludeFirewallPorts(excludeFirewallPorts(previous, ports), required)
|
current = append(current, request.Rule)
|
||||||
txCtx := context.WithValue(ctx, constant.DB, tx)
|
current, err = firewall.ValidatePortWhitelist(current)
|
||||||
if err := firewallService.releaseSystemPorts(txCtx, systemPorts(removed)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
value, err := json.Marshal(ports)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return tx.Where("key = ?", constant.FirewallPortWhiteList).
|
value, err := json.Marshal(current)
|
||||||
Assign(map[string]interface{}{"value": string(value)}).
|
if err != nil {
|
||||||
FirstOrCreate(&model.Setting{Key: constant.FirewallPortWhiteList}).Error
|
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
|
||||||
})
|
})
|
||||||
return removed, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FirewallSettingService) portWhitelistReadiness(provider filter.Provider, providerErr error, firewallService *FirewallService) func(dto.FirewallSystemPort) (bool, error) {
|
func (s *FirewallSettingService) UpdatePortWhitelist(ctx context.Context, request dto.FirewallPortWhitelistUpdate) error {
|
||||||
type state struct {
|
firewallWhitelistMu.Lock()
|
||||||
ready bool
|
defer firewallWhitelistMu.Unlock()
|
||||||
err error
|
firewallRuleMutationMu.Lock()
|
||||||
}
|
defer firewallRuleMutationMu.Unlock()
|
||||||
states := make(map[string]state)
|
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
return func(port dto.FirewallSystemPort) (bool, error) {
|
current, err := loadPortWhitelistSetting(tx)
|
||||||
if providerErr != nil {
|
|
||||||
return false, providerErr
|
|
||||||
}
|
|
||||||
key := "service"
|
|
||||||
if isDirectFirewallProvider(provider) {
|
|
||||||
key = port.Family
|
|
||||||
}
|
|
||||||
if cached, ok := states[key]; ok {
|
|
||||||
return cached.ready, cached.err
|
|
||||||
}
|
|
||||||
var result state
|
|
||||||
if isDirectFirewallProvider(provider) {
|
|
||||||
initialized, bound, err := loadSystemFirewallFamilyStatus(string(provider), port.Family)
|
|
||||||
result = state{ready: initialized && bound, err: err}
|
|
||||||
} else {
|
|
||||||
client, err := firewallService.baseClient()
|
|
||||||
result.err = err
|
|
||||||
if err == nil {
|
|
||||||
result.ready, result.err = client.Status()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
states[key] = result
|
|
||||||
return result.ready, result.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func syncPortWhitelist(
|
|
||||||
ctx context.Context,
|
|
||||||
ports, required []firewall.PortWhitelist,
|
|
||||||
ready func(dto.FirewallSystemPort) (bool, error),
|
|
||||||
ensure func(context.Context, dto.FirewallSystemPort) error,
|
|
||||||
report whitelistReporter,
|
|
||||||
) error {
|
|
||||||
var failures []error
|
|
||||||
for _, port := range systemPorts(ports) {
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return errors.Join(append(failures, err)...)
|
|
||||||
}
|
|
||||||
label := whitelistPortLabel(port)
|
|
||||||
if containsFirewallPort(required, firewall.PortWhitelist{Family: port.Family, Port: port.Port, Protocol: port.Protocol}) {
|
|
||||||
report("FirewallWhitelistRequired", label, nil)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
active, err := ready(port)
|
|
||||||
if err == nil && !active {
|
|
||||||
report("FirewallWhitelistDeferred", label, nil)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
err = ensure(ctx, port)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
report("failed", label, err)
|
return err
|
||||||
failures = append(failures, fmt.Errorf("%s: %w", label, err))
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
report("applied", label, nil)
|
index, err := findPortWhitelistRule(current, request.OldRule)
|
||||||
}
|
if err != nil {
|
||||||
return errors.Join(failures...)
|
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 whitelistPortLabel(port dto.FirewallSystemPort) string {
|
func (s *FirewallSettingService) DeletePortWhitelist(ctx context.Context, request dto.FirewallPortWhitelistDelete) error {
|
||||||
return fmt.Sprintf("%s %s/%s", port.Family, port.Port, port.Protocol)
|
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) {
|
func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings, error) {
|
||||||
result := dto.FirewallSettings{PingStatus: ping.LoadStatus()}
|
result := dto.FirewallSettings{PingStatus: firewall.LoadPingStatus()}
|
||||||
if ports, err := settingRepo.GetValueByKey(constant.FirewallPortWhiteList); err == nil {
|
|
||||||
result.PortWhitelist = ports
|
|
||||||
} else {
|
|
||||||
result.PortWhitelist = constant.FirewallPortWhiteListValue
|
|
||||||
}
|
|
||||||
|
|
||||||
installed := make(map[string]bool)
|
installed := make(map[string]bool)
|
||||||
for _, name := range lifecycle.InstalledProviders() {
|
for _, name := range lifecycle.InstalledProviders() {
|
||||||
installed[name] = true
|
installed[name] = true
|
||||||
}
|
}
|
||||||
result.System.Selected = configuredSystemFirewallBackend()
|
systemBackend, _ := settingRepo.GetValueByKey(constant.FirewallSystemBackendKey)
|
||||||
|
result.System.Selected = strings.TrimSpace(systemBackend)
|
||||||
if result.System.Selected == "" {
|
if result.System.Selected == "" {
|
||||||
if client, err := lifecycle.NewClient(); err == nil {
|
if client, err := lifecycle.NewClient(""); err == nil {
|
||||||
result.System.Selected = client.Name()
|
result.System.Selected = client.Name()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,16 +149,16 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
|
|||||||
} {
|
} {
|
||||||
option := dto.FirewallBackendOption{Name: name, Installed: installed[name], Supported: true}
|
option := dto.FirewallBackendOption{Name: name, Installed: installed[name], Supported: true}
|
||||||
if option.Installed && name == result.System.Selected {
|
if option.Installed && name == result.System.Selected {
|
||||||
client, err := lifecycle.NewClientFor(name)
|
client, err := lifecycle.NewClient(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
option.Message = err.Error()
|
option.Message = err.Error()
|
||||||
} else if supportsManagedFilterChains(name) {
|
} else if name == constant.FirewallProviderIptables || name == constant.FirewallProviderNftables {
|
||||||
option.Initialized, option.Bound, err = loadFirewallInitStatus(name, "base")
|
overview, err := loadSystemFirewallOverview(name, "base")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
option.Message = err.Error()
|
option.Message = err.Error()
|
||||||
}
|
}
|
||||||
option.IPv4 = loadSystemFirewallFamilyInfo(name, constant.FirewallFamilyIPv4)
|
option.Initialized, option.Bound = overview.IsInit, overview.IsBind
|
||||||
option.IPv6 = loadSystemFirewallFamilyInfo(name, constant.FirewallFamilyIPv6)
|
option.IPv4, option.IPv6 = overview.IPv4, overview.IPv6
|
||||||
} else if option.Active, err = client.Status(); err != nil {
|
} else if option.Active, err = client.Status(); err != nil {
|
||||||
option.Message = err.Error()
|
option.Message = err.Error()
|
||||||
}
|
}
|
||||||
@@ -273,32 +171,29 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
|
|||||||
result.System.Options = append(result.System.Options, option)
|
result.System.Options = append(result.System.Options, option)
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Forwarding.Selected = configuredForwardingBackend()
|
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
|
result.Forwarding.Current = result.Forwarding.Selected
|
||||||
for _, name := range []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables} {
|
for _, name := range []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables} {
|
||||||
option := dto.FirewallBackendOption{Name: name, Installed: installed[name], Supported: true}
|
option := dto.FirewallBackendOption{Name: name, Installed: installed[name], Supported: true}
|
||||||
if option.Installed && name == result.Forwarding.Selected {
|
if option.Installed && name == result.Forwarding.Selected {
|
||||||
manager, err := newForwardingManagerFor(name)
|
manager, err := newForwardingAdapterFor(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
option.Message = err.Error()
|
option.Message = err.Error()
|
||||||
} else if status, err := manager.Status(); err != nil {
|
|
||||||
option.Message = err.Error()
|
|
||||||
} else {
|
} else {
|
||||||
option.Initialized, option.Bound = status.IsInit, status.IsBind
|
status, statusErr := loadForwardingFirewallOverview(manager)
|
||||||
ipv4Init, ipv4Bound, ipv4Err := manager.FamilyStatus(constant.FirewallFamilyIPv4)
|
option.IPv4, option.IPv6 = status.IPv4, status.IPv6
|
||||||
ipv6Init, ipv6Bound, ipv6Err := manager.FamilyStatus(constant.FirewallFamilyIPv6)
|
if statusErr != nil {
|
||||||
option.IPv4 = dto.FirewallBackendFamilyStatus{
|
option.Message = statusErr.Error()
|
||||||
Available: ipv4Err == nil, Initialized: ipv4Init, Bound: ipv4Bound,
|
} else {
|
||||||
|
option.Initialized, option.Bound = status.IsInit, status.IsBind
|
||||||
}
|
}
|
||||||
option.IPv6 = dto.FirewallBackendFamilyStatus{
|
if name == constant.FirewallProviderIptables && !option.IPv6.Available {
|
||||||
Available: ipv6Err == nil, Initialized: ipv6Init, Bound: ipv6Bound,
|
if commands, err := lifecycle.ResolveIptablesCommands(); err == nil && !commands.IPv6Available() {
|
||||||
}
|
option.IPv6.Reason = dockerfirewall.ReasonCommandMissing
|
||||||
if name == constant.FirewallProviderIptables {
|
|
||||||
if commands, commandErr := lifecycle.ResolveIptablesCommands(); commandErr == nil {
|
|
||||||
option.IPv6.Available = option.IPv6.Available && commands.IPv6Available()
|
|
||||||
if !commands.IPv6Available() {
|
|
||||||
option.IPv6.Reason = docker_guard.ReasonCommandMissing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,7 +211,11 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
|
|||||||
if dockerInstalled {
|
if dockerInstalled {
|
||||||
dockerVersion = loadDockerEngineVersion(ctx)
|
dockerVersion = loadDockerEngineVersion(ctx)
|
||||||
}
|
}
|
||||||
result.Docker.Selected = configuredDockerFirewallBackend()
|
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
|
result.Docker.Current = result.Docker.Selected
|
||||||
for _, name := range []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables} {
|
for _, name := range []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables} {
|
||||||
option := dto.FirewallBackendOption{
|
option := dto.FirewallBackendOption{
|
||||||
@@ -329,53 +228,42 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
|
|||||||
option.Active = false
|
option.Active = false
|
||||||
}
|
}
|
||||||
if option.Active {
|
if option.Active {
|
||||||
guard := docker_guard.NewRuntime(name)
|
guard := newDockerFirewallRuntime(name)
|
||||||
ipv4, ipv6 := guard.Status(docker_guard.FamilyIPv4), guard.Status(docker_guard.FamilyIPv6)
|
ipv4, ipv6 := guard.Status(dockerfirewall.FamilyIPv4), guard.Status(dockerfirewall.FamilyIPv6)
|
||||||
option.Initialized = ipv4.Initialized || ipv6.Initialized
|
option.Initialized = ipv4.Initialized || ipv6.Initialized
|
||||||
option.Bound = ipv4.Bound || ipv6.Bound
|
option.Bound = ipv4.Bound || ipv6.Bound
|
||||||
option.IPv4.Initialized, option.IPv4.Bound = ipv4.Initialized, ipv4.Bound
|
option.IPv4.Initialized, option.IPv4.Bound = ipv4.Initialized, ipv4.Bound
|
||||||
option.IPv6.Initialized, option.IPv6.Bound = ipv6.Initialized, ipv6.Bound
|
option.IPv6.Initialized, option.IPv6.Bound = ipv6.Initialized, ipv6.Bound
|
||||||
option.IPv4.Available = ipv4.Reason != docker_guard.ReasonCommandMissing
|
option.IPv4.Available = ipv4.Reason != dockerfirewall.ReasonCommandMissing
|
||||||
option.IPv6.Available = ipv6.Reason != docker_guard.ReasonCommandMissing
|
option.IPv6.Available = ipv6.Reason != dockerfirewall.ReasonCommandMissing
|
||||||
option.IPv4.Reason, option.IPv6.Reason = ipv4.Reason, ipv6.Reason
|
option.IPv4.Reason, option.IPv6.Reason = ipv4.Reason, ipv6.Reason
|
||||||
}
|
}
|
||||||
result.Docker.Options = append(result.Docker.Options, option)
|
result.Docker.Options = append(result.Docker.Options, option)
|
||||||
}
|
}
|
||||||
|
var err error
|
||||||
return result, nil
|
result.PortWhitelist, err = loadPortWhitelistSetting(global.DB.WithContext(ctx))
|
||||||
}
|
if err != nil {
|
||||||
|
return result, err
|
||||||
func loadSystemFirewallFamilyStatus(provider, family string) (bool, bool, error) {
|
|
||||||
switch provider {
|
|
||||||
case constant.FirewallProviderIptables:
|
|
||||||
return iptables_helper.LoadFamilyInitStatus(family, "base")
|
|
||||||
case constant.FirewallProviderNftables:
|
|
||||||
return nftables_helper.LoadFamilyInitStatus(filter.Family(family), "base")
|
|
||||||
default:
|
|
||||||
return false, false, fmt.Errorf("unsupported firewall provider %q", provider)
|
|
||||||
}
|
}
|
||||||
}
|
result.PanelPort = LoadPanelPort()
|
||||||
|
sshPort, sshErr := loadSSHWhitelistPortFrom(sshPath)
|
||||||
func loadSystemFirewallFamilyInfo(provider, family string) dto.FirewallBackendFamilyStatus {
|
if sshErr != nil {
|
||||||
if provider == constant.FirewallProviderIptables && family == constant.FirewallFamilyIPv6 {
|
global.LOG.Warnf("load SSH port for firewall settings: %v", sshErr)
|
||||||
commands, err := lifecycle.ResolveIptablesCommands()
|
} else {
|
||||||
if err != nil || !commands.IPv6Available() {
|
result.SSHPort = sshPort
|
||||||
return dto.FirewallBackendFamilyStatus{Reason: docker_guard.ReasonCommandMissing}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
initialized, bound, err := loadSystemFirewallFamilyStatus(provider, family)
|
|
||||||
return dto.FirewallBackendFamilyStatus{
|
|
||||||
Available: err == nil,
|
|
||||||
Initialized: initialized,
|
|
||||||
Bound: bound,
|
|
||||||
}
|
}
|
||||||
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FirewallSettingService) Operate(ctx context.Context, request dto.FirewallBackendOperation) error {
|
func (s *FirewallSettingService) Operate(ctx context.Context, request dto.FirewallBackendOperation) error {
|
||||||
|
if err := lockFirewallLifecycleIdle(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer firewallLifecycleTaskMu.Unlock()
|
||||||
if request.Subsystem != "system" && request.Backend != constant.FirewallProviderIptables && request.Backend != constant.FirewallProviderNftables {
|
if request.Subsystem != "system" && request.Backend != constant.FirewallProviderIptables && request.Backend != constant.FirewallProviderNftables {
|
||||||
return fmt.Errorf("%s only supports iptables or nftables", request.Subsystem)
|
return fmt.Errorf("%s only supports iptables or nftables", request.Subsystem)
|
||||||
}
|
}
|
||||||
if request.Subsystem == "system" && !supportsManagedFilterChains(request.Backend) && request.Operation != "select" {
|
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)
|
return fmt.Errorf("%s does not support initialization or cleanup", request.Backend)
|
||||||
}
|
}
|
||||||
switch request.Subsystem {
|
switch request.Subsystem {
|
||||||
@@ -385,10 +273,9 @@ func (s *FirewallSettingService) Operate(ctx context.Context, request dto.Firewa
|
|||||||
}
|
}
|
||||||
if request.Operation == "initialize" {
|
if request.Operation == "initialize" {
|
||||||
service := newFirewallService()
|
service := newFirewallService()
|
||||||
if err := service.restoreStoredFirewallRules(ctx, filter.Provider(request.Backend)); err != nil {
|
rulesErr := service.restoreStoredFirewallRules(ctx, filter.Provider(request.Backend), nil)
|
||||||
return err
|
whitelistErr := service.SyncPortWhitelist(ctx)
|
||||||
}
|
return errors.Join(rulesErr, whitelistErr)
|
||||||
return service.syncConfiguredFirewallPorts(ctx)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case "forwarding":
|
case "forwarding":
|
||||||
@@ -400,64 +287,14 @@ func (s *FirewallSettingService) Operate(ctx context.Context, request dto.Firewa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FirewallSettingService) operateDocker(ctx context.Context, request dto.FirewallBackendOperation) error {
|
func NewIFirewallSettingService() IFirewallSettingService {
|
||||||
guard := docker_guard.NewRuntime(request.Backend)
|
return &FirewallSettingService{}
|
||||||
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 = alternateDirectBackend(request.Backend)
|
|
||||||
}
|
|
||||||
initialized, err := dockerGuardBackendInitialized(current)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if current != request.Backend && initialized {
|
|
||||||
return firewallBackendCleanupRequired(current, request.Backend)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if request.Operation == "initialize" {
|
|
||||||
if err := newDockerPortGuardService().Operate(ctx, dto.DockerPortGuardOperation{Operation: "initialize"}); err != nil {
|
|
||||||
_ = settingRepo.UpdateOrCreate(constant.FirewallDockerBackendKey, previous)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dockerGuardBackendInitialized(backend string) (bool, error) {
|
|
||||||
guard := docker_guard.NewRuntime(backend)
|
|
||||||
for _, family := range []string{docker_guard.FamilyIPv4, docker_guard.FamilyIPv6} {
|
|
||||||
initialized, err := guard.Initialized(family)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if initialized {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperation) error {
|
func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperation) error {
|
||||||
firewallRuleMutationMu.Lock()
|
firewallRuleMutationMu.Lock()
|
||||||
defer firewallRuleMutationMu.Unlock()
|
defer firewallRuleMutationMu.Unlock()
|
||||||
if _, err := lifecycle.NewClientFor(request.Backend); err != nil {
|
if _, err := lifecycle.NewClient(request.Backend); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if request.Operation == "cleanup" {
|
if request.Operation == "cleanup" {
|
||||||
@@ -465,7 +302,7 @@ func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperat
|
|||||||
}
|
}
|
||||||
previous, _ := settingRepo.GetValueByKey(constant.FirewallSystemBackendKey)
|
previous, _ := settingRepo.GetValueByKey(constant.FirewallSystemBackendKey)
|
||||||
if previous == "" {
|
if previous == "" {
|
||||||
if client, err := lifecycle.NewClient(); err == nil {
|
if client, err := lifecycle.NewClient(""); err == nil {
|
||||||
previous = client.Name()
|
previous = client.Name()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -475,7 +312,7 @@ func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperat
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if initialized {
|
if initialized {
|
||||||
return firewallBackendCleanupRequired(previous, request.Backend)
|
return buserr.WithMap("ErrFirewallBackendCleanupRequired", map[string]interface{}{"current": previous, "target": request.Backend}, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := settingRepo.UpdateOrCreate(constant.FirewallSystemBackendKey, request.Backend); err != nil {
|
if err := settingRepo.UpdateOrCreate(constant.FirewallSystemBackendKey, request.Backend); err != nil {
|
||||||
@@ -501,21 +338,14 @@ func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperat
|
|||||||
}
|
}
|
||||||
|
|
||||||
func systemFirewallBackendInitialized(backend string) (bool, error) {
|
func systemFirewallBackendInitialized(backend string) (bool, error) {
|
||||||
return systemFirewallBackendInitializedWithClientFactory(backend, lifecycle.NewClientFor)
|
client, err := lifecycle.NewClient(backend)
|
||||||
}
|
|
||||||
|
|
||||||
func systemFirewallBackendInitializedWithClientFactory(
|
|
||||||
backend string,
|
|
||||||
newClient func(string) (lifecycle.Client, error),
|
|
||||||
) (bool, error) {
|
|
||||||
client, err := newClient(backend)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, lifecycle.ErrNotInstalled) {
|
if errors.Is(err, lifecycle.ErrNotInstalled) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if supportsManagedFilterChains(backend) {
|
if backend == constant.FirewallProviderIptables || backend == constant.FirewallProviderNftables {
|
||||||
for _, family := range []string{constant.FirewallFamilyIPv4, constant.FirewallFamilyIPv6} {
|
for _, family := range []string{constant.FirewallFamilyIPv4, constant.FirewallFamilyIPv6} {
|
||||||
initialized, _, err := loadSystemFirewallFamilyStatus(backend, family)
|
initialized, _, err := loadSystemFirewallFamilyStatus(backend, family)
|
||||||
if family == constant.FirewallFamilyIPv6 && errors.Is(err, filter.ErrFamilyUnavailable) {
|
if family == constant.FirewallFamilyIPv6 && errors.Is(err, filter.ErrFamilyUnavailable) {
|
||||||
@@ -533,30 +363,8 @@ func systemFirewallBackendInitializedWithClientFactory(
|
|||||||
return client.Status()
|
return client.Status()
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanupSystemBackend(backend string) error {
|
|
||||||
switch backend {
|
|
||||||
case constant.FirewallProviderIptables:
|
|
||||||
return newIptablesHelperManager().Cleanup()
|
|
||||||
case constant.FirewallProviderNftables:
|
|
||||||
return newNftablesHelperManager().Cleanup()
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("cleanup is only available for 1Panel-owned iptables and nftables resources")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupInactiveSystemBackend(backend string) error {
|
|
||||||
switch backend {
|
|
||||||
case constant.FirewallProviderIptables:
|
|
||||||
return (&iptables_helper.Manager{}).Cleanup()
|
|
||||||
case constant.FirewallProviderNftables:
|
|
||||||
return (&nftables_helper.Manager{}).Cleanup()
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("cleanup is only available for 1Panel-owned iptables and nftables resources")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FirewallSettingService) operateForwarding(request dto.FirewallBackendOperation) error {
|
func (s *FirewallSettingService) operateForwarding(request dto.FirewallBackendOperation) error {
|
||||||
manager, err := newForwardingManagerFor(request.Backend)
|
manager, err := newForwardingAdapterFor(request.Backend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -574,7 +382,7 @@ func (s *FirewallSettingService) operateForwarding(request dto.FirewallBackendOp
|
|||||||
if request.Operation == "select" {
|
if request.Operation == "select" {
|
||||||
current := previous
|
current := previous
|
||||||
if current == "" {
|
if current == "" {
|
||||||
detected, err := newForwardingManager()
|
detected, err := newForwardingAdapter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -585,7 +393,7 @@ func (s *FirewallSettingService) operateForwarding(request dto.FirewallBackendOp
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if current != request.Backend && initialized {
|
if current != request.Backend && initialized {
|
||||||
return firewallBackendCleanupRequired(current, request.Backend)
|
return buserr.WithMap("ErrFirewallBackendCleanupRequired", map[string]interface{}{"current": current, "target": request.Backend}, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := settingRepo.UpdateOrCreate(constant.FirewallForwardingBackendKey, request.Backend); err != nil {
|
if err := settingRepo.UpdateOrCreate(constant.FirewallForwardingBackendKey, request.Backend); err != nil {
|
||||||
@@ -599,7 +407,7 @@ func (s *FirewallSettingService) operateForwarding(request dto.FirewallBackendOp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func forwardingBackendInitialized(backend string) (bool, error) {
|
func forwardingBackendInitialized(backend string) (bool, error) {
|
||||||
manager, err := newForwardingManagerFor(backend)
|
manager, err := newForwardingAdapterFor(backend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, lifecycle.ErrNotInstalled) {
|
if errors.Is(err, lifecycle.ErrNotInstalled) {
|
||||||
return false, nil
|
return false, nil
|
||||||
@@ -618,9 +426,59 @@ func forwardingBackendInitialized(backend string) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func alternateDirectBackend(backend string) string {
|
func (s *FirewallSettingService) operateDocker(ctx context.Context, request dto.FirewallBackendOperation) error {
|
||||||
if backend == constant.FirewallProviderNftables {
|
guard := newDockerFirewallRuntime(request.Backend)
|
||||||
return constant.FirewallProviderIptables
|
if request.Operation == "cleanup" {
|
||||||
|
if err := guard.Cleanup(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return settingRepo.UpdateOrCreate(constant.FirewallDockerPortGuardStatusKey, constant.StatusDisable)
|
||||||
}
|
}
|
||||||
return constant.FirewallProviderNftables
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if request.Operation == "initialize" {
|
||||||
|
if err := newDockerPortGuardService().Operate(ctx, dto.DockerPortGuardOperation{Operation: "initialize"}); err != nil {
|
||||||
|
_ = settingRepo.UpdateOrCreate(constant.FirewallDockerBackendKey, previous)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if initialized {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+271
-900
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+304
-320
@@ -4,9 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
@@ -16,10 +18,17 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
|
"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/forwarding"
|
||||||
forwardingproviders "github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding/providers"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/ping"
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
forwardingSyncConverged = "converged"
|
||||||
|
forwardingSyncMissing = "missing"
|
||||||
|
forwardingSyncRuntimeOnly = "runtime_only"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IForwardingService interface {
|
type IForwardingService interface {
|
||||||
@@ -32,7 +41,7 @@ type IForwardingService interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ForwardingService struct {
|
type ForwardingService struct {
|
||||||
managerFactory func() (*forwarding.Manager, error)
|
clientFactory func() (forwarding.Adapter, error)
|
||||||
rules repo.IForwardingRuleRepo
|
rules repo.IForwardingRuleRepo
|
||||||
enabled func() (bool, error)
|
enabled func() (bool, error)
|
||||||
persistBackend func(string) error
|
persistBackend func(string) error
|
||||||
@@ -40,43 +49,27 @@ type ForwardingService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var errForwardingBackendUnavailable = errors.New("no supported forwarding backend detected")
|
var errForwardingBackendUnavailable = errors.New("no supported forwarding backend detected")
|
||||||
var forwardingMutationMu sync.Mutex
|
|
||||||
|
|
||||||
const (
|
var forwardingMutationMu sync.Mutex
|
||||||
forwardingSyncConverged = "converged"
|
|
||||||
forwardingSyncMissing = "missing"
|
|
||||||
forwardingSyncRuntimeOnly = "runtime_only"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
forwardingSyncStateMu sync.RWMutex
|
forwardingSyncStateMu sync.RWMutex
|
||||||
forwardingLastSyncErr error
|
forwardingLastSyncErr error
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewIForwardingService() IForwardingService {
|
|
||||||
return newForwardingService()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newForwardingService() *ForwardingService {
|
|
||||||
return &ForwardingService{
|
|
||||||
managerFactory: newForwardingManager,
|
|
||||||
rules: repo.NewIForwardingRuleRepo(),
|
|
||||||
enabled: forwardingPersistedEnabled,
|
|
||||||
markEnabled: func() error {
|
|
||||||
return settingRepo.UpdateOrCreate(constant.FirewallForwardingInitializedKey, constant.StatusEnable)
|
|
||||||
},
|
|
||||||
persistBackend: func(backend string) error {
|
|
||||||
return settingRepo.UpdateOrCreate(constant.FirewallForwardingBackendKey, backend)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ForwardingService) LoadBaseInfo() (dto.FirewallSubsystemStatus, error) {
|
func (s *ForwardingService) LoadBaseInfo() (dto.FirewallSubsystemStatus, error) {
|
||||||
selected := configuredForwardingBackend()
|
selected, _ := settingRepo.GetValueByKey(constant.FirewallForwardingBackendKey)
|
||||||
baseInfo := dto.FirewallSubsystemStatus{
|
selected = strings.TrimSpace(selected)
|
||||||
Version: "-", Name: forwardingDisplayName(selected), Backend: selected, SyncError: lastForwardingSyncError(),
|
if selected == "" {
|
||||||
|
selected = constant.FirewallProviderIptables
|
||||||
}
|
}
|
||||||
manager, err := s.manager()
|
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 err != nil {
|
||||||
if errors.Is(err, errForwardingBackendUnavailable) {
|
if errors.Is(err, errForwardingBackendUnavailable) {
|
||||||
baseInfo.Reason = constant.FirewallBackendNotInstalled
|
baseInfo.Reason = constant.FirewallBackendNotInstalled
|
||||||
@@ -84,39 +77,41 @@ func (s *ForwardingService) LoadBaseInfo() (dto.FirewallSubsystemStatus, error)
|
|||||||
}
|
}
|
||||||
return baseInfo, err
|
return baseInfo, err
|
||||||
}
|
}
|
||||||
status, err := manager.Status()
|
client, err := lifecycle.NewClient(manager.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return baseInfo, err
|
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.IsExist = true
|
||||||
baseInfo.Name, baseInfo.Backend = forwardingDisplayName(status.Name), status.Name
|
baseInfo.Name, baseInfo.Backend = manager.Name(), manager.Name()
|
||||||
baseInfo.Version = status.Version
|
if baseInfo.Backend == constant.FirewallProviderIptables || baseInfo.Backend == constant.FirewallProviderNftables {
|
||||||
baseInfo.PingStatus = ping.LoadStatus()
|
baseInfo.Name += "-forward"
|
||||||
|
}
|
||||||
|
baseInfo.Version = version
|
||||||
|
baseInfo.PingStatus = firewall.LoadPingStatus()
|
||||||
baseInfo.IsInit, baseInfo.IsBind = status.IsInit, status.IsBind
|
baseInfo.IsInit, baseInfo.IsBind = status.IsInit, status.IsBind
|
||||||
baseInfo.IPv4 = loadForwardingFamilyInfo(manager, status.Name, constant.FirewallFamilyIPv4)
|
baseInfo.IPv4, baseInfo.IPv6 = status.IPv4, status.IPv6
|
||||||
baseInfo.IPv6 = loadForwardingFamilyInfo(manager, status.Name, constant.FirewallFamilyIPv6)
|
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
|
return baseInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadForwardingFamilyInfo(manager *forwarding.Manager, backend, family string) dto.FirewallBackendFamilyStatus {
|
|
||||||
initialized, bound, err := manager.FamilyStatus(family)
|
|
||||||
available := err == nil
|
|
||||||
if backend == constant.FirewallProviderIptables && family == constant.FirewallFamilyIPv6 {
|
|
||||||
commands, commandErr := lifecycle.ResolveIptablesCommands()
|
|
||||||
available = available && commandErr == nil && commands.IPv6Available()
|
|
||||||
}
|
|
||||||
return dto.FirewallBackendFamilyStatus{Available: available, Initialized: initialized, Bound: bound}
|
|
||||||
}
|
|
||||||
|
|
||||||
func forwardingDisplayName(backend string) string {
|
|
||||||
switch backend {
|
|
||||||
case constant.FirewallProviderIptables, constant.FirewallProviderNftables:
|
|
||||||
return backend + "-forward"
|
|
||||||
default:
|
|
||||||
return backend
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ForwardingService) SearchRules(request dto.ForwardRuleSearch) (int64, []dto.ForwardRule, error) {
|
func (s *ForwardingService) SearchRules(request dto.ForwardRuleSearch) (int64, []dto.ForwardRule, error) {
|
||||||
if request.Strategy != "" {
|
if request.Strategy != "" {
|
||||||
return 0, nil, nil
|
return 0, nil, nil
|
||||||
@@ -125,11 +120,11 @@ func (s *ForwardingService) SearchRules(request dto.ForwardRuleSearch) (int64, [
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
manager, err := s.manager()
|
manager, err := s.clientFactory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
runtime, err := manager.List("", "")
|
runtime, err := manager.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
@@ -179,24 +174,18 @@ func (s *ForwardingService) SearchRules(request dto.ForwardRuleSearch) (int64, [
|
|||||||
return int64(total), items, nil
|
return int64(total), items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func forwardingRuleMatchesKeyword(item forwardingInventoryItem, keyword string) bool {
|
func (s *ForwardingService) OperateRules(request dto.ForwardRuleOperate) (dto.FilterChainOperationResponse, error) {
|
||||||
values := []string{
|
count := 0
|
||||||
item.Rule.Family, item.Rule.Protocol, item.Rule.Port, item.Rule.TargetIP,
|
for _, rule := range request.Rules {
|
||||||
item.Rule.TargetPort, item.Rule.Interface, item.SyncStatus(),
|
if rule.Operation == "add" {
|
||||||
}
|
count += strings.Count(rule.Protocol, "/") + 1
|
||||||
for _, value := range values {
|
}
|
||||||
if strings.Contains(strings.ToLower(value), keyword) {
|
if count > filter.MaxAtomicExpansion {
|
||||||
return true
|
return dto.FilterChainOperationResponse{}, fmt.Errorf("create or import at most %d rules per batch (after expansion)", filter.MaxAtomicExpansion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ForwardingService) OperateRules(request dto.ForwardRuleOperate) (dto.FilterChainOperationResponse, error) {
|
|
||||||
labels := make([]string, len(request.Rules))
|
|
||||||
operation := task.TaskCreate
|
operation := task.TaskCreate
|
||||||
for i, rule := range request.Rules {
|
for _, rule := range request.Rules {
|
||||||
labels[i] = fmt.Sprintf("[%d/%d] %s %s %s %s -> %s:%s", i+1, len(request.Rules), rule.Operation, rule.Family, rule.Protocol, rule.Port, rule.TargetIP, rule.TargetPort)
|
|
||||||
if rule.Operation != "add" {
|
if rule.Operation != "add" {
|
||||||
operation = task.TaskUpdate
|
operation = task.TaskUpdate
|
||||||
}
|
}
|
||||||
@@ -204,48 +193,26 @@ func (s *ForwardingService) OperateRules(request dto.ForwardRuleOperate) (dto.Fi
|
|||||||
if forwardingOperationsOnlyRemove(request.Rules) {
|
if forwardingOperationsOnlyRemove(request.Rules) {
|
||||||
operation = task.TaskDelete
|
operation = task.TaskDelete
|
||||||
}
|
}
|
||||||
return queueFirewallRuleTask(firewallTaskForwarding, operation, labels, func(ctx context.Context) error {
|
taskItem, err := task.NewTask(firewallTaskName(operation, firewallTaskForwarding, ""), operation, task.TaskScopeFirewall, "", 0)
|
||||||
return s.operateRules(ctx, request)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ForwardingService) operateRules(ctx context.Context, request dto.ForwardRuleOperate) error {
|
|
||||||
forwardingMutationMu.Lock()
|
|
||||||
defer forwardingMutationMu.Unlock()
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
stored, err := s.rules.List(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return dto.FilterChainOperationResponse{}, err
|
||||||
}
|
}
|
||||||
desired, err := applyForwardingOperations(forwardingRulesFromModels(stored), request.Rules)
|
taskItem.AddSubTaskWithOps(taskItem.Name, func(t *task.Task) error {
|
||||||
if errors.Is(err, forwarding.ErrRuleExists) {
|
return s.operateRules(t.TaskCtx, request, t)
|
||||||
return buserr.New("ErrRecordExist")
|
}, nil, 0, 0)
|
||||||
} else if err != nil {
|
if err := taskRepo.Save(context.Background(), taskItem.Task); err != nil {
|
||||||
return err
|
taskItem.LogFailedWithErr(taskItem.Name, err)
|
||||||
|
closeUnstartedFirewallTask(taskItem)
|
||||||
|
return dto.FilterChainOperationResponse{}, err
|
||||||
}
|
}
|
||||||
if err := s.rules.ReplaceAll(ctx, forwardingRuleModels(desired)); err != nil {
|
go func() { _ = taskItem.Execute() }()
|
||||||
return err
|
return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil
|
||||||
}
|
|
||||||
if err := s.reconcile(desired); err != nil {
|
|
||||||
recordForwardingSyncError(err)
|
|
||||||
if request.ForceDelete && forwardingOperationsOnlyRemove(request.Rules) {
|
|
||||||
if global.LOG != nil {
|
|
||||||
global.LOG.Error(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
recordForwardingSyncError(nil)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ForwardingService) Enable() error {
|
func (s *ForwardingService) Enable() error {
|
||||||
forwardingMutationMu.Lock()
|
forwardingMutationMu.Lock()
|
||||||
defer forwardingMutationMu.Unlock()
|
defer forwardingMutationMu.Unlock()
|
||||||
manager, err := s.manager()
|
manager, err := s.clientFactory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
recordForwardingSyncError(err)
|
recordForwardingSyncError(err)
|
||||||
return err
|
return err
|
||||||
@@ -254,7 +221,7 @@ func (s *ForwardingService) Enable() error {
|
|||||||
recordForwardingSyncError(err)
|
recordForwardingSyncError(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := s.activateManager(manager); err != nil {
|
if err := s.initializeForwarding(manager); err != nil {
|
||||||
recordForwardingSyncError(err)
|
recordForwardingSyncError(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -263,14 +230,12 @@ func (s *ForwardingService) Enable() error {
|
|||||||
recordForwardingSyncError(err)
|
recordForwardingSyncError(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = manager.Reconcile(forwardingRulesFromModels(rules))
|
err = manager.ReplaceRules(forwardingRulesFromModels(rules))
|
||||||
recordForwardingSyncError(err)
|
recordForwardingSyncError(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ForwardingService) QueueInitialization(
|
func (s *ForwardingService) QueueInitialization(request dto.FirewallInitializationTask) (dto.FilterChainOperationResponse, error) {
|
||||||
request dto.FirewallInitializationTask,
|
|
||||||
) (dto.FilterChainOperationResponse, error) {
|
|
||||||
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeFirewall, 0); err != nil {
|
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeFirewall, 0); err != nil {
|
||||||
return dto.FilterChainOperationResponse{}, err
|
return dto.FilterChainOperationResponse{}, err
|
||||||
}
|
}
|
||||||
@@ -278,13 +243,13 @@ func (s *ForwardingService) QueueInitialization(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("create forwarding initialization task: %w", err)
|
return dto.FilterChainOperationResponse{}, fmt.Errorf("create forwarding initialization task: %w", err)
|
||||||
}
|
}
|
||||||
var manager *forwarding.Manager
|
var manager forwarding.Adapter
|
||||||
var backend string
|
var backend string
|
||||||
taskItem.AddSubTask(i18n.GetMsgByKey("FirewallEnableForwardingStep"), func(t *task.Task) error {
|
taskItem.AddSubTask(i18n.GetMsgByKey("FirewallEnableForwardingStep"), func(t *task.Task) error {
|
||||||
forwardingMutationMu.Lock()
|
forwardingMutationMu.Lock()
|
||||||
defer forwardingMutationMu.Unlock()
|
defer forwardingMutationMu.Unlock()
|
||||||
var err error
|
var err error
|
||||||
manager, err = s.manager()
|
manager, err = s.clientFactory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
recordForwardingSyncError(err)
|
recordForwardingSyncError(err)
|
||||||
return err
|
return err
|
||||||
@@ -295,7 +260,7 @@ func (s *ForwardingService) QueueInitialization(
|
|||||||
recordForwardingSyncError(err)
|
recordForwardingSyncError(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := s.activateManager(manager); err != nil {
|
if err := s.initializeForwarding(manager); err != nil {
|
||||||
recordForwardingSyncError(err)
|
recordForwardingSyncError(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -309,7 +274,7 @@ func (s *ForwardingService) QueueInitialization(
|
|||||||
recordForwardingSyncError(err)
|
recordForwardingSyncError(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = manager.Reconcile(forwardingRulesFromModels(rules))
|
err = manager.ReplaceRules(forwardingRulesFromModels(rules))
|
||||||
recordForwardingSyncError(err)
|
recordForwardingSyncError(err)
|
||||||
return err
|
return err
|
||||||
}, nil)
|
}, nil)
|
||||||
@@ -320,131 +285,43 @@ func (s *ForwardingService) QueueInitialization(
|
|||||||
return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil
|
return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ForwardingService) Restore(ctx context.Context) error {
|
func NewIForwardingService() IForwardingService {
|
||||||
forwardingMutationMu.Lock()
|
return newForwardingService()
|
||||||
defer forwardingMutationMu.Unlock()
|
}
|
||||||
enabled, err := s.forwardingEnabled()
|
|
||||||
if err != nil || !enabled {
|
func loadForwardPolicy(command string) (string, error) {
|
||||||
if err != nil {
|
if !cmd.Which(command) {
|
||||||
recordForwardingSyncError(err)
|
command += "-nft"
|
||||||
|
if !cmd.Which(command) {
|
||||||
|
return "", nil
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
manager, err := s.manager()
|
output, err := cmd.NewCommandMgr(cmd.WithTimeout(5*time.Second)).RunWithOptionalSudoAndStdout(command, "-t", "filter", "-w", "2", "-S", "FORWARD")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
recordForwardingSyncError(err)
|
return "", err
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
stored, err := s.rules.List(ctx)
|
for _, line := range strings.Split(output, "\n") {
|
||||||
if err != nil {
|
fields := strings.Fields(line)
|
||||||
recordForwardingSyncError(err)
|
if len(fields) == 3 && fields[0] == "-P" && fields[1] == "FORWARD" {
|
||||||
return err
|
if fields[2] != "ACCEPT" && fields[2] != "DROP" {
|
||||||
|
return "", fmt.Errorf("unexpected FORWARD policy: %s", fields[2])
|
||||||
|
}
|
||||||
|
return fields[2], nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := s.activateManager(manager); err != nil {
|
return "", errors.New("FORWARD default policy was not found")
|
||||||
recordForwardingSyncError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = manager.Reconcile(forwardingRulesFromModels(stored))
|
|
||||||
recordForwardingSyncError(err)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ForwardingService) reconcile(rules []forwarding.Rule) error {
|
func lastForwardingSyncError() string {
|
||||||
manager, err := s.manager()
|
forwardingSyncStateMu.RLock()
|
||||||
if err != nil {
|
defer forwardingSyncStateMu.RUnlock()
|
||||||
return err
|
if forwardingLastSyncErr == nil {
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
return s.reconcileWithManager(manager, rules)
|
return forwardingLastSyncErr.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ForwardingService) reconcileWithManager(manager *forwarding.Manager, rules []forwarding.Rule) error {
|
func mergeForwardingInventory(stored []model.ForwardingRule, runtime []forwarding.Rule) ([]forwardingInventoryItem, error) {
|
||||||
enabled, err := s.forwardingEnabled()
|
|
||||||
if err != nil || !enabled {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.activateManager(manager); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return manager.Reconcile(rules)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ForwardingService) activateManager(manager *forwarding.Manager) error {
|
|
||||||
if err := s.saveForwardingBackend(manager.Name()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return manager.Enable()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ForwardingService) forwardingEnabled() (bool, error) {
|
|
||||||
if s.enabled != nil {
|
|
||||||
return s.enabled()
|
|
||||||
}
|
|
||||||
return forwardingPersistedEnabled()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ForwardingService) saveForwardingBackend(backend string) error {
|
|
||||||
if s.persistBackend != nil {
|
|
||||||
return s.persistBackend(backend)
|
|
||||||
}
|
|
||||||
return settingRepo.UpdateOrCreate(constant.FirewallForwardingBackendKey, backend)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ForwardingService) persistForwardingEnabled() error {
|
|
||||||
if s.markEnabled != nil {
|
|
||||||
return s.markEnabled()
|
|
||||||
}
|
|
||||||
return settingRepo.UpdateOrCreate(constant.FirewallForwardingInitializedKey, constant.StatusEnable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func forwardingPersistedEnabled() (bool, error) {
|
|
||||||
status, err := settingRepo.GetValueByKey(constant.FirewallForwardingInitializedKey)
|
|
||||||
return status == constant.StatusEnable, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func forwardingRulesFromModels(stored []model.ForwardingRule) []forwarding.Rule {
|
|
||||||
rules := make([]forwarding.Rule, 0, len(stored))
|
|
||||||
for _, rule := range stored {
|
|
||||||
rules = append(rules, forwarding.Rule{
|
|
||||||
Family: rule.Family, Protocol: rule.Protocol, Port: rule.Port, TargetIP: rule.TargetIP,
|
|
||||||
TargetPort: rule.TargetPort, Interface: rule.Interface,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return rules
|
|
||||||
}
|
|
||||||
|
|
||||||
func forwardingRuleModels(rules []forwarding.Rule) []model.ForwardingRule {
|
|
||||||
stored := make([]model.ForwardingRule, 0, len(rules))
|
|
||||||
for _, rule := range rules {
|
|
||||||
stored = append(stored, model.ForwardingRule{
|
|
||||||
Family: rule.Family, Protocol: rule.Protocol, Port: rule.Port, TargetIP: rule.TargetIP,
|
|
||||||
TargetPort: rule.TargetPort, Interface: rule.Interface,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return stored
|
|
||||||
}
|
|
||||||
|
|
||||||
type forwardingInventoryItem struct {
|
|
||||||
ID uint
|
|
||||||
Rule forwarding.Rule
|
|
||||||
IsDesired bool
|
|
||||||
IsRuntime bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i forwardingInventoryItem) SyncStatus() string {
|
|
||||||
switch {
|
|
||||||
case i.IsDesired && i.IsRuntime:
|
|
||||||
return forwardingSyncConverged
|
|
||||||
case i.IsDesired:
|
|
||||||
return forwardingSyncMissing
|
|
||||||
default:
|
|
||||||
return forwardingSyncRuntimeOnly
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeForwardingInventory(
|
|
||||||
stored []model.ForwardingRule,
|
|
||||||
runtime []forwarding.Rule,
|
|
||||||
) ([]forwardingInventoryItem, error) {
|
|
||||||
items := make([]forwardingInventoryItem, 0, len(stored)+len(runtime))
|
items := make([]forwardingInventoryItem, 0, len(stored)+len(runtime))
|
||||||
byIdentity := make(map[string]int, len(stored)+len(runtime))
|
byIdentity := make(map[string]int, len(stored)+len(runtime))
|
||||||
for _, record := range stored {
|
for _, record := range stored {
|
||||||
@@ -475,108 +352,215 @@ func mergeForwardingInventory(
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func recordForwardingSyncError(err error) {
|
func forwardingRuleMatchesKeyword(item forwardingInventoryItem, keyword string) bool {
|
||||||
forwardingSyncStateMu.Lock()
|
values := []string{
|
||||||
forwardingLastSyncErr = err
|
item.Rule.Family, item.Rule.Protocol, item.Rule.Port, item.Rule.TargetIP,
|
||||||
forwardingSyncStateMu.Unlock()
|
item.Rule.TargetPort, item.Rule.Interface, item.SyncStatus(),
|
||||||
}
|
|
||||||
|
|
||||||
func lastForwardingSyncError() string {
|
|
||||||
forwardingSyncStateMu.RLock()
|
|
||||||
defer forwardingSyncStateMu.RUnlock()
|
|
||||||
if forwardingLastSyncErr == nil {
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
return forwardingLastSyncErr.Error()
|
for _, value := range values {
|
||||||
}
|
if strings.Contains(strings.ToLower(value), keyword) {
|
||||||
|
return true
|
||||||
func applyForwardingOperations(current []forwarding.Rule, requested []dto.ForwardRuleOperation) ([]forwarding.Rule, error) {
|
|
||||||
desired := make([]forwarding.Rule, 0, len(current)+len(requested))
|
|
||||||
for _, rule := range current {
|
|
||||||
normalized, err := forwarding.NormalizeRule(rule)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("normalize persisted forwarding rule: %w", err)
|
|
||||||
}
|
}
|
||||||
desired = append(desired, normalized)
|
|
||||||
}
|
}
|
||||||
for _, operation := range requested {
|
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, "/") {
|
for _, protocol := range strings.Split(operation.Protocol, "/") {
|
||||||
rule, err := forwarding.NormalizeRule(forwarding.Rule{
|
rule, err := forwarding.NormalizeRule(forwarding.Rule{
|
||||||
Family: operation.Family, Protocol: protocol, Port: operation.Port, TargetIP: operation.TargetIP,
|
Family: operation.Family, Protocol: protocol, Port: operation.Port,
|
||||||
TargetPort: operation.TargetPort, Interface: operation.Interface,
|
TargetIP: operation.TargetIP, TargetPort: operation.TargetPort, Interface: operation.Interface,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
index := forwardingRuleIndex(desired, rule)
|
groups[len(groups)-1].rules = append(groups[len(groups)-1].rules, rule)
|
||||||
switch forwarding.OperationType(operation.Operation) {
|
}
|
||||||
case forwarding.OperationAdd:
|
}
|
||||||
if index >= 0 {
|
stored, err := s.rules.List(ctx)
|
||||||
return nil, forwarding.ErrRuleExists
|
if err != nil {
|
||||||
}
|
return err
|
||||||
desired = append(desired, rule)
|
}
|
||||||
case forwarding.OperationRemove:
|
byIdentity := make(map[string]model.ForwardingRule, len(stored))
|
||||||
if index >= 0 {
|
for index, rule := range forwardingRulesFromModels(stored) {
|
||||||
desired = append(desired[:index], desired[index+1:]...)
|
normalized, err := forwarding.NormalizeRule(rule)
|
||||||
}
|
if err != nil {
|
||||||
default:
|
return err
|
||||||
return nil, fmt.Errorf("unsupported forwarding operation %q", operation.Operation)
|
}
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return desired, nil
|
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 {
|
||||||
func forwardingRuleIndex(rules []forwarding.Rule, wanted forwarding.Rule) int {
|
old[rule.Identity()] = true
|
||||||
wantedIdentity := wanted.Identity()
|
}
|
||||||
for index, rule := range rules {
|
unchanged := len(old) == len(groups[1].rules)
|
||||||
if rule.Identity() == wantedIdentity {
|
duplicate := false
|
||||||
return index
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1
|
var client forwarding.Adapter
|
||||||
}
|
var failures []error
|
||||||
|
for _, group := range groups {
|
||||||
func forwardingOperationsOnlyRemove(operations []dto.ForwardRuleOperation) bool {
|
byFamily := make(map[string][]forwarding.Rule, 2)
|
||||||
if len(operations) == 0 {
|
seen := make(map[string]bool, len(group.rules))
|
||||||
return false
|
for _, rule := range group.rules {
|
||||||
}
|
key := rule.Identity()
|
||||||
for _, operation := range operations {
|
_, exists := byIdentity[key]
|
||||||
if operation.Operation != string(forwarding.OperationRemove) {
|
if seen[key] || (group.operation == forwarding.OperationAdd && exists) {
|
||||||
return false
|
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 true
|
return errors.Join(failures...)
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ForwardingService) manager() (*forwarding.Manager, error) {
|
|
||||||
return s.managerFactory()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newForwardingManager() (*forwarding.Manager, error) {
|
|
||||||
return newForwardingManagerFor(configuredForwardingBackend())
|
|
||||||
}
|
|
||||||
|
|
||||||
func configuredForwardingBackend() string {
|
|
||||||
selected, _ := settingRepo.GetValueByKey(constant.FirewallForwardingBackendKey)
|
|
||||||
selected = strings.TrimSpace(selected)
|
|
||||||
if selected == "" {
|
|
||||||
return constant.FirewallProviderIptables
|
|
||||||
}
|
|
||||||
return selected
|
|
||||||
}
|
|
||||||
|
|
||||||
func newForwardingManagerFor(backend string) (*forwarding.Manager, error) {
|
|
||||||
client, err := lifecycle.NewClientFor(backend)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"%w: selected forwarding backend %s: %w",
|
|
||||||
errForwardingBackendUnavailable, backend, err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
adapter, err := forwardingproviders.New(client.Name())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return forwarding.NewManager(adapter, client), nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -325,18 +324,16 @@ func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
client, err := docker.NewDockerClient()
|
|
||||||
if err != nil {
|
|
||||||
taskItem.Log("Failed to create Docker client: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
for _, itemPath := range req.Paths {
|
for _, itemPath := range req.Paths {
|
||||||
currentPath := itemPath
|
currentPath := itemPath
|
||||||
itemName := path.Base(currentPath)
|
itemName := path.Base(currentPath)
|
||||||
taskItem.AddSubTask(i18n.GetWithName("TaskImport", itemName), func(t *task.Task) error {
|
taskItem.AddSubTask(i18n.GetWithName("TaskImport", itemName), func(t *task.Task) error {
|
||||||
taskItem.Logf("----------------- %s -----------------", itemName)
|
taskItem.Logf("----------------- %s -----------------", itemName)
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
file, err := os.Open(currentPath)
|
file, err := os.Open(currentPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -347,14 +344,9 @@ func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
content, err := io.ReadAll(res.Body)
|
return consumeImageLoadResponse(res.Body, func(message string) {
|
||||||
if err != nil {
|
taskItem.Log(message)
|
||||||
return err
|
})
|
||||||
}
|
|
||||||
if strings.Contains(string(content), "Error") {
|
|
||||||
return errors.New(string(content))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
_ = taskItem.Execute()
|
_ = taskItem.Execute()
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Docker may report load failures in a successful HTTP response's JSON stream.
|
||||||
|
func consumeImageLoadResponse(reader io.Reader, log func(string)) error {
|
||||||
|
decoder := json.NewDecoder(reader)
|
||||||
|
for {
|
||||||
|
var message struct {
|
||||||
|
Stream string `json:"stream"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
ErrorDetail struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
} `json:"errorDetail"`
|
||||||
|
}
|
||||||
|
if err := decoder.Decode(&message); err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if message.Error != "" {
|
||||||
|
return errors.New(message.Error)
|
||||||
|
}
|
||||||
|
if message.ErrorDetail.Message != "" {
|
||||||
|
return errors.New(message.ErrorDetail.Message)
|
||||||
|
}
|
||||||
|
if text := strings.TrimSpace(message.Stream); text != "" {
|
||||||
|
log(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+216
-104
@@ -128,115 +128,135 @@ func (m *MonitorService) LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorDa
|
|||||||
|
|
||||||
func (m *MonitorService) LoadGPUOptions() dto.MonitorGPUOptions {
|
func (m *MonitorService) LoadGPUOptions() dto.MonitorGPUOptions {
|
||||||
var data dto.MonitorGPUOptions
|
var data dto.MonitorGPUOptions
|
||||||
exist, client := accelerator.New()
|
seen := make(map[string]bool)
|
||||||
if !exist {
|
if exist, client := accelerator.New(); exist {
|
||||||
return data
|
snapshot, err := client.Collect(context.Background())
|
||||||
}
|
if err != nil {
|
||||||
snapshot, err := client.Collect(context.Background())
|
global.LOG.Warnf("Load accelerator options failed: %v", err)
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("Load accelerator info failed, err: %v", err)
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
if warning := snapshot.Warning(); warning != nil {
|
|
||||||
global.LOG.Warnf("Load accelerator info partially failed, err: %v", warning)
|
|
||||||
}
|
|
||||||
return loadGPUOptions(snapshot)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadGPUOptions(snapshot *accelerator.Snapshot) dto.MonitorGPUOptions {
|
|
||||||
var data dto.MonitorGPUOptions
|
|
||||||
hasGPUOrNPU := false
|
|
||||||
hasXPU := false
|
|
||||||
for _, item := range snapshot.Devices {
|
|
||||||
if item.Kind == accelerator.KindXPU {
|
|
||||||
hasXPU = true
|
|
||||||
} else {
|
} else {
|
||||||
hasGPUOrNPU = true
|
data = loadGPUOptions(snapshot)
|
||||||
|
for _, item := range data.ChartHide {
|
||||||
|
seen[item.DeviceID] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch {
|
devices, err := monitorRepo.GetGPUDevices()
|
||||||
case hasGPUOrNPU && hasXPU:
|
if err != nil {
|
||||||
data.GPUType = "mixed"
|
global.LOG.Warnf("Load accelerator history options failed: %v", err)
|
||||||
case hasXPU:
|
return data
|
||||||
data.GPUType = "xpu"
|
|
||||||
case hasGPUOrNPU:
|
|
||||||
data.GPUType = "gpu"
|
|
||||||
}
|
}
|
||||||
|
for _, device := range devices {
|
||||||
sort.Slice(snapshot.Devices, func(i, j int) bool {
|
key := device.DeviceID
|
||||||
if snapshot.Devices[i].Kind != snapshot.Devices[j].Kind {
|
if key == "" {
|
||||||
return snapshot.Devices[i].Kind < snapshot.Devices[j].Kind
|
key = "legacy:" + device.ProductName
|
||||||
}
|
}
|
||||||
if snapshot.Devices[i].Vendor != snapshot.Devices[j].Vendor {
|
if seen[key] {
|
||||||
return snapshot.Devices[i].Vendor < snapshot.Devices[j].Vendor
|
continue
|
||||||
}
|
}
|
||||||
if snapshot.Devices[i].NPUIndex != snapshot.Devices[j].NPUIndex {
|
seen[key] = true
|
||||||
return snapshot.Devices[i].NPUIndex < snapshot.Devices[j].NPUIndex
|
data.ChartHide = append(data.ChartHide, dto.GPUChartHide{DeviceID: device.DeviceID, ProductName: device.ProductName, Type: device.DeviceType, Legacy: device.DeviceID == ""})
|
||||||
}
|
data.Options = append(data.Options, device.ProductName)
|
||||||
if snapshot.Devices[i].ChipIndex != snapshot.Devices[j].ChipIndex {
|
|
||||||
return snapshot.Devices[i].ChipIndex < snapshot.Devices[j].ChipIndex
|
|
||||||
}
|
|
||||||
return snapshot.Devices[i].Index < snapshot.Devices[j].Index
|
|
||||||
})
|
|
||||||
for _, item := range snapshot.Devices {
|
|
||||||
optionType := "gpu"
|
|
||||||
if item.Kind == accelerator.KindXPU {
|
|
||||||
optionType = "xpu"
|
|
||||||
}
|
|
||||||
chartHide := dto.GPUChartHide{
|
|
||||||
ProductName: item.Label,
|
|
||||||
Type: optionType,
|
|
||||||
GPU: !item.Capabilities.Utilization,
|
|
||||||
Memory: !item.Capabilities.Memory,
|
|
||||||
Power: !item.Capabilities.Power,
|
|
||||||
PowerLimit: !item.Capabilities.PowerLimit,
|
|
||||||
Temperature: !item.Capabilities.Temperature,
|
|
||||||
Speed: !item.Capabilities.FanSpeed,
|
|
||||||
}
|
|
||||||
data.ChartHide = append(data.ChartHide, chartHide)
|
|
||||||
data.Options = append(data.Options, chartHide.ProductName)
|
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MonitorService) LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error) {
|
func (m *MonitorService) LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error) {
|
||||||
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
|
||||||
req.StartTime = req.StartTime.In(loc)
|
|
||||||
req.EndTime = req.EndTime.In(loc)
|
|
||||||
var data dto.MonitorGPUData
|
var data dto.MonitorGPUData
|
||||||
gpuList, err := monitorRepo.GetGPU(repo.WithByCreatedAt(req.StartTime, req.EndTime), monitorRepo.WithByProductName(req.ProductName))
|
if req.StartTime.IsZero() || req.EndTime.IsZero() || !req.EndTime.After(req.StartTime) {
|
||||||
|
return data, fmt.Errorf("invalid GPU history time range")
|
||||||
|
}
|
||||||
|
if req.DeviceID == "" && req.ProductName == "" {
|
||||||
|
return data, fmt.Errorf("GPU history requires a device")
|
||||||
|
}
|
||||||
|
if req.Aggregation != "" && req.Aggregation != "avg" && req.Aggregation != "max" {
|
||||||
|
return data, fmt.Errorf("invalid GPU history aggregation")
|
||||||
|
}
|
||||||
|
loc, err := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
|
req.StartTime, req.EndTime = req.StartTime.In(loc), req.EndTime.In(loc)
|
||||||
for _, gpu := range gpuList {
|
opts := []repo.DBOption{repo.WithByCreatedAt(req.StartTime, req.EndTime), monitorRepo.WithByGPUDevice(req.DeviceID, req.ProductName, req.Legacy)}
|
||||||
data.Date = append(data.Date, gpu.CreatedAt)
|
data.SampleCount, err = monitorRepo.CountGPU(opts...)
|
||||||
data.GPUValue = append(data.GPUValue, gpu.GPUUtil)
|
if err != nil || data.SampleCount == 0 {
|
||||||
data.TemperatureValue = append(data.TemperatureValue, gpu.Temperature)
|
return data, err
|
||||||
data.PowerUsed = append(data.PowerUsed, gpu.PowerDraw)
|
}
|
||||||
data.PowerTotal = append(data.PowerTotal, gpu.MaxPowerLimit)
|
if data.SampleCount > 1200 {
|
||||||
if gpu.MaxPowerLimit != 0 {
|
seconds := req.EndTime.Unix() - req.StartTime.Unix() + 1
|
||||||
data.PowerPercent = append(data.PowerPercent, gpu.PowerDraw/gpu.MaxPowerLimit*100)
|
data.BucketSeconds = (seconds + 599) / 600
|
||||||
} else {
|
}
|
||||||
data.PowerPercent = append(data.PowerPercent, float64(0))
|
points, err := monitorRepo.GetGPUHistory(req.StartTime, data.BucketSeconds, req.Aggregation, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
samples := make([]repo.GPUHistoryPoint, 0, len(points))
|
||||||
|
if data.BucketSeconds > 0 {
|
||||||
|
next := 0
|
||||||
|
for bucket := int64(0); bucket <= (req.EndTime.Unix()-req.StartTime.Unix())/data.BucketSeconds; bucket++ {
|
||||||
|
point := repo.GPUHistoryPoint{}
|
||||||
|
if next < len(points) && points[next].Bucket == bucket {
|
||||||
|
point = points[next]
|
||||||
|
next++
|
||||||
|
}
|
||||||
|
point.CreatedAt = time.Unix(req.StartTime.Unix()+bucket*data.BucketSeconds, 0).In(loc)
|
||||||
|
if bucket == 0 {
|
||||||
|
point.CreatedAt = req.StartTime
|
||||||
|
}
|
||||||
|
samples = append(samples, point)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
data.MemoryTotal = append(data.MemoryTotal, gpu.MemTotal)
|
for i, point := range points {
|
||||||
data.MemoryUsed = append(data.MemoryUsed, gpu.MemUsed)
|
if i > 0 && points[i-1].IntervalSeconds > 0 {
|
||||||
if gpu.MemTotal != 0 {
|
interval := time.Duration(points[i-1].IntervalSeconds) * time.Second
|
||||||
data.MemoryPercent = append(data.MemoryPercent, gpu.MemUsed/gpu.MemTotal*100)
|
if point.CreatedAt.Sub(points[i-1].CreatedAt) > 2*interval {
|
||||||
} else {
|
samples = append(samples, repo.GPUHistoryPoint{MonitorGPU: model.MonitorGPU{BaseModel: model.BaseModel{CreatedAt: points[i-1].CreatedAt.Add(interval)}}})
|
||||||
data.MemoryPercent = append(data.MemoryPercent, float64(0))
|
}
|
||||||
|
}
|
||||||
|
samples = append(samples, point)
|
||||||
}
|
}
|
||||||
var process []dto.GPUProcess
|
}
|
||||||
if err := json.Unmarshal([]byte(gpu.Processes), &process); err == nil {
|
for _, point := range samples {
|
||||||
data.ProcessCount = append(data.ProcessCount, len(process))
|
data.Date = append(data.Date, point.CreatedAt)
|
||||||
data.GPUProcesses = append(data.GPUProcesses, process)
|
data.MemoryActivity = append(data.MemoryActivity, point.MemoryActivity)
|
||||||
} else {
|
data.EncoderUtil = append(data.EncoderUtil, point.EncoderUtil)
|
||||||
data.ProcessCount = append(data.ProcessCount, 0)
|
data.DecoderUtil = append(data.DecoderUtil, point.DecoderUtil)
|
||||||
data.GPUProcesses = append(data.GPUProcesses, []dto.GPUProcess{})
|
data.JPEGUtil = append(data.JPEGUtil, point.JPEGUtil)
|
||||||
|
data.OFAUtil = append(data.OFAUtil, point.OFAUtil)
|
||||||
|
data.MediaUtil = append(data.MediaUtil, point.MediaUtil)
|
||||||
|
data.ComputeUtil = append(data.ComputeUtil, point.ComputeUtil)
|
||||||
|
data.CopyUtil = append(data.CopyUtil, point.CopyUtil)
|
||||||
|
data.HotspotTemperature = append(data.HotspotTemperature, point.HotspotTemperature)
|
||||||
|
data.FanRPM = append(data.FanRPM, point.FanRPM)
|
||||||
|
data.AICPUUtil = append(data.AICPUUtil, point.AICPUUtil)
|
||||||
|
data.CtrlCPUUtil = append(data.CtrlCPUUtil, point.CtrlCPUUtil)
|
||||||
|
data.DDRUsed = append(data.DDRUsed, point.DDRUsed)
|
||||||
|
data.DDRTotal = append(data.DDRTotal, point.DDRTotal)
|
||||||
|
data.HBMUsed = append(data.HBMUsed, point.HBMUsed)
|
||||||
|
data.HBMTotal = append(data.HBMTotal, point.HBMTotal)
|
||||||
|
data.DDRBandwidth = append(data.DDRBandwidth, point.DDRBandwidth)
|
||||||
|
data.HBMBandwidth = append(data.HBMBandwidth, point.HBMBandwidth)
|
||||||
|
data.MemoryBandwidth = append(data.MemoryBandwidth, point.MemoryBandwidth)
|
||||||
|
data.MediaFrequency = append(data.MediaFrequency, point.MediaFrequency)
|
||||||
|
data.HugepagesUsed = append(data.HugepagesUsed, point.HugepagesUsed)
|
||||||
|
data.HugepagesTotal = append(data.HugepagesTotal, point.HugepagesTotal)
|
||||||
|
data.GPUValue = append(data.GPUValue, point.GPUUtil)
|
||||||
|
data.TemperatureValue = append(data.TemperatureValue, point.Temperature)
|
||||||
|
data.MemoryTemperatureValue = append(data.MemoryTemperatureValue, point.MemoryTemperature)
|
||||||
|
data.PowerUsed = append(data.PowerUsed, point.PowerDraw)
|
||||||
|
data.PowerTotal = append(data.PowerTotal, point.MaxPowerLimit)
|
||||||
|
data.PowerPercent = append(data.PowerPercent, point.PowerPercent)
|
||||||
|
data.MemoryPercent = append(data.MemoryPercent, point.MemoryPercent)
|
||||||
|
data.MemoryTotal = append(data.MemoryTotal, point.MemTotal)
|
||||||
|
data.MemoryUsed = append(data.MemoryUsed, point.MemUsed)
|
||||||
|
data.SpeedValue = append(data.SpeedValue, point.FanSpeed)
|
||||||
|
data.FrequencyValue = append(data.FrequencyValue, point.Frequency)
|
||||||
|
data.MemoryFrequencyValue = append(data.MemoryFrequencyValue, point.MemoryFrequency)
|
||||||
|
data.ProcessCount = append(data.ProcessCount, point.ProcessCount)
|
||||||
|
var processes []dto.GPUProcess
|
||||||
|
if data.BucketSeconds == 0 && point.ProcessCount != nil {
|
||||||
|
_ = json.Unmarshal([]byte(point.Processes), &processes)
|
||||||
}
|
}
|
||||||
data.SpeedValue = append(data.SpeedValue, gpu.FanSpeed)
|
data.GPUProcesses = append(data.GPUProcesses, processes)
|
||||||
}
|
}
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
@@ -307,7 +327,6 @@ func (m *MonitorService) CleanData() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MonitorService) Run() {
|
func (m *MonitorService) Run() {
|
||||||
saveAcceleratorDataToDB()
|
|
||||||
var itemModel model.MonitorBase
|
var itemModel model.MonitorBase
|
||||||
totalPercent, _ := cpu.Percent(3*time.Second, false)
|
totalPercent, _ := cpu.Percent(3*time.Second, false)
|
||||||
if len(totalPercent) == 1 {
|
if len(totalPercent) == 1 {
|
||||||
@@ -343,6 +362,7 @@ func (m *MonitorService) Run() {
|
|||||||
|
|
||||||
m.loadDiskIO()
|
m.loadDiskIO()
|
||||||
m.loadNetIO()
|
m.loadNetIO()
|
||||||
|
m.saveGPUData()
|
||||||
|
|
||||||
MonitorStoreDays, err := settingRepo.Get(settingRepo.WithByKey("MonitorStoreDays"))
|
MonitorStoreDays, err := settingRepo.Get(settingRepo.WithByKey("MonitorStoreDays"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -602,7 +622,62 @@ func StartMonitor(removeBefore bool, interval string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveAcceleratorDataToDB() {
|
func loadGPUOptions(snapshot *accelerator.Snapshot) dto.MonitorGPUOptions {
|
||||||
|
var data dto.MonitorGPUOptions
|
||||||
|
hasGPUOrNPU := false
|
||||||
|
hasXPU := false
|
||||||
|
for _, item := range snapshot.Devices {
|
||||||
|
if item.Kind == accelerator.KindXPU {
|
||||||
|
hasXPU = true
|
||||||
|
} else {
|
||||||
|
hasGPUOrNPU = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case hasGPUOrNPU && hasXPU:
|
||||||
|
data.GPUType = "mixed"
|
||||||
|
case hasXPU:
|
||||||
|
data.GPUType = "xpu"
|
||||||
|
case hasGPUOrNPU:
|
||||||
|
data.GPUType = "gpu"
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(snapshot.Devices, func(i, j int) bool {
|
||||||
|
if snapshot.Devices[i].Kind != snapshot.Devices[j].Kind {
|
||||||
|
return snapshot.Devices[i].Kind < snapshot.Devices[j].Kind
|
||||||
|
}
|
||||||
|
if snapshot.Devices[i].Vendor != snapshot.Devices[j].Vendor {
|
||||||
|
return snapshot.Devices[i].Vendor < snapshot.Devices[j].Vendor
|
||||||
|
}
|
||||||
|
if snapshot.Devices[i].NPUIndex != snapshot.Devices[j].NPUIndex {
|
||||||
|
return snapshot.Devices[i].NPUIndex < snapshot.Devices[j].NPUIndex
|
||||||
|
}
|
||||||
|
if snapshot.Devices[i].ChipIndex != snapshot.Devices[j].ChipIndex {
|
||||||
|
return snapshot.Devices[i].ChipIndex < snapshot.Devices[j].ChipIndex
|
||||||
|
}
|
||||||
|
return snapshot.Devices[i].Index < snapshot.Devices[j].Index
|
||||||
|
})
|
||||||
|
for _, item := range snapshot.Devices {
|
||||||
|
chartHide := dto.GPUChartHide{
|
||||||
|
DeviceID: item.ID,
|
||||||
|
ProductName: item.Label,
|
||||||
|
Type: string(item.Kind),
|
||||||
|
}
|
||||||
|
data.ChartHide = append(data.ChartHide, chartHide)
|
||||||
|
data.Options = append(data.Options, chartHide.ProductName)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MonitorService) saveGPUData() {
|
||||||
|
status, err := settingRepo.GetValueByKey("MonitorStatus")
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("load monitor status failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if status != constant.StatusEnable {
|
||||||
|
return
|
||||||
|
}
|
||||||
exist, client := accelerator.New()
|
exist, client := accelerator.New()
|
||||||
if !exist {
|
if !exist {
|
||||||
return
|
return
|
||||||
@@ -615,27 +690,64 @@ func saveAcceleratorDataToDB() {
|
|||||||
if warning := snapshot.Warning(); warning != nil {
|
if warning := snapshot.Warning(); warning != nil {
|
||||||
global.LOG.Warnf("load accelerator monitor data partially failed, err: %v", warning)
|
global.LOG.Warnf("load accelerator monitor data partially failed, err: %v", warning)
|
||||||
}
|
}
|
||||||
|
intervalSeconds := 0
|
||||||
|
if setting, err := settingRepo.Get(settingRepo.WithByKey("MonitorInterval")); err == nil {
|
||||||
|
intervalSeconds, _ = strconv.Atoi(setting.Value)
|
||||||
|
}
|
||||||
list := make([]model.MonitorGPU, 0, len(snapshot.Devices))
|
list := make([]model.MonitorGPU, 0, len(snapshot.Devices))
|
||||||
for _, device := range snapshot.Devices {
|
for _, device := range snapshot.Devices {
|
||||||
list = append(list, newMonitorGPU(device))
|
item := newMonitorGPU(device)
|
||||||
|
item.CreatedAt = snapshot.Info.CollectedAt
|
||||||
|
item.IntervalSeconds = intervalSeconds
|
||||||
|
list = append(list, item)
|
||||||
}
|
}
|
||||||
if err := repo.NewIMonitorRepo().BatchCreateMonitorGPU(list); err != nil {
|
if err := monitorRepo.BatchCreateMonitorGPU(list); err != nil {
|
||||||
global.LOG.Errorf("batch create accelerator monitor data failed, err: %v", err)
|
global.LOG.Errorf("batch create accelerator monitor data failed, err: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMonitorGPU(device accelerator.Device) model.MonitorGPU {
|
func newMonitorGPU(device accelerator.Device) model.MonitorGPU {
|
||||||
item := model.MonitorGPU{
|
item := model.MonitorGPU{
|
||||||
ProductName: device.Label,
|
MemoryUtil: device.Metrics.MemoryUtil.Value,
|
||||||
GPUUtil: device.Metrics.Utilization.ValueOrZero(),
|
MemoryActivity: device.Metrics.MemoryActivity.Value,
|
||||||
Temperature: device.Metrics.Temperature.ValueOrZero(),
|
EncoderUtil: device.Metrics.EncoderUtil.Value,
|
||||||
PowerDraw: device.Metrics.Power.ValueOrZero(),
|
DecoderUtil: device.Metrics.DecoderUtil.Value,
|
||||||
MaxPowerLimit: device.Metrics.PowerLimit.ValueOrZero(),
|
JPEGUtil: device.Metrics.JPEGUtil.Value,
|
||||||
MemUsed: device.Metrics.MemoryUsed.ValueOrZero(),
|
OFAUtil: device.Metrics.OFAUtil.Value,
|
||||||
MemTotal: device.Metrics.MemoryTotal.ValueOrZero(),
|
MediaUtil: device.Metrics.MediaUtil.Value,
|
||||||
FanSpeed: int(device.Metrics.FanSpeed.ValueOrZero()),
|
ComputeUtil: device.Metrics.ComputeUtil.Value,
|
||||||
|
CopyUtil: device.Metrics.CopyUtil.Value,
|
||||||
|
HotspotTemperature: device.Metrics.HotspotTemperature.Value,
|
||||||
|
FanRPM: device.Metrics.FanRPM.Value,
|
||||||
|
AICPUUtil: device.Metrics.AICPUUtil.Value,
|
||||||
|
CtrlCPUUtil: device.Metrics.CtrlCPUUtil.Value,
|
||||||
|
DDRUsed: device.Metrics.DDRUsed.Value,
|
||||||
|
DDRTotal: device.Metrics.DDRTotal.Value,
|
||||||
|
HBMUsed: device.Metrics.HBMUsed.Value,
|
||||||
|
HBMTotal: device.Metrics.HBMTotal.Value,
|
||||||
|
DDRBandwidth: device.Metrics.DDRBandwidth.Value,
|
||||||
|
HBMBandwidth: device.Metrics.HBMBandwidth.Value,
|
||||||
|
MemoryBandwidth: device.Metrics.MemoryBandwidth.Value,
|
||||||
|
MediaFrequency: device.Metrics.MediaFrequency.Value,
|
||||||
|
HugepagesUsed: device.Metrics.HugepagesUsed.Value,
|
||||||
|
HugepagesTotal: device.Metrics.HugepagesTotal.Value,
|
||||||
|
|
||||||
|
ProductName: device.Label,
|
||||||
|
DeviceID: device.ID,
|
||||||
|
DeviceType: string(device.Kind),
|
||||||
|
ProcessStatus: device.ProcessStatus,
|
||||||
|
Frequency: device.Metrics.Frequency.Value,
|
||||||
|
MemoryFrequency: device.Metrics.MemoryFrequency.Value,
|
||||||
|
MemoryTemperature: device.Metrics.MemoryTemperature.Value,
|
||||||
|
GPUUtil: device.Metrics.Utilization.Value,
|
||||||
|
Temperature: device.Metrics.Temperature.Value,
|
||||||
|
PowerDraw: device.Metrics.Power.Value,
|
||||||
|
MaxPowerLimit: device.Metrics.PowerLimit.Value,
|
||||||
|
MemUsed: device.Metrics.MemoryUsed.Value,
|
||||||
|
MemTotal: device.Metrics.MemoryTotal.Value,
|
||||||
|
FanSpeed: device.Metrics.FanSpeed.Value,
|
||||||
}
|
}
|
||||||
if len(device.Processes) == 0 {
|
if device.ProcessStatus != "ok" {
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
processes := make([]dto.GPUProcess, 0, len(device.Processes))
|
processes := make([]dto.GPUProcess, 0, len(device.Processes))
|
||||||
|
|||||||
+31
-24
@@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -22,6 +23,7 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
||||||
csvexport "github.com/1Panel-dev/1Panel/agent/utils/csv_export"
|
csvexport "github.com/1Panel-dev/1Panel/agent/utils/csv_export"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/geo"
|
"github.com/1Panel-dev/1Panel/agent/utils/geo"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
@@ -217,10 +219,21 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
oldPortValue := strings.Join(loadSSHPortValues(directives), ",")
|
oldPortValue := strings.Join(loadSSHPortValues(directives), ",")
|
||||||
|
if req.Key == "Port" {
|
||||||
|
if err := checkSSHPortAvailability(splitSSHPorts(oldPortValue), splitSSHPorts(req.NewValue)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := updateSSHDirectiveValue(req.Key, req.NewValue, directives); err != nil {
|
if err := updateSSHDirectiveValue(req.Key, req.NewValue, directives); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if req.Key == "Port" {
|
if req.Key == "Port" {
|
||||||
|
if err := newFirewallService().updateSystemAccessPortWhitelist(context.Background(), firewall.PortWhitelistTypeSSH, splitSSHPorts(req.NewValue)); err != nil {
|
||||||
|
if restoreErr := rewriteSSHManagedDirectives(sshPath, "Port", buildSSHDirectiveLines("Port", oldPortValue)); restoreErr != nil {
|
||||||
|
return fmt.Errorf("save SSH whitelist: %w; restore SSH configuration: %v", err, restoreErr)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
handleSSHPortUpdate(oldPortValue, req.NewValue)
|
handleSSHPortUpdate(oldPortValue, req.NewValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,18 +333,6 @@ func handleSSHPortUpdate(oldValue, newValue string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removedPorts, err := parseSSHPortsToInts(diffSSHPorts(oldPorts, newPorts))
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("parse removed ssh ports failed, err: %v", err)
|
|
||||||
} else {
|
|
||||||
addedPorts, err := parseSSHPortsToInts(diffSSHPorts(newPorts, oldPorts))
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("parse added ssh ports failed, err: %v", err)
|
|
||||||
} else if err := OperateFirewallPort(removedPorts, addedPorts); err != nil {
|
|
||||||
global.LOG.Errorf("reset firewall rules %s -> %s failed, err: %v", oldValue, newValue, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
primaryPort, err := loadPrimarySSHPort(newValue)
|
primaryPort, err := loadPrimarySSHPort(newValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.LOG.Errorf("load primary ssh port from %s failed, err: %v", newValue, err)
|
global.LOG.Errorf("load primary ssh port from %s failed, err: %v", newValue, err)
|
||||||
@@ -371,6 +372,24 @@ func diffSSHPorts(left, right []string) []string {
|
|||||||
return diff
|
return diff
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkSSHPortAvailability(oldPorts, newPorts []string) error {
|
||||||
|
for _, port := range diffSSHPorts(newPorts, oldPorts) {
|
||||||
|
value, err := strconv.Atoi(port)
|
||||||
|
if err != nil || value < 1 || value > 65535 {
|
||||||
|
return fmt.Errorf("invalid SSH port %q", port)
|
||||||
|
}
|
||||||
|
if common.ScanPort(value) {
|
||||||
|
return buserr.WithDetail("ErrPortInUsed", value, nil)
|
||||||
|
}
|
||||||
|
listener, err := net.Listen("tcp4", ":"+strconv.Itoa(value))
|
||||||
|
if err != nil {
|
||||||
|
return buserr.WithDetail("ErrPortInUsed", value, nil)
|
||||||
|
}
|
||||||
|
_ = listener.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func loadPrimarySSHPort(value string) (int, error) {
|
func loadPrimarySSHPort(value string) (int, error) {
|
||||||
ports := splitSSHPorts(value)
|
ports := splitSSHPorts(value)
|
||||||
if len(ports) == 0 {
|
if len(ports) == 0 {
|
||||||
@@ -379,18 +398,6 @@ func loadPrimarySSHPort(value string) (int, error) {
|
|||||||
return strconv.Atoi(ports[0])
|
return strconv.Atoi(ports[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSSHPortsToInts(ports []string) ([]int, error) {
|
|
||||||
var values []int
|
|
||||||
for _, port := range ports {
|
|
||||||
value, err := strconv.Atoi(port)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
values = append(values, value)
|
|
||||||
}
|
|
||||||
return values, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runWithOptionalSudo(sudo, name string, args ...string) (string, error) {
|
func runWithOptionalSudo(sudo, name string, args ...string) (string, error) {
|
||||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second))
|
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second))
|
||||||
if sudo != "" {
|
if sudo != "" {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -32,7 +33,9 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
_ = OperateFirewallPort(nil, addPorts)
|
if err := ensureFirewallPorts(addPorts); err != nil {
|
||||||
|
global.LOG.Errorf("allow website firewall ports failed: %v", err)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||||
|
|||||||
+12
-2
@@ -154,7 +154,7 @@ func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, e
|
|||||||
logPath := path.Join(global.Dir.TaskDir, taskScope, taskID+".log")
|
logPath := path.Join(global.Dir.TaskDir, taskScope, taskID+".log")
|
||||||
logger := logrus.New()
|
logger := logrus.New()
|
||||||
logger.SetFormatter(&SimpleFormatter{})
|
logger.SetFormatter(&SimpleFormatter{})
|
||||||
logFile, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, constant.FilePerm)
|
logFile, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY|os.O_APPEND, constant.FilePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open log file: %w", err)
|
return nil, fmt.Errorf("failed to open log file: %w", err)
|
||||||
}
|
}
|
||||||
@@ -283,8 +283,18 @@ func (t *Task) updateTask(task *model.Task) {
|
|||||||
_ = t.taskRepo.Update(context.Background(), task)
|
_ = t.taskRepo.Update(context.Background(), task)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) Execute() error {
|
// Prepare makes a task visible before dispatching it to a background worker.
|
||||||
|
func (t *Task) Prepare() error {
|
||||||
if err := t.taskRepo.Save(context.Background(), t.Task); err != nil {
|
if err := t.taskRepo.Save(context.Background(), t.Task); err != nil {
|
||||||
|
_ = t.logFile.Close()
|
||||||
|
global.RemoveTaskCancel(t.TaskID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Task) Execute() error {
|
||||||
|
if err := t.Prepare(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@@ -644,6 +644,13 @@
|
|||||||
"formatZH": "创建容器网络 name",
|
"formatZH": "创建容器网络 name",
|
||||||
"formatEN": "create container network [name]"
|
"formatEN": "create container network [name]"
|
||||||
},
|
},
|
||||||
|
"/containers/network/clean": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "清理未使用的容器网络",
|
||||||
|
"formatEN": "Clean unused container networks"
|
||||||
|
},
|
||||||
"/containers/network/del": {
|
"/containers/network/del": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"names"
|
"names"
|
||||||
@@ -2470,10 +2477,19 @@
|
|||||||
},
|
},
|
||||||
"/core/xpack/vms/del": {
|
"/core/xpack/vms/del": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"name"
|
"id"
|
||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "virtual_machines",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
"formatZH": "删除虚拟机 [name]",
|
"formatZH": "删除虚拟机 [name]",
|
||||||
"formatEN": "delete VM [name]"
|
"formatEN": "delete VM [name]"
|
||||||
},
|
},
|
||||||
@@ -3770,12 +3786,31 @@
|
|||||||
},
|
},
|
||||||
"/hosts/firewall/settings/whitelist": {
|
"/hosts/firewall/settings/whitelist": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"value"
|
"rule"
|
||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "更新防火墙端口白名单 [value]",
|
"formatZH": "创建防火墙端口白名单",
|
||||||
"formatEN": "update firewall port whitelist [value]"
|
"formatEN": "create firewall port whitelist"
|
||||||
|
},
|
||||||
|
"/hosts/firewall/settings/whitelist/delete": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"rules"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "删除防火墙端口白名单",
|
||||||
|
"formatEN": "delete firewall port whitelist"
|
||||||
|
},
|
||||||
|
"/hosts/firewall/settings/whitelist/update": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"oldRule",
|
||||||
|
"rule"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "编辑防火墙端口白名单",
|
||||||
|
"formatEN": "update firewall port whitelist"
|
||||||
},
|
},
|
||||||
"/hosts/monitor/clean": {
|
"/hosts/monitor/clean": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -4000,12 +4035,21 @@
|
|||||||
},
|
},
|
||||||
"/runtimes/operate": {
|
"/runtimes/operate": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"id"
|
"ID"
|
||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [
|
||||||
"formatZH": "操作运行环境 [id]",
|
{
|
||||||
"formatEN": "Operate runtime [id]"
|
"input_column": "id",
|
||||||
|
"input_value": "ID",
|
||||||
|
"isList": false,
|
||||||
|
"db": "runtimes",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "操作运行环境 [name]",
|
||||||
|
"formatEN": "Operate runtime [name]"
|
||||||
},
|
},
|
||||||
"/runtimes/php/config": {
|
"/runtimes/php/config": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const (
|
|||||||
FirewallPingStatusKey = "BanPing"
|
FirewallPingStatusKey = "BanPing"
|
||||||
|
|
||||||
FirewallPortWhiteList = "FirewallPortWhiteList"
|
FirewallPortWhiteList = "FirewallPortWhiteList"
|
||||||
FirewallPortWhiteListValue = "80/tcp,443/tcp,443/udp"
|
FirewallPortWhiteListValue = `[{"port":"80","protocol":"tcp","sources":["0.0.0.0/0","::/0"]},{"port":"443","protocol":"tcp","sources":["0.0.0.0/0","::/0"]},{"port":"443","protocol":"udp","sources":["0.0.0.0/0","::/0"]}]`
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
+24
-4
@@ -649,6 +649,7 @@ CommonAlert: "Panel {{ .node }}{{ .ip }}: {{ .msg }}. Log in to view details."
|
|||||||
NodeExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} nodes are abnormal. Log in to view details."
|
NodeExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} nodes are abnormal. Log in to view details."
|
||||||
LicenseExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} licenses are abnormal. Log in to view details."
|
LicenseExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} licenses are abnormal. Log in to view details."
|
||||||
SSHAndPanelLoginAlert: "Panel {{ .node }}{{ .ip }}: abnormal {{ .name }} login from {{ .loginIp }}. Log in to view details."
|
SSHAndPanelLoginAlert: "Panel {{ .node }}{{ .ip }}: abnormal {{ .name }} login from {{ .loginIp }}. Log in to view details."
|
||||||
|
CronJobSuccessAlert: "Panel {{ .node }}{{ .ip }}: scheduled task {{ .name }} completed successfully. Log in to view details."
|
||||||
|
|
||||||
# disk
|
# disk
|
||||||
DeviceNotFound: "Device {{ .name }} not found"
|
DeviceNotFound: "Device {{ .name }} not found"
|
||||||
@@ -702,7 +703,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
|||||||
FirewallCreateRuleExecutionFailed: 'Rule creation failed; no database record was saved and executed commands were not rolled back'
|
FirewallCreateRuleExecutionFailed: 'Rule creation failed; no database record was saved and executed commands were not rolled back'
|
||||||
FirewallCreateRulePersistenceFailed: 'The rule was created, but its management record could not be saved'
|
FirewallCreateRulePersistenceFailed: 'The rule was created, but its management record could not be saved'
|
||||||
FirewallAdoptRulePersistenceFailed: 'The rule was adopted, but its management record could not be saved'
|
FirewallAdoptRulePersistenceFailed: 'The rule was adopted, but its management record could not be saved'
|
||||||
FirewallSyncOperationsResult: 'Synchronization operations: {{ .removed }} deleted, {{ .created }} created, {{ .failed }} failed, {{ .skipped }} not executed'
|
FirewallSyncOperationsResult: 'Synchronization operations: {{ .removed }} deleted, {{ .created }} created, {{ .failed }} failed, {{ .skipped }} not executed, {{ .unchanged }} already matching (no changes needed)'
|
||||||
|
FirewallSyncRuleUnchanged: 'Already matching; no changes needed'
|
||||||
FirewallSyncStep: 'Synchronize rules to {{ .name }}'
|
FirewallSyncStep: 'Synchronize rules to {{ .name }}'
|
||||||
FirewallSyncFailed: '{{ .failed }} firewall rules failed to synchronize'
|
FirewallSyncFailed: '{{ .failed }} firewall rules failed to synchronize'
|
||||||
FirewallResetSourceStep: 'Reset and disable source firewall {{ .name }}'
|
FirewallResetSourceStep: 'Reset and disable source firewall {{ .name }}'
|
||||||
@@ -716,8 +718,26 @@ FirewallInspectDockerGuardStep: 'Inspect Docker firewall backend and policies'
|
|||||||
FirewallInitializeDockerGuardStep: 'Initialize and bind {{ .name }} port guard chains'
|
FirewallInitializeDockerGuardStep: 'Initialize and bind {{ .name }} port guard chains'
|
||||||
FirewallPersistDockerGuardStep: 'Persist Docker port guard status'
|
FirewallPersistDockerGuardStep: 'Persist Docker port guard status'
|
||||||
ErrFirewallRuleScopeChange: "The current firewall does not support changing a rule's scope (such as its IPv4/IPv6 address family). Please create a new rule."
|
ErrFirewallRuleScopeChange: "The current firewall does not support changing a rule's scope (such as its IPv4/IPv6 address family). Please create a new rule."
|
||||||
FirewallWhitelistTask: "Update firewall port whitelist"
|
|
||||||
FirewallWhitelistSaved: "Whitelist configuration saved"
|
|
||||||
FirewallWhitelistDeferred: "{{ .name }}: configuration saved; pending firewall activation"
|
|
||||||
FirewallWhitelistReleased: "{{ .name }}: whitelist protection released; allow rule retained. To close the port, delete the rule manually from the rule list"
|
FirewallWhitelistReleased: "{{ .name }}: whitelist protection released; allow rule retained. To close the port, delete the rule manually from the rule list"
|
||||||
FirewallWhitelistRequired: "{{ .name }}: protected by mandatory system port rules"
|
FirewallWhitelistRequired: "{{ .name }}: protected by mandatory system port rules"
|
||||||
|
FileTaskCopy: 'Copy files to {{ .dst }}'
|
||||||
|
FileTaskMove: 'Move files to {{ .dst }}'
|
||||||
|
FileTaskCompress: 'Compress files to {{ .dst }}'
|
||||||
|
FileTaskDecompress: 'Extract files to {{ .dst }}'
|
||||||
|
FileTaskSource: 'Source path: {{ .path }}'
|
||||||
|
FileTaskFormat: 'Archive format: {{ .format }}'
|
||||||
|
FileTaskRename: 'Target filename: {{ .name }}'
|
||||||
|
|
||||||
|
ErrFirewallBackendCleanupRequired: "The current backend {{ .current }} still contains 1Panel rules. Clean it up before switching to {{ .target }}."
|
||||||
|
ErrDockerIPv4ForwardingDisabled: "IPv4 forwarding is disabled. Set net.ipv4.ip_forward=1 before using Docker's firewall backend."
|
||||||
|
ErrFirewallRuleSavedApplyFailed: "The new rule configuration was saved, but applying it to the firewall failed. Retry by synchronizing: {{ .detail }}"
|
||||||
|
|
||||||
|
NetworkCleanupDeleted: "Deleted network [{{ .name }}] ({{ .id }})"
|
||||||
|
NetworkCleanupProtected: "Skipped network [{{ .name }}] ({{ .id }}): reserved network"
|
||||||
|
NetworkCleanupConnected: "Skipped network [{{ .name }}] ({{ .id }}): containers connected or network in use; not deleted"
|
||||||
|
NetworkCleanupUnsupported: "Skipped network [{{ .name }}] ({{ .id }}): special network"
|
||||||
|
NetworkCleanupGone: "Skipped network [{{ .name }}] ({{ .id }}): already removed"
|
||||||
|
NetworkCleanupInspectFailed: "Failed to inspect network [{{ .name }}] ({{ .id }}); not deleted"
|
||||||
|
NetworkCleanupRemoveFailed: "Failed to remove network [{{ .name }}] ({{ .id }})"
|
||||||
|
NetworkCleanupSummary: "Network cleanup finished: deleted {{ .deleted }}, skipped {{ .skipped }}, failed {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "Some networks could not be cleaned; see the task log"
|
||||||
|
|||||||
@@ -649,6 +649,7 @@ CommonAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .msg }}. Inicie sesión en el pa
|
|||||||
NodeExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} nodos son anómalos. Inicie sesión en el panel para ver los detalles.'
|
NodeExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} nodos son anómalos. Inicie sesión en el panel para ver los detalles.'
|
||||||
LicenseExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} licencias son anómalas. Inicie sesión en el panel para ver los detalles.'
|
LicenseExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} licencias son anómalas. Inicie sesión en el panel para ver los detalles.'
|
||||||
SSHAndPanelLoginAlert: 'Su Panel {{ .node }}{{ .ip }}, el inicio de sesión {{ .name }} desde {{ .loginIp }} es anómalo. Inicie sesión en el panel para ver los detalles.'
|
SSHAndPanelLoginAlert: 'Su Panel {{ .node }}{{ .ip }}, el inicio de sesión {{ .name }} desde {{ .loginIp }} es anómalo. Inicie sesión en el panel para ver los detalles.'
|
||||||
|
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: la tarea programada {{ .name }} se completó correctamente. Inicie sesión para ver los detalles.'
|
||||||
|
|
||||||
# disco
|
# disco
|
||||||
DeviceNotFound: 'Dispositivo {{ .name }} no encontrado'
|
DeviceNotFound: 'Dispositivo {{ .name }} no encontrado'
|
||||||
@@ -702,7 +703,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
|||||||
FirewallCreateRuleExecutionFailed: 'Error al crear la regla. No se guardó ningún registro en la base de datos ni se revirtieron los comandos ejecutados'
|
FirewallCreateRuleExecutionFailed: 'Error al crear la regla. No se guardó ningún registro en la base de datos ni se revirtieron los comandos ejecutados'
|
||||||
FirewallCreateRulePersistenceFailed: 'La regla se creó, pero no se pudo guardar su registro de gestión'
|
FirewallCreateRulePersistenceFailed: 'La regla se creó, pero no se pudo guardar su registro de gestión'
|
||||||
FirewallAdoptRulePersistenceFailed: 'Se ejecutó la adopción de la regla, pero no se pudo guardar su registro de gestión'
|
FirewallAdoptRulePersistenceFailed: 'Se ejecutó la adopción de la regla, pero no se pudo guardar su registro de gestión'
|
||||||
FirewallSyncOperationsResult: 'Operaciones de sincronización: {{ .removed }} eliminadas, {{ .created }} creadas, {{ .failed }} fallidas, {{ .skipped }} sin ejecutar'
|
FirewallSyncOperationsResult: 'Operaciones de sincronización: {{ .removed }} eliminadas, {{ .created }} creadas, {{ .failed }} fallidas, {{ .skipped }} sin ejecutar, {{ .unchanged }} ya coinciden (sin cambios necesarios)'
|
||||||
|
FirewallSyncRuleUnchanged: 'Ya coincide; no requiere cambios'
|
||||||
FirewallSyncStep: 'Sincronizar reglas con {{ .name }}'
|
FirewallSyncStep: 'Sincronizar reglas con {{ .name }}'
|
||||||
FirewallSyncFailed: 'No se pudieron sincronizar {{ .failed }} reglas del firewall'
|
FirewallSyncFailed: 'No se pudieron sincronizar {{ .failed }} reglas del firewall'
|
||||||
FirewallResetSourceStep: 'Restablecer y desactivar el firewall de origen {{ .name }}'
|
FirewallResetSourceStep: 'Restablecer y desactivar el firewall de origen {{ .name }}'
|
||||||
@@ -716,8 +718,16 @@ FirewallInspectDockerGuardStep: 'Inspeccionar el backend del firewall de Docker
|
|||||||
FirewallInitializeDockerGuardStep: 'Inicializar y vincular las cadenas de protección de puertos de {{ .name }}'
|
FirewallInitializeDockerGuardStep: 'Inicializar y vincular las cadenas de protección de puertos de {{ .name }}'
|
||||||
FirewallPersistDockerGuardStep: 'Guardar el estado de protección de puertos de Docker'
|
FirewallPersistDockerGuardStep: 'Guardar el estado de protección de puertos de Docker'
|
||||||
ErrFirewallRuleScopeChange: "El cortafuegos actual no permite cambiar el ámbito de una regla (como su familia de direcciones IPv4/IPv6). Cree una regla nueva."
|
ErrFirewallRuleScopeChange: "El cortafuegos actual no permite cambiar el ámbito de una regla (como su familia de direcciones IPv4/IPv6). Cree una regla nueva."
|
||||||
FirewallWhitelistTask: "Actualizar la lista de puertos permitidos del cortafuegos"
|
|
||||||
FirewallWhitelistSaved: "Configuración de la lista de permitidos guardada"
|
|
||||||
FirewallWhitelistDeferred: "{{ .name }}: configuración guardada; pendiente de activar el cortafuegos"
|
|
||||||
FirewallWhitelistReleased: "{{ .name }}: protección de la lista de permitidos retirada; se conserva la regla de permiso. Para cerrar el puerto, elimine la regla manualmente de la lista"
|
FirewallWhitelistReleased: "{{ .name }}: protección de la lista de permitidos retirada; se conserva la regla de permiso. Para cerrar el puerto, elimine la regla manualmente de la lista"
|
||||||
FirewallWhitelistRequired: "{{ .name }}: protegido por las reglas de puertos obligatorios del sistema"
|
FirewallWhitelistRequired: "{{ .name }}: protegido por las reglas de puertos obligatorios del sistema"
|
||||||
|
FileTaskCopy: 'Copiar archivos a {{ .dst }}'
|
||||||
|
FileTaskMove: 'Mover archivos a {{ .dst }}'
|
||||||
|
FileTaskCompress: 'Comprimir archivos en {{ .dst }}'
|
||||||
|
FileTaskDecompress: 'Extraer archivos a {{ .dst }}'
|
||||||
|
FileTaskSource: 'Ruta de origen: {{ .path }}'
|
||||||
|
FileTaskFormat: 'Formato del archivo: {{ .format }}'
|
||||||
|
FileTaskRename: 'Nombre del archivo de destino: {{ .name }}'
|
||||||
|
|
||||||
|
ErrFirewallBackendCleanupRequired: "El backend actual {{ .current }} aún contiene reglas de 1Panel. Elimínelas antes de cambiar a {{ .target }}."
|
||||||
|
ErrDockerIPv4ForwardingDisabled: "El reenvío IPv4 está desactivado. Configure net.ipv4.ip_forward=1 antes de usar el backend de cortafuegos de Docker."
|
||||||
|
ErrFirewallRuleSavedApplyFailed: "Se guardó la configuración de la nueva regla, pero no se pudo aplicar al cortafuegos. Vuelva a intentarlo mediante la sincronización: {{ .detail }}"
|
||||||
|
|||||||
+15
-4
@@ -649,6 +649,8 @@ CommonAlert: "پنل {{ .node }}{{ .ip }}: {{ .msg }}. برای مشاهده ج
|
|||||||
NodeExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} گره غیرعادی هستند. برای مشاهده جزئیات وارد شوید."
|
NodeExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} گره غیرعادی هستند. برای مشاهده جزئیات وارد شوید."
|
||||||
LicenseExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} مجوز غیرعادی است. برای مشاهده جزئیات وارد شوید."
|
LicenseExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} مجوز غیرعادی است. برای مشاهده جزئیات وارد شوید."
|
||||||
SSHAndPanelLoginAlert: "پنل {{ .node }}{{ .ip }}: ورود غیرعادی {{ .name }} از {{ .loginIp }}. برای مشاهده جزئیات وارد شوید."
|
SSHAndPanelLoginAlert: "پنل {{ .node }}{{ .ip }}: ورود غیرعادی {{ .name }} از {{ .loginIp }}. برای مشاهده جزئیات وارد شوید."
|
||||||
|
CronJobSuccessAlert: "پنل {{ .node }}{{ .ip }}: وظیفه زمانبندیشده {{ .name }} با موفقیت تکمیل شد. برای مشاهده جزئیات وارد شوید."
|
||||||
|
|
||||||
|
|
||||||
# دیسک
|
# دیسک
|
||||||
DeviceNotFound: "دستگاه {{ .name }} یافت نشد"
|
DeviceNotFound: "دستگاه {{ .name }} یافت نشد"
|
||||||
@@ -702,7 +704,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
|||||||
FirewallCreateRuleExecutionFailed: 'ایجاد قانون ناموفق بود. هیچ رکوردی در پایگاه داده ذخیره نشد و دستورات اجراشده بازگردانی نشدند'
|
FirewallCreateRuleExecutionFailed: 'ایجاد قانون ناموفق بود. هیچ رکوردی در پایگاه داده ذخیره نشد و دستورات اجراشده بازگردانی نشدند'
|
||||||
FirewallCreateRulePersistenceFailed: 'قانون ایجاد شد، اما ذخیره رکورد مدیریتی آن ناموفق بود'
|
FirewallCreateRulePersistenceFailed: 'قانون ایجاد شد، اما ذخیره رکورد مدیریتی آن ناموفق بود'
|
||||||
FirewallAdoptRulePersistenceFailed: 'دستور پذیرش قانون برای مدیریت اجرا شد، اما اطلاعات مدیریت ذخیره نشد'
|
FirewallAdoptRulePersistenceFailed: 'دستور پذیرش قانون برای مدیریت اجرا شد، اما اطلاعات مدیریت ذخیره نشد'
|
||||||
FirewallSyncOperationsResult: 'عملیات همگامسازی: {{ .removed }} حذفشده، {{ .created }} ایجادشده، {{ .failed }} ناموفق، {{ .skipped }} اجرانشده'
|
FirewallSyncOperationsResult: 'عملیات همگامسازی: {{ .removed }} حذفشده، {{ .created }} ایجادشده، {{ .failed }} ناموفق، {{ .skipped }} اجرانشده، {{ .unchanged }} از قبل مطابق (بدون نیاز به تغییر)'
|
||||||
|
FirewallSyncRuleUnchanged: 'از قبل مطابق است؛ نیازی به تغییر نیست'
|
||||||
FirewallSyncStep: 'همگامسازی قوانین با {{ .name }}'
|
FirewallSyncStep: 'همگامسازی قوانین با {{ .name }}'
|
||||||
FirewallSyncFailed: 'همگامسازی {{ .failed }} قانون فایروال ناموفق بود'
|
FirewallSyncFailed: 'همگامسازی {{ .failed }} قانون فایروال ناموفق بود'
|
||||||
FirewallResetSourceStep: 'بازنشانی و غیرفعالکردن فایروال مبدأ {{ .name }}'
|
FirewallResetSourceStep: 'بازنشانی و غیرفعالکردن فایروال مبدأ {{ .name }}'
|
||||||
@@ -716,8 +719,16 @@ FirewallInspectDockerGuardStep: 'بررسی پشتیبان فایروال Docker
|
|||||||
FirewallInitializeDockerGuardStep: 'راهاندازی و اتصال زنجیرههای محافظت پورت {{ .name }}'
|
FirewallInitializeDockerGuardStep: 'راهاندازی و اتصال زنجیرههای محافظت پورت {{ .name }}'
|
||||||
FirewallPersistDockerGuardStep: 'ذخیره وضعیت محافظت پورت Docker'
|
FirewallPersistDockerGuardStep: 'ذخیره وضعیت محافظت پورت Docker'
|
||||||
ErrFirewallRuleScopeChange: "فایروال فعلی از تغییر محدودهٔ قانون (مانند خانوادهٔ آدرس IPv4/IPv6) پشتیبانی نمیکند. لطفاً یک قانون جدید ایجاد کنید."
|
ErrFirewallRuleScopeChange: "فایروال فعلی از تغییر محدودهٔ قانون (مانند خانوادهٔ آدرس IPv4/IPv6) پشتیبانی نمیکند. لطفاً یک قانون جدید ایجاد کنید."
|
||||||
FirewallWhitelistTask: "بهروزرسانی فهرست پورتهای مجاز فایروال"
|
|
||||||
FirewallWhitelistSaved: "پیکربندی فهرست مجاز ذخیره شد"
|
|
||||||
FirewallWhitelistDeferred: "{{ .name }}: پیکربندی ذخیره شد؛ در انتظار فعالسازی فایروال"
|
|
||||||
FirewallWhitelistReleased: "{{ .name }}: حفاظت فهرست مجاز برداشته شد؛ قانون اجازه حفظ میشود. برای بستن پورت، قانون را بهصورت دستی از فهرست قوانین حذف کنید"
|
FirewallWhitelistReleased: "{{ .name }}: حفاظت فهرست مجاز برداشته شد؛ قانون اجازه حفظ میشود. برای بستن پورت، قانون را بهصورت دستی از فهرست قوانین حذف کنید"
|
||||||
FirewallWhitelistRequired: "{{ .name }}: توسط قوانین پورتهای ضروری سیستم محافظت میشود"
|
FirewallWhitelistRequired: "{{ .name }}: توسط قوانین پورتهای ضروری سیستم محافظت میشود"
|
||||||
|
FileTaskCopy: 'کپی فایلها به {{ .dst }}'
|
||||||
|
FileTaskMove: 'انتقال فایلها به {{ .dst }}'
|
||||||
|
FileTaskCompress: 'فشردهسازی فایلها در {{ .dst }}'
|
||||||
|
FileTaskDecompress: 'استخراج فایلها در {{ .dst }}'
|
||||||
|
FileTaskSource: 'مسیر مبدأ: {{ .path }}'
|
||||||
|
FileTaskFormat: 'قالب بایگانی: {{ .format }}'
|
||||||
|
FileTaskRename: 'نام فایل مقصد: {{ .name }}'
|
||||||
|
|
||||||
|
ErrFirewallBackendCleanupRequired: "بکاند فعلی {{ .current }} هنوز شامل قوانین 1Panel است. پیش از تغییر به {{ .target }} آنها را پاک کنید."
|
||||||
|
ErrDockerIPv4ForwardingDisabled: "ارسال IPv4 غیرفعال است. پیش از استفاده از بکاند فایروال Docker، مقدار net.ipv4.ip_forward=1 را تنظیم کنید."
|
||||||
|
ErrFirewallRuleSavedApplyFailed: "پیکربندی قانون جدید ذخیره شد، اما اعمال آن در دیوار آتش ناموفق بود. با همگامسازی دوباره تلاش کنید: {{ .detail }}"
|
||||||
|
|||||||
+14
-4
@@ -649,6 +649,7 @@ CommonAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .msg }}。詳細
|
|||||||
NodeExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のノードに異常が発生しています。詳細はパネルにログインして'
|
NodeExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のノードに異常が発生しています。詳細はパネルにログインして'
|
||||||
LicenseExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のライセンスに異常が発生しています。詳細はパネルにログインして'
|
LicenseExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のライセンスに異常が発生しています。詳細はパネルにログインして'
|
||||||
SSHAndPanelLoginAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .loginIp }} からの {{ .name }} ログインに異常があります。詳細はパネルにログインして'
|
SSHAndPanelLoginAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .loginIp }} からの {{ .name }} ログインに異常があります。詳細はパネルにログインして'
|
||||||
|
CronJobSuccessAlert: 'パネル {{ .node }}{{ .ip }}: スケジュールタスク {{ .name }} が正常に完了しました。詳細はパネルにログインして確認してください。'
|
||||||
|
|
||||||
# ディスク
|
# ディスク
|
||||||
DeviceNotFound: 'デバイス {{ .name }} が見つかりません'
|
DeviceNotFound: 'デバイス {{ .name }} が見つかりません'
|
||||||
@@ -702,7 +703,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
|||||||
FirewallCreateRuleExecutionFailed: 'ルールの作成に失敗しました。データベースには保存せず、実行済みのコマンドはロールバックしません'
|
FirewallCreateRuleExecutionFailed: 'ルールの作成に失敗しました。データベースには保存せず、実行済みのコマンドはロールバックしません'
|
||||||
FirewallCreateRulePersistenceFailed: 'ルールは作成されましたが、管理情報の保存に失敗しました'
|
FirewallCreateRulePersistenceFailed: 'ルールは作成されましたが、管理情報の保存に失敗しました'
|
||||||
FirewallAdoptRulePersistenceFailed: 'ルールの管理対象への取り込みコマンドは実行されましたが、管理情報の保存に失敗しました'
|
FirewallAdoptRulePersistenceFailed: 'ルールの管理対象への取り込みコマンドは実行されましたが、管理情報の保存に失敗しました'
|
||||||
FirewallSyncOperationsResult: '同期操作:削除成功 {{ .removed }} 件、作成成功 {{ .created }} 件、失敗 {{ .failed }} 件、未実行 {{ .skipped }} 件'
|
FirewallSyncOperationsResult: '同期操作:削除成功 {{ .removed }} 件、作成成功 {{ .created }} 件、失敗 {{ .failed }} 件、未実行 {{ .skipped }} 件、一致済みで変更不要 {{ .unchanged }} 件'
|
||||||
|
FirewallSyncRuleUnchanged: '一致済み、変更不要'
|
||||||
FirewallSyncStep: '{{ .name }} にルールを同期'
|
FirewallSyncStep: '{{ .name }} にルールを同期'
|
||||||
FirewallSyncFailed: '{{ .failed }} 件のファイアウォールルールを同期できませんでした'
|
FirewallSyncFailed: '{{ .failed }} 件のファイアウォールルールを同期できませんでした'
|
||||||
FirewallResetSourceStep: '移行元ファイアウォール {{ .name }} をリセットして無効化'
|
FirewallResetSourceStep: '移行元ファイアウォール {{ .name }} をリセットして無効化'
|
||||||
@@ -716,8 +718,16 @@ FirewallInspectDockerGuardStep: 'Docker ファイアウォールバックエン
|
|||||||
FirewallInitializeDockerGuardStep: '{{ .name }} のポート保護チェーンを初期化してバインド'
|
FirewallInitializeDockerGuardStep: '{{ .name }} のポート保護チェーンを初期化してバインド'
|
||||||
FirewallPersistDockerGuardStep: 'Docker ポート保護状態を保存'
|
FirewallPersistDockerGuardStep: 'Docker ポート保護状態を保存'
|
||||||
ErrFirewallRuleScopeChange: "現在のファイアウォールでは、ルールの適用範囲(IPv4/IPv6 アドレスファミリーなど)を変更できません。新しいルールを作成してください。"
|
ErrFirewallRuleScopeChange: "現在のファイアウォールでは、ルールの適用範囲(IPv4/IPv6 アドレスファミリーなど)を変更できません。新しいルールを作成してください。"
|
||||||
FirewallWhitelistTask: "ファイアウォールのポート許可リストを更新"
|
|
||||||
FirewallWhitelistSaved: "許可リストの設定を保存しました"
|
|
||||||
FirewallWhitelistDeferred: "{{ .name }}:設定を保存しました。ファイアウォールの有効化後に適用します"
|
|
||||||
FirewallWhitelistReleased: "{{ .name }}:許可リストの保護を解除しました。許可ルールは保持されます。ポートを閉じるには、ルール一覧から手動で削除してください"
|
FirewallWhitelistReleased: "{{ .name }}:許可リストの保護を解除しました。許可ルールは保持されます。ポートを閉じるには、ルール一覧から手動で削除してください"
|
||||||
FirewallWhitelistRequired: "{{ .name }}:システム必須ポートのルールで保護されています"
|
FirewallWhitelistRequired: "{{ .name }}:システム必須ポートのルールで保護されています"
|
||||||
|
FileTaskCopy: 'ファイルを {{ .dst }} にコピー'
|
||||||
|
FileTaskMove: 'ファイルを {{ .dst }} に移動'
|
||||||
|
FileTaskCompress: 'ファイルを {{ .dst }} に圧縮'
|
||||||
|
FileTaskDecompress: 'ファイルを {{ .dst }} に展開'
|
||||||
|
FileTaskSource: '元のパス:{{ .path }}'
|
||||||
|
FileTaskFormat: '圧縮形式:{{ .format }}'
|
||||||
|
FileTaskRename: '保存先ファイル名:{{ .name }}'
|
||||||
|
|
||||||
|
ErrFirewallBackendCleanupRequired: "現在のバックエンド {{ .current }} に 1Panel ルールが残っています。{{ .target }} に切り替える前に削除してください。"
|
||||||
|
ErrDockerIPv4ForwardingDisabled: "IPv4 転送が無効です。Docker のファイアウォールバックエンドを使用する前に net.ipv4.ip_forward=1 を設定してください。"
|
||||||
|
ErrFirewallRuleSavedApplyFailed: "新しいルール設定は保存されましたが、ファイアウォールへの適用に失敗しました。同期で再試行してください:{{ .detail }}"
|
||||||
|
|||||||
+14
-4
@@ -649,6 +649,7 @@ CommonAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .msg }}。자세한 내
|
|||||||
NodeExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
NodeExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
||||||
LicenseExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 라이센스에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
LicenseExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 라이센스에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
||||||
SSHAndPanelLoginAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .loginIp }}에서의 {{ .name }} 로그인에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
SSHAndPanelLoginAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .loginIp }}에서의 {{ .name }} 로그인에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
||||||
|
CronJobSuccessAlert: '패널 {{ .node }}{{ .ip }}: 예약 작업 {{ .name }}이(가) 성공적으로 완료되었습니다. 로그인하여 자세한 내용을 확인하십시오.'
|
||||||
|
|
||||||
# 디스크
|
# 디스크
|
||||||
DeviceNotFound: '장치 {{ .name }} 을(를) 찾을 수 없습니다'
|
DeviceNotFound: '장치 {{ .name }} 을(를) 찾을 수 없습니다'
|
||||||
@@ -702,7 +703,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
|||||||
FirewallCreateRuleExecutionFailed: '규칙 생성에 실패했습니다. 데이터베이스에 저장하지 않았으며 실행된 명령은 롤백하지 않습니다'
|
FirewallCreateRuleExecutionFailed: '규칙 생성에 실패했습니다. 데이터베이스에 저장하지 않았으며 실행된 명령은 롤백하지 않습니다'
|
||||||
FirewallCreateRulePersistenceFailed: '규칙은 생성되었지만 관리 정보를 저장하지 못했습니다'
|
FirewallCreateRulePersistenceFailed: '규칙은 생성되었지만 관리 정보를 저장하지 못했습니다'
|
||||||
FirewallAdoptRulePersistenceFailed: '규칙 관리 등록 명령은 실행되었지만 관리 정보를 저장하지 못했습니다'
|
FirewallAdoptRulePersistenceFailed: '규칙 관리 등록 명령은 실행되었지만 관리 정보를 저장하지 못했습니다'
|
||||||
FirewallSyncOperationsResult: '동기화 작업: 삭제 성공 {{ .removed }}개, 생성 성공 {{ .created }}개, 실패 {{ .failed }}개, 미실행 {{ .skipped }}개'
|
FirewallSyncOperationsResult: '동기화 작업: 삭제 성공 {{ .removed }}개, 생성 성공 {{ .created }}개, 실패 {{ .failed }}개, 미실행 {{ .skipped }}개, 이미 일치하여 변경 불필요 {{ .unchanged }}개'
|
||||||
|
FirewallSyncRuleUnchanged: '이미 일치하여 변경이 필요하지 않음'
|
||||||
FirewallSyncStep: '{{ .name }}에 규칙 동기화'
|
FirewallSyncStep: '{{ .name }}에 규칙 동기화'
|
||||||
FirewallSyncFailed: '방화벽 규칙 {{ .failed }}개를 동기화하지 못했습니다'
|
FirewallSyncFailed: '방화벽 규칙 {{ .failed }}개를 동기화하지 못했습니다'
|
||||||
FirewallResetSourceStep: '원본 방화벽 {{ .name }} 초기화 및 비활성화'
|
FirewallResetSourceStep: '원본 방화벽 {{ .name }} 초기화 및 비활성화'
|
||||||
@@ -716,8 +718,16 @@ FirewallInspectDockerGuardStep: 'Docker 방화벽 백엔드 및 보호 정책
|
|||||||
FirewallInitializeDockerGuardStep: '{{ .name }} 포트 보호 체인 초기화 및 바인딩'
|
FirewallInitializeDockerGuardStep: '{{ .name }} 포트 보호 체인 초기화 및 바인딩'
|
||||||
FirewallPersistDockerGuardStep: 'Docker 포트 보호 상태 저장'
|
FirewallPersistDockerGuardStep: 'Docker 포트 보호 상태 저장'
|
||||||
ErrFirewallRuleScopeChange: "현재 방화벽에서는 규칙의 적용 범위(예: IPv4/IPv6 주소 패밀리)를 변경할 수 없습니다. 새 규칙을 생성하세요."
|
ErrFirewallRuleScopeChange: "현재 방화벽에서는 규칙의 적용 범위(예: IPv4/IPv6 주소 패밀리)를 변경할 수 없습니다. 새 규칙을 생성하세요."
|
||||||
FirewallWhitelistTask: "방화벽 포트 허용 목록 업데이트"
|
|
||||||
FirewallWhitelistSaved: "허용 목록 설정이 저장되었습니다"
|
|
||||||
FirewallWhitelistDeferred: "{{ .name }}: 설정이 저장되었으며 방화벽 활성화 후 적용됩니다"
|
|
||||||
FirewallWhitelistReleased: "{{ .name }}: 허용 목록 보호가 해제되었으며 허용 규칙은 유지됩니다. 포트를 닫으려면 규칙 목록에서 수동으로 삭제하세요"
|
FirewallWhitelistReleased: "{{ .name }}: 허용 목록 보호가 해제되었으며 허용 규칙은 유지됩니다. 포트를 닫으려면 규칙 목록에서 수동으로 삭제하세요"
|
||||||
FirewallWhitelistRequired: "{{ .name }}: 시스템 필수 포트 규칙으로 보호됩니다"
|
FirewallWhitelistRequired: "{{ .name }}: 시스템 필수 포트 규칙으로 보호됩니다"
|
||||||
|
FileTaskCopy: '{{ .dst }}에 파일 복사'
|
||||||
|
FileTaskMove: '{{ .dst }}로 파일 이동'
|
||||||
|
FileTaskCompress: '{{ .dst }}에 파일 압축'
|
||||||
|
FileTaskDecompress: '{{ .dst }}에 압축 해제'
|
||||||
|
FileTaskSource: '원본 경로: {{ .path }}'
|
||||||
|
FileTaskFormat: '압축 형식: {{ .format }}'
|
||||||
|
FileTaskRename: '대상 파일 이름: {{ .name }}'
|
||||||
|
|
||||||
|
ErrFirewallBackendCleanupRequired: "현재 백엔드 {{ .current }}에 1Panel 규칙이 남아 있습니다. {{ .target }}로 전환하기 전에 정리하세요."
|
||||||
|
ErrDockerIPv4ForwardingDisabled: "IPv4 전달이 비활성화되어 있습니다. Docker 방화벽 백엔드를 사용하기 전에 net.ipv4.ip_forward=1을 설정하세요."
|
||||||
|
ErrFirewallRuleSavedApplyFailed: "새 규칙 설정이 저장되었지만 방화벽에 적용하지 못했습니다. 동기화하여 다시 시도하세요: {{ .detail }}"
|
||||||
|
|||||||
+14
-4
@@ -639,6 +639,7 @@ CommonAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: {{ .msg }}. ເ
|
|||||||
NodeExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໂນດ {{ .num }} ແຫ່ງຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
NodeExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໂນດ {{ .num }} ແຫ່ງຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||||
LicenseExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໃບອະນຸຍາດ {{ .num }} ສະບັບຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
LicenseExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໃບອະນຸຍາດ {{ .num }} ສະບັບຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||||
SSHAndPanelLoginAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ມີການເຂົ້າສູ່ລະບົບ {{ .name }} ທີ່ຜິດປົກກະຕິຈາກ {{ .loginIp }}. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
SSHAndPanelLoginAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ມີການເຂົ້າສູ່ລະບົບ {{ .name }} ທີ່ຜິດປົກກະຕິຈາກ {{ .loginIp }}. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||||
|
CronJobSuccessAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ວຽກທີ່ຕັ້ງເວລາ {{ .name }} ສຳເລັດແລ້ວ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||||
|
|
||||||
#disk
|
#disk
|
||||||
DeviceNotFound: "ບໍ່ພົບອຸປະກອນ {{ .name }}"
|
DeviceNotFound: "ບໍ່ພົບອຸປະກອນ {{ .name }}"
|
||||||
@@ -693,7 +694,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
|||||||
FirewallCreateRuleExecutionFailed: 'ການສ້າງກົດລົ້ມເຫຼວ. ບໍ່ໄດ້ບັນທຶກໃນຖານຂໍ້ມູນ ແລະ ບໍ່ໄດ້ຍ້ອນກັບຄຳສັ່ງທີ່ດຳເນີນການແລ້ວ'
|
FirewallCreateRuleExecutionFailed: 'ການສ້າງກົດລົ້ມເຫຼວ. ບໍ່ໄດ້ບັນທຶກໃນຖານຂໍ້ມູນ ແລະ ບໍ່ໄດ້ຍ້ອນກັບຄຳສັ່ງທີ່ດຳເນີນການແລ້ວ'
|
||||||
FirewallCreateRulePersistenceFailed: 'ສ້າງກົດແລ້ວ ແຕ່ບັນທຶກຂໍ້ມູນການຈັດການບໍ່ສຳເລັດ'
|
FirewallCreateRulePersistenceFailed: 'ສ້າງກົດແລ້ວ ແຕ່ບັນທຶກຂໍ້ມູນການຈັດການບໍ່ສຳເລັດ'
|
||||||
FirewallAdoptRulePersistenceFailed: 'ຄຳສັ່ງນຳກົດເຂົ້າການຈັດການໄດ້ດຳເນີນການແລ້ວ ແຕ່ບໍ່ສາມາດບັນທຶກຂໍ້ມູນການຈັດການໄດ້'
|
FirewallAdoptRulePersistenceFailed: 'ຄຳສັ່ງນຳກົດເຂົ້າການຈັດການໄດ້ດຳເນີນການແລ້ວ ແຕ່ບໍ່ສາມາດບັນທຶກຂໍ້ມູນການຈັດການໄດ້'
|
||||||
FirewallSyncOperationsResult: 'ການດຳເນີນການຊິງຄ໌: ລຶບ {{ .removed }}, ສ້າງ {{ .created }}, ລົ້ມເຫຼວ {{ .failed }}, ບໍ່ໄດ້ດຳເນີນການ {{ .skipped }}'
|
FirewallSyncOperationsResult: 'ການດຳເນີນການຊິງຄ໌: ລຶບ {{ .removed }}, ສ້າງ {{ .created }}, ລົ້ມເຫຼວ {{ .failed }}, ບໍ່ໄດ້ດຳເນີນການ {{ .skipped }}, ກົງກັນແລ້ວ {{ .unchanged }} (ບໍ່ຕ້ອງປ່ຽນແປງ)'
|
||||||
|
FirewallSyncRuleUnchanged: 'ກົງກັນແລ້ວ; ບໍ່ຕ້ອງປ່ຽນແປງ'
|
||||||
FirewallSyncStep: 'ຊິງຄ໌ກົດໄປຫາ {{ .name }}'
|
FirewallSyncStep: 'ຊິງຄ໌ກົດໄປຫາ {{ .name }}'
|
||||||
FirewallSyncFailed: 'ຊິງຄ໌ກົດໄຟວອລ {{ .failed }} ລາຍການບໍ່ສຳເລັດ'
|
FirewallSyncFailed: 'ຊິງຄ໌ກົດໄຟວອລ {{ .failed }} ລາຍການບໍ່ສຳເລັດ'
|
||||||
FirewallResetSourceStep: 'ຣີເຊັດ ແລະ ປິດໃຊ້ໄຟວອລຕົ້ນທາງ {{ .name }}'
|
FirewallResetSourceStep: 'ຣີເຊັດ ແລະ ປິດໃຊ້ໄຟວອລຕົ້ນທາງ {{ .name }}'
|
||||||
@@ -707,8 +709,16 @@ FirewallInspectDockerGuardStep: 'ກວດສອບ backend firewall Docker ແ
|
|||||||
FirewallInitializeDockerGuardStep: 'ເລີ່ມຕົ້ນ ແລະ ຜູກ chain ປ້ອງກັນພອດ {{ .name }}'
|
FirewallInitializeDockerGuardStep: 'ເລີ່ມຕົ້ນ ແລະ ຜູກ chain ປ້ອງກັນພອດ {{ .name }}'
|
||||||
FirewallPersistDockerGuardStep: 'ບັນທຶກສະຖານະປ້ອງກັນພອດ Docker'
|
FirewallPersistDockerGuardStep: 'ບັນທຶກສະຖານະປ້ອງກັນພອດ Docker'
|
||||||
ErrFirewallRuleScopeChange: "ໄຟວໍປັດຈຸບັນບໍ່ຮອງຮັບການປ່ຽນຂອບເຂດຂອງກົດ (ເຊັ່ນ ຕະກູນທີ່ຢູ່ IPv4/IPv6). ກະລຸນາສ້າງກົດໃໝ່."
|
ErrFirewallRuleScopeChange: "ໄຟວໍປັດຈຸບັນບໍ່ຮອງຮັບການປ່ຽນຂອບເຂດຂອງກົດ (ເຊັ່ນ ຕະກູນທີ່ຢູ່ IPv4/IPv6). ກະລຸນາສ້າງກົດໃໝ່."
|
||||||
FirewallWhitelistTask: "ອັບເດດລາຍຊື່ພອດທີ່ອະນຸຍາດຂອງໄຟວໍ"
|
|
||||||
FirewallWhitelistSaved: "ບັນທຶກການຕັ້ງຄ່າລາຍຊື່ທີ່ອະນຸຍາດແລ້ວ"
|
|
||||||
FirewallWhitelistDeferred: "{{ .name }}: ບັນທຶກການຕັ້ງຄ່າແລ້ວ; ລໍຖ້າເປີດໃຊ້ໄຟວໍ"
|
|
||||||
FirewallWhitelistReleased: "{{ .name }}: ຍົກເລີກການປ້ອງກັນລາຍຊື່ທີ່ອະນຸຍາດແລ້ວ; ຍັງຄົງກົດອະນຸຍາດໄວ້. ຫາກຕ້ອງການປິດພອດ ໃຫ້ລຶບກົດດ້ວຍຕົນເອງຈາກລາຍການກົດ"
|
FirewallWhitelistReleased: "{{ .name }}: ຍົກເລີກການປ້ອງກັນລາຍຊື່ທີ່ອະນຸຍາດແລ້ວ; ຍັງຄົງກົດອະນຸຍາດໄວ້. ຫາກຕ້ອງການປິດພອດ ໃຫ້ລຶບກົດດ້ວຍຕົນເອງຈາກລາຍການກົດ"
|
||||||
FirewallWhitelistRequired: "{{ .name }}: ປ້ອງກັນໂດຍກົດພອດທີ່ຈຳເປັນຂອງລະບົບ"
|
FirewallWhitelistRequired: "{{ .name }}: ປ້ອງກັນໂດຍກົດພອດທີ່ຈຳເປັນຂອງລະບົບ"
|
||||||
|
FileTaskCopy: 'ສຳເນົາໄຟລ໌ໄປທີ່ {{ .dst }}'
|
||||||
|
FileTaskMove: 'ຍ້າຍໄຟລ໌ໄປທີ່ {{ .dst }}'
|
||||||
|
FileTaskCompress: 'ບີບອັດໄຟລ໌ໄປທີ່ {{ .dst }}'
|
||||||
|
FileTaskDecompress: 'ແຕກໄຟລ໌ໄປທີ່ {{ .dst }}'
|
||||||
|
FileTaskSource: 'ເສັ້ນທາງຕົ້ນທາງ: {{ .path }}'
|
||||||
|
FileTaskFormat: 'ຮູບແບບໄຟລ໌ບີບອັດ: {{ .format }}'
|
||||||
|
FileTaskRename: 'ຊື່ໄຟລ໌ປາຍທາງ: {{ .name }}'
|
||||||
|
|
||||||
|
ErrFirewallBackendCleanupRequired: "ແບັກເອນປັດຈຸບັນ {{ .current }} ຍັງມີກົດຂອງ 1Panel. ກະລຸນາລຶບອອກກ່ອນປ່ຽນໄປ {{ .target }}."
|
||||||
|
ErrDockerIPv4ForwardingDisabled: "ການສົ່ງຕໍ່ IPv4 ຖືກປິດ. ກະລຸນາຕັ້ງ net.ipv4.ip_forward=1 ກ່ອນໃຊ້ແບັກເອນໄຟວໍຂອງ Docker."
|
||||||
|
ErrFirewallRuleSavedApplyFailed: "ບັນທຶກການຕັ້ງຄ່າກົດໃໝ່ແລ້ວ ແຕ່ນຳໃຊ້ກັບໄຟວໍບໍ່ສຳເລັດ. ລອງອີກຄັ້ງດ້ວຍການຊິງຂໍ້ມູນ: {{ .detail }}"
|
||||||
|
|||||||
+14
-4
@@ -649,6 +649,7 @@ CommonAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .msg }}. Sila log masuk ke pan
|
|||||||
NodeExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} nod tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
|
NodeExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} nod tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
|
||||||
LicenseExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} lesen tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
|
LicenseExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} lesen tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
|
||||||
SSHAndPanelLoginAlert: 'Panel {{ .node }}{{ .ip }} Anda, log masuk {{ .name }} dari {{ .loginIp }} tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
|
SSHAndPanelLoginAlert: 'Panel {{ .node }}{{ .ip }} Anda, log masuk {{ .name }} dari {{ .loginIp }} tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
|
||||||
|
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: tugas berjadual {{ .name }} berjaya diselesaikan. Log masuk untuk melihat butiran.'
|
||||||
|
|
||||||
# cakera
|
# cakera
|
||||||
DeviceNotFound: 'Peranti {{ .name }} tidak ditemui'
|
DeviceNotFound: 'Peranti {{ .name }} tidak ditemui'
|
||||||
@@ -702,7 +703,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
|||||||
FirewallCreateRuleExecutionFailed: 'Penciptaan peraturan gagal. Tiada rekod disimpan dalam pangkalan data dan arahan yang dilaksanakan tidak dibatalkan'
|
FirewallCreateRuleExecutionFailed: 'Penciptaan peraturan gagal. Tiada rekod disimpan dalam pangkalan data dan arahan yang dilaksanakan tidak dibatalkan'
|
||||||
FirewallCreateRulePersistenceFailed: 'Peraturan telah dicipta, tetapi rekod pengurusannya tidak dapat disimpan'
|
FirewallCreateRulePersistenceFailed: 'Peraturan telah dicipta, tetapi rekod pengurusannya tidak dapat disimpan'
|
||||||
FirewallAdoptRulePersistenceFailed: 'Arahan pengambilalihan peraturan telah dilaksanakan, tetapi rekod pengurusannya tidak dapat disimpan'
|
FirewallAdoptRulePersistenceFailed: 'Arahan pengambilalihan peraturan telah dilaksanakan, tetapi rekod pengurusannya tidak dapat disimpan'
|
||||||
FirewallSyncOperationsResult: 'Operasi penyegerakan: {{ .removed }} dipadam, {{ .created }} dicipta, {{ .failed }} gagal, {{ .skipped }} tidak dilaksanakan'
|
FirewallSyncOperationsResult: 'Operasi penyegerakan: {{ .removed }} dipadam, {{ .created }} dicipta, {{ .failed }} gagal, {{ .skipped }} tidak dilaksanakan, {{ .unchanged }} sudah sepadan (tiada perubahan diperlukan)'
|
||||||
|
FirewallSyncRuleUnchanged: 'Sudah sepadan; tiada perubahan diperlukan'
|
||||||
FirewallSyncStep: 'Segerakkan peraturan ke {{ .name }}'
|
FirewallSyncStep: 'Segerakkan peraturan ke {{ .name }}'
|
||||||
FirewallSyncFailed: '{{ .failed }} peraturan firewall gagal disegerakkan'
|
FirewallSyncFailed: '{{ .failed }} peraturan firewall gagal disegerakkan'
|
||||||
FirewallResetSourceStep: 'Tetapkan semula dan nyahdayakan firewall sumber {{ .name }}'
|
FirewallResetSourceStep: 'Tetapkan semula dan nyahdayakan firewall sumber {{ .name }}'
|
||||||
@@ -716,8 +718,16 @@ FirewallInspectDockerGuardStep: 'Periksa backend firewall Docker dan polisi'
|
|||||||
FirewallInitializeDockerGuardStep: 'Mulakan dan ikat rantai perlindungan port {{ .name }}'
|
FirewallInitializeDockerGuardStep: 'Mulakan dan ikat rantai perlindungan port {{ .name }}'
|
||||||
FirewallPersistDockerGuardStep: 'Simpan status perlindungan port Docker'
|
FirewallPersistDockerGuardStep: 'Simpan status perlindungan port Docker'
|
||||||
ErrFirewallRuleScopeChange: "Tembok api semasa tidak menyokong perubahan skop peraturan (seperti keluarga alamat IPv4/IPv6). Sila cipta peraturan baharu."
|
ErrFirewallRuleScopeChange: "Tembok api semasa tidak menyokong perubahan skop peraturan (seperti keluarga alamat IPv4/IPv6). Sila cipta peraturan baharu."
|
||||||
FirewallWhitelistTask: "Kemas kini senarai port dibenarkan tembok api"
|
|
||||||
FirewallWhitelistSaved: "Konfigurasi senarai dibenarkan telah disimpan"
|
|
||||||
FirewallWhitelistDeferred: "{{ .name }}: konfigurasi disimpan; menunggu pengaktifan tembok api"
|
|
||||||
FirewallWhitelistReleased: "{{ .name }}: perlindungan senarai dibenarkan telah dilepaskan; peraturan izin dikekalkan. Untuk menutup port, padamkan peraturan secara manual daripada senarai peraturan"
|
FirewallWhitelistReleased: "{{ .name }}: perlindungan senarai dibenarkan telah dilepaskan; peraturan izin dikekalkan. Untuk menutup port, padamkan peraturan secara manual daripada senarai peraturan"
|
||||||
FirewallWhitelistRequired: "{{ .name }}: dilindungi oleh peraturan port wajib sistem"
|
FirewallWhitelistRequired: "{{ .name }}: dilindungi oleh peraturan port wajib sistem"
|
||||||
|
FileTaskCopy: 'Salin fail ke {{ .dst }}'
|
||||||
|
FileTaskMove: 'Pindahkan fail ke {{ .dst }}'
|
||||||
|
FileTaskCompress: 'Mampatkan fail ke {{ .dst }}'
|
||||||
|
FileTaskDecompress: 'Ekstrak fail ke {{ .dst }}'
|
||||||
|
FileTaskSource: 'Laluan sumber: {{ .path }}'
|
||||||
|
FileTaskFormat: 'Format arkib: {{ .format }}'
|
||||||
|
FileTaskRename: 'Nama fail sasaran: {{ .name }}'
|
||||||
|
|
||||||
|
ErrFirewallBackendCleanupRequired: "Bahagian belakang semasa {{ .current }} masih mengandungi peraturan 1Panel. Buangkannya sebelum beralih kepada {{ .target }}."
|
||||||
|
ErrDockerIPv4ForwardingDisabled: "Pemajuan IPv4 dilumpuhkan. Tetapkan net.ipv4.ip_forward=1 sebelum menggunakan bahagian belakang tembok api Docker."
|
||||||
|
ErrFirewallRuleSavedApplyFailed: "Konfigurasi peraturan baharu telah disimpan, tetapi gagal digunakan pada tembok api. Cuba lagi melalui penyegerakan: {{ .detail }}"
|
||||||
|
|||||||
@@ -649,6 +649,7 @@ CommonAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .msg }}. Faça login no painel
|
|||||||
NodeExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} nós estão anormais. Faça login no painel para obter detalhes.'
|
NodeExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} nós estão anormais. Faça login no painel para obter detalhes.'
|
||||||
LicenseExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} licenças estão anormais. Faça login no painel para obter detalhes.'
|
LicenseExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} licenças estão anormais. Faça login no painel para obter detalhes.'
|
||||||
SSHAndPanelLoginAlert: 'Seu Painel {{ .node }}{{ .ip }}, o login {{ .name }} a partir de {{ .loginIp }} é anormal. Faça login no painel para obter detalhes.'
|
SSHAndPanelLoginAlert: 'Seu Painel {{ .node }}{{ .ip }}, o login {{ .name }} a partir de {{ .loginIp }} é anormal. Faça login no painel para obter detalhes.'
|
||||||
|
CronJobSuccessAlert: 'Painel {{ .node }}{{ .ip }}: a tarefa agendada {{ .name }} foi concluída com sucesso. Faça login para ver os detalhes.'
|
||||||
|
|
||||||
# disco
|
# disco
|
||||||
DeviceNotFound: 'Dispositivo {{ .name }} não encontrado'
|
DeviceNotFound: 'Dispositivo {{ .name }} não encontrado'
|
||||||
@@ -702,7 +703,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
|||||||
FirewallCreateRuleExecutionFailed: 'Falha ao criar a regra. Nenhum registro foi salvo no banco de dados e os comandos executados não foram revertidos'
|
FirewallCreateRuleExecutionFailed: 'Falha ao criar a regra. Nenhum registro foi salvo no banco de dados e os comandos executados não foram revertidos'
|
||||||
FirewallCreateRulePersistenceFailed: 'A regra foi criada, mas não foi possível salvar seu registro de gerenciamento'
|
FirewallCreateRulePersistenceFailed: 'A regra foi criada, mas não foi possível salvar seu registro de gerenciamento'
|
||||||
FirewallAdoptRulePersistenceFailed: 'A regra foi adotada, mas não foi possível salvar seu registro de gerenciamento'
|
FirewallAdoptRulePersistenceFailed: 'A regra foi adotada, mas não foi possível salvar seu registro de gerenciamento'
|
||||||
FirewallSyncOperationsResult: 'Operações de sincronização: {{ .removed }} excluídas, {{ .created }} criadas, {{ .failed }} falhas, {{ .skipped }} não executadas'
|
FirewallSyncOperationsResult: 'Operações de sincronização: {{ .removed }} excluídas, {{ .created }} criadas, {{ .failed }} falhas, {{ .skipped }} não executadas, {{ .unchanged }} já correspondem (sem alterações necessárias)'
|
||||||
|
FirewallSyncRuleUnchanged: 'Já corresponde; nenhuma alteração necessária'
|
||||||
FirewallSyncStep: 'Sincronizar regras com {{ .name }}'
|
FirewallSyncStep: 'Sincronizar regras com {{ .name }}'
|
||||||
FirewallSyncFailed: '{{ .failed }} regras de firewall falharam na sincronização'
|
FirewallSyncFailed: '{{ .failed }} regras de firewall falharam na sincronização'
|
||||||
FirewallResetSourceStep: 'Redefinir e desativar o firewall de origem {{ .name }}'
|
FirewallResetSourceStep: 'Redefinir e desativar o firewall de origem {{ .name }}'
|
||||||
@@ -716,8 +718,16 @@ FirewallInspectDockerGuardStep: 'Inspecionar o backend do firewall Docker e as p
|
|||||||
FirewallInitializeDockerGuardStep: 'Inicializar e vincular as cadeias de proteção de portas do {{ .name }}'
|
FirewallInitializeDockerGuardStep: 'Inicializar e vincular as cadeias de proteção de portas do {{ .name }}'
|
||||||
FirewallPersistDockerGuardStep: 'Salvar o status da proteção de portas do Docker'
|
FirewallPersistDockerGuardStep: 'Salvar o status da proteção de portas do Docker'
|
||||||
ErrFirewallRuleScopeChange: "O firewall atual não permite alterar o escopo de uma regra (como a família de endereços IPv4/IPv6). Crie uma nova regra."
|
ErrFirewallRuleScopeChange: "O firewall atual não permite alterar o escopo de uma regra (como a família de endereços IPv4/IPv6). Crie uma nova regra."
|
||||||
FirewallWhitelistTask: "Atualizar a lista de portas permitidas do firewall"
|
|
||||||
FirewallWhitelistSaved: "Configuração da lista de permissões salva"
|
|
||||||
FirewallWhitelistDeferred: "{{ .name }}: configuração salva; aguardando a ativação do firewall"
|
|
||||||
FirewallWhitelistReleased: "{{ .name }}: proteção da lista de permissões removida; regra de permissão mantida. Para fechar a porta, exclua a regra manualmente da lista"
|
FirewallWhitelistReleased: "{{ .name }}: proteção da lista de permissões removida; regra de permissão mantida. Para fechar a porta, exclua a regra manualmente da lista"
|
||||||
FirewallWhitelistRequired: "{{ .name }}: protegido pelas regras de portas obrigatórias do sistema"
|
FirewallWhitelistRequired: "{{ .name }}: protegido pelas regras de portas obrigatórias do sistema"
|
||||||
|
FileTaskCopy: 'Copiar arquivos para {{ .dst }}'
|
||||||
|
FileTaskMove: 'Mover arquivos para {{ .dst }}'
|
||||||
|
FileTaskCompress: 'Compactar arquivos em {{ .dst }}'
|
||||||
|
FileTaskDecompress: 'Extrair arquivos para {{ .dst }}'
|
||||||
|
FileTaskSource: 'Caminho de origem: {{ .path }}'
|
||||||
|
FileTaskFormat: 'Formato do arquivo: {{ .format }}'
|
||||||
|
FileTaskRename: 'Nome do arquivo de destino: {{ .name }}'
|
||||||
|
|
||||||
|
ErrFirewallBackendCleanupRequired: "O backend atual {{ .current }} ainda contém regras do 1Panel. Remova-as antes de mudar para {{ .target }}."
|
||||||
|
ErrDockerIPv4ForwardingDisabled: "O encaminhamento IPv4 está desativado. Defina net.ipv4.ip_forward=1 antes de usar o backend de firewall do Docker."
|
||||||
|
ErrFirewallRuleSavedApplyFailed: "A configuração da nova regra foi salva, mas não pôde ser aplicada ao firewall. Tente novamente por meio da sincronização: {{ .detail }}"
|
||||||
|
|||||||
+14
-4
@@ -649,6 +649,7 @@ CommonAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .msg }}. Войдит
|
|||||||
NodeExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} узлов работают с ошибками. Войдите в панель для получения деталей.'
|
NodeExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} узлов работают с ошибками. Войдите в панель для получения деталей.'
|
||||||
LicenseExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} лицензий имеют ошибки. Войдите в панель для получения деталей.'
|
LicenseExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} лицензий имеют ошибки. Войдите в панель для получения деталей.'
|
||||||
SSHAndPanelLoginAlert: 'Ваш панель {{ .node }}{{ .ip }}, вход {{ .name }} с адреса {{ .loginIp }} является аномальным. Войдите в панель для получения деталей.'
|
SSHAndPanelLoginAlert: 'Ваш панель {{ .node }}{{ .ip }}, вход {{ .name }} с адреса {{ .loginIp }} является аномальным. Войдите в панель для получения деталей.'
|
||||||
|
CronJobSuccessAlert: 'Панель {{ .node }}{{ .ip }}: запланированная задача {{ .name }} успешно завершена. Войдите для просмотра подробностей.'
|
||||||
|
|
||||||
# диск
|
# диск
|
||||||
DeviceNotFound: 'Устройство {{ .name }} не найдено'
|
DeviceNotFound: 'Устройство {{ .name }} не найдено'
|
||||||
@@ -702,7 +703,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
|||||||
FirewallCreateRuleExecutionFailed: 'Не удалось создать правило. Запись в базе данных не сохранена, выполненные команды не отменены'
|
FirewallCreateRuleExecutionFailed: 'Не удалось создать правило. Запись в базе данных не сохранена, выполненные команды не отменены'
|
||||||
FirewallCreateRulePersistenceFailed: 'Правило создано, но не удалось сохранить запись управления'
|
FirewallCreateRulePersistenceFailed: 'Правило создано, но не удалось сохранить запись управления'
|
||||||
FirewallAdoptRulePersistenceFailed: 'Команды принятия правила под управление выполнены, но сохранить запись управления не удалось'
|
FirewallAdoptRulePersistenceFailed: 'Команды принятия правила под управление выполнены, но сохранить запись управления не удалось'
|
||||||
FirewallSyncOperationsResult: 'Операции синхронизации: удалено {{ .removed }}, создано {{ .created }}, ошибок {{ .failed }}, не выполнено {{ .skipped }}'
|
FirewallSyncOperationsResult: 'Операции синхронизации: удалено {{ .removed }}, создано {{ .created }}, ошибок {{ .failed }}, не выполнено {{ .skipped }}, уже совпадают (изменения не нужны): {{ .unchanged }}'
|
||||||
|
FirewallSyncRuleUnchanged: 'Уже совпадает; изменения не нужны'
|
||||||
FirewallSyncStep: 'Синхронизировать правила с {{ .name }}'
|
FirewallSyncStep: 'Синхронизировать правила с {{ .name }}'
|
||||||
FirewallSyncFailed: 'Не удалось синхронизировать правил межсетевого экрана: {{ .failed }}'
|
FirewallSyncFailed: 'Не удалось синхронизировать правил межсетевого экрана: {{ .failed }}'
|
||||||
FirewallResetSourceStep: 'Сбросить и отключить исходный межсетевой экран {{ .name }}'
|
FirewallResetSourceStep: 'Сбросить и отключить исходный межсетевой экран {{ .name }}'
|
||||||
@@ -716,8 +718,16 @@ FirewallInspectDockerGuardStep: 'Проверить бэкенд межсете
|
|||||||
FirewallInitializeDockerGuardStep: 'Инициализировать и привязать цепочки защиты портов {{ .name }}'
|
FirewallInitializeDockerGuardStep: 'Инициализировать и привязать цепочки защиты портов {{ .name }}'
|
||||||
FirewallPersistDockerGuardStep: 'Сохранить состояние защиты портов Docker'
|
FirewallPersistDockerGuardStep: 'Сохранить состояние защиты портов Docker'
|
||||||
ErrFirewallRuleScopeChange: "Текущий межсетевой экран не поддерживает изменение области действия правила (например, семейства адресов IPv4/IPv6). Создайте новое правило."
|
ErrFirewallRuleScopeChange: "Текущий межсетевой экран не поддерживает изменение области действия правила (например, семейства адресов IPv4/IPv6). Создайте новое правило."
|
||||||
FirewallWhitelistTask: "Обновить список разрешённых портов межсетевого экрана"
|
|
||||||
FirewallWhitelistSaved: "Настройки списка разрешённых портов сохранены"
|
|
||||||
FirewallWhitelistDeferred: "{{ .name }}: настройки сохранены; ожидается включение межсетевого экрана"
|
|
||||||
FirewallWhitelistReleased: "{{ .name }}: защита списка разрешённых портов снята; разрешающее правило сохранено. Чтобы закрыть порт, удалите правило вручную из списка правил"
|
FirewallWhitelistReleased: "{{ .name }}: защита списка разрешённых портов снята; разрешающее правило сохранено. Чтобы закрыть порт, удалите правило вручную из списка правил"
|
||||||
FirewallWhitelistRequired: "{{ .name }}: защищён обязательными правилами системных портов"
|
FirewallWhitelistRequired: "{{ .name }}: защищён обязательными правилами системных портов"
|
||||||
|
FileTaskCopy: 'Копирование файлов в {{ .dst }}'
|
||||||
|
FileTaskMove: 'Перемещение файлов в {{ .dst }}'
|
||||||
|
FileTaskCompress: 'Сжатие файлов в {{ .dst }}'
|
||||||
|
FileTaskDecompress: 'Распаковка файлов в {{ .dst }}'
|
||||||
|
FileTaskSource: 'Исходный путь: {{ .path }}'
|
||||||
|
FileTaskFormat: 'Формат архива: {{ .format }}'
|
||||||
|
FileTaskRename: 'Имя целевого файла: {{ .name }}'
|
||||||
|
|
||||||
|
ErrFirewallBackendCleanupRequired: "В текущем бэкенде {{ .current }} остались правила 1Panel. Удалите их перед переключением на {{ .target }}."
|
||||||
|
ErrDockerIPv4ForwardingDisabled: "Пересылка IPv4 отключена. Перед использованием бэкенда межсетевого экрана Docker установите net.ipv4.ip_forward=1."
|
||||||
|
ErrFirewallRuleSavedApplyFailed: "Настройки нового правила сохранены, но применить их к межсетевому экрану не удалось. Повторите попытку с помощью синхронизации: {{ .detail }}"
|
||||||
|
|||||||
+14
-4
@@ -649,6 +649,7 @@ CommonAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .msg }}. Detaylar için panelin
|
|||||||
NodeExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} düğüm anormal durumda. Detaylar için paneline giriş yapın.'
|
NodeExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} düğüm anormal durumda. Detaylar için paneline giriş yapın.'
|
||||||
LicenseExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} lisans anormal durumda. Detaylar için paneline giriş yapın.'
|
LicenseExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} lisans anormal durumda. Detaylar için paneline giriş yapın.'
|
||||||
SSHAndPanelLoginAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .loginIp }} adresinden {{ .name }} girişi anormal. Detaylar için paneline giriş yapın.'
|
SSHAndPanelLoginAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .loginIp }} adresinden {{ .name }} girişi anormal. Detaylar için paneline giriş yapın.'
|
||||||
|
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: zamanlanmış görev {{ .name }} başarıyla tamamlandı. Ayrıntıları görmek için giriş yapın.'
|
||||||
|
|
||||||
# disk
|
# disk
|
||||||
DeviceNotFound: 'Cihaz {{ .name }} bulunamadı'
|
DeviceNotFound: 'Cihaz {{ .name }} bulunamadı'
|
||||||
@@ -702,7 +703,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
|||||||
FirewallCreateRuleExecutionFailed: 'Kural oluşturma başarısız. Veritabanına kayıt yazılmadı ve yürütülen komutlar geri alınmadı'
|
FirewallCreateRuleExecutionFailed: 'Kural oluşturma başarısız. Veritabanına kayıt yazılmadı ve yürütülen komutlar geri alınmadı'
|
||||||
FirewallCreateRulePersistenceFailed: 'Kural oluşturuldu ancak yönetim kaydı kaydedilemedi'
|
FirewallCreateRulePersistenceFailed: 'Kural oluşturuldu ancak yönetim kaydı kaydedilemedi'
|
||||||
FirewallAdoptRulePersistenceFailed: 'Kuralı yönetime alma komutu yürütüldü, ancak yönetim kaydı kaydedilemedi'
|
FirewallAdoptRulePersistenceFailed: 'Kuralı yönetime alma komutu yürütüldü, ancak yönetim kaydı kaydedilemedi'
|
||||||
FirewallSyncOperationsResult: 'Eşitleme işlemleri: {{ .removed }} silindi, {{ .created }} oluşturuldu, {{ .failed }} başarısız, {{ .skipped }} yürütülmedi'
|
FirewallSyncOperationsResult: 'Eşitleme işlemleri: {{ .removed }} silindi, {{ .created }} oluşturuldu, {{ .failed }} başarısız, {{ .skipped }} yürütülmedi, {{ .unchanged }} zaten eşleşiyor (değişiklik gerekmiyor)'
|
||||||
|
FirewallSyncRuleUnchanged: 'Zaten eşleşiyor; değişiklik gerekmiyor'
|
||||||
FirewallSyncStep: 'Kuralları {{ .name }} ile eşitle'
|
FirewallSyncStep: 'Kuralları {{ .name }} ile eşitle'
|
||||||
FirewallSyncFailed: '{{ .failed }} güvenlik duvarı kuralı eşitlenemedi'
|
FirewallSyncFailed: '{{ .failed }} güvenlik duvarı kuralı eşitlenemedi'
|
||||||
FirewallResetSourceStep: 'Kaynak güvenlik duvarı {{ .name }} sıfırla ve devre dışı bırak'
|
FirewallResetSourceStep: 'Kaynak güvenlik duvarı {{ .name }} sıfırla ve devre dışı bırak'
|
||||||
@@ -716,8 +718,16 @@ FirewallInspectDockerGuardStep: 'Docker güvenlik duvarı arka ucunu ve ilkeleri
|
|||||||
FirewallInitializeDockerGuardStep: '{{ .name }} bağlantı noktası koruma zincirlerini başlat ve bağla'
|
FirewallInitializeDockerGuardStep: '{{ .name }} bağlantı noktası koruma zincirlerini başlat ve bağla'
|
||||||
FirewallPersistDockerGuardStep: 'Docker bağlantı noktası koruma durumunu kaydet'
|
FirewallPersistDockerGuardStep: 'Docker bağlantı noktası koruma durumunu kaydet'
|
||||||
ErrFirewallRuleScopeChange: "Mevcut güvenlik duvarı, kuralın kapsamını (IPv4/IPv6 adres ailesi gibi) değiştirmeyi desteklemiyor. Lütfen yeni bir kural oluşturun."
|
ErrFirewallRuleScopeChange: "Mevcut güvenlik duvarı, kuralın kapsamını (IPv4/IPv6 adres ailesi gibi) değiştirmeyi desteklemiyor. Lütfen yeni bir kural oluşturun."
|
||||||
FirewallWhitelistTask: "Güvenlik duvarı izin verilen port listesini güncelle"
|
|
||||||
FirewallWhitelistSaved: "İzin verilenler listesi yapılandırması kaydedildi"
|
|
||||||
FirewallWhitelistDeferred: "{{ .name }}: yapılandırma kaydedildi; güvenlik duvarının etkinleştirilmesi bekleniyor"
|
|
||||||
FirewallWhitelistReleased: "{{ .name }}: izin listesi koruması kaldırıldı; izin kuralı korundu. Portu kapatmak için kuralı kural listesinden elle silin"
|
FirewallWhitelistReleased: "{{ .name }}: izin listesi koruması kaldırıldı; izin kuralı korundu. Portu kapatmak için kuralı kural listesinden elle silin"
|
||||||
FirewallWhitelistRequired: "{{ .name }}: zorunlu sistem portu kuralları tarafından korunuyor"
|
FirewallWhitelistRequired: "{{ .name }}: zorunlu sistem portu kuralları tarafından korunuyor"
|
||||||
|
FileTaskCopy: 'Dosyaları {{ .dst }} konumuna kopyala'
|
||||||
|
FileTaskMove: 'Dosyaları {{ .dst }} konumuna taşı'
|
||||||
|
FileTaskCompress: 'Dosyaları {{ .dst }} konumuna sıkıştır'
|
||||||
|
FileTaskDecompress: 'Dosyaları {{ .dst }} konumuna çıkar'
|
||||||
|
FileTaskSource: 'Kaynak yol: {{ .path }}'
|
||||||
|
FileTaskFormat: 'Arşiv biçimi: {{ .format }}'
|
||||||
|
FileTaskRename: 'Hedef dosya adı: {{ .name }}'
|
||||||
|
|
||||||
|
ErrFirewallBackendCleanupRequired: "Mevcut {{ .current }} arka ucunda hâlâ 1Panel kuralları var. {{ .target }} arka ucuna geçmeden önce bunları temizleyin."
|
||||||
|
ErrDockerIPv4ForwardingDisabled: "IPv4 yönlendirmesi devre dışı. Docker güvenlik duvarı arka ucunu kullanmadan önce net.ipv4.ip_forward=1 ayarını yapın."
|
||||||
|
ErrFirewallRuleSavedApplyFailed: "Yeni kural yapılandırması kaydedildi, ancak güvenlik duvarına uygulanamadı. Eşitleme yaparak yeniden deneyin: {{ .detail }}"
|
||||||
|
|||||||
@@ -649,6 +649,7 @@ CommonAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},詳情請登入
|
|||||||
NodeExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個節點出現異常,詳情請登入面板檢視。'
|
NodeExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個節點出現異常,詳情請登入面板檢視。'
|
||||||
LicenseExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個授權出現異常,詳情請登入面板檢視。'
|
LicenseExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個授權出現異常,詳情請登入面板檢視。'
|
||||||
SSHAndPanelLoginAlert: '您的 {{ .node }}{{ .ip }} 面板,來自 {{ .loginIp }} 的 {{ .name }} 登入出現異常,詳情請登入面板檢視。'
|
SSHAndPanelLoginAlert: '您的 {{ .node }}{{ .ip }} 面板,來自 {{ .loginIp }} 的 {{ .name }} 登入出現異常,詳情請登入面板檢視。'
|
||||||
|
CronJobSuccessAlert: '您的 {{ .node }}{{ .ip }} 面板,排程任務 {{ .name }} 執行成功,詳情請登入面板檢視。'
|
||||||
|
|
||||||
# 磁碟
|
# 磁碟
|
||||||
DeviceNotFound: '裝置 {{ .name }} 未找到'
|
DeviceNotFound: '裝置 {{ .name }} 未找到'
|
||||||
@@ -702,7 +703,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
|||||||
FirewallCreateRuleExecutionFailed: '規則建立失敗,未寫入資料庫,已執行的命令不回復'
|
FirewallCreateRuleExecutionFailed: '規則建立失敗,未寫入資料庫,已執行的命令不回復'
|
||||||
FirewallCreateRulePersistenceFailed: '規則已建立,但納管資訊儲存失敗'
|
FirewallCreateRulePersistenceFailed: '規則已建立,但納管資訊儲存失敗'
|
||||||
FirewallAdoptRulePersistenceFailed: '規則納管命令已執行,但納管資訊儲存失敗'
|
FirewallAdoptRulePersistenceFailed: '規則納管命令已執行,但納管資訊儲存失敗'
|
||||||
FirewallSyncOperationsResult: '同步操作彙總:刪除成功 {{ .removed }} 條,建立成功 {{ .created }} 條,失敗 {{ .failed }} 條,未執行 {{ .skipped }} 條'
|
FirewallSyncOperationsResult: '同步操作彙總:刪除成功 {{ .removed }} 條,建立成功 {{ .created }} 條,失敗 {{ .failed }} 條,未執行 {{ .skipped }} 條,已一致無需變更 {{ .unchanged }} 條'
|
||||||
|
FirewallSyncRuleUnchanged: '已一致,無需變更'
|
||||||
FirewallSyncStep: '同步規則到 {{ .name }}'
|
FirewallSyncStep: '同步規則到 {{ .name }}'
|
||||||
FirewallSyncFailed: '{{ .failed }} 條防火牆規則同步失敗'
|
FirewallSyncFailed: '{{ .failed }} 條防火牆規則同步失敗'
|
||||||
FirewallResetSourceStep: '重設並停用來源防火牆 {{ .name }}'
|
FirewallResetSourceStep: '重設並停用來源防火牆 {{ .name }}'
|
||||||
@@ -716,8 +718,26 @@ FirewallInspectDockerGuardStep: '檢查 Docker 防火牆後端與防護策略'
|
|||||||
FirewallInitializeDockerGuardStep: '初始化並綁定 {{ .name }} 連接埠防護鏈'
|
FirewallInitializeDockerGuardStep: '初始化並綁定 {{ .name }} 連接埠防護鏈'
|
||||||
FirewallPersistDockerGuardStep: '儲存 Docker 連接埠防護狀態'
|
FirewallPersistDockerGuardStep: '儲存 Docker 連接埠防護狀態'
|
||||||
ErrFirewallRuleScopeChange: "目前的防火牆不支援修改規則的作用範圍(如 IPv4/IPv6 位址族),請建立新規則。"
|
ErrFirewallRuleScopeChange: "目前的防火牆不支援修改規則的作用範圍(如 IPv4/IPv6 位址族),請建立新規則。"
|
||||||
FirewallWhitelistTask: "更新防火牆連接埠白名單"
|
|
||||||
FirewallWhitelistSaved: "白名單設定已儲存"
|
|
||||||
FirewallWhitelistDeferred: "{{ .name }}:設定已儲存,待防火牆啟用後套用"
|
|
||||||
FirewallWhitelistReleased: "{{ .name }}:已解除白名單保護,放行規則保留;如需關閉連接埠,請在規則清單手動刪除"
|
FirewallWhitelistReleased: "{{ .name }}:已解除白名單保護,放行規則保留;如需關閉連接埠,請在規則清單手動刪除"
|
||||||
FirewallWhitelistRequired: "{{ .name }}:由系統必要連接埠規則保護"
|
FirewallWhitelistRequired: "{{ .name }}:由系統必要連接埠規則保護"
|
||||||
|
FileTaskCopy: '複製檔案至 {{ .dst }}'
|
||||||
|
FileTaskMove: '移動檔案至 {{ .dst }}'
|
||||||
|
FileTaskCompress: '壓縮檔案至 {{ .dst }}'
|
||||||
|
FileTaskDecompress: '解壓檔案至 {{ .dst }}'
|
||||||
|
FileTaskSource: '來源路徑:{{ .path }}'
|
||||||
|
FileTaskFormat: '壓縮格式:{{ .format }}'
|
||||||
|
FileTaskRename: '目標檔名:{{ .name }}'
|
||||||
|
|
||||||
|
ErrFirewallBackendCleanupRequired: "目前後端 {{ .current }} 中仍有 1Panel 規則,請先清理後再切換至 {{ .target }}。"
|
||||||
|
ErrDockerIPv4ForwardingDisabled: "IPv4 轉送尚未啟用,請先設定 net.ipv4.ip_forward=1,再使用 Docker 防火牆後端。"
|
||||||
|
ErrFirewallRuleSavedApplyFailed: "新規則設定已儲存,但套用至防火牆失敗。可透過同步重試:{{ .detail }}"
|
||||||
|
|
||||||
|
NetworkCleanupDeleted: "網路 [{{ .name }}] ({{ .id }}) 已刪除"
|
||||||
|
NetworkCleanupProtected: "跳過網路 [{{ .name }}] ({{ .id }}):預設保留網路,未刪除"
|
||||||
|
NetworkCleanupConnected: "跳過網路 [{{ .name }}] ({{ .id }}):仍有容器連接或正在使用,未刪除"
|
||||||
|
NetworkCleanupUnsupported: "跳過網路 [{{ .name }}] ({{ .id }}):特殊網路,未刪除"
|
||||||
|
NetworkCleanupGone: "跳過網路 [{{ .name }}] ({{ .id }}):網路已不存在"
|
||||||
|
NetworkCleanupInspectFailed: "網路 [{{ .name }}] ({{ .id }}) 檢查失敗,未刪除"
|
||||||
|
NetworkCleanupRemoveFailed: "網路 [{{ .name }}] ({{ .id }}) 刪除失敗"
|
||||||
|
NetworkCleanupSummary: "網路清理完成:已刪除 {{ .deleted }},已跳過 {{ .skipped }},失敗 {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "部分網路清理失敗,請查看任務日誌"
|
||||||
|
|||||||
+24
-4
@@ -649,6 +649,7 @@ CommonAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},请登录面板
|
|||||||
NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个节点存在异常,请登录面板查看详情。"
|
NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个节点存在异常,请登录面板查看详情。"
|
||||||
LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个许可证存在异常,请登录面板查看详情。"
|
LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个许可证存在异常,请登录面板查看详情。"
|
||||||
SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,面板 {{ .name }} 登录 {{ .loginIp }} 异常,请登录面板查看详情。"
|
SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,面板 {{ .name }} 登录 {{ .loginIp }} 异常,请登录面板查看详情。"
|
||||||
|
CronJobSuccessAlert: "您的 {{ .node }}{{ .ip }} 面板,计划任务-{{ .name }}执行成功,请登录面板查看详情。"
|
||||||
|
|
||||||
# 磁盘
|
# 磁盘
|
||||||
DeviceNotFound: "设备 {{ .name }} 未找到"
|
DeviceNotFound: "设备 {{ .name }} 未找到"
|
||||||
@@ -702,7 +703,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
|||||||
FirewallCreateRuleExecutionFailed: '规则创建失败,未入库,已执行的命令不回滚'
|
FirewallCreateRuleExecutionFailed: '规则创建失败,未入库,已执行的命令不回滚'
|
||||||
FirewallCreateRulePersistenceFailed: '规则已创建,但纳管信息保存失败'
|
FirewallCreateRulePersistenceFailed: '规则已创建,但纳管信息保存失败'
|
||||||
FirewallAdoptRulePersistenceFailed: '规则纳管命令已执行,但纳管信息保存失败'
|
FirewallAdoptRulePersistenceFailed: '规则纳管命令已执行,但纳管信息保存失败'
|
||||||
FirewallSyncOperationsResult: '同步操作汇总:删除成功 {{ .removed }} 条,创建成功 {{ .created }} 条,失败 {{ .failed }} 条,未执行 {{ .skipped }} 条'
|
FirewallSyncOperationsResult: '同步操作汇总:删除成功 {{ .removed }} 条,创建成功 {{ .created }} 条,失败 {{ .failed }} 条,未执行 {{ .skipped }} 条,已一致无需变更 {{ .unchanged }} 条'
|
||||||
|
FirewallSyncRuleUnchanged: '已一致,无需变更'
|
||||||
FirewallSyncStep: "同步规则到 {{ .name }}"
|
FirewallSyncStep: "同步规则到 {{ .name }}"
|
||||||
FirewallSyncFailed: "{{ .failed }} 条防火墙规则同步失败"
|
FirewallSyncFailed: "{{ .failed }} 条防火墙规则同步失败"
|
||||||
FirewallResetSourceStep: "重置并停用源防火墙 {{ .name }}"
|
FirewallResetSourceStep: "重置并停用源防火墙 {{ .name }}"
|
||||||
@@ -716,8 +718,26 @@ FirewallInspectDockerGuardStep: "检查 Docker 防火墙后端和防护策略"
|
|||||||
FirewallInitializeDockerGuardStep: "初始化并绑定 {{ .name }} 端口防护链"
|
FirewallInitializeDockerGuardStep: "初始化并绑定 {{ .name }} 端口防护链"
|
||||||
FirewallPersistDockerGuardStep: "保存 Docker 端口防护状态"
|
FirewallPersistDockerGuardStep: "保存 Docker 端口防护状态"
|
||||||
ErrFirewallRuleScopeChange: "当前防火墙不支持修改规则的作用范围(如 IPv4/IPv6 地址族),请新建规则。"
|
ErrFirewallRuleScopeChange: "当前防火墙不支持修改规则的作用范围(如 IPv4/IPv6 地址族),请新建规则。"
|
||||||
FirewallWhitelistTask: "更新防火墙端口白名单"
|
|
||||||
FirewallWhitelistSaved: "白名单配置已保存"
|
|
||||||
FirewallWhitelistDeferred: "{{ .name }}:配置已保存,待防火墙启用后下发"
|
|
||||||
FirewallWhitelistReleased: "{{ .name }}:已解除白名单保护,放行规则保留;如需关闭端口,请在规则列表手动删除"
|
FirewallWhitelistReleased: "{{ .name }}:已解除白名单保护,放行规则保留;如需关闭端口,请在规则列表手动删除"
|
||||||
FirewallWhitelistRequired: "{{ .name }}:由系统必需端口规则保护"
|
FirewallWhitelistRequired: "{{ .name }}:由系统必需端口规则保护"
|
||||||
|
FileTaskCopy: '复制文件到 {{ .dst }}'
|
||||||
|
FileTaskMove: '移动文件到 {{ .dst }}'
|
||||||
|
FileTaskCompress: '压缩文件到 {{ .dst }}'
|
||||||
|
FileTaskDecompress: '解压文件到 {{ .dst }}'
|
||||||
|
FileTaskSource: '源路径:{{ .path }}'
|
||||||
|
FileTaskFormat: '压缩格式:{{ .format }}'
|
||||||
|
FileTaskRename: '目标文件名:{{ .name }}'
|
||||||
|
|
||||||
|
ErrFirewallBackendCleanupRequired: "当前后端 {{ .current }} 中仍有 1Panel 规则,请先清理后再切换到 {{ .target }}。"
|
||||||
|
ErrDockerIPv4ForwardingDisabled: "IPv4 转发未开启,请先设置 net.ipv4.ip_forward=1,再使用 Docker 防火墙后端。"
|
||||||
|
ErrFirewallRuleSavedApplyFailed: "新规则配置已保存,但应用到防火墙失败。可通过同步重试:{{ .detail }}"
|
||||||
|
|
||||||
|
NetworkCleanupDeleted: "网络 [{{ .name }}] ({{ .id }}) 已删除"
|
||||||
|
NetworkCleanupProtected: "跳过网络 [{{ .name }}] ({{ .id }}):默认保留网络,未删除"
|
||||||
|
NetworkCleanupConnected: "跳过网络 [{{ .name }}] ({{ .id }}):仍有容器连接或正在使用,未删除"
|
||||||
|
NetworkCleanupUnsupported: "跳过网络 [{{ .name }}] ({{ .id }}):特殊网络,未删除"
|
||||||
|
NetworkCleanupGone: "跳过网络 [{{ .name }}] ({{ .id }}):网络已不存在"
|
||||||
|
NetworkCleanupInspectFailed: "网络 [{{ .name }}] ({{ .id }}) 检查失败,未删除"
|
||||||
|
NetworkCleanupRemoveFailed: "网络 [{{ .name }}] ({{ .id }}) 删除失败"
|
||||||
|
NetworkCleanupSummary: "网络清理完成:已删除 {{ .deleted }},已跳过 {{ .skipped }},失败 {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "部分网络清理失败,请查看任务日志"
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/app/service"
|
"github.com/1Panel-dev/1Panel/agent/app/service"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/init/migration/migrations"
|
||||||
migrationutils "github.com/1Panel-dev/1Panel/agent/init/migration/migrations/utils"
|
migrationutils "github.com/1Panel-dev/1Panel/agent/init/migration/migrations/utils"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/iptables_helper"
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/iptables_helper"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/nftables_helper"
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/nftables_helper"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/ping"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
@@ -26,6 +26,15 @@ func Init() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
clientName := client.Name()
|
clientName := client.Name()
|
||||||
|
initialize := false
|
||||||
|
defer func() {
|
||||||
|
if err := migrations.TransferFirewalldSSHService(ctx, client, service.NewIFirewallService().SyncPortWhitelist); err != nil {
|
||||||
|
global.LOG.Warnf("synchronize firewall whitelist on startup failed, err: %v", err)
|
||||||
|
}
|
||||||
|
if initialize {
|
||||||
|
initDockerPortGuard(ctx)
|
||||||
|
}
|
||||||
|
}()
|
||||||
if err := migrationutils.TransferHostFirewall(ctx, clientName); err != nil {
|
if err := migrationutils.TransferHostFirewall(ctx, clientName); err != nil {
|
||||||
global.LOG.Errorf("transfer legacy host firewall records failed, err: %v", err)
|
global.LOG.Errorf("transfer legacy host firewall records failed, err: %v", err)
|
||||||
return
|
return
|
||||||
@@ -40,11 +49,11 @@ func Init() {
|
|||||||
if err := initForwardingRules(ctx); err != nil {
|
if err := initForwardingRules(ctx); err != nil {
|
||||||
global.LOG.Warnf("restore forwarding rules failed, manual synchronization is available, err: %v", err)
|
global.LOG.Warnf("restore forwarding rules failed, manual synchronization is available, err: %v", err)
|
||||||
}
|
}
|
||||||
if !needInit() {
|
initialize = needInit()
|
||||||
|
if !initialize {
|
||||||
repairIptablesBaseChains(clientName)
|
repairIptablesBaseChains(clientName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer initDockerPortGuard(ctx)
|
|
||||||
InitPingStatus()
|
InitPingStatus()
|
||||||
global.LOG.Info("initializing firewall settings...")
|
global.LOG.Info("initializing firewall settings...")
|
||||||
if clientName == "nftables" {
|
if clientName == "nftables" {
|
||||||
@@ -64,17 +73,12 @@ func Init() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
settingRepo := repo.NewISettingRepo()
|
settingRepo := repo.NewISettingRepo()
|
||||||
panelPort := service.LoadPanelPort()
|
|
||||||
if len(panelPort) == 0 {
|
|
||||||
global.LOG.Errorf("find 1panel service port failed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
requiredPorts, err := service.LoadRequiredFirewallPortWhiteList()
|
requiredPorts, err := service.LoadRequiredFirewallPortWhiteList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.LOG.Errorf("load required firewall ports failed, err: %v", err)
|
global.LOG.Errorf("load required firewall ports failed, err: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := iptables_helper.RestoreBaseChains(panelPort, requiredPorts); err != nil {
|
if err := iptables_helper.RestoreBaseChains(requiredPorts); err != nil {
|
||||||
global.LOG.Errorf("restore iptables base chains failed, err: %v", err)
|
global.LOG.Errorf("restore iptables base chains failed, err: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -99,11 +103,12 @@ func repairIptablesBaseChains(clientName string) {
|
|||||||
if status != constant.StatusEnable {
|
if status != constant.StatusEnable {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
manager := iptables_helper.Manager{
|
ports, err := service.LoadRequiredFirewallPortWhiteList()
|
||||||
PanelPort: service.LoadPanelPort,
|
if err != nil {
|
||||||
LoadRequiredPorts: service.LoadRequiredFirewallPortWhiteList,
|
global.LOG.Warnf("load required firewall ports for base chain repair failed, err: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if err := manager.RepairBaseChains(); err != nil {
|
if err := iptables_helper.RepairBaseChains(ports); err != nil {
|
||||||
global.LOG.Warnf("repair iptables base chains failed, err: %v", err)
|
global.LOG.Warnf("repair iptables base chains failed, err: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +155,7 @@ func needInit() bool {
|
|||||||
|
|
||||||
func InitPingStatus() {
|
func InitPingStatus() {
|
||||||
global.LOG.Info("initializing ban ping status from settings...")
|
global.LOG.Info("initializing ban ping status from settings...")
|
||||||
status := ping.LoadStatus()
|
status := firewall.LoadPingStatus()
|
||||||
statusInDB, _ := repo.NewISettingRepo().GetValueByKey("BanPing")
|
statusInDB, _ := repo.NewISettingRepo().GetValueByKey("BanPing")
|
||||||
if statusInDB == status {
|
if statusInDB == status {
|
||||||
return
|
return
|
||||||
@@ -160,7 +165,7 @@ func InitPingStatus() {
|
|||||||
if statusInDB == constant.StatusDisable {
|
if statusInDB == constant.StatusDisable {
|
||||||
enable = "0"
|
enable = "0"
|
||||||
}
|
}
|
||||||
if err := ping.UpdateStatus(enable); err != nil {
|
if err := firewall.UpdatePingStatus(enable); err != nil {
|
||||||
global.LOG.Errorf("initialize ping status failed: %v", err)
|
global.LOG.Errorf("initialize ping status failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ func agentDBMigrations() []*gormigrate.Migration {
|
|||||||
migrations.NormalizeFirewallBackendSelections,
|
migrations.NormalizeFirewallBackendSelections,
|
||||||
migrations.SimplifyFirewallRulePolicy,
|
migrations.SimplifyFirewallRulePolicy,
|
||||||
migrations.AddDockerPortGuardReadOnly,
|
migrations.AddDockerPortGuardReadOnly,
|
||||||
|
migrations.MigrateFirewallPortWhitelistSources,
|
||||||
|
migrations.AddAcceleratorMetrics,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/service"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle/providers"
|
||||||
|
"github.com/go-gormigrate/gormigrate/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
const firewalldSSHServiceMigrationID = "20260916-remove-firewalld-ssh-service"
|
||||||
|
|
||||||
|
func TransferFirewalldSSHService(ctx context.Context, client lifecycle.Client, syncWhitelist func(context.Context) error) error {
|
||||||
|
return transferFirewalldSSHService(ctx, global.DB, client, syncWhitelist)
|
||||||
|
}
|
||||||
|
|
||||||
|
func transferFirewalldSSHService(ctx context.Context, db *gorm.DB, client lifecycle.Client, syncWhitelist func(context.Context) error) error {
|
||||||
|
if err := syncWhitelist(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if client.Name() != lifecycle.ProviderFirewalld {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var count int64
|
||||||
|
if err := db.WithContext(ctx).Table("migrations").Where("id = ?", firewalldSSHServiceMigrationID).Count(&count).Error; err != nil {
|
||||||
|
return fmt.Errorf("check firewalld SSH service migration: %w", err)
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
active, err := client.Status()
|
||||||
|
if err != nil || !active {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := providers.RemoveFirewalldSSHService(); err != nil {
|
||||||
|
return fmt.Errorf("transfer firewalld SSH access to whitelist: %w", err)
|
||||||
|
}
|
||||||
|
if err := db.WithContext(ctx).Table("migrations").Clauses(clause.OnConflict{DoNothing: true}).
|
||||||
|
Create(map[string]interface{}{"id": firewalldSSHServiceMigrationID}).Error; err != nil {
|
||||||
|
return fmt.Errorf("record firewalld SSH service migration: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var MigrateFirewallPortWhitelistSources = &gormigrate.Migration{
|
||||||
|
ID: "20260915-migrate-firewall-port-whitelist-sources",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
var setting model.Setting
|
||||||
|
err := tx.Where("key = ?", constant.FirewallPortWhiteList).First(&setting).Error
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rules, err := migrateFirewallPortWhitelist(setting.Value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("migrate firewall port whitelist: %w", err)
|
||||||
|
}
|
||||||
|
value, err := json.Marshal(rules)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if setting.ID == 0 {
|
||||||
|
err = tx.Create(&model.Setting{Key: constant.FirewallPortWhiteList, Value: string(value)}).Error
|
||||||
|
} else {
|
||||||
|
err = tx.Model(&setting).Update("value", string(value)).Error
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Where("key = ?", "FirewallPortWhiteListPending").Delete(&model.Setting{}).Error
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyPortWhitelist struct {
|
||||||
|
Ports []string `json:"ports"`
|
||||||
|
Family string `json:"family"`
|
||||||
|
Port string `json:"port"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Sources []string `json:"sources"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry legacyPortWhitelist) singlePortRule() firewall.PortWhitelist {
|
||||||
|
rule := firewall.PortWhitelist{Port: entry.Port, Protocol: entry.Protocol, Type: entry.Type, Sources: entry.Sources}
|
||||||
|
if strings.TrimSpace(rule.Type) != "" && rule.Port == "" && len(entry.Ports) > 0 {
|
||||||
|
rule.Port = entry.Ports[0]
|
||||||
|
}
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateFirewallPortWhitelist(value string) ([]firewall.PortWhitelist, error) {
|
||||||
|
legacy, err := parseLegacyPortWhitelist(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rules := make([]firewall.PortWhitelist, 0, len(legacy)+5)
|
||||||
|
indexes := make(map[string]int)
|
||||||
|
key := func(rule firewall.PortWhitelist) string {
|
||||||
|
if rule.Type != "" {
|
||||||
|
return rule.Type + "/" + rule.Protocol
|
||||||
|
}
|
||||||
|
return rule.Type + "/" + rule.Protocol + "/" + rule.Port
|
||||||
|
}
|
||||||
|
for index, entry := range legacy {
|
||||||
|
family := strings.ToLower(strings.TrimSpace(entry.Family))
|
||||||
|
if family != "" && family != constant.FirewallFamilyIPv4 && family != constant.FirewallFamilyIPv6 {
|
||||||
|
return nil, fmt.Errorf("entry #%d: invalid address family %q", index+1, entry.Family)
|
||||||
|
}
|
||||||
|
rule := entry.singlePortRule()
|
||||||
|
if strings.TrimSpace(rule.Protocol) == "" {
|
||||||
|
rule.Protocol = "tcp"
|
||||||
|
}
|
||||||
|
if len(rule.Sources) == 0 {
|
||||||
|
rule.Sources = []string{"0.0.0.0/0"}
|
||||||
|
if family == constant.FirewallFamilyIPv6 {
|
||||||
|
rule.Sources = []string{"::/0"}
|
||||||
|
} else if family == "" && strings.TrimSpace(rule.Type) != "" {
|
||||||
|
rule.Sources = append(rule.Sources, "::/0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rule.Sources, err = firewall.NormalizeWhitelistSources(family, rule.Sources)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("entry #%d: %w", index+1, err)
|
||||||
|
}
|
||||||
|
normalized, err := service.InitializeFirewallWhitelistPorts([]firewall.PortWhitelist{rule})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("entry #%d: %w", index+1, err)
|
||||||
|
}
|
||||||
|
rule = normalized[0]
|
||||||
|
if existing, found := indexes[key(rule)]; found {
|
||||||
|
rules[existing].Sources, err = firewall.NormalizeWhitelistSources("", append(rules[existing].Sources, rule.Sources...))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
indexes[key(rule)] = len(rules)
|
||||||
|
rules = append(rules, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
defaults := []firewall.PortWhitelist{
|
||||||
|
{Type: firewall.PortWhitelistTypePanel, Protocol: "tcp"},
|
||||||
|
{Type: firewall.PortWhitelistTypeSSH, Protocol: "tcp"},
|
||||||
|
{Port: "443", Protocol: "tcp"},
|
||||||
|
{Port: "443", Protocol: "udp"},
|
||||||
|
{Port: "80", Protocol: "tcp"},
|
||||||
|
}
|
||||||
|
for _, rule := range defaults {
|
||||||
|
index, found := indexes[key(rule)]
|
||||||
|
if !found {
|
||||||
|
if rule.Type == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
index = len(rules)
|
||||||
|
indexes[key(rule)] = index
|
||||||
|
rules = append(rules, rule)
|
||||||
|
}
|
||||||
|
var ipv4, ipv6 bool
|
||||||
|
for _, source := range rules[index].Sources {
|
||||||
|
if strings.Contains(source, ":") {
|
||||||
|
ipv6 = true
|
||||||
|
} else {
|
||||||
|
ipv4 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ipv4 {
|
||||||
|
rules[index].Sources = append(rules[index].Sources, "0.0.0.0/0")
|
||||||
|
}
|
||||||
|
if !ipv6 {
|
||||||
|
rules[index].Sources = append(rules[index].Sources, "::/0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return service.InitializeFirewallWhitelistPorts(rules)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLegacyPortWhitelist(value string) ([]legacyPortWhitelist, error) {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if value == "" || value == "null" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(value, "[") {
|
||||||
|
var rules []legacyPortWhitelist
|
||||||
|
err := json.Unmarshal([]byte(value), &rules)
|
||||||
|
return rules, err
|
||||||
|
}
|
||||||
|
items := strings.FieldsFunc(value, func(r rune) bool { return r == ',' || r == ';' || unicode.IsSpace(r) })
|
||||||
|
rules := make([]legacyPortWhitelist, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
parts := strings.Split(item, "/")
|
||||||
|
rule := legacyPortWhitelist{}
|
||||||
|
switch len(parts) {
|
||||||
|
case 1:
|
||||||
|
rule.Port = parts[0]
|
||||||
|
case 2:
|
||||||
|
rule.Port, rule.Protocol = parts[0], parts[1]
|
||||||
|
case 3:
|
||||||
|
rule.Family, rule.Port, rule.Protocol = parts[0], parts[1], parts[2]
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid legacy whitelist entry %q", item)
|
||||||
|
}
|
||||||
|
rules = append(rules, rule)
|
||||||
|
}
|
||||||
|
return rules, nil
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/ping"
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
|
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||||
|
|
||||||
@@ -1131,7 +1131,7 @@ var AddisIPtoWebsiteSSL = &gormigrate.Migration{
|
|||||||
var InitPingStatus = &gormigrate.Migration{
|
var InitPingStatus = &gormigrate.Migration{
|
||||||
ID: "20251201-init-ping-status",
|
ID: "20251201-init-ping-status",
|
||||||
Migrate: func(tx *gorm.DB) error {
|
Migrate: func(tx *gorm.DB) error {
|
||||||
status := ping.LoadStatus()
|
status := firewall.LoadPingStatus()
|
||||||
if err := tx.Create(&model.Setting{Key: "BanPing", Value: status}).Error; err != nil {
|
if err := tx.Create(&model.Setting{Key: "BanPing", Value: status}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1908,3 +1908,10 @@ var AddDockerPortGuardReadOnly = &gormigrate.Migration{
|
|||||||
return tx.Migrator().CreateIndex(&model.DockerPortGuardPolicy{}, "idx_docker_port_guard_endpoint")
|
return tx.Migrator().CreateIndex(&model.DockerPortGuardPolicy{}, "idx_docker_port_guard_endpoint")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var AddAcceleratorMetrics = &gormigrate.Migration{
|
||||||
|
ID: "20260928-accelerator-vendor-metrics",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
return global.GPUMonitorDB.AutoMigrate(&model.MonitorGPU{})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -142,7 +145,7 @@ func convertLegacyHostFirewallRecords(records []legacyHostFirewallRecord, provid
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
identity := item.PolicyKey()
|
identity := hostFirewallPolicyKey(item)
|
||||||
if index, exists := byIdentity[identity]; exists {
|
if index, exists := byIdentity[identity]; exists {
|
||||||
if item.Description != "" {
|
if item.Description != "" {
|
||||||
converted[index].Description = item.Description
|
converted[index].Description = item.Description
|
||||||
@@ -336,10 +339,30 @@ func legacyIPOrPrefix(value string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func hostFirewallRuleModel(rule filter.FirewallRule) (model.FirewallRule, error) {
|
func hostFirewallRuleModel(rule filter.FirewallRule) (model.FirewallRule, error) {
|
||||||
record, err := model.FirewallRuleFromDomain(rule)
|
normalized, err := filter.NormalizeRule(rule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.FirewallRule{}, err
|
return model.FirewallRule{}, err
|
||||||
}
|
}
|
||||||
|
switch normalized.NativeKind {
|
||||||
|
case "", filter.NativeKindRule, filter.NativeKindZonePort, filter.NativeKindRichRule, filter.NativeKindUFWRule:
|
||||||
|
default:
|
||||||
|
return model.FirewallRule{}, fmt.Errorf("%w: native rule %q cannot be stored as a provider-neutral policy", filter.ErrUnsupportedScope, normalized.NativeKind)
|
||||||
|
}
|
||||||
|
record := model.FirewallRule{
|
||||||
|
Family: string(normalized.Scope.Family),
|
||||||
|
Protocol: normalized.Protocol,
|
||||||
|
SourceAddress: normalized.SourceAddress,
|
||||||
|
SourcePort: normalized.SourcePort,
|
||||||
|
DestinationAddress: normalized.DestinationAddress,
|
||||||
|
DestinationPort: normalized.DestinationPort,
|
||||||
|
Interface: normalized.Interface,
|
||||||
|
ConnectionStates: strings.Join(normalized.ConnectionStates, ","),
|
||||||
|
Action: string(normalized.Action),
|
||||||
|
Description: normalized.Description,
|
||||||
|
}
|
||||||
|
if normalized.Scope.Provider == filter.ProviderFirewalld {
|
||||||
|
record.Priority = normalized.Priority
|
||||||
|
}
|
||||||
record.UUID = uuid.NewString()
|
record.UUID = uuid.NewString()
|
||||||
record.Origin = constant.FirewallRuleOriginAdopted
|
record.Origin = constant.FirewallRuleOriginAdopted
|
||||||
record.Owner = constant.FirewallRuleSourceUser
|
record.Owner = constant.FirewallRuleSourceUser
|
||||||
@@ -347,6 +370,27 @@ func hostFirewallRuleModel(rule filter.FirewallRule) (model.FirewallRule, error)
|
|||||||
return record, nil
|
return record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hostFirewallPolicyKey(rule model.FirewallRule) string {
|
||||||
|
payload, _ := json.Marshal(struct {
|
||||||
|
Family string `json:"family"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
SourceAddress string `json:"sourceAddress,omitempty"`
|
||||||
|
SourcePort string `json:"sourcePort,omitempty"`
|
||||||
|
DestinationAddress string `json:"destinationAddress,omitempty"`
|
||||||
|
DestinationPort string `json:"destinationPort,omitempty"`
|
||||||
|
Interface string `json:"interface,omitempty"`
|
||||||
|
ConnectionStates string `json:"connectionStates,omitempty"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
}{
|
||||||
|
Family: rule.Family, Protocol: rule.Protocol,
|
||||||
|
SourceAddress: rule.SourceAddress, SourcePort: rule.SourcePort,
|
||||||
|
DestinationAddress: rule.DestinationAddress, DestinationPort: rule.DestinationPort,
|
||||||
|
Interface: rule.Interface, ConnectionStates: rule.ConnectionStates, Action: rule.Action,
|
||||||
|
})
|
||||||
|
sum := sha256.Sum256(payload)
|
||||||
|
return hex.EncodeToString(sum[:])
|
||||||
|
}
|
||||||
|
|
||||||
func importLegacyHostFirewallRules(tx *gorm.DB, rules []model.FirewallRule) error {
|
func importLegacyHostFirewallRules(tx *gorm.DB, rules []model.FirewallRule) error {
|
||||||
var existing []model.FirewallRule
|
var existing []model.FirewallRule
|
||||||
if err := tx.Find(&existing).Error; err != nil {
|
if err := tx.Find(&existing).Error; err != nil {
|
||||||
@@ -354,10 +398,10 @@ func importLegacyHostFirewallRules(tx *gorm.DB, rules []model.FirewallRule) erro
|
|||||||
}
|
}
|
||||||
byIdentity := make(map[string]model.FirewallRule, len(existing))
|
byIdentity := make(map[string]model.FirewallRule, len(existing))
|
||||||
for _, item := range existing {
|
for _, item := range existing {
|
||||||
byIdentity[item.PolicyKey()] = item
|
byIdentity[hostFirewallPolicyKey(item)] = item
|
||||||
}
|
}
|
||||||
for _, item := range rules {
|
for _, item := range rules {
|
||||||
identity := item.PolicyKey()
|
identity := hostFirewallPolicyKey(item)
|
||||||
if current, exists := byIdentity[identity]; exists {
|
if current, exists := byIdentity[identity]; exists {
|
||||||
if current.Description == "" && item.Description != "" {
|
if current.Description == "" && item.Description != "" {
|
||||||
if err := tx.Model(&model.FirewallRule{}).Where("uuid = ?", current.UUID).
|
if err := tx.Model(&model.FirewallRule{}).Where("uuid = ?", current.UUID).
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/cmd/server/conf"
|
"github.com/1Panel-dev/1Panel/agent/cmd/server/conf"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
@@ -61,4 +62,10 @@ func initBaseInfo() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
global.CONF.Base.InstallDir = nodeInfo.BaseDir
|
global.CONF.Base.InstallDir = nodeInfo.BaseDir
|
||||||
|
if !global.IsMaster {
|
||||||
|
global.CONF.Base.Port = strconv.FormatUint(uint64(nodeInfo.NodePort), 10)
|
||||||
|
if nodeInfo.NodePort == 0 {
|
||||||
|
global.CONF.Base.Port = "9999"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
|
|
||||||
baRouter.GET("/network", baseApi.ListNetwork)
|
baRouter.GET("/network", baseApi.ListNetwork)
|
||||||
baRouter.POST("/network/del", baseApi.DeleteNetwork)
|
baRouter.POST("/network/del", baseApi.DeleteNetwork)
|
||||||
|
baRouter.POST("/network/clean", baseApi.CleanNetworks)
|
||||||
baRouter.POST("/network/search", baseApi.SearchNetwork)
|
baRouter.POST("/network/search", baseApi.SearchNetwork)
|
||||||
baRouter.POST("/network", baseApi.CreateNetwork)
|
baRouter.POST("/network", baseApi.CreateNetwork)
|
||||||
baRouter.GET("/volume", baseApi.ListVolume)
|
baRouter.GET("/volume", baseApi.ListVolume)
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
cmdRouter.POST("/redis/status", baseApi.LoadRedisStatus)
|
cmdRouter.POST("/redis/status", baseApi.LoadRedisStatus)
|
||||||
cmdRouter.POST("/redis/conf", baseApi.LoadRedisConf)
|
cmdRouter.POST("/redis/conf", baseApi.LoadRedisConf)
|
||||||
cmdRouter.GET("/redis/check", baseApi.CheckHasCli)
|
cmdRouter.GET("/redis/check", baseApi.CheckHasCli)
|
||||||
|
cmdRouter.GET("/redis/cli/status", baseApi.LoadRedisCliStatus)
|
||||||
cmdRouter.POST("/redis/install/cli", baseApi.InstallCli)
|
cmdRouter.POST("/redis/install/cli", baseApi.InstallCli)
|
||||||
cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword)
|
cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword)
|
||||||
cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf)
|
cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
hostRouter := Router.Group("hosts")
|
hostRouter := Router.Group("hosts")
|
||||||
baseApi := v2.ApiGroupApp.BaseApi
|
baseApi := v2.ApiGroupApp.BaseApi
|
||||||
Router.POST("/internal/terminal/sessions/revoke", baseApi.RevokeTerminalSessions)
|
Router.POST("/internal/terminal/sessions/revoke", baseApi.RevokeTerminalSessions)
|
||||||
|
Router.GET("/internal/terminal/capabilities", baseApi.TerminalCapabilities)
|
||||||
{
|
{
|
||||||
hostRouter.POST("", baseApi.CreateHost)
|
hostRouter.POST("", baseApi.CreateHost)
|
||||||
hostRouter.POST("/info", baseApi.GetHostByID)
|
hostRouter.POST("/info", baseApi.GetHostByID)
|
||||||
@@ -27,7 +28,9 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
hostRouter.POST("/firewall/port", baseApi.UpdatePanelFirewallPort)
|
hostRouter.POST("/firewall/port", baseApi.UpdatePanelFirewallPort)
|
||||||
hostRouter.GET("/firewall/settings", baseApi.LoadFirewallSettings)
|
hostRouter.GET("/firewall/settings", baseApi.LoadFirewallSettings)
|
||||||
hostRouter.POST("/firewall/settings/operate", baseApi.OperateFirewallBackend)
|
hostRouter.POST("/firewall/settings/operate", baseApi.OperateFirewallBackend)
|
||||||
hostRouter.POST("/firewall/settings/whitelist", baseApi.UpdateFirewallPortWhitelist)
|
hostRouter.POST("/firewall/settings/whitelist", baseApi.CreateFirewallPortWhitelist)
|
||||||
|
hostRouter.POST("/firewall/settings/whitelist/update", baseApi.UpdateFirewallPortWhitelist)
|
||||||
|
hostRouter.POST("/firewall/settings/whitelist/delete", baseApi.DeleteFirewallPortWhitelist)
|
||||||
hostRouter.POST("/firewall/forward/base", baseApi.LoadForwardingBaseInfo)
|
hostRouter.POST("/firewall/forward/base", baseApi.LoadForwardingBaseInfo)
|
||||||
hostRouter.POST("/firewall/forward/search", baseApi.SearchForwardingRules)
|
hostRouter.POST("/firewall/forward/search", baseApi.SearchForwardingRules)
|
||||||
hostRouter.POST("/firewall/forward/operate", baseApi.OperateForwardingRules)
|
hostRouter.POST("/firewall/forward/operate", baseApi.OperateForwardingRules)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
|
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
|
||||||
@@ -52,6 +53,11 @@ func (c Client) LoadInfo() (*Info, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
||||||
|
defer cancel()
|
||||||
results := make([]providerResult, len(c.providers))
|
results := make([]providerResult, len(c.providers))
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for index, item := range c.providers {
|
for index, item := range c.providers {
|
||||||
@@ -66,8 +72,11 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
snapshot := &Snapshot{DriverVersions: make(map[string]string)}
|
snapshot := &Snapshot{}
|
||||||
var (
|
var (
|
||||||
errs []error
|
errs []error
|
||||||
active []*ProviderSnapshot
|
active []*ProviderSnapshot
|
||||||
@@ -78,6 +87,11 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
|||||||
errs = append(errs, result.err)
|
errs = append(errs, result.err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if result.snapshot != nil {
|
||||||
|
for _, warning := range result.snapshot.Warnings {
|
||||||
|
errs = append(errs, errors.New(warning))
|
||||||
|
}
|
||||||
|
}
|
||||||
if result.snapshot == nil || len(result.snapshot.Devices) == 0 {
|
if result.snapshot == nil || len(result.snapshot.Devices) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -90,11 +104,8 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
|||||||
if item.CudaVersion != "" {
|
if item.CudaVersion != "" {
|
||||||
snapshot.Info.CudaVersion = item.CudaVersion
|
snapshot.Info.CudaVersion = item.CudaVersion
|
||||||
}
|
}
|
||||||
if item.DriverVersion != "" {
|
if item.Type == "xpu" {
|
||||||
snapshot.DriverVersions[item.Type] = item.DriverVersion
|
xpuVersion = item.DriverVersion
|
||||||
if item.Type == "xpu" {
|
|
||||||
xpuVersion = item.DriverVersion
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
snapshot.Warnings = append(snapshot.Warnings, errs...)
|
snapshot.Warnings = append(snapshot.Warnings, errs...)
|
||||||
@@ -102,6 +113,10 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
|||||||
return nil, fmt.Errorf("calling accelerator monitoring tools failed: %w", errors.Join(errs...))
|
return nil, fmt.Errorf("calling accelerator monitoring tools failed: %w", errors.Join(errs...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
snapshot.Info.CollectedAt = time.Now()
|
||||||
|
for _, err := range errs {
|
||||||
|
snapshot.Info.Warnings = append(snapshot.Info.Warnings, err.Error())
|
||||||
|
}
|
||||||
snapshot.Info.XPUDriverVersion = xpuVersion
|
snapshot.Info.XPUDriverVersion = xpuVersion
|
||||||
snapshot.Info.Type, snapshot.Info.DriverVersion = mergeProviderMetadata(active)
|
snapshot.Info.Type, snapshot.Info.DriverVersion = mergeProviderMetadata(active)
|
||||||
return snapshot, nil
|
return snapshot, nil
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package accelerator
|
package accelerator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -18,11 +19,11 @@ func metric(display, unit string) Metric {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
parsed, err := strconv.ParseFloat(matched[1], 64)
|
parsed, err := strconv.ParseFloat(matched[1], 64)
|
||||||
if err != nil {
|
if err != nil || math.IsNaN(parsed) || math.IsInf(parsed, 0) || (parsed < 0 && unit != "°C") {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
normalized, ok := convertMetricUnit(parsed, matched[2], unit)
|
normalized, ok := convertMetricUnit(parsed, matched[2], unit)
|
||||||
if !ok {
|
if !ok || math.IsNaN(normalized) || math.IsInf(normalized, 0) || (unit == "%" && normalized > 100) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
result.Value = &normalized
|
result.Value = &normalized
|
||||||
|
|||||||
@@ -12,26 +12,38 @@ import (
|
|||||||
|
|
||||||
func normalizeGPU(item *gpu.Device) Device {
|
func normalizeGPU(item *gpu.Device) Device {
|
||||||
metrics := Metrics{
|
metrics := Metrics{
|
||||||
Utilization: metric(item.GPUUtil, "%"),
|
MemoryActivity: metric(item.MemoryActivity, "%"),
|
||||||
Temperature: metric(item.Temperature, "°C"),
|
EncoderUtil: metric(item.EncoderUtil, "%"),
|
||||||
Power: metric(item.PowerDraw, "W"),
|
DecoderUtil: metric(item.DecoderUtil, "%"),
|
||||||
PowerLimit: metric(item.MaxPowerLimit, "W"),
|
JPEGUtil: metric(item.JPEGUtil, "%"),
|
||||||
MemoryUsed: memoryMetric(item.MemUsed),
|
OFAUtil: metric(item.OFAUtil, "%"),
|
||||||
MemoryTotal: memoryMetric(item.MemTotal),
|
MediaUtil: metric(item.MediaUtil, "%"),
|
||||||
FanSpeed: metric(item.FanSpeed, "%"),
|
HotspotTemperature: metric(item.HotspotTemperature, "°C"),
|
||||||
|
FanRPM: metric(item.FanRPM, "RPM"),
|
||||||
|
MediaFrequency: metric(item.MediaFrequency, "MHz"),
|
||||||
|
Utilization: metric(item.GPUUtil, "%"),
|
||||||
|
Temperature: metric(item.Temperature, "°C"),
|
||||||
|
MemoryTemperature: metric(item.MemoryTemperature, "°C"),
|
||||||
|
Power: metric(item.PowerDraw, "W"),
|
||||||
|
PowerLimit: metric(item.PowerLimit, "W"),
|
||||||
|
Frequency: metric(item.Frequency, "MHz"),
|
||||||
|
MemoryFrequency: metric(item.MemoryFrequency, "MHz"),
|
||||||
|
MemoryUsed: memoryMetric(item.MemUsed),
|
||||||
|
MemoryTotal: memoryMetric(item.MemTotal),
|
||||||
|
FanSpeed: metric(item.FanSpeed, "%"),
|
||||||
}
|
}
|
||||||
device := Device{
|
device := Device{
|
||||||
ID: stableID(item.Type, item.BusID, strconv.FormatUint(uint64(item.Index), 10)),
|
ProcessStatus: item.ProcessStatus,
|
||||||
Kind: KindGPU,
|
ID: stableID(item.Type, item.UUID, stableID("pci", item.BusID, strconv.FormatUint(uint64(item.Index), 10))),
|
||||||
Vendor: item.Type,
|
Kind: KindGPU,
|
||||||
Index: int(item.Index),
|
Vendor: item.Type,
|
||||||
Name: item.ProductName,
|
Index: int(item.Index),
|
||||||
Label: fmt.Sprintf("%d - %s", item.Index, item.ProductName),
|
Name: item.ProductName,
|
||||||
BusID: item.BusID,
|
Label: fmt.Sprintf("%d - %s", item.Index, item.ProductName),
|
||||||
Metrics: metrics,
|
BusID: item.BusID,
|
||||||
GPU: item,
|
Metrics: metrics,
|
||||||
|
GPU: item,
|
||||||
}
|
}
|
||||||
device.Capabilities = capabilities(metrics)
|
|
||||||
for _, process := range item.Processes {
|
for _, process := range item.Processes {
|
||||||
device.Processes = append(device.Processes, Process{
|
device.Processes = append(device.Processes, Process{
|
||||||
PID: process.PID,
|
PID: process.PID,
|
||||||
@@ -45,26 +57,36 @@ func normalizeGPU(item *gpu.Device) Device {
|
|||||||
|
|
||||||
func normalizeNPU(item *npu.Device) Device {
|
func normalizeNPU(item *npu.Device) Device {
|
||||||
metrics := Metrics{
|
metrics := Metrics{
|
||||||
Utilization: metric(item.AICore, "%"),
|
AICPUUtil: metric(item.AICPUUtil, "%"),
|
||||||
Temperature: metric(item.Temperature, "°C"),
|
CtrlCPUUtil: metric(item.CtrlCPUUtil, "%"),
|
||||||
Power: metric(item.PowerDraw, "W"),
|
DDRBandwidth: metric(item.DDRBandwidth, "%"),
|
||||||
MemoryUsed: memoryMetric(item.MemUsed),
|
HBMBandwidth: metric(item.HBMBandwidth, "%"),
|
||||||
MemoryTotal: memoryMetric(item.MemTotal),
|
DDRUsed: metric(item.MemoryUsed, "MiB"),
|
||||||
|
DDRTotal: metric(item.MemoryTotal, "MiB"),
|
||||||
|
HBMUsed: metric(item.HBMUsed, "MiB"),
|
||||||
|
HBMTotal: metric(item.HBMTotal, "MiB"),
|
||||||
|
HugepagesUsed: metric(item.HugepagesUsed, "pages"),
|
||||||
|
HugepagesTotal: metric(item.HugepagesTotal, "pages"),
|
||||||
|
Utilization: metric(item.AICore, "%"),
|
||||||
|
Temperature: metric(item.Temperature, "°C"),
|
||||||
|
Power: metric(item.PowerDraw, "W"),
|
||||||
|
MemoryUsed: memoryMetric(item.MemUsed),
|
||||||
|
MemoryTotal: memoryMetric(item.MemTotal),
|
||||||
}
|
}
|
||||||
device := Device{
|
device := Device{
|
||||||
ID: fmt.Sprintf("ascend:%d:%d", item.NPUIndex, item.ChipIndex),
|
ProcessStatus: item.ProcessStatus,
|
||||||
Kind: KindNPU,
|
ID: fmt.Sprintf("%s:%d", stableID("ascend", item.BusID, strconv.FormatUint(uint64(item.NPUIndex), 10)), item.ChipIndex),
|
||||||
Vendor: "ascend",
|
Kind: KindNPU,
|
||||||
Index: int(item.Index),
|
Vendor: "ascend",
|
||||||
NPUIndex: int(item.NPUIndex),
|
Index: int(item.Index),
|
||||||
ChipIndex: int(item.ChipIndex),
|
NPUIndex: int(item.NPUIndex),
|
||||||
Name: item.ProductName,
|
ChipIndex: int(item.ChipIndex),
|
||||||
Label: fmt.Sprintf("NPU %d / Chip %d - %s", item.NPUIndex, item.ChipIndex, item.ProductName),
|
Name: item.ProductName,
|
||||||
BusID: item.BusID,
|
Label: fmt.Sprintf("NPU %d / Chip %d - %s", item.NPUIndex, item.ChipIndex, item.ProductName),
|
||||||
Metrics: metrics,
|
BusID: item.BusID,
|
||||||
NPU: item,
|
Metrics: metrics,
|
||||||
|
NPU: item,
|
||||||
}
|
}
|
||||||
device.Capabilities = capabilities(metrics)
|
|
||||||
for _, process := range item.Processes {
|
for _, process := range item.Processes {
|
||||||
device.Processes = append(device.Processes, Process{
|
device.Processes = append(device.Processes, Process{
|
||||||
PID: process.PID,
|
PID: process.PID,
|
||||||
@@ -78,26 +100,32 @@ func normalizeNPU(item *npu.Device) Device {
|
|||||||
|
|
||||||
func normalizeXPU(item *xpu.Device) Device {
|
func normalizeXPU(item *xpu.Device) Device {
|
||||||
metrics := Metrics{
|
metrics := Metrics{
|
||||||
Utilization: metric(item.Stats.GPUUtil, "%"),
|
MediaUtil: metric(item.Stats.MediaUtil, "%"),
|
||||||
Temperature: metric(item.Stats.Temperature, "°C"),
|
ComputeUtil: metric(item.Stats.ComputeUtil, "%"),
|
||||||
Power: metric(item.Stats.Power, "W"),
|
CopyUtil: metric(item.Stats.CopyUtil, "%"),
|
||||||
MemoryUsed: memoryMetric(item.Stats.MemoryUsed),
|
MediaFrequency: metric(item.Stats.MediaFrequency, "MHz"),
|
||||||
MemoryTotal: memoryMetric(item.Basic.Memory),
|
MemoryTemperature: metric(item.Stats.MemoryTemperature, "°C"),
|
||||||
MemoryUtil: metric(item.Stats.MemoryUtil, "%"),
|
MemoryBandwidth: metric(item.Stats.MemoryBandwidthUtil, "%"),
|
||||||
Frequency: metric(item.Stats.Frequency, "MHz"),
|
Utilization: metric(item.Stats.GPUUtil, "%"),
|
||||||
|
Temperature: metric(item.Stats.Temperature, "°C"),
|
||||||
|
Power: metric(item.Stats.Power, "W"),
|
||||||
|
MemoryUsed: memoryMetric(item.Stats.MemoryUsed),
|
||||||
|
MemoryTotal: memoryMetric(item.Basic.Memory),
|
||||||
|
MemoryUtil: metric(item.Stats.MemoryUtil, "%"),
|
||||||
|
Frequency: metric(item.Stats.Frequency, "MHz"),
|
||||||
}
|
}
|
||||||
device := Device{
|
device := Device{
|
||||||
ID: stableID("xpu", item.Basic.PciBdfAddress, strconv.Itoa(item.Basic.DeviceID)),
|
ProcessStatus: item.ProcessStatus,
|
||||||
Kind: KindXPU,
|
ID: stableID("xpu", item.Basic.UUID, stableID("pci", item.Basic.PciBdfAddress, strconv.Itoa(item.Basic.DeviceID))),
|
||||||
Vendor: item.Basic.VendorName,
|
Kind: KindXPU,
|
||||||
Index: item.Basic.DeviceID,
|
Vendor: item.Basic.VendorName,
|
||||||
Name: item.Basic.DeviceName,
|
Index: item.Basic.DeviceID,
|
||||||
Label: fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName),
|
Name: item.Basic.DeviceName,
|
||||||
BusID: item.Basic.PciBdfAddress,
|
Label: fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName),
|
||||||
Metrics: metrics,
|
BusID: item.Basic.PciBdfAddress,
|
||||||
XPU: item,
|
Metrics: metrics,
|
||||||
|
XPU: item,
|
||||||
}
|
}
|
||||||
device.Capabilities = capabilities(metrics)
|
|
||||||
for _, process := range item.Processes {
|
for _, process := range item.Processes {
|
||||||
device.Processes = append(device.Processes, Process{
|
device.Processes = append(device.Processes, Process{
|
||||||
PID: strconv.Itoa(process.PID),
|
PID: strconv.Itoa(process.PID),
|
||||||
@@ -110,18 +138,6 @@ func normalizeXPU(item *xpu.Device) Device {
|
|||||||
return device
|
return device
|
||||||
}
|
}
|
||||||
|
|
||||||
func capabilities(metrics Metrics) Capabilities {
|
|
||||||
return Capabilities{
|
|
||||||
Utilization: metrics.Utilization.Available(),
|
|
||||||
Temperature: metrics.Temperature.Available(),
|
|
||||||
Power: metrics.Power.Available(),
|
|
||||||
PowerLimit: metrics.PowerLimit.Available(),
|
|
||||||
Memory: metrics.MemoryUsed.Available() || metrics.MemoryTotal.Available(),
|
|
||||||
FanSpeed: metrics.FanSpeed.Available(),
|
|
||||||
Frequency: metrics.Frequency.Available(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stableID(vendor, busID, fallback string) string {
|
func stableID(vendor, busID, fallback string) string {
|
||||||
if busID != "" && !strings.EqualFold(busID, "N/A") {
|
if busID != "" && !strings.EqualFold(busID, "N/A") {
|
||||||
return vendor + ":" + busID
|
return vendor + ":" + busID
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package accelerator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
|
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
|
||||||
@@ -26,6 +27,7 @@ func (p gpuProvider) Collect(ctx context.Context) (*ProviderSnapshot, error) {
|
|||||||
}
|
}
|
||||||
result := &ProviderSnapshot{
|
result := &ProviderSnapshot{
|
||||||
Type: info.Type,
|
Type: info.Type,
|
||||||
|
Warnings: info.Warnings,
|
||||||
DriverVersion: info.DriverVersion,
|
DriverVersion: info.DriverVersion,
|
||||||
CudaVersion: info.CudaVersion,
|
CudaVersion: info.CudaVersion,
|
||||||
GPUs: info.Devices,
|
GPUs: info.Devices,
|
||||||
@@ -49,6 +51,7 @@ func (p npuProvider) Collect(ctx context.Context) (*ProviderSnapshot, error) {
|
|||||||
}
|
}
|
||||||
result := &ProviderSnapshot{
|
result := &ProviderSnapshot{
|
||||||
Type: info.Type,
|
Type: info.Type,
|
||||||
|
Warnings: info.Warnings,
|
||||||
DriverVersion: info.DriverVersion,
|
DriverVersion: info.DriverVersion,
|
||||||
NPUs: info.Devices,
|
NPUs: info.Devices,
|
||||||
}
|
}
|
||||||
@@ -71,11 +74,24 @@ func (p xpuProvider) Collect(ctx context.Context) (*ProviderSnapshot, error) {
|
|||||||
}
|
}
|
||||||
result := &ProviderSnapshot{
|
result := &ProviderSnapshot{
|
||||||
Type: info.Type,
|
Type: info.Type,
|
||||||
|
Warnings: info.Warnings,
|
||||||
DriverVersion: info.DriverVersion,
|
DriverVersion: info.DriverVersion,
|
||||||
XPUs: info.Devices,
|
XPUs: info.Devices,
|
||||||
}
|
}
|
||||||
for index := range result.XPUs {
|
for index := range result.XPUs {
|
||||||
result.Devices = append(result.Devices, normalizeXPU(&result.XPUs[index]))
|
|
||||||
|
parent := normalizeXPU(&result.XPUs[index])
|
||||||
|
result.Devices = append(result.Devices, parent)
|
||||||
|
for _, tile := range result.XPUs[index].Tiles {
|
||||||
|
raw := xpu.Device{Basic: result.XPUs[index].Basic, Stats: tile.Stats, ProcessStatus: "unavailable"}
|
||||||
|
raw.Basic.Memory = ""
|
||||||
|
raw.Basic.FreeMemory = ""
|
||||||
|
device := normalizeXPU(&raw)
|
||||||
|
device.ParentID = parent.ID
|
||||||
|
device.ID = fmt.Sprintf("%s:tile:%d", parent.ID, tile.TileID)
|
||||||
|
device.Label = fmt.Sprintf("%s / Tile %d", parent.Label, tile.TileID)
|
||||||
|
result.Devices = append(result.Devices, device)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package accelerator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
|
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
|
||||||
@@ -17,6 +18,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Info struct {
|
type Info struct {
|
||||||
|
CollectedAt time.Time `json:"collectedAt"`
|
||||||
|
Warnings []string `json:"warnings"`
|
||||||
|
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
CudaVersion string `json:"cudaVersion"`
|
CudaVersion string `json:"cudaVersion"`
|
||||||
DriverVersion string `json:"driverVersion"`
|
DriverVersion string `json:"driverVersion"`
|
||||||
@@ -44,25 +48,40 @@ func (m Metric) ValueOrZero() float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Metrics struct {
|
type Metrics struct {
|
||||||
Utilization Metric
|
MemoryActivity Metric
|
||||||
Temperature Metric
|
EncoderUtil Metric
|
||||||
Power Metric
|
DecoderUtil Metric
|
||||||
PowerLimit Metric
|
JPEGUtil Metric
|
||||||
MemoryUsed Metric
|
OFAUtil Metric
|
||||||
MemoryTotal Metric
|
MediaUtil Metric
|
||||||
MemoryUtil Metric
|
ComputeUtil Metric
|
||||||
FanSpeed Metric
|
CopyUtil Metric
|
||||||
Frequency Metric
|
HotspotTemperature Metric
|
||||||
}
|
FanRPM Metric
|
||||||
|
AICPUUtil Metric
|
||||||
|
CtrlCPUUtil Metric
|
||||||
|
DDRUsed Metric
|
||||||
|
DDRTotal Metric
|
||||||
|
HBMUsed Metric
|
||||||
|
HBMTotal Metric
|
||||||
|
DDRBandwidth Metric
|
||||||
|
HBMBandwidth Metric
|
||||||
|
MemoryBandwidth Metric
|
||||||
|
MediaFrequency Metric
|
||||||
|
HugepagesUsed Metric
|
||||||
|
HugepagesTotal Metric
|
||||||
|
|
||||||
type Capabilities struct {
|
MemoryTemperature Metric
|
||||||
Utilization bool
|
Utilization Metric
|
||||||
Temperature bool
|
Temperature Metric
|
||||||
Power bool
|
Power Metric
|
||||||
PowerLimit bool
|
PowerLimit Metric
|
||||||
Memory bool
|
MemoryUsed Metric
|
||||||
FanSpeed bool
|
MemoryTotal Metric
|
||||||
Frequency bool
|
MemoryUtil Metric
|
||||||
|
FanSpeed Metric
|
||||||
|
Frequency Metric
|
||||||
|
MemoryFrequency Metric
|
||||||
}
|
}
|
||||||
|
|
||||||
type Process struct {
|
type Process struct {
|
||||||
@@ -74,18 +93,19 @@ type Process struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
ID string
|
ParentID string
|
||||||
Kind Kind
|
ProcessStatus string
|
||||||
Vendor string
|
ID string
|
||||||
Index int
|
Kind Kind
|
||||||
NPUIndex int
|
Vendor string
|
||||||
ChipIndex int
|
Index int
|
||||||
Name string
|
NPUIndex int
|
||||||
Label string
|
ChipIndex int
|
||||||
BusID string
|
Name string
|
||||||
Metrics Metrics
|
Label string
|
||||||
Capabilities Capabilities
|
BusID string
|
||||||
Processes []Process
|
Metrics Metrics
|
||||||
|
Processes []Process
|
||||||
|
|
||||||
GPU *gpu.Device `json:"-"`
|
GPU *gpu.Device `json:"-"`
|
||||||
NPU *npu.Device `json:"-"`
|
NPU *npu.Device `json:"-"`
|
||||||
@@ -93,10 +113,9 @@ type Device struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Snapshot struct {
|
type Snapshot struct {
|
||||||
Info Info
|
Info Info
|
||||||
Devices []Device
|
Devices []Device
|
||||||
DriverVersions map[string]string
|
Warnings []error
|
||||||
Warnings []error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Snapshot) Warning() error {
|
func (s Snapshot) Warning() error {
|
||||||
@@ -104,6 +123,7 @@ func (s Snapshot) Warning() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProviderSnapshot struct {
|
type ProviderSnapshot struct {
|
||||||
|
Warnings []string
|
||||||
Type string
|
Type string
|
||||||
DriverVersion string
|
DriverVersion string
|
||||||
CudaVersion string
|
CudaVersion string
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ package gpu
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,16 +33,22 @@ func findAMDSMI() (string, bool) {
|
|||||||
|
|
||||||
func (a amdSMI) LoadInfo(ctx context.Context) (*Info, error) {
|
func (a amdSMI) LoadInfo(ctx context.Context) (*Info, error) {
|
||||||
var (
|
var (
|
||||||
staticData string
|
staticData string
|
||||||
metricData string
|
metricData string
|
||||||
processData string
|
extendedData string
|
||||||
staticErr error
|
extendedErr error
|
||||||
metricErr error
|
processData string
|
||||||
processErr error
|
staticErr error
|
||||||
wg sync.WaitGroup
|
metricErr error
|
||||||
|
processErr error
|
||||||
|
wg sync.WaitGroup
|
||||||
)
|
)
|
||||||
|
|
||||||
wg.Add(3)
|
wg.Add(4)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
extendedData, extendedErr = runAMDSMI(ctx, a.command, "metric", "--clock", "--ecc", "--json")
|
||||||
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
staticData, staticErr = runAMDSMI(ctx, a.command, "static", "--asic", "--bus", "--driver", "--limit", "--json")
|
staticData, staticErr = runAMDSMI(ctx, a.command, "static", "--asic", "--bus", "--driver", "--limit", "--json")
|
||||||
@@ -64,16 +71,25 @@ func (a amdSMI) LoadInfo(ctx context.Context) (*Info, error) {
|
|||||||
return nil, fmt.Errorf("parsing %s static output failed: %w", a.command, err)
|
return nil, fmt.Errorf("parsing %s static output failed: %w", a.command, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if metricErr == nil {
|
||||||
|
metricErr = applyAMDMetrics(info, metricData)
|
||||||
|
}
|
||||||
if metricErr != nil {
|
if metricErr != nil {
|
||||||
global.LOG.Warnf("calling %s metric failed, metrics will be omitted: %v", a.command, metricErr)
|
info.Warnings = append(info.Warnings, fmt.Sprintf("%s metrics: %v", a.command, metricErr))
|
||||||
} else if err := applyAMDMetrics(info, metricData); err != nil {
|
}
|
||||||
global.LOG.Warnf("parsing %s metric output failed, metrics will be omitted: %v", a.command, err)
|
if extendedErr == nil {
|
||||||
|
extendedErr = applyAMDMetrics(info, extendedData)
|
||||||
|
}
|
||||||
|
if extendedErr != nil {
|
||||||
|
info.Warnings = append(info.Warnings, fmt.Sprintf("%s extended metrics: %v", a.command, extendedErr))
|
||||||
|
}
|
||||||
|
if processErr == nil {
|
||||||
|
processErr = applyAMDProcesses(info, processData)
|
||||||
}
|
}
|
||||||
if processErr != nil {
|
if processErr != nil {
|
||||||
global.LOG.Warnf("calling %s process failed, process information will be omitted: %v", a.command, processErr)
|
info.Warnings = append(info.Warnings, fmt.Sprintf("%s processes: %v", a.command, processErr))
|
||||||
} else if err := applyAMDProcesses(info, processData); err != nil {
|
|
||||||
global.LOG.Warnf("parsing %s process output failed, process information will be omitted: %v", a.command, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +112,9 @@ func parseAMDStatic(data string) (*Info, error) {
|
|||||||
}
|
}
|
||||||
device := Device{
|
device := Device{
|
||||||
Type: "amd",
|
Type: "amd",
|
||||||
|
UUID: amdStringAt(row, "uuid", "asic.uuid"),
|
||||||
|
DriverVersion: amdStringAt(row, "driver.version", "driver_version", "amdgpu_version"),
|
||||||
|
ProcessStatus: "unavailable",
|
||||||
Index: index,
|
Index: index,
|
||||||
ProductName: amdStringAt(row, "asic.market_name", "market_name", "gpu_name"),
|
ProductName: amdStringAt(row, "asic.market_name", "market_name", "gpu_name"),
|
||||||
PersistenceMode: "N/A",
|
PersistenceMode: "N/A",
|
||||||
@@ -111,6 +130,7 @@ func parseAMDStatic(data string) (*Info, error) {
|
|||||||
"limit.max_power_limit",
|
"limit.max_power_limit",
|
||||||
"limit.max_power",
|
"limit.max_power",
|
||||||
),
|
),
|
||||||
|
PowerLimit: amdMetricAt(row, "W", "limit.ppt0.socket_power_limit", "limit.socket_power_limit"),
|
||||||
MemUsed: "N/A",
|
MemUsed: "N/A",
|
||||||
MemTotal: "N/A",
|
MemTotal: "N/A",
|
||||||
GPUUtil: "N/A",
|
GPUUtil: "N/A",
|
||||||
@@ -150,11 +170,31 @@ func applyAMDMetrics(info *Info, data string) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
setAMDMetric(&device.GPUUtil, row, "%", "usage.gfx_activity", "usage.gfx", "gfx_activity", "gfx_usage")
|
setAMDMetric(&device.GPUUtil, row, "%", "usage.gfx_activity", "usage.gfx", "gfx_activity", "gfx_usage")
|
||||||
setAMDMetric(&device.Temperature, row, "°C", "temperature.hotspot", "temperature.edge", "hotspot_temperature", "gpu_temperature", "gpu_temp")
|
setAMDMetric(&device.Temperature, row, "°C", "temperature.edge", "gpu_temperature", "gpu_temp")
|
||||||
setAMDMetric(&device.PowerDraw, row, "W", "power.socket_power", "socket_power", "power_usage")
|
setAMDMetric(&device.PowerDraw, row, "W", "power.socket_power", "socket_power", "power_usage")
|
||||||
setAMDMetric(&device.MemUsed, row, "MB", "mem_usage.used_vram", "vram.used", "used_vram", "vram_used")
|
setAMDMetric(&device.MemUsed, row, "MB", "mem_usage.used_vram", "vram.used", "used_vram", "vram_used")
|
||||||
setAMDMetric(&device.MemTotal, row, "MB", "mem_usage.total_vram", "vram.total", "total_vram", "vram_total")
|
setAMDMetric(&device.MemTotal, row, "MB", "mem_usage.total_vram", "vram.total", "total_vram", "vram_total")
|
||||||
setAMDMetric(&device.FanSpeed, row, "%", "fan.speed", "fan_speed")
|
|
||||||
|
setAMDMetric(&device.FanSpeed, row, "%", "fan.usage")
|
||||||
|
if amdMetricAt(row, "%", "fan.usage") == "" {
|
||||||
|
speed, speedErr := strconv.ParseFloat(amdStringAt(row, "fan.speed"), 64)
|
||||||
|
maximum, maxErr := strconv.ParseFloat(amdStringAt(row, "fan.max"), 64)
|
||||||
|
if speedErr == nil && maxErr == nil && !math.IsNaN(speed) && !math.IsInf(speed, 0) && maximum > 0 && !math.IsInf(maximum, 0) && speed >= 0 && speed <= maximum {
|
||||||
|
device.FanSpeed = fmt.Sprintf("%.2f %%", speed/maximum*100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setAMDMetric(&device.FanRPM, row, "RPM", "fan.rpm")
|
||||||
|
setAMDMetric(&device.HotspotTemperature, row, "°C", "temperature.hotspot", "hotspot_temperature")
|
||||||
|
setAMDMetric(&device.MemoryTemperature, row, "°C", "temperature.mem")
|
||||||
|
setAMDMetric(&device.MemoryActivity, row, "%", "usage.umc_activity")
|
||||||
|
setAMDMetric(&device.MediaUtil, row, "%", "usage.mm_activity")
|
||||||
|
setAMDMetric(&device.Frequency, row, "MHz", "clock.gfx_0.clk")
|
||||||
|
setAMDMetric(&device.MemoryFrequency, row, "MHz", "clock.mem_0.clk")
|
||||||
|
correctable := amdStringAt(row, "ecc.total_correctable_count", "ecc.correctable_count")
|
||||||
|
uncorrectable := amdStringAt(row, "ecc.total_uncorrectable_count", "ecc.uncorrectable_count")
|
||||||
|
if correctable != "" || uncorrectable != "" {
|
||||||
|
device.ECCErrors = []ECCError{{Scope: "Total", Correctable: correctable, Uncorrectable: uncorrectable}}
|
||||||
|
}
|
||||||
if value := amdStringAt(row, "perf_level", "performance_level"); value != "" {
|
if value := amdStringAt(row, "perf_level", "performance_level"); value != "" {
|
||||||
device.PerformanceState = value
|
device.PerformanceState = value
|
||||||
}
|
}
|
||||||
@@ -177,6 +217,7 @@ func applyAMDProcesses(info *Info, data string) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
device.ProcessStatus = "ok"
|
||||||
processList, _ := amdValueAt(row, "process_list")
|
processList, _ := amdValueAt(row, "process_list")
|
||||||
items := amdObjectList(processList)
|
items := amdObjectList(processList)
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
|
|||||||
if result.info == nil {
|
if result.info == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
merged.Warnings = append(merged.Warnings, result.info.Warnings...)
|
||||||
if result.info.Type != "" {
|
if result.info.Type != "" {
|
||||||
types = append(types, result.info.Type)
|
types = append(types, result.info.Type)
|
||||||
}
|
}
|
||||||
@@ -86,6 +87,9 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
|
|||||||
if len(merged.Devices) == 0 && len(errs) > 0 {
|
if len(merged.Devices) == 0 && len(errs) > 0 {
|
||||||
return nil, fmt.Errorf("calling GPU monitoring tools failed: %w", errors.Join(errs...))
|
return nil, fmt.Errorf("calling GPU monitoring tools failed: %w", errors.Join(errs...))
|
||||||
}
|
}
|
||||||
|
for _, err := range errs {
|
||||||
|
merged.Warnings = append(merged.Warnings, err.Error())
|
||||||
|
}
|
||||||
return merged, nil
|
return merged, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
package gpu
|
package gpu
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,41 +20,10 @@ func (n nvidiaSMI) LoadInfo(ctx context.Context) (*Info, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("calling %s failed: %w", nvidiaSMICommand, err)
|
return nil, fmt.Errorf("calling %s failed: %w", nvidiaSMICommand, err)
|
||||||
}
|
}
|
||||||
data := []byte(itemData)
|
return parseNvidiaSMI([]byte(itemData))
|
||||||
version := "v11"
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(data)
|
|
||||||
decoder := xml.NewDecoder(buf)
|
|
||||||
for {
|
|
||||||
token, err := decoder.Token()
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("reading token failed: %w", err)
|
|
||||||
}
|
|
||||||
d, ok := token.(xml.Directive)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
directive := string(d)
|
|
||||||
if !strings.HasPrefix(directive, "DOCTYPE") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parts := strings.Split(directive, " ")
|
|
||||||
s := strings.Trim(parts[len(parts)-1], "\" ")
|
|
||||||
if strings.HasPrefix(s, "nvsmi_device_") && strings.HasSuffix(s, ".dtd") {
|
|
||||||
version = strings.TrimSuffix(strings.TrimPrefix(s, "nvsmi_device_"), ".dtd")
|
|
||||||
} else {
|
|
||||||
global.LOG.Debugf("Cannot find schema version in %q", directive)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseNvidiaSMI(data, version)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseNvidiaSMI(buf []byte, version string) (*Info, error) {
|
func parseNvidiaSMI(buf []byte) (*Info, error) {
|
||||||
var (
|
var (
|
||||||
s nvidiaSMIResponse
|
s nvidiaSMIResponse
|
||||||
info Info
|
info Info
|
||||||
@@ -72,42 +37,84 @@ func parseNvidiaSMI(buf []byte, version string) (*Info, error) {
|
|||||||
info.DriverVersion = s.DriverVersion
|
info.DriverVersion = s.DriverVersion
|
||||||
for i := range s.Gpu {
|
for i := range s.Gpu {
|
||||||
gpuItem := Device{
|
gpuItem := Device{
|
||||||
Type: "nvidia",
|
Type: "nvidia",
|
||||||
Index: uint(i),
|
MemoryActivity: s.Gpu[i].Utilization.MemoryUtil,
|
||||||
ProductName: s.Gpu[i].ProductName,
|
EncoderUtil: s.Gpu[i].Utilization.EncoderUtil,
|
||||||
PersistenceMode: s.Gpu[i].PersistenceMode,
|
DecoderUtil: s.Gpu[i].Utilization.DecoderUtil,
|
||||||
BusID: s.Gpu[i].ID,
|
JPEGUtil: s.Gpu[i].Utilization.JpegUtil,
|
||||||
DisplayActive: s.Gpu[i].DisplayActive,
|
OFAUtil: s.Gpu[i].Utilization.OfaUtil,
|
||||||
ECC: s.Gpu[i].EccErrors.Volatile.DramUncorrectable,
|
MediaFrequency: s.Gpu[i].Clocks.VideoClock,
|
||||||
FanSpeed: s.Gpu[i].FanSpeed,
|
UUID: s.Gpu[i].UUID,
|
||||||
Temperature: s.Gpu[i].Temperature.GpuTemp,
|
DriverVersion: s.DriverVersion,
|
||||||
PerformanceState: s.Gpu[i].PerformanceState,
|
Architecture: s.Gpu[i].ProductArchitecture,
|
||||||
MemUsed: s.Gpu[i].FbMemoryUsage.Used,
|
Frequency: s.Gpu[i].Clocks.GraphicsClock,
|
||||||
MemTotal: s.Gpu[i].FbMemoryUsage.Total,
|
MemoryFrequency: s.Gpu[i].Clocks.MemClock,
|
||||||
GPUUtil: s.Gpu[i].Utilization.GpuUtil,
|
MemoryTemperature: s.Gpu[i].Temperature.MemoryTemp,
|
||||||
ComputeMode: s.Gpu[i].ComputeMode,
|
MemoryFree: s.Gpu[i].FbMemoryUsage.Free,
|
||||||
MigMode: s.Gpu[i].MigMode.CurrentMig,
|
MemoryReserved: s.Gpu[i].FbMemoryUsage.Reserved,
|
||||||
}
|
PCIeGeneration: firstSMIValue(s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.DeviceCurrentLinkGen, s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.CurrentLinkGen),
|
||||||
if version == "v12" || version == "v13" {
|
PCIeMaxGeneration: firstSMIValue(s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.MaxDeviceLinkGen, s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.MaxLinkGen),
|
||||||
gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.PowerDraw
|
PCIeWidth: s.Gpu[i].Pci.PciGpuLinkInfo.LinkWidths.CurrentLinkWidth,
|
||||||
if gpuItem.PowerDraw == "" {
|
PCIeMaxWidth: s.Gpu[i].Pci.PciGpuLinkInfo.LinkWidths.MaxLinkWidth,
|
||||||
gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.InstantPowerDraw
|
ProcessStatus: "unavailable",
|
||||||
}
|
Index: uint(i),
|
||||||
gpuItem.MaxPowerLimit = s.Gpu[i].GpuPowerReadings.CurrentPowerLimit
|
ProductName: s.Gpu[i].ProductName,
|
||||||
} else {
|
PersistenceMode: s.Gpu[i].PersistenceMode,
|
||||||
gpuItem.PowerDraw = s.Gpu[i].PowerReadings.PowerDraw
|
BusID: s.Gpu[i].ID,
|
||||||
gpuItem.MaxPowerLimit = s.Gpu[i].PowerReadings.MaxPowerLimit
|
DisplayActive: s.Gpu[i].DisplayActive,
|
||||||
|
ECC: s.Gpu[i].EccMode.CurrentEcc,
|
||||||
|
FanSpeed: s.Gpu[i].FanSpeed,
|
||||||
|
Temperature: s.Gpu[i].Temperature.GpuTemp,
|
||||||
|
PerformanceState: s.Gpu[i].PerformanceState,
|
||||||
|
MemUsed: s.Gpu[i].FbMemoryUsage.Used,
|
||||||
|
MemTotal: s.Gpu[i].FbMemoryUsage.Total,
|
||||||
|
GPUUtil: s.Gpu[i].Utilization.GpuUtil,
|
||||||
|
ComputeMode: s.Gpu[i].ComputeMode,
|
||||||
|
MigMode: s.Gpu[i].MigMode.CurrentMig,
|
||||||
}
|
}
|
||||||
|
gpuItem.ECCPending = s.Gpu[i].EccMode.PendingEcc
|
||||||
|
gpuItem.ECCErrors = []ECCError{
|
||||||
|
{Scope: "Volatile Total", Correctable: s.Gpu[i].EccErrors.Volatile.SingleBit.Total, Uncorrectable: s.Gpu[i].EccErrors.Volatile.DoubleBit.Total},
|
||||||
|
{Scope: "Aggregate Total", Correctable: s.Gpu[i].EccErrors.Aggregate.SingleBit.Total, Uncorrectable: s.Gpu[i].EccErrors.Aggregate.DoubleBit.Total},
|
||||||
|
|
||||||
for _, process := range s.Gpu[i].Processes.ProcessInfo {
|
{Scope: "Volatile DRAM", Correctable: s.Gpu[i].EccErrors.Volatile.DramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Volatile.DramUncorrectable},
|
||||||
gpuItem.Processes = append(gpuItem.Processes, Process{
|
{Scope: "Volatile SRAM", Correctable: s.Gpu[i].EccErrors.Volatile.SramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Volatile.SramUncorrectable},
|
||||||
PID: process.Pid,
|
{Scope: "Aggregate DRAM", Correctable: s.Gpu[i].EccErrors.Aggregate.DramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Aggregate.DramUncorrectable},
|
||||||
Type: process.Type,
|
{Scope: "Aggregate SRAM", Correctable: s.Gpu[i].EccErrors.Aggregate.SramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Aggregate.SramUncorrectable},
|
||||||
ProcessName: process.ProcessName,
|
}
|
||||||
UsedMemory: process.UsedMemory,
|
gpuItem.PowerDraw = firstSMIValue(s.Gpu[i].GpuPowerReadings.PowerDraw, s.Gpu[i].GpuPowerReadings.InstantPowerDraw, s.Gpu[i].PowerReadings.PowerDraw)
|
||||||
})
|
gpuItem.PowerLimit = firstSMIValue(s.Gpu[i].GpuPowerReadings.CurrentPowerLimit, s.Gpu[i].PowerReadings.EnforcedPowerLimit, s.Gpu[i].PowerReadings.PowerLimit)
|
||||||
|
gpuItem.MaxPowerLimit = firstSMIValue(s.Gpu[i].GpuPowerReadings.MaxPowerLimit, s.Gpu[i].PowerReadings.MaxPowerLimit)
|
||||||
|
gpuItem.DefaultPowerLimit = firstSMIValue(s.Gpu[i].GpuPowerReadings.DefaultPowerLimit, s.Gpu[i].PowerReadings.DefaultPowerLimit)
|
||||||
|
for _, event := range append(s.Gpu[i].ClocksEventReasons.Reasons, s.Gpu[i].ClocksThrottleReasons.Reasons...) {
|
||||||
|
if strings.EqualFold(strings.TrimSpace(event.Value), "Active") {
|
||||||
|
gpuItem.ClockEvents = append(gpuItem.ClockEvents, strings.TrimPrefix(strings.TrimPrefix(event.XMLName.Local, "clocks_event_reason_"), "clocks_throttle_reason_"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.Gpu[i].Processes != nil && strings.TrimSpace(s.Gpu[i].Processes.Text) == "" {
|
||||||
|
gpuItem.ProcessStatus = "ok"
|
||||||
|
}
|
||||||
|
if s.Gpu[i].Processes != nil {
|
||||||
|
for _, process := range s.Gpu[i].Processes.ProcessInfo {
|
||||||
|
gpuItem.Processes = append(gpuItem.Processes, Process{
|
||||||
|
PID: process.Pid,
|
||||||
|
Type: process.Type,
|
||||||
|
ProcessName: process.ProcessName,
|
||||||
|
UsedMemory: process.UsedMemory,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
info.Devices = append(info.Devices, gpuItem)
|
info.Devices = append(info.Devices, gpuItem)
|
||||||
}
|
}
|
||||||
return &info, nil
|
return &info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func firstSMIValue(values ...string) string {
|
||||||
|
for _, value := range values {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if value != "" && !strings.EqualFold(value, "N/A") && !strings.EqualFold(value, "Not Supported") {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package gpu
|
package gpu
|
||||||
|
|
||||||
|
import "encoding/xml"
|
||||||
|
|
||||||
type nvidiaSMIResponse struct {
|
type nvidiaSMIResponse struct {
|
||||||
AttachedGpus string `xml:"attached_gpus"`
|
XMLName xml.Name `xml:"nvidia_smi_log"`
|
||||||
CudaVersion string `xml:"cuda_version"`
|
AttachedGpus string `xml:"attached_gpus"`
|
||||||
DriverVersion string `xml:"driver_version"`
|
CudaVersion string `xml:"cuda_version"`
|
||||||
|
DriverVersion string `xml:"driver_version"`
|
||||||
Gpu []struct {
|
Gpu []struct {
|
||||||
ID string `xml:"id,attr"`
|
ID string `xml:"id,attr"`
|
||||||
AccountedProcesses struct{} `xml:"accounted_processes"`
|
AccountedProcesses struct{} `xml:"accounted_processes"`
|
||||||
@@ -37,16 +40,17 @@ type nvidiaSMIResponse struct {
|
|||||||
VideoClock string `xml:"video_clock"`
|
VideoClock string `xml:"video_clock"`
|
||||||
} `xml:"clocks"`
|
} `xml:"clocks"`
|
||||||
ClocksEventReasons struct {
|
ClocksEventReasons struct {
|
||||||
ClocksEventReasonApplicationsClocksSetting string `xml:"clocks_event_reason_applications_clocks_setting"`
|
Reasons []struct {
|
||||||
ClocksEventReasonDisplayClocksSetting string `xml:"clocks_event_reason_display_clocks_setting"`
|
XMLName xml.Name
|
||||||
ClocksEventReasonGpuIdle string `xml:"clocks_event_reason_gpu_idle"`
|
Value string `xml:",chardata"`
|
||||||
ClocksEventReasonHwPowerBrakeSlowdown string `xml:"clocks_event_reason_hw_power_brake_slowdown"`
|
} `xml:",any"`
|
||||||
ClocksEventReasonHwSlowdown string `xml:"clocks_event_reason_hw_slowdown"`
|
|
||||||
ClocksEventReasonHwThermalSlowdown string `xml:"clocks_event_reason_hw_thermal_slowdown"`
|
|
||||||
ClocksEventReasonSwPowerCap string `xml:"clocks_event_reason_sw_power_cap"`
|
|
||||||
ClocksEventReasonSwThermalSlowdown string `xml:"clocks_event_reason_sw_thermal_slowdown"`
|
|
||||||
ClocksEventReasonSyncBoost string `xml:"clocks_event_reason_sync_boost"`
|
|
||||||
} `xml:"clocks_event_reasons"`
|
} `xml:"clocks_event_reasons"`
|
||||||
|
ClocksThrottleReasons struct {
|
||||||
|
Reasons []struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
} `xml:",any"`
|
||||||
|
} `xml:"clocks_throttle_reasons"`
|
||||||
ComputeMode string `xml:"compute_mode"`
|
ComputeMode string `xml:"compute_mode"`
|
||||||
DefaultApplicationsClocks struct {
|
DefaultApplicationsClocks struct {
|
||||||
GraphicsClock string `xml:"graphics_clock"`
|
GraphicsClock string `xml:"graphics_clock"`
|
||||||
@@ -63,12 +67,24 @@ type nvidiaSMIResponse struct {
|
|||||||
} `xml:"driver_model"`
|
} `xml:"driver_model"`
|
||||||
EccErrors struct {
|
EccErrors struct {
|
||||||
Aggregate struct {
|
Aggregate struct {
|
||||||
|
SingleBit struct {
|
||||||
|
Total string `xml:"total"`
|
||||||
|
} `xml:"single_bit"`
|
||||||
|
DoubleBit struct {
|
||||||
|
Total string `xml:"total"`
|
||||||
|
} `xml:"double_bit"`
|
||||||
DramCorrectable string `xml:"dram_correctable"`
|
DramCorrectable string `xml:"dram_correctable"`
|
||||||
DramUncorrectable string `xml:"dram_uncorrectable"`
|
DramUncorrectable string `xml:"dram_uncorrectable"`
|
||||||
SramCorrectable string `xml:"sram_correctable"`
|
SramCorrectable string `xml:"sram_correctable"`
|
||||||
SramUncorrectable string `xml:"sram_uncorrectable"`
|
SramUncorrectable string `xml:"sram_uncorrectable"`
|
||||||
} `xml:"aggregate"`
|
} `xml:"aggregate"`
|
||||||
Volatile struct {
|
Volatile struct {
|
||||||
|
SingleBit struct {
|
||||||
|
Total string `xml:"total"`
|
||||||
|
} `xml:"single_bit"`
|
||||||
|
DoubleBit struct {
|
||||||
|
Total string `xml:"total"`
|
||||||
|
} `xml:"double_bit"`
|
||||||
DramCorrectable string `xml:"dram_correctable"`
|
DramCorrectable string `xml:"dram_correctable"`
|
||||||
DramUncorrectable string `xml:"dram_uncorrectable"`
|
DramUncorrectable string `xml:"dram_uncorrectable"`
|
||||||
SramCorrectable string `xml:"sram_correctable"`
|
SramCorrectable string `xml:"sram_correctable"`
|
||||||
@@ -226,7 +242,8 @@ type nvidiaSMIResponse struct {
|
|||||||
MinPowerLimit string `xml:"min_power_limit"`
|
MinPowerLimit string `xml:"min_power_limit"`
|
||||||
MaxPowerLimit string `xml:"max_power_limit"`
|
MaxPowerLimit string `xml:"max_power_limit"`
|
||||||
} `xml:"power_readings"`
|
} `xml:"power_readings"`
|
||||||
Processes struct {
|
Processes *struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
ProcessInfo []struct {
|
ProcessInfo []struct {
|
||||||
Pid string `xml:"pid"`
|
Pid string `xml:"pid"`
|
||||||
Type string `xml:"type"`
|
Type string `xml:"type"`
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package gpu
|
package gpu
|
||||||
|
|
||||||
type Info struct {
|
type Info struct {
|
||||||
|
Warnings []string `json:"warnings"`
|
||||||
|
|
||||||
CudaVersion string `json:"cudaVersion"`
|
CudaVersion string `json:"cudaVersion"`
|
||||||
DriverVersion string `json:"driverVersion"`
|
DriverVersion string `json:"driverVersion"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@@ -8,7 +10,42 @@ type Info struct {
|
|||||||
Devices []Device `json:"gpu"`
|
Devices []Device `json:"gpu"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ECCError struct {
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
Correctable string `json:"correctable"`
|
||||||
|
Uncorrectable string `json:"uncorrectable"`
|
||||||
|
}
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
|
ECCPending string `json:"eccPending"`
|
||||||
|
ECCErrors []ECCError `json:"eccErrors"`
|
||||||
|
MemoryActivity string `json:"memoryActivity"`
|
||||||
|
EncoderUtil string `json:"encoderUtil"`
|
||||||
|
DecoderUtil string `json:"decoderUtil"`
|
||||||
|
JPEGUtil string `json:"jpegUtil"`
|
||||||
|
OFAUtil string `json:"ofaUtil"`
|
||||||
|
MediaUtil string `json:"mediaUtil"`
|
||||||
|
HotspotTemperature string `json:"hotspotTemperature"`
|
||||||
|
FanRPM string `json:"fanRPM"`
|
||||||
|
MediaFrequency string `json:"mediaFrequency"`
|
||||||
|
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
DriverVersion string `json:"driverVersion"`
|
||||||
|
Architecture string `json:"architecture"`
|
||||||
|
Frequency string `json:"frequency"`
|
||||||
|
MemoryFrequency string `json:"memoryFrequency"`
|
||||||
|
MemoryTemperature string `json:"memoryTemperature"`
|
||||||
|
MemoryFree string `json:"memoryFree"`
|
||||||
|
MemoryReserved string `json:"memoryReserved"`
|
||||||
|
PowerLimit string `json:"powerLimit"`
|
||||||
|
DefaultPowerLimit string `json:"defaultPowerLimit"`
|
||||||
|
PCIeGeneration string `json:"pcieGeneration"`
|
||||||
|
PCIeMaxGeneration string `json:"pcieMaxGeneration"`
|
||||||
|
PCIeWidth string `json:"pcieWidth"`
|
||||||
|
PCIeMaxWidth string `json:"pcieMaxWidth"`
|
||||||
|
ProcessStatus string `json:"processStatus"`
|
||||||
|
ClockEvents []string `json:"clockEvents"`
|
||||||
|
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Index uint `json:"index"`
|
Index uint `json:"index"`
|
||||||
ProductName string `json:"productName"`
|
ProductName string `json:"productName"`
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||||
@@ -34,7 +35,31 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("calling %s failed: %w", ascendSMICommand, err)
|
return nil, fmt.Errorf("calling %s failed: %w", ascendSMICommand, err)
|
||||||
}
|
}
|
||||||
return parseAscendSMI(itemData), nil
|
|
||||||
|
info := parseAscendSMI(itemData)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
warnings := make([]string, len(info.Devices))
|
||||||
|
for i := range info.Devices {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(index int) {
|
||||||
|
defer wg.Done()
|
||||||
|
device := &info.Devices[index]
|
||||||
|
mgr := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(5*time.Second))
|
||||||
|
data, err := mgr.RunWithStdout(ascendSMICommand, "info", "-t", "usages", "-i", strconv.FormatUint(uint64(device.NPUIndex), 10), "-c", strconv.FormatUint(uint64(device.ChipIndex), 10))
|
||||||
|
if err != nil {
|
||||||
|
warnings[index] = fmt.Sprintf("npu-smi usages %d/%d: %v", device.NPUIndex, device.ChipIndex, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
applyAscendUsages(device, data)
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
for _, warning := range warnings {
|
||||||
|
if warning != "" {
|
||||||
|
info.Warnings = append(info.Warnings, warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAscendSMI(data string) *Info {
|
func parseAscendSMI(data string) *Info {
|
||||||
@@ -158,6 +183,12 @@ func parseAscendSMI(data string) *Info {
|
|||||||
pending = nil
|
pending = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := range info.Devices {
|
||||||
|
info.Devices[i].ProcessStatus = "unavailable"
|
||||||
|
if processSection {
|
||||||
|
info.Devices[i].ProcessStatus = "ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,6 +239,9 @@ func ascendMemoryPools(value, header string) (string, string, string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hbm := usage[len(usage)-1]
|
hbm := usage[len(usage)-1]
|
||||||
|
if !strings.Contains(normalizedHeader, "MEMORYUSAGE") {
|
||||||
|
memoryUsed, memoryTotal = "", ""
|
||||||
|
}
|
||||||
return memoryUsed, memoryTotal, hbm[0] + " MB", hbm[1] + " MB"
|
return memoryUsed, memoryTotal, hbm[0] + " MB", hbm[1] + " MB"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,3 +267,29 @@ func ascendValueWithUnit(value, unit string) string {
|
|||||||
}
|
}
|
||||||
return value + " " + unit
|
return value + " " + unit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyAscendUsages(device *Device, data string) {
|
||||||
|
for _, line := range strings.Split(data, "\n") {
|
||||||
|
key, value, ok := strings.Cut(line, ":")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key = strings.ToLower(strings.Join(strings.Fields(key), ""))
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
switch key {
|
||||||
|
case "aicoreusagerate(%)":
|
||||||
|
usage, err := strconv.ParseFloat(strings.TrimSpace(strings.TrimSuffix(value, "%")), 64)
|
||||||
|
if err == nil && usage >= 0 && usage <= 100 {
|
||||||
|
device.AICore = ascendValueWithUnit(value, "%")
|
||||||
|
}
|
||||||
|
case "aicpuusagerate(%)":
|
||||||
|
device.AICPUUtil = ascendValueWithUnit(value, "%")
|
||||||
|
case "ctrlcpuusagerate(%)":
|
||||||
|
device.CtrlCPUUtil = ascendValueWithUnit(value, "%")
|
||||||
|
case "ddrbandwidthusagerate(%)", "memorybandwidthusagerate(%)":
|
||||||
|
device.DDRBandwidth = ascendValueWithUnit(value, "%")
|
||||||
|
case "hbmbandwidthusagerate(%)":
|
||||||
|
device.HBMBandwidth = ascendValueWithUnit(value, "%")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
package npu
|
package npu
|
||||||
|
|
||||||
type Info struct {
|
type Info struct {
|
||||||
|
Warnings []string `json:"warnings"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
DriverVersion string `json:"driverVersion"`
|
DriverVersion string `json:"driverVersion"`
|
||||||
Devices []Device `json:"npu"`
|
Devices []Device `json:"npu"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
|
AICPUUtil string `json:"aiCPUUtil"`
|
||||||
|
CtrlCPUUtil string `json:"ctrlCPUUtil"`
|
||||||
|
DDRBandwidth string `json:"ddrBandwidth"`
|
||||||
|
HBMBandwidth string `json:"hbmBandwidth"`
|
||||||
|
|
||||||
|
ProcessStatus string `json:"processStatus"`
|
||||||
|
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Index uint `json:"index"`
|
Index uint `json:"index"`
|
||||||
NPUIndex uint `json:"npuIndex"`
|
NPUIndex uint `json:"npuIndex"`
|
||||||
|
|||||||
@@ -33,16 +33,29 @@ type discoveryInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DeviceLevelMetric struct {
|
type DeviceLevelMetric struct {
|
||||||
MetricsType string `json:"metrics_type"`
|
MetricsType string `json:"metrics_type"`
|
||||||
Value float64 `json:"value"`
|
Value *float64 `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TileMetrics struct {
|
||||||
|
TileID int `json:"tile_id"`
|
||||||
|
DataList []DeviceLevelMetric `json:"data_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TileStats struct {
|
||||||
|
TileID int `json:"tileID"`
|
||||||
|
Stats Stats `json:"stats"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeviceStats struct {
|
type DeviceStats struct {
|
||||||
|
TileLevel []TileMetrics `json:"tile_level"`
|
||||||
DeviceID int `json:"device_id"`
|
DeviceID int `json:"device_id"`
|
||||||
DeviceLevel []DeviceLevelMetric `json:"device_level"`
|
DeviceLevel []DeviceLevelMetric `json:"device_level"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Info struct {
|
type Info struct {
|
||||||
|
Warnings []string `json:"warnings"`
|
||||||
|
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
DriverVersion string `json:"driverVersion"`
|
DriverVersion string `json:"driverVersion"`
|
||||||
|
|
||||||
@@ -50,12 +63,17 @@ type Info struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
|
Tiles []TileStats `json:"tiles"`
|
||||||
|
ProcessStatus string `json:"processStatus"`
|
||||||
|
|
||||||
Basic Basic `json:"basic"`
|
Basic Basic `json:"basic"`
|
||||||
Stats Stats `json:"stats"`
|
Stats Stats `json:"stats"`
|
||||||
Processes []Process `json:"processes"`
|
Processes []Process `json:"processes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Basic struct {
|
type Basic struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
|
||||||
DeviceID int `json:"deviceID"`
|
DeviceID int `json:"deviceID"`
|
||||||
DeviceName string `json:"deviceName"`
|
DeviceName string `json:"deviceName"`
|
||||||
VendorName string `json:"vendorName"`
|
VendorName string `json:"vendorName"`
|
||||||
@@ -66,6 +84,14 @@ type Basic struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
|
MediaUtil string `json:"mediaUtil"`
|
||||||
|
ComputeUtil string `json:"computeUtil"`
|
||||||
|
CopyUtil string `json:"copyUtil"`
|
||||||
|
MediaFrequency string `json:"mediaFrequency"`
|
||||||
|
MemoryTemperature string `json:"memoryTemperature"`
|
||||||
|
|
||||||
|
MemoryBandwidthUtil string `json:"memoryBandwidthUtil"`
|
||||||
|
|
||||||
Power string `json:"power"`
|
Power string `json:"power"`
|
||||||
GPUUtil string `json:"gpuUtil"`
|
GPUUtil string `json:"gpuUtil"`
|
||||||
Frequency string `json:"frequency"`
|
Frequency string `json:"frequency"`
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -51,12 +52,15 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
|
|||||||
|
|
||||||
processData, err := cmdMgr.RunWithStdout(xpuSMICommand, "ps", "-j")
|
processData, err := cmdMgr.RunWithStdout(xpuSMICommand, "ps", "-j")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.LOG.Warnf("calling xpu-smi ps failed, process information will be omitted: %v", err)
|
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi ps: %v", err))
|
||||||
} else {
|
} else {
|
||||||
var psList DeviceUtilByProcList
|
var psList DeviceUtilByProcList
|
||||||
if err := json.Unmarshal([]byte(processData), &psList); err != nil {
|
if err := json.Unmarshal([]byte(processData), &psList); err != nil {
|
||||||
global.LOG.Warnf("processData json unmarshal failed, process information will be omitted: %v", err)
|
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi ps output: %v", err))
|
||||||
} else {
|
} else {
|
||||||
|
for i := range res.Devices {
|
||||||
|
res.Devices[i].ProcessStatus = "ok"
|
||||||
|
}
|
||||||
for _, ps := range psList.DeviceUtilByProcList {
|
for _, ps := range psList.DeviceUtilByProcList {
|
||||||
process := Process{
|
process := Process{
|
||||||
PID: ps.ProcessID,
|
PID: ps.ProcessID,
|
||||||
@@ -87,7 +91,9 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
xpu := Device{
|
xpu := Device{
|
||||||
|
ProcessStatus: "unavailable",
|
||||||
Basic: Basic{
|
Basic: Basic{
|
||||||
|
UUID: device.UUID,
|
||||||
DeviceID: device.DeviceID,
|
DeviceID: device.DeviceID,
|
||||||
DeviceName: device.DeviceName,
|
DeviceName: device.DeviceName,
|
||||||
VendorName: device.VendorName,
|
VendorName: device.VendorName,
|
||||||
@@ -115,13 +121,19 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
|
|||||||
wgCmd.Wait()
|
wgCmd.Wait()
|
||||||
|
|
||||||
if xpuErr != nil {
|
if xpuErr != nil {
|
||||||
global.LOG.Errorf("calling xpu-smi discovery failed for device %d, %v", device.DeviceID, xpuErr)
|
mu.Lock()
|
||||||
|
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi device %d: %v", device.DeviceID, xpuErr))
|
||||||
|
res.Devices = append(res.Devices, xpu)
|
||||||
|
mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var info discoveryDevice
|
var info discoveryDevice
|
||||||
if err := json.Unmarshal([]byte(xpuData), &info); err != nil {
|
if err := json.Unmarshal([]byte(xpuData), &info); err != nil {
|
||||||
global.LOG.Errorf("xpuData json unmarshal failed for device %d, err: %v", device.DeviceID, err)
|
mu.Lock()
|
||||||
|
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi device %d output: %v", device.DeviceID, err))
|
||||||
|
res.Devices = append(res.Devices, xpu)
|
||||||
|
mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,13 +154,22 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
|
|||||||
}
|
}
|
||||||
|
|
||||||
if statsErr != nil {
|
if statsErr != nil {
|
||||||
global.LOG.Warnf("calling xpu-smi stats failed for device %d, metrics will be omitted: %v", device.DeviceID, statsErr)
|
mu.Lock()
|
||||||
|
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi stats %d: %v", device.DeviceID, statsErr))
|
||||||
|
mu.Unlock()
|
||||||
} else {
|
} else {
|
||||||
var stats DeviceStats
|
var stats DeviceStats
|
||||||
if err := json.Unmarshal([]byte(statsData), &stats); err != nil {
|
if err := json.Unmarshal([]byte(statsData), &stats); err != nil {
|
||||||
global.LOG.Warnf("statsData json unmarshal failed for device %d, metrics will be omitted: %v", device.DeviceID, err)
|
mu.Lock()
|
||||||
|
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi stats %d output: %v", device.DeviceID, err))
|
||||||
|
mu.Unlock()
|
||||||
} else {
|
} else {
|
||||||
loadStats(&xpu.Stats, stats.DeviceLevel)
|
loadStats(&xpu.Stats, stats.DeviceLevel)
|
||||||
|
for _, tile := range stats.TileLevel {
|
||||||
|
item := TileStats{TileID: tile.TileID}
|
||||||
|
loadStats(&item.Stats, tile.DataList)
|
||||||
|
xpu.Tiles = append(xpu.Tiles, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,19 +183,38 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
|
|||||||
|
|
||||||
func loadStats(stats *Stats, metrics []DeviceLevelMetric) {
|
func loadStats(stats *Stats, metrics []DeviceLevelMetric) {
|
||||||
for _, stat := range metrics {
|
for _, stat := range metrics {
|
||||||
|
if stat.Value == nil || math.IsNaN(*stat.Value) || math.IsInf(*stat.Value, 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value := *stat.Value
|
||||||
|
if value < 0 && stat.MetricsType != "XPUM_STATS_GPU_CORE_TEMPERATURE" && stat.MetricsType != "XPUM_STATS_MEMORY_TEMPERATURE" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
switch stat.MetricsType {
|
switch stat.MetricsType {
|
||||||
|
case "XPUM_STATS_MEMORY_TEMPERATURE":
|
||||||
|
stats.MemoryTemperature = fmt.Sprintf("%.1f°C", value)
|
||||||
|
case "XPUM_STATS_ENGINE_GROUP_COMPUTE_ALL_UTILIZATION":
|
||||||
|
stats.ComputeUtil = fmt.Sprintf("%.1f%%", value)
|
||||||
|
case "XPUM_STATS_ENGINE_GROUP_MEDIA_ALL_UTILIZATION":
|
||||||
|
stats.MediaUtil = fmt.Sprintf("%.1f%%", value)
|
||||||
|
case "XPUM_STATS_ENGINE_GROUP_COPY_ALL_UTILIZATION":
|
||||||
|
stats.CopyUtil = fmt.Sprintf("%.1f%%", value)
|
||||||
|
case "XPUM_STATS_MEDIA_ENGINE_FREQUENCY":
|
||||||
|
stats.MediaFrequency = fmt.Sprintf("%.1fMHz", value)
|
||||||
case "XPUM_STATS_POWER":
|
case "XPUM_STATS_POWER":
|
||||||
stats.Power = fmt.Sprintf("%.1fW", stat.Value)
|
stats.Power = fmt.Sprintf("%.1fW", value)
|
||||||
case "XPUM_STATS_GPU_UTILIZATION":
|
case "XPUM_STATS_GPU_UTILIZATION":
|
||||||
stats.GPUUtil = fmt.Sprintf("%.1f%%", stat.Value)
|
stats.GPUUtil = fmt.Sprintf("%.1f%%", value)
|
||||||
case "XPUM_STATS_GPU_FREQUENCY":
|
case "XPUM_STATS_GPU_FREQUENCY":
|
||||||
stats.Frequency = fmt.Sprintf("%.1fMHz", stat.Value)
|
stats.Frequency = fmt.Sprintf("%.1fMHz", value)
|
||||||
case "XPUM_STATS_GPU_CORE_TEMPERATURE":
|
case "XPUM_STATS_GPU_CORE_TEMPERATURE":
|
||||||
stats.Temperature = fmt.Sprintf("%.1f°C", stat.Value)
|
stats.Temperature = fmt.Sprintf("%.1f°C", value)
|
||||||
case "XPUM_STATS_MEMORY_USED":
|
case "XPUM_STATS_MEMORY_USED":
|
||||||
stats.MemoryUsed = fmt.Sprintf("%.1f MiB", stat.Value)
|
stats.MemoryUsed = fmt.Sprintf("%.1f MiB", value)
|
||||||
case "XPUM_STATS_MEMORY_UTILIZATION", "XPUM_STATS_MEMORY_BANDWIDTH", "XPUM_STATS_MEMORY_BANDWIDTH_UTILIZATION":
|
case "XPUM_STATS_MEMORY_UTILIZATION":
|
||||||
stats.MemoryUtil = fmt.Sprintf("%.1f%%", stat.Value)
|
stats.MemoryUtil = fmt.Sprintf("%.1f%%", value)
|
||||||
|
case "XPUM_STATS_MEMORY_BANDWIDTH", "XPUM_STATS_MEMORY_BANDWIDTH_UTILIZATION":
|
||||||
|
stats.MemoryBandwidthUtil = fmt.Sprintf("%.1f%%", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ import (
|
|||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cronJobAlertTypes = []string{"shell", "app", "website", "database", "directory", "log", "snapshot", "curl", "cutWebsiteLog", "clean", "ntp"}
|
var cronJobAlertTypes = []string{"shell", "app", "website", "database", "directory", "log", "snapshot", "curl", "cutWebsiteLog", "clean", "ntp", "syncIpGroup", "cleanLog"}
|
||||||
|
|
||||||
func CreateTaskScanEmailAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo, emailConfig model.AlertConfig) error {
|
func CreateTaskScanEmailAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo, emailConfig model.AlertConfig) error {
|
||||||
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
|
params := CreateTaskAlertParams(pushAlert)
|
||||||
alertDetail := ProcessAlertDetail(alert, pushAlert.TaskName, params, method)
|
alertDetail := ProcessAlertDetail(alert, pushAlert.TaskName, params, method)
|
||||||
alertRule := ProcessAlertRule(alert)
|
alertRule := ProcessAlertRule(alert)
|
||||||
create.AlertRule = alertRule
|
create.AlertRule = alertRule
|
||||||
@@ -76,14 +76,14 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
|
|||||||
Encryption: emailInfo.Encryption,
|
Encryption: emailInfo.Encryption,
|
||||||
Recipient: emailInfo.Recipient,
|
Recipient: emailInfo.Recipient,
|
||||||
}
|
}
|
||||||
content := GetSendContent(alert.Type, params, agentInfo)
|
content := GetAlertLogContent(create, alert, params, agentInfo)
|
||||||
if content == "" {
|
if content == "" {
|
||||||
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
|
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
|
||||||
}
|
}
|
||||||
msg := email.EmailMessage{
|
msg := email.EmailMessage{
|
||||||
Subject: i18n.GetMsgByKey("PanelAlertTitle"),
|
Subject: i18n.GetMsgByKey("PanelAlertTitle"),
|
||||||
Body: content,
|
Body: content,
|
||||||
IsHTML: true,
|
IsHTML: GetCronJobType(alert.Type) != "cronJob",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = email.SendMail(smtpConfig, msg, transport); err != nil {
|
if err = email.SendMail(smtpConfig, msg, transport); err != nil {
|
||||||
@@ -110,7 +110,7 @@ func CreateBarkAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []
|
|||||||
return SaveAlertLog(create, &alertLog)
|
return SaveAlertLog(create, &alertLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
content := GetSendContent(alert.Type, params, agentInfo)
|
content := GetAlertLogContent(create, alert, params, agentInfo)
|
||||||
if content == "" {
|
if content == "" {
|
||||||
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
|
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
|
||||||
}
|
}
|
||||||
@@ -268,7 +268,7 @@ func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param,
|
|||||||
alertDetail := dto.AlertDetail{
|
alertDetail := dto.AlertDetail{
|
||||||
Type: GetCronJobType(alert.Type),
|
Type: GetCronJobType(alert.Type),
|
||||||
SubType: alert.Type,
|
SubType: alert.Type,
|
||||||
Title: alert.Title,
|
Title: TaskAlertTitle(alert.Type, alert.Title, project, params),
|
||||||
Method: method,
|
Method: method,
|
||||||
Project: project,
|
Project: project,
|
||||||
Params: params,
|
Params: params,
|
||||||
@@ -281,6 +281,14 @@ func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param,
|
|||||||
return string(marshal)
|
return string(marshal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TaskAlertTitle(alertType, title, project string, params []dto.Param) string {
|
||||||
|
if GetCronJobType(alertType) != "cronJob" || CronJobAlertResultFromParams(params) != CronJobAlertSuccess {
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
name := cronJobTaskName(project, params)
|
||||||
|
return i18n.GetMsgWithMap("TaskSuccess", map[string]interface{}{"name": name})
|
||||||
|
}
|
||||||
|
|
||||||
func ProcessAlertRule(alert dto.AlertDTO) string {
|
func ProcessAlertRule(alert dto.AlertDTO) string {
|
||||||
marshal, err := json.Marshal(alert)
|
marshal, err := json.Marshal(alert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -324,6 +332,10 @@ func GetCronJobTypeName(cronJobType string) string {
|
|||||||
module = "系统快照"
|
module = "系统快照"
|
||||||
case "ntp":
|
case "ntp":
|
||||||
module = "同步服务器时间"
|
module = "同步服务器时间"
|
||||||
|
case "syncIpGroup":
|
||||||
|
module = "同步 IP 组"
|
||||||
|
case "cleanLog":
|
||||||
|
module = "清理日志"
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
return module
|
return module
|
||||||
@@ -444,6 +456,30 @@ func isWithinTimeRange(savedTimeString string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetSendContent(alertType string, params []dto.Param, agentInfo *dto.AgentInfo) string {
|
func GetSendContent(alertType string, params []dto.Param, agentInfo *dto.AgentInfo) string {
|
||||||
|
return GetAlertDetailContent(dto.AlertDetail{Type: alertType, Params: params}, agentInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAlertLogContent(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, agentInfo *dto.AgentInfo) string {
|
||||||
|
detail := dto.AlertDetail{Type: alert.Type, Params: params}
|
||||||
|
var stored dto.AlertDetail
|
||||||
|
if json.Unmarshal([]byte(create.AlertDetail), &stored) == nil {
|
||||||
|
detail = stored
|
||||||
|
if detail.Type == "" {
|
||||||
|
detail.Type = alert.Type
|
||||||
|
}
|
||||||
|
if detail.Params == nil {
|
||||||
|
detail.Params = params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return GetAlertDetailContent(detail, agentInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAlertDetailContent(detail dto.AlertDetail, agentInfo *dto.AgentInfo) string {
|
||||||
|
alertType := detail.SubType
|
||||||
|
if alertType == "" {
|
||||||
|
alertType = detail.Type
|
||||||
|
}
|
||||||
|
params := detail.Params
|
||||||
switch GetCronJobType(alertType) {
|
switch GetCronJobType(alertType) {
|
||||||
case "ssl":
|
case "ssl":
|
||||||
return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||||
@@ -464,7 +500,12 @@ func GetSendContent(alertType string, params []dto.Param, agentInfo *dto.AgentIn
|
|||||||
case "disk":
|
case "disk":
|
||||||
return i18n.GetMsgWithMap("DiskUsedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "used": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
return i18n.GetMsgWithMap("DiskUsedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "used": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||||
case "cronJob":
|
case "cronJob":
|
||||||
return i18n.GetMsgWithMap("CronJobFailedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
messageKey := "CronJobFailedAlert"
|
||||||
|
if CronJobAlertResultFromParams(params) == CronJobAlertSuccess {
|
||||||
|
messageKey = "CronJobSuccessAlert"
|
||||||
|
}
|
||||||
|
name := cronJobTaskName(detail.Project, params)
|
||||||
|
return i18n.GetMsgWithMap(messageKey, map[string]interface{}{"name": name, "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||||
case "clams":
|
case "clams":
|
||||||
return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||||
case "panelLogin":
|
case "panelLogin":
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package alert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CronJobAlertFailed = "failed"
|
||||||
|
CronJobAlertSuccess = "success"
|
||||||
|
CronJobAlertBoth = "both"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cronJobAlertParams(advanced string) (map[string]json.RawMessage, error) {
|
||||||
|
params := make(map[string]json.RawMessage)
|
||||||
|
if strings.TrimSpace(advanced) != "" {
|
||||||
|
if err := json.Unmarshal([]byte(advanced), ¶ms); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid cronjob alert advanced parameters: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if params == nil {
|
||||||
|
params = make(map[string]json.RawMessage)
|
||||||
|
}
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CronJobAlertTriggerMode(advanced string) (string, error) {
|
||||||
|
params, err := cronJobAlertParams(advanced)
|
||||||
|
if err != nil {
|
||||||
|
return CronJobAlertFailed, err
|
||||||
|
}
|
||||||
|
mode := CronJobAlertFailed
|
||||||
|
if raw, ok := params["alertTriggerMode"]; ok {
|
||||||
|
if err := json.Unmarshal(raw, &mode); err != nil {
|
||||||
|
return CronJobAlertFailed, fmt.Errorf("invalid cronjob alert trigger mode: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch mode {
|
||||||
|
case CronJobAlertFailed, CronJobAlertSuccess, CronJobAlertBoth:
|
||||||
|
return mode, nil
|
||||||
|
default:
|
||||||
|
return CronJobAlertFailed, fmt.Errorf("invalid cronjob alert trigger mode %q", mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeCronJobAlertParams(previous, incoming string) (string, error) {
|
||||||
|
params, err := cronJobAlertParams(previous)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
updates, err := cronJobAlertParams(incoming)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for key, value := range updates {
|
||||||
|
params[key] = value
|
||||||
|
}
|
||||||
|
if _, ok := params["alertTriggerMode"]; !ok {
|
||||||
|
params["alertTriggerMode"] = json.RawMessage(`"failed"`)
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(params)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if _, err := CronJobAlertTriggerMode(string(data)); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MatchCronJobAlertResult(advanced, result string) bool {
|
||||||
|
mode, err := CronJobAlertTriggerMode(advanced)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if result == "" {
|
||||||
|
result = CronJobAlertFailed
|
||||||
|
}
|
||||||
|
if result != CronJobAlertFailed && result != CronJobAlertSuccess {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return mode == CronJobAlertBoth || mode == result
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTaskAlertParams(pushAlert dto.PushAlert) []dto.Param {
|
||||||
|
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
|
||||||
|
if GetCronJobType(pushAlert.AlertType) != "cronJob" {
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
params = append(params, CreateCronJobResultParam(pushAlert.Result))
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
func cronJobTaskName(project string, params []dto.Param) string {
|
||||||
|
if project != "" {
|
||||||
|
return project
|
||||||
|
}
|
||||||
|
if name := getValueByIndex(params, "taskName"); name != "" {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return getValueByIndex(params, "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateCronJobResultParam(result string) dto.Param {
|
||||||
|
value := "失败"
|
||||||
|
if result == CronJobAlertSuccess {
|
||||||
|
value = "成功"
|
||||||
|
}
|
||||||
|
return dto.Param{Index: "2", Key: "result", Value: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CronJobAlertResultFromParams(params []dto.Param) string {
|
||||||
|
for _, param := range params {
|
||||||
|
if param.Index == "result" {
|
||||||
|
if param.Value == CronJobAlertSuccess {
|
||||||
|
return CronJobAlertSuccess
|
||||||
|
}
|
||||||
|
return CronJobAlertFailed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, param := range params {
|
||||||
|
if param.Index == "2" && param.Key == "result" && param.Value == "成功" {
|
||||||
|
return CronJobAlertSuccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CronJobAlertFailed
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ func CreateTaskScanCustomWebhookAlertLog(
|
|||||||
transport *http.Transport,
|
transport *http.Transport,
|
||||||
agentInfo *dto.AgentInfo,
|
agentInfo *dto.AgentInfo,
|
||||||
) error {
|
) error {
|
||||||
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
|
params := CreateTaskAlertParams(pushAlert)
|
||||||
alertInfo := info
|
alertInfo := info
|
||||||
alertInfo.Type = alertType
|
alertInfo.Type = alertType
|
||||||
create.Type = GetCronJobType(alertType)
|
create.Type = GetCronJobType(alertType)
|
||||||
@@ -99,7 +99,7 @@ func customWebhookTemplateData(rawDetail string, agentInfo *dto.AgentInfo, occur
|
|||||||
if businessType == "" {
|
if businessType == "" {
|
||||||
return webhook_sender.TemplateData{}, errors.New("resolve custom webhook alert detail failed")
|
return webhook_sender.TemplateData{}, errors.New("resolve custom webhook alert detail failed")
|
||||||
}
|
}
|
||||||
content := GetSendContent(businessType, detail.Params, agentInfo)
|
content := GetAlertDetailContent(detail, agentInfo)
|
||||||
if content == "" {
|
if content == "" {
|
||||||
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": detail.Title})
|
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": detail.Title})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ func PushAlert(pushAlert dto.PushAlert) error {
|
|||||||
}
|
}
|
||||||
var alert dto.AlertDTO
|
var alert dto.AlertDTO
|
||||||
_ = copier.Copy(&alert, &alertInfo)
|
_ = copier.Copy(&alert, &alertInfo)
|
||||||
|
if alertUtil.GetCronJobType(pushAlert.AlertType) == "cronJob" && !alertUtil.MatchCronJobAlertResult(alert.AdvancedParams, pushAlert.Result) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
methods := strings.Split(alert.Method, ",")
|
methods := strings.Split(alert.Method, ",")
|
||||||
for _, m := range methods {
|
for _, m := range methods {
|
||||||
@@ -126,7 +129,7 @@ func sendAlert(alertRepo repo.IAlertRepo, alert dto.AlertDTO, pushAlert dto.Push
|
|||||||
}
|
}
|
||||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||||
params := alertUtil.CreateAlertParams(alertUtil.GetCronJobTypeName(pushAlert.Param))
|
params := alertUtil.CreateTaskAlertParams(pushAlert)
|
||||||
alertDetail := alertUtil.ProcessAlertDetail(alert, pushAlert.TaskName, params, constant.Bark)
|
alertDetail := alertUtil.ProcessAlertDetail(alert, pushAlert.TaskName, params, constant.Bark)
|
||||||
alertRule := alertUtil.ProcessAlertRule(alert)
|
alertRule := alertUtil.ProcessAlertRule(alert)
|
||||||
create.AlertRule = alertRule
|
create.AlertRule = alertRule
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ type CommandHelper struct {
|
|||||||
outputFile string
|
outputFile string
|
||||||
scriptPath string
|
scriptPath string
|
||||||
stdin io.Reader
|
stdin io.Reader
|
||||||
|
stderr io.Writer
|
||||||
env []string
|
env []string
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
taskItem *task.Task
|
taskItem *task.Task
|
||||||
@@ -360,6 +361,9 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
|
|||||||
cmd.Stdout = &stdout
|
cmd.Stdout = &stdout
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
}
|
}
|
||||||
|
if c.stderr != nil {
|
||||||
|
cmd.Stderr = io.MultiWriter(cmd.Stderr, c.stderr)
|
||||||
|
}
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
env = append(env, c.env...)
|
env = append(env, c.env...)
|
||||||
cmd.Env = env
|
cmd.Env = env
|
||||||
@@ -481,6 +485,11 @@ func WithStdin(stdin io.Reader) Option {
|
|||||||
s.stdin = stdin
|
s.stdin = stdin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func WithStderr(stderr io.Writer) Option {
|
||||||
|
return func(s *CommandHelper) {
|
||||||
|
s.stderr = stderr
|
||||||
|
}
|
||||||
|
}
|
||||||
func WithEnv(env ...string) Option {
|
func WithEnv(env ...string) Option {
|
||||||
return func(s *CommandHelper) {
|
return func(s *CommandHelper) {
|
||||||
s.env = append(s.env, env...)
|
s.env = append(s.env, env...)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package manager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||||
@@ -18,5 +19,19 @@ func handlerErr(out string, err error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func run(name string, args ...string) (string, error) {
|
func run(name string, args ...string) (string, error) {
|
||||||
return cmd.NewCommandMgr(cmd.WithTimeout(10*time.Second), cmd.WithEnv("LANGUAGE=en_US:en")).RunWithStdout(name, args...)
|
return runWithTimeout(10*time.Second, name, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runWithTimeout(timeout time.Duration, name string, args ...string) (string, error) {
|
||||||
|
return cmd.NewCommandMgr(cmd.WithTimeout(timeout), cmd.WithEnv("LANGUAGE=en_US:en")).RunWithStdout(name, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceOperationTimeout(operation, serviceName string) time.Duration {
|
||||||
|
if operation == "restart" {
|
||||||
|
switch strings.TrimSuffix(serviceName, ".service") {
|
||||||
|
case "docker", "dockerd", "docker.dockerd", "snap.docker.dockerd":
|
||||||
|
return 2 * time.Minute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 10 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func (s *Openrc) Operate(operate, serviceName string) error {
|
|||||||
case "disable":
|
case "disable":
|
||||||
return handlerErr(run("rc-update", "del", serviceName, "default"))
|
return handlerErr(run("rc-update", "del", serviceName, "default"))
|
||||||
default:
|
default:
|
||||||
return handlerErr(run(s.toolCmd, serviceName, operate))
|
return handlerErr(runWithTimeout(serviceOperationTimeout(operate, serviceName), s.toolCmd, serviceName, operate))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package manager
|
package manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ func (s *Snap) IsEnable(serviceName string) bool {
|
|||||||
|
|
||||||
func (s *Snap) Operate(operate, serviceName string) error {
|
func (s *Snap) Operate(operate, serviceName string) error {
|
||||||
if s.IsExist(serviceName) {
|
if s.IsExist(serviceName) {
|
||||||
return handlerErr(run(s.toolCmd, operate, serviceName))
|
return handlerErr(runWithTimeout(serviceOperationTimeout(operate, serviceName), s.toolCmd, operate, serviceName))
|
||||||
}
|
}
|
||||||
return nil
|
return fmt.Errorf("snap service %q does not exist", serviceName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func (s *Systemd) Status(serviceName string) (string, error) {
|
|||||||
return run(s.toolCmd, "status", serviceName)
|
return run(s.toolCmd, "status", serviceName)
|
||||||
}
|
}
|
||||||
func (s *Systemd) Operate(operate, serviceName string) error {
|
func (s *Systemd) Operate(operate, serviceName string) error {
|
||||||
out, err := run(s.toolCmd, operate, serviceName)
|
out, err := runWithTimeout(serviceOperationTimeout(operate, serviceName), s.toolCmd, operate, serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if fallbackName := systemdAliasFallbackName(serviceName); fallbackName != "" && strings.Contains(out, "alias name or linked unit file") {
|
if fallbackName := systemdAliasFallbackName(serviceName); fallbackName != "" && strings.Contains(out, "alias name or linked unit file") {
|
||||||
return s.Operate(operate, fallbackName)
|
return s.Operate(operate, fallbackName)
|
||||||
@@ -65,7 +65,7 @@ func (s *Systemd) Operate(operate, serviceName string) error {
|
|||||||
if err := NewSnap().Operate(operate, serviceName); err == nil {
|
if err := NewSnap().Operate(operate, serviceName); err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return handlerErr(run(s.toolCmd, operate, serviceName))
|
return handlerErr(out, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func (s *Sysvinit) Status(serviceName string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sysvinit) Operate(operate, serviceName string) error {
|
func (s *Sysvinit) Operate(operate, serviceName string) error {
|
||||||
return handlerErr(run(s.toolCmd, serviceName, operate))
|
return handlerErr(runWithTimeout(serviceOperationTimeout(operate, serviceName), s.toolCmd, serviceName, operate))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sysvinit) Reload() error {
|
func (s *Sysvinit) Reload() error {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user