mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
43
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ade1accd3f | ||
|
|
ce14a104cc | ||
|
|
2eaabccf94 | ||
|
|
e119239d3d | ||
|
|
14a57af4e9 | ||
|
|
2701af9054 | ||
|
|
4954067736 | ||
|
|
b962144e7d | ||
|
|
8f1be42b6b | ||
|
|
c6969f231b | ||
|
|
3571fb8d86 | ||
|
|
d21288788e | ||
|
|
f65e4b06ab | ||
|
|
d26bc9a3a4 | ||
|
|
7088a903fe | ||
|
|
864ebeed82 | ||
|
|
c1b2b92708 | ||
|
|
2189d9229a | ||
|
|
d0183f460e | ||
|
|
d27d6db1ea | ||
|
|
d2bb3813d9 | ||
|
|
cac73e6d45 | ||
|
|
4712ceaf2c | ||
|
|
65243c68c4 | ||
|
|
256e79ca81 | ||
|
|
4861eb69cb | ||
|
|
fb8cf15537 | ||
|
|
c4a6791271 | ||
|
|
f58e147636 | ||
|
|
387e9fbeed | ||
|
|
4eb627bc79 | ||
|
|
850c86229c | ||
|
|
f984917a66 | ||
|
|
8588217fbf | ||
|
|
a2307c5f64 | ||
|
|
5923290de8 | ||
|
|
1c994fba4a | ||
|
|
415ab96aab | ||
|
|
19bb823b05 | ||
|
|
3a5371652e | ||
|
|
a267b4148a | ||
|
|
36a01eb60d | ||
|
|
65f6fdd045 |
+110
-159
@@ -2,14 +2,14 @@ package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/service"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -88,7 +88,7 @@ func (b *BaseApi) OperateFirewall(c *gin.Context) {
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/forward/base [post]
|
||||
func (b *BaseApi) LoadForwardingBaseInfo(c *gin.Context) {
|
||||
data, err := forwardingService.LoadBaseInfo()
|
||||
data, err := forwardingService.LoadBaseInfo(c.Request.Context())
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -109,7 +109,7 @@ func (b *BaseApi) SearchForwardingRules(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, items, err := forwardingService.SearchRules(request)
|
||||
total, items, err := forwardingService.SearchRules(c.Request.Context(), request)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -260,28 +260,6 @@ func (b *BaseApi) LoadFirewallNativeDetail(c *gin.Context) {
|
||||
helper.SuccessWithData(c, info)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Adopt an external firewall rule
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleAdopt true "request"
|
||||
// @Success 200
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/adopt [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"纳管防火墙规则","formatEN":"adopt firewall rule"}
|
||||
func (b *BaseApi) AdoptFirewallRule(c *gin.Context) {
|
||||
var request dto.FirewallRuleAdopt
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := firewallService.Adopt(c.Request.Context(), request); err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Queue firewall rule creation
|
||||
// @Description Creation and import return a taskID immediately; validation and execution results are written to the task log.
|
||||
@@ -307,69 +285,9 @@ func (b *BaseApi) CreateFirewallRules(c *gin.Context) {
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Preview firewall rule synchronization
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleSyncRequest true "request"
|
||||
// @Success 200 {object} dto.FirewallRuleSyncPreview
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/sync/preview [post]
|
||||
func (b *BaseApi) PreviewFirewallRuleSync(c *gin.Context) {
|
||||
var request dto.FirewallRuleSyncRequest
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := firewallService.PreviewRuleSync(c.Request.Context(), c.ClientIP(), request)
|
||||
if err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Load the currently executing firewall rule synchronization task
|
||||
// @Success 200 {object} dto.FirewallRuleSyncTask
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/sync/task [get]
|
||||
func (b *BaseApi) LoadFirewallRuleSyncTask(c *gin.Context) {
|
||||
result, err := firewallService.CurrentRuleSyncTask()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Synchronize firewall rules to a target backend
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleSyncRequest true "request"
|
||||
// @Success 200 {object} dto.FirewallRuleSyncResult
|
||||
// @Failure 400 {object} dto.Response
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/sync [post]
|
||||
// @x-panel-log {"bodyKeys":["subsystem","sourceProvider","targetProvider"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 [subsystem] 防火墙规则到 [targetProvider]","formatEN":"sync [subsystem] firewall rules to [targetProvider]"}
|
||||
func (b *BaseApi) SyncFirewallRules(c *gin.Context) {
|
||||
var request dto.FirewallRuleSyncRequest
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := firewallService.SyncRules(c.Request.Context(), c.ClientIP(), request)
|
||||
if err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Queue firewall rule deletion
|
||||
// @Description Deletes managed rules by UUID or unprotected before-chain rules by instance key. Returns a taskID immediately; results are written to the task log.
|
||||
// @Description Deletes non-whitelist rules by scope and instance key. Returns a taskID immediately; results are written to the task log.
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleDelete true "request"
|
||||
// @Success 200 {object} dto.FirewallRuleDeleteResponse
|
||||
@@ -392,7 +310,7 @@ func (b *BaseApi) DeleteFirewallRules(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Update a managed unified firewall v2 rule
|
||||
// @Summary Update a firewall rule
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleUpdate true "request"
|
||||
// @Success 200
|
||||
@@ -400,16 +318,13 @@ func (b *BaseApi) DeleteFirewallRules(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/update [post]
|
||||
// @x-panel-log {"bodyKeys":["uuid"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新防火墙规则 [uuid]","formatEN":"update firewall rule [uuid]"}
|
||||
// @x-panel-log {"bodyKeys":["instanceKey"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新防火墙规则 [instanceKey]","formatEN":"update firewall rule [instanceKey]"}
|
||||
func (b *BaseApi) UpdateFirewallRule(c *gin.Context) {
|
||||
var request dto.FirewallRuleUpdate
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
if !normalizeFirewallRuleUUID(c, &request.UUID) {
|
||||
return
|
||||
}
|
||||
if err := firewallService.Update(c.Request.Context(), c.ClientIP(), request); err != nil {
|
||||
if err := firewallService.Update(c.Request.Context(), request); err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
@@ -417,7 +332,7 @@ func (b *BaseApi) UpdateFirewallRule(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Reorder a managed unified firewall v2 rule
|
||||
// @Summary Reorder a firewall rule
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallRuleReorder true "request"
|
||||
// @Success 200
|
||||
@@ -425,53 +340,41 @@ func (b *BaseApi) UpdateFirewallRule(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/reorder [post]
|
||||
// @x-panel-log {"bodyKeys":["uuid"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"调整防火墙规则顺序 [uuid]","formatEN":"reorder firewall rule [uuid]"}
|
||||
// @x-panel-log {"bodyKeys":["instanceKey"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"调整防火墙规则顺序 [instanceKey]","formatEN":"reorder firewall rule [instanceKey]"}
|
||||
func (b *BaseApi) ReorderFirewallRule(c *gin.Context) {
|
||||
var request dto.FirewallRuleReorder
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
if !normalizeFirewallRuleUUID(c, &request.UUID) {
|
||||
return
|
||||
}
|
||||
if err := firewallService.Reorder(c.Request.Context(), c.ClientIP(), request); err != nil {
|
||||
if err := firewallService.Reorder(c.Request.Context(), request); err != nil {
|
||||
handleFirewallRuleError(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
func normalizeFirewallRuleUUID(c *gin.Context, value *string) bool {
|
||||
if value == nil {
|
||||
helper.BadRequest(c, repo.ErrFirewallPersistenceInvalid)
|
||||
return false
|
||||
}
|
||||
*value = strings.TrimSpace(*value)
|
||||
if *value == "" {
|
||||
helper.BadRequest(c, repo.ErrFirewallPersistenceInvalid)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func handleFirewallRuleError(c *gin.Context, err error) {
|
||||
var businessErr buserr.BusinessError
|
||||
isBusinessError := errors.As(err, &businessErr)
|
||||
switch {
|
||||
case errors.Is(err, filter.ErrProtectedRule):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_LOCKOUT_RISK", "ErrInvalidParams", err)
|
||||
case errors.Is(err, filter.ErrRuleStale):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_STALE", "ErrInvalidParams", err)
|
||||
case errors.Is(err, repo.ErrFirewallRuleRevisionConflict):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_REVISION_CONFLICT", "ErrInvalidParams", err)
|
||||
case errors.Is(err, filter.ErrManagedScopeChange):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_SCOPE_UNSUPPORTED", "ErrFirewallRuleScopeChange", err)
|
||||
case errors.Is(err, filter.ErrUnsupportedScope), errors.Is(err, filter.ErrInvalidScope),
|
||||
errors.Is(err, filter.ErrProviderUnavailable), errors.Is(err, filter.ErrAdapterUnavailable):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_SCOPE_UNSUPPORTED", "ErrInvalidParams", err)
|
||||
case errors.Is(err, filter.ErrInvalidRule), errors.Is(err, filter.ErrRuleOperation), errors.Is(err, filter.ErrRuleConflict),
|
||||
errors.Is(err, repo.ErrFirewallPersistenceInvalid):
|
||||
case errors.Is(err, filter.ErrInvalidRule), errors.Is(err, filter.ErrRuleOperation):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_RULE_UNSUPPORTED", "ErrInvalidParams", err)
|
||||
case errors.Is(err, filter.ErrVerificationFailed):
|
||||
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_VERIFY_FAILED", "ErrInternalServer", err)
|
||||
case isBusinessError && businessErr.Msg == "ErrRecordExist":
|
||||
c.JSON(http.StatusOK, dto.Response{Code: http.StatusConflict, ErrorCode: "FW_RULE_DUPLICATE", Message: err.Error()})
|
||||
c.Abort()
|
||||
case isBusinessError && businessErr.Msg == "ErrFirewallRuleConflict":
|
||||
c.JSON(http.StatusOK, dto.Response{Code: http.StatusConflict, ErrorCode: "FW_RULE_CONFLICT", Message: err.Error()})
|
||||
c.Abort()
|
||||
case isBusinessError && businessErr.Msg == "ErrInvalidParams":
|
||||
c.JSON(http.StatusOK, dto.Response{Code: http.StatusBadRequest, ErrorCode: "FW_RULE_UNSUPPORTED", Message: err.Error()})
|
||||
c.Abort()
|
||||
default:
|
||||
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_APPLY_FAILED", "ErrInternalServer", err)
|
||||
}
|
||||
@@ -494,7 +397,7 @@ func (b *BaseApi) LoadFirewallSettings(c *gin.Context) {
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Create firewall port whitelist rules
|
||||
// @Description Saves whitelist configuration only. Missing rules are added on startup, restart, initialization, or synchronization; existing rules are not removed.
|
||||
// @Description Saves whitelist configuration and applies missing allowances; existing rules are not removed.
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallPortWhitelistCreate true "request"
|
||||
// @Success 200
|
||||
@@ -516,7 +419,7 @@ func (b *BaseApi) CreateFirewallPortWhitelist(c *gin.Context) {
|
||||
|
||||
// @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.
|
||||
// @Description Saves whitelist configuration and applies missing allowances; existing rules are not removed.
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallPortWhitelistUpdate true "request"
|
||||
// @Success 200
|
||||
@@ -538,7 +441,7 @@ func (b *BaseApi) UpdateFirewallPortWhitelist(c *gin.Context) {
|
||||
|
||||
// @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.
|
||||
// @Description Removes whitelist configuration; existing firewall rules are not removed.
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallPortWhitelistDelete true "request"
|
||||
// @Success 200
|
||||
@@ -573,14 +476,10 @@ func (b *BaseApi) OperateFirewallBackend(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := firewallSettingService.Operate(c.Request.Context(), request); err != nil {
|
||||
if errors.Is(err, service.ErrFirewallBackendCleanupRequired) {
|
||||
helper.ErrorWithBusinessCode(
|
||||
c,
|
||||
http.StatusConflict,
|
||||
"FW_BACKEND_CLEANUP_REQUIRED",
|
||||
"ErrInvalidParams",
|
||||
err,
|
||||
)
|
||||
var businessErr buserr.BusinessError
|
||||
if errors.As(err, &businessErr) && businessErr.Msg == "ErrFirewallBackendCleanupRequired" {
|
||||
c.JSON(http.StatusOK, dto.Response{Code: http.StatusConflict, ErrorCode: "FW_BACKEND_CLEANUP_REQUIRED", Message: err.Error()})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
helper.InternalServer(c, err)
|
||||
@@ -619,21 +518,6 @@ func (b *BaseApi) ListDockerPublishedPorts(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Sync Docker port guard rules
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/docker/sync [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 Docker 端口防护规则","formatEN":"sync Docker port guard rules"}
|
||||
func (b *BaseApi) SyncDockerPortGuard(c *gin.Context) {
|
||||
if err := dockerPortGuardService.Reconcile(c.Request.Context()); err != nil {
|
||||
handleDockerPortGuardError(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Operate Docker port guard
|
||||
// @Accept json
|
||||
@@ -709,21 +593,88 @@ func (b *BaseApi) UpsertDockerPortGuardPolicies(c *gin.Context) {
|
||||
}
|
||||
|
||||
func handleDockerPortGuardError(c *gin.Context, err error) {
|
||||
if errors.Is(err, service.ErrDockerIptablesChainUnavailable) {
|
||||
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_IPTABLES_CHAIN_UNAVAILABLE", "ErrDockerIptablesChainUnavailable", err)
|
||||
return
|
||||
var businessErr buserr.BusinessError
|
||||
if errors.As(err, &businessErr) {
|
||||
code, errorCode := http.StatusInternalServerError, ""
|
||||
switch businessErr.Msg {
|
||||
case "ErrDockerIptablesChainUnavailable":
|
||||
code, errorCode = http.StatusServiceUnavailable, "FW_DOCKER_IPTABLES_CHAIN_UNAVAILABLE"
|
||||
case "ErrDockerNftablesChainUnavailable":
|
||||
code, errorCode = http.StatusServiceUnavailable, "FW_DOCKER_NFTABLES_CHAIN_UNAVAILABLE"
|
||||
case "ErrInvalidParams":
|
||||
code, errorCode = http.StatusBadRequest, "FW_DOCKER_GUARD_INVALID"
|
||||
case "ErrDockerFailed":
|
||||
code, errorCode = http.StatusServiceUnavailable, "FW_DOCKER_UNAVAILABLE"
|
||||
}
|
||||
if errorCode != "" {
|
||||
c.JSON(http.StatusOK, dto.Response{Code: code, ErrorCode: errorCode, Message: err.Error()})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
if errors.Is(err, service.ErrDockerNftablesChainUnavailable) {
|
||||
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) {
|
||||
if errors.Is(err, docker.ErrUnavailable) {
|
||||
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_UNAVAILABLE", "ErrDockerFailed", err)
|
||||
return
|
||||
}
|
||||
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_DOCKER_GUARD_FAILED", "ErrInternalServer", err)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary List firewall rule backups
|
||||
// @Param subsystem query string false "Firewall subsystem" Enums(system,forwarding,docker) default(system)
|
||||
// @Success 200 {object} dto.FirewallRuleBackups
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/rules/backups [get]
|
||||
func (b *BaseApi) ListFirewallRuleBackups(c *gin.Context) {
|
||||
result, err := firewallService.ListRuleBackups(c.Request.Context(), c.DefaultQuery("subsystem", "system"))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Initialize, repair or bind one firewall address family
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallFamilyOperation true "request"
|
||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/family/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["subsystem","family","operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] [subsystem] [family] 防火墙链","formatEN":"[operation] [subsystem] [family] firewall chains"}
|
||||
func (b *BaseApi) OperateFirewallFamily(c *gin.Context) {
|
||||
var request dto.FirewallFamilyOperation
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := firewallSettingService.OperateFamily(request)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Update firewall IPv6 support
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallIPv6Operation true "request"
|
||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/settings/ipv6 [post]
|
||||
// @x-panel-log {"bodyKeys":["status"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"设置防火墙 IPv6 支持为 [status]","formatEN":"Set firewall IPv6 support to [status]"}
|
||||
func (b *BaseApi) OperateFirewallIPv6(c *gin.Context) {
|
||||
var request dto.FirewallIPv6Operation
|
||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := firewallSettingService.OperateIPv6(request)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
+10
-23
@@ -1,13 +1,9 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
// @Tags Monitor
|
||||
@@ -32,14 +28,19 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags Monitor
|
||||
// @Summary Clean monitor data
|
||||
// @Summary Clean host or GPU monitor data
|
||||
// @Param request body dto.MonitorClean true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/clean [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空监控数据","formatEN":"clean monitor datas"}
|
||||
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空 [type] 监控数据","formatEN":"clean [type] monitoring data"}
|
||||
func (b *BaseApi) CleanMonitor(c *gin.Context) {
|
||||
if err := monitorService.CleanData(); err != nil {
|
||||
var req dto.MonitorClean
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := monitorService.CleanData(req.Type); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -91,14 +92,7 @@ func (b *BaseApi) UpdateMonitorSetting(c *gin.Context) {
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/netoptions [get]
|
||||
func (b *BaseApi) GetNetworkOptions(c *gin.Context) {
|
||||
netStat, _ := net.IOCounters(true)
|
||||
var options []string
|
||||
options = append(options, "all")
|
||||
for _, net := range netStat {
|
||||
options = append(options, net.Name)
|
||||
}
|
||||
sort.Strings(options)
|
||||
helper.SuccessWithData(c, options)
|
||||
helper.SuccessWithData(c, monitorService.LoadNetworkOptions())
|
||||
}
|
||||
|
||||
// @Tags Monitor
|
||||
@@ -108,12 +102,5 @@ func (b *BaseApi) GetNetworkOptions(c *gin.Context) {
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/iooptions [get]
|
||||
func (b *BaseApi) GetIOOptions(c *gin.Context) {
|
||||
diskStat, _ := disk.IOCounters()
|
||||
var options []string
|
||||
options = append(options, "all")
|
||||
for _, net := range diskStat {
|
||||
options = append(options, net.Name)
|
||||
}
|
||||
sort.Strings(options)
|
||||
helper.SuccessWithData(c, options)
|
||||
helper.SuccessWithData(c, monitorService.LoadIOOptions())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (b *BaseApi) LoadVLLMMonitor(c *gin.Context) {
|
||||
var req dto.MonitorVLLMSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := monitorService.LoadVLLMMonitorData(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
func (b *BaseApi) LoadVLLMCurrent(c *gin.Context) {
|
||||
var req dto.MonitorVLLMCurrent
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := monitorService.LoadVLLMCurrent(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
func (b *BaseApi) CleanVLLMMonitor(c *gin.Context) {
|
||||
var req dto.MonitorVLLMClean
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := monitorService.CleanVLLMMonitor(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
@@ -169,7 +169,7 @@ func (b *BaseApi) GetNodePackageRunScript(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"操作运行环境 [id]","formatEN":"Operate runtime [id]"}
|
||||
// @x-panel-log {"bodyKeys":["ID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ID","isList":false,"db":"runtimes","output_column":"name","output_value":"name"}],"formatZH":"操作运行环境 [name]","formatEN":"Operate runtime [name]"}
|
||||
func (b *BaseApi) OperateRuntime(c *gin.Context) {
|
||||
var req request.RuntimeOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -83,6 +83,7 @@ func (b *BaseApi) CreateRootCert(c *gin.Context) {
|
||||
}
|
||||
if err := loadCertAfterDecrypt(&req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
if err := sshService.CreateRootCert(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
@@ -107,6 +108,7 @@ func (b *BaseApi) EditRootCert(c *gin.Context) {
|
||||
}
|
||||
if err := loadCertAfterDecrypt(&req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
if err := sshService.EditRootCert(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
|
||||
@@ -243,6 +243,16 @@ func loadTerminalIdentity(c *gin.Context) (terminal.Identity, bool) {
|
||||
UserID: strings.TrimSpace(c.GetHeader(terminal.HeaderUserID)),
|
||||
AuthSessionID: strings.TrimSpace(c.GetHeader(terminal.HeaderAuthSessionID)),
|
||||
}
|
||||
if value := c.GetHeader(terminal.HeaderAuthLeaseUntil); value != "" {
|
||||
millis, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil || millis <= 0 {
|
||||
return terminal.Identity{}, false
|
||||
}
|
||||
identity.AuthLeaseUntil = time.UnixMilli(millis)
|
||||
if maximum := time.Now().Add(90 * time.Second); identity.AuthLeaseUntil.After(maximum) {
|
||||
identity.AuthLeaseUntil = maximum
|
||||
}
|
||||
}
|
||||
return identity, identity.Valid()
|
||||
}
|
||||
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
@@ -21,6 +21,7 @@ type AlertBase struct {
|
||||
}
|
||||
|
||||
type PushAlert struct {
|
||||
Result string `json:"result,omitempty"`
|
||||
TaskName string `json:"taskName"`
|
||||
AlertType string `json:"alertType"`
|
||||
EntryID uint `json:"entryID"`
|
||||
@@ -53,6 +54,7 @@ type AlertDTO struct {
|
||||
Method string `json:"method"`
|
||||
Title string `json:"title"`
|
||||
Project string `json:"project"`
|
||||
TaskName string `json:"taskName,omitempty"`
|
||||
Status string `json:"status"`
|
||||
SendCount uint `json:"sendCount"`
|
||||
AdvancedParams string `json:"advancedParams"`
|
||||
|
||||
@@ -51,9 +51,10 @@ type CronjobOperate struct {
|
||||
Secret string `json:"secret"`
|
||||
Args string `json:"args"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
|
||||
|
||||
CleanLogConfig
|
||||
}
|
||||
@@ -126,7 +127,8 @@ type CronjobInfo struct {
|
||||
Secret string `json:"secret"`
|
||||
Args string `json:"args"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTriggerMode string `json:"alertTriggerMode"`
|
||||
}
|
||||
|
||||
type CronjobImport struct {
|
||||
@@ -169,9 +171,10 @@ type CronjobTrans struct {
|
||||
SourceAccounts []string `json:"sourceAccounts"`
|
||||
DownloadAccount string `json:"downloadAccount"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
|
||||
}
|
||||
type TransHelper struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
+92
-123
@@ -2,11 +2,13 @@ package dto
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
dockerfirewall "github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||
firewallsync "github.com/1Panel-dev/1Panel/agent/utils/firewall/sync"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
|
||||
)
|
||||
|
||||
type FirewallSubsystemStatus struct {
|
||||
IPv6Enabled bool `json:"ipv6Enabled"`
|
||||
Name string `json:"name"`
|
||||
Backend string `json:"backend"`
|
||||
ConflictBackend string `json:"conflictBackend,omitempty"`
|
||||
@@ -18,7 +20,6 @@ type FirewallSubsystemStatus struct {
|
||||
PingStatus string `json:"pingStatus"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
SyncError string `json:"syncError,omitempty"`
|
||||
LifecycleTaskID string `json:"lifecycleTaskID,omitempty"`
|
||||
IPv4 FirewallBackendFamilyStatus `json:"ipv4"`
|
||||
IPv6 FirewallBackendFamilyStatus `json:"ipv6"`
|
||||
@@ -49,10 +50,13 @@ type FirewallBackendOption struct {
|
||||
}
|
||||
|
||||
type FirewallBackendFamilyStatus struct {
|
||||
Available bool `json:"available"`
|
||||
Initialized bool `json:"initialized"`
|
||||
Bound bool `json:"bound"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Partial bool `json:"partial"`
|
||||
Available bool `json:"available"`
|
||||
Initialized bool `json:"initialized"`
|
||||
Bound bool `json:"bound"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
ForwardPolicy string `json:"forwardPolicy,omitempty"`
|
||||
RAInterfaces []string `json:"raInterfaces,omitempty"`
|
||||
}
|
||||
|
||||
type FirewallBackendGroup struct {
|
||||
@@ -62,6 +66,7 @@ type FirewallBackendGroup struct {
|
||||
}
|
||||
|
||||
type FirewallSettings struct {
|
||||
IPv6Enabled bool `json:"ipv6Enabled"`
|
||||
System FirewallBackendGroup `json:"system"`
|
||||
Forwarding FirewallBackendGroup `json:"forwarding"`
|
||||
Docker FirewallBackendGroup `json:"docker"`
|
||||
@@ -90,6 +95,17 @@ type FirewallBackendOperation struct {
|
||||
Operation string `json:"operation" validate:"required,oneof=select initialize cleanup"`
|
||||
}
|
||||
|
||||
type FirewallIPv6Operation struct {
|
||||
Status string `json:"status" validate:"required,oneof=Enable Disable"`
|
||||
}
|
||||
|
||||
type FirewallFamilyOperation struct {
|
||||
Subsystem string `json:"subsystem" validate:"required,oneof=system forwarding docker"`
|
||||
Backend string `json:"backend" validate:"required,oneof=iptables nftables"`
|
||||
Family string `json:"family" validate:"required,oneof=ipv4 ipv6"`
|
||||
Operation string `json:"operation" validate:"required,oneof=initialize repair bind"`
|
||||
}
|
||||
|
||||
type FilterChainOperation struct {
|
||||
Name string `json:"name" validate:"required,eq=1PANEL_BASIC"`
|
||||
Operate string `json:"operate" validate:"required,oneof=init-base bind-base unbind-base"`
|
||||
@@ -102,42 +118,55 @@ type FilterChainOperationResponse struct {
|
||||
}
|
||||
|
||||
type FirewallInitializationTask struct {
|
||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
||||
BackupFile string `json:"backupFile,omitempty" validate:"omitempty,max=255"`
|
||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
||||
}
|
||||
|
||||
type FirewallSystemPort = firewall.SystemPort
|
||||
|
||||
type FirewallRuleInventoryResponse struct {
|
||||
IPv4Range filter.PositionRange `json:"ipv4Range"`
|
||||
IPv6Range filter.PositionRange `json:"ipv6Range"`
|
||||
Total int64 `json:"total"`
|
||||
AllTotal int64 `json:"allTotal"`
|
||||
ManagedTotal int64 `json:"managedTotal"`
|
||||
Items []filter.InventoryItem `json:"items"`
|
||||
Notices []filter.ScopeNotice `json:"notices,omitempty"`
|
||||
IPv4Range filter.PositionRange `json:"ipv4Range"`
|
||||
IPv6Range filter.PositionRange `json:"ipv6Range"`
|
||||
Total int64 `json:"total"`
|
||||
AllTotal int64 `json:"allTotal"`
|
||||
Items []filter.InventoryItem `json:"items"`
|
||||
Notices []filter.ScopeNotice `json:"notices,omitempty"`
|
||||
}
|
||||
|
||||
type FirewallRuleBackup struct {
|
||||
Name string `json:"name"`
|
||||
Provider filter.Provider `json:"provider"`
|
||||
RuleCount int `json:"ruleCount"`
|
||||
ModifiedAt int64 `json:"modifiedAt"`
|
||||
}
|
||||
|
||||
type FirewallRuleBackups struct {
|
||||
Directory string `json:"directory"`
|
||||
Files []FirewallRuleBackup `json:"files"`
|
||||
}
|
||||
|
||||
type FirewallRuleResetResponse struct {
|
||||
Removed int `json:"removed"`
|
||||
Disabled bool `json:"disabled"`
|
||||
BackupPath string `json:"backupPath"`
|
||||
Removed int `json:"removed"`
|
||||
Disabled bool `json:"disabled"`
|
||||
}
|
||||
|
||||
type FirewallRuleReset struct {
|
||||
Subsystem string `json:"subsystem,omitempty" validate:"omitempty,oneof=system forwarding docker"`
|
||||
Backup *bool `json:"backup,omitempty" default:"true"`
|
||||
Provider filter.Provider `json:"provider,omitempty" validate:"omitempty,oneof=firewalld ufw iptables nftables"`
|
||||
WithDockerRestart bool `json:"withDockerRestart"`
|
||||
}
|
||||
|
||||
type FirewallRuleInventory struct {
|
||||
Refresh bool `json:"refresh,omitempty"`
|
||||
PageInfo
|
||||
Scope filter.Scope `json:"scope,omitempty"`
|
||||
Scopes []filter.Scope `json:"scopes,omitempty" validate:"max=16"`
|
||||
All bool `json:"all,omitempty"`
|
||||
Info string `json:"info"`
|
||||
Families []filter.Family `json:"families,omitempty" validate:"omitempty,dive,oneof=ipv4 ipv6"`
|
||||
Actions []string `json:"actions,omitempty" validate:"omitempty,dive,oneof=accept deny"`
|
||||
States []filter.InventoryState `json:"states,omitempty" validate:"omitempty,dive,oneof=managed adopted external drifted protected"`
|
||||
ExcludeChains []string `json:"excludeChains,omitempty" validate:"omitempty,dive,oneof=1PANEL_BASIC_BEFORE 1PANEL_BASIC 1PANEL_BASIC_AFTER"`
|
||||
Scope filter.Scope `json:"scope,omitempty"`
|
||||
Scopes []filter.Scope `json:"scopes,omitempty" validate:"max=16"`
|
||||
All bool `json:"all,omitempty"`
|
||||
Info string `json:"info"`
|
||||
Families []filter.Family `json:"families,omitempty" validate:"omitempty,dive,oneof=ipv4 ipv6"`
|
||||
Actions []string `json:"actions,omitempty" validate:"omitempty,dive,oneof=accept deny"`
|
||||
ExcludeChains []string `json:"excludeChains,omitempty" validate:"omitempty,dive,oneof=1PANEL_BASIC_BEFORE 1PANEL_BASIC 1PANEL_BASIC_AFTER"`
|
||||
}
|
||||
|
||||
type FirewallNativeDetail struct {
|
||||
@@ -148,6 +177,7 @@ type FirewallNativeDetail struct {
|
||||
}
|
||||
|
||||
type DockerPortGuardBase struct {
|
||||
IPv6Enabled bool `json:"ipv6Enabled"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
IsExist bool `json:"isExist"`
|
||||
@@ -160,6 +190,7 @@ type DockerPortGuardBase struct {
|
||||
}
|
||||
|
||||
type DockerPortGuardFamilyStatus struct {
|
||||
Partial bool `json:"partial"`
|
||||
State string `json:"state"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Initialized bool `json:"initialized"`
|
||||
@@ -180,11 +211,8 @@ type DockerPortGuardEndpoint struct {
|
||||
Application string `json:"application,omitempty"`
|
||||
PolicyUUID string `json:"policyUUID,omitempty"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
NativeAction string `json:"nativeAction,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
Sources []string `json:"sources"`
|
||||
Effective bool `json:"effective"`
|
||||
Description string `json:"description,omitempty"`
|
||||
TrafficPath string `json:"trafficPath"`
|
||||
ManagementTarget string `json:"managementTarget"`
|
||||
ManagementReason string `json:"managementReason,omitempty"`
|
||||
@@ -221,6 +249,7 @@ type DockerPortGuardEndpointIdentity struct {
|
||||
|
||||
type DockerPortGuardPolicyBatch struct {
|
||||
Policies []DockerPortGuardPolicy `json:"policies" validate:"required,min=1,dive"`
|
||||
Import bool `json:"import"`
|
||||
}
|
||||
|
||||
type DockerPortGuardPolicyBatchDelete struct {
|
||||
@@ -229,29 +258,27 @@ type DockerPortGuardPolicyBatchDelete struct {
|
||||
|
||||
type DockerPortGuardPolicy struct {
|
||||
DockerPortGuardEndpointIdentity
|
||||
Mode string `json:"mode" validate:"required,oneof=deny_sources allow_sources deny_all"`
|
||||
Sources []string `json:"sources" validate:"dive,required,max=64"`
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
Mode string `json:"mode" validate:"required,oneof=deny_sources allow_sources deny_all accept_sources accept_all"`
|
||||
Sources []string `json:"sources" validate:"dive,required,max=64"`
|
||||
}
|
||||
|
||||
type DockerPortGuardOperation struct {
|
||||
Operation string `json:"operation" validate:"required,oneof=initialize bind unbind"`
|
||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
||||
}
|
||||
|
||||
type FirewallRuleAdopt struct {
|
||||
Scope filter.Scope `json:"scope" validate:"required"`
|
||||
InstanceKey string `json:"instanceKey" validate:"required,max=128"`
|
||||
BackupFile string `json:"backupFile,omitempty" validate:"omitempty,max=255"`
|
||||
Operation string `json:"operation" validate:"required,oneof=initialize bind unbind"`
|
||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
||||
}
|
||||
|
||||
type FirewallRuleCreateItem struct {
|
||||
Rule filter.FirewallRule `json:"rule" validate:"required"`
|
||||
SourceKind string `json:"sourceKind" validate:"omitempty,oneof=user imported"`
|
||||
SourceID string `json:"sourceID"`
|
||||
Raw string `json:"raw,omitempty"`
|
||||
ParseStatus filter.ParseStatus `json:"parseStatus,omitempty"`
|
||||
Rule filter.FirewallRule `json:"rule" validate:"required"`
|
||||
SourceKind string `json:"sourceKind" validate:"omitempty,oneof=user imported"`
|
||||
}
|
||||
|
||||
type FirewallRuleCreate struct {
|
||||
Items []FirewallRuleCreateItem `json:"items" validate:"required,min=1,dive"`
|
||||
BackupFile string `json:"backupFile,omitempty" validate:"omitempty,max=255"`
|
||||
Initialize bool `json:"initialize"`
|
||||
Items []FirewallRuleCreateItem `json:"items" validate:"dive"`
|
||||
}
|
||||
|
||||
type FirewallRuleCreateResponse struct {
|
||||
@@ -270,66 +297,13 @@ type FirewallRuleCreateFailure struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type FirewallRuleSyncRequest struct {
|
||||
Subsystem string `json:"subsystem" validate:"omitempty,oneof=system forwarding docker"`
|
||||
SourceProvider filter.Provider `json:"sourceProvider,omitempty" validate:"omitempty,oneof=firewalld ufw iptables nftables"`
|
||||
TargetProvider filter.Provider `json:"targetProvider" validate:"required,oneof=firewalld ufw iptables nftables"`
|
||||
ResetSource bool `json:"resetSource"`
|
||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
||||
}
|
||||
|
||||
type FirewallRuleSyncItem struct {
|
||||
SourceUUID string `json:"sourceUUID"`
|
||||
Rule *filter.FirewallRule `json:"rule,omitempty"`
|
||||
ForwardRule *ForwardRule `json:"forwardRule,omitempty"`
|
||||
DockerRule *DockerPortGuardEndpoint `json:"dockerRule,omitempty"`
|
||||
Status firewallsync.Status `json:"status"`
|
||||
ReasonCode firewallsync.ReasonCode `json:"reasonCode,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
type FirewallRuleSyncPreview struct {
|
||||
Subsystem string `json:"subsystem"`
|
||||
SourceProvider filter.Provider `json:"sourceProvider,omitempty"`
|
||||
TargetProvider filter.Provider `json:"targetProvider"`
|
||||
Total int `json:"total"`
|
||||
Ready int `json:"ready"`
|
||||
Existing int `json:"existing"`
|
||||
Removed int `json:"removed"`
|
||||
Blocked int `json:"blocked"`
|
||||
Items []FirewallRuleSyncItem `json:"items"`
|
||||
}
|
||||
|
||||
type FirewallRuleSyncResult struct {
|
||||
Subsystem string `json:"subsystem"`
|
||||
SourceProvider filter.Provider `json:"sourceProvider,omitempty"`
|
||||
TargetProvider filter.Provider `json:"targetProvider"`
|
||||
Total int `json:"total"`
|
||||
Succeeded int `json:"succeeded"`
|
||||
Skipped int `json:"skipped"`
|
||||
Removed int `json:"removed"`
|
||||
Failed int `json:"failed"`
|
||||
Errors []FirewallRuleSyncFailure `json:"errors,omitempty"`
|
||||
TaskID string `json:"taskID,omitempty"`
|
||||
Queued bool `json:"queued,omitempty"`
|
||||
}
|
||||
|
||||
type FirewallRuleSyncTask struct {
|
||||
TaskID string `json:"taskID,omitempty"`
|
||||
Executing bool `json:"executing"`
|
||||
}
|
||||
|
||||
type FirewallRuleSyncFailure struct {
|
||||
SourceUUID string `json:"sourceUUID"`
|
||||
Rule *filter.FirewallRule `json:"rule,omitempty"`
|
||||
ForwardRule *ForwardRule `json:"forwardRule,omitempty"`
|
||||
DockerRule *DockerPortGuardEndpoint `json:"dockerRule,omitempty"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type FirewallRuleDelete struct {
|
||||
UUIDs []string `json:"uuids" validate:"omitempty,dive,required,max=64"`
|
||||
BeforeRules []FirewallRuleDeleteTarget `json:"beforeRules,omitempty" validate:"omitempty,dive"`
|
||||
Targets []FirewallRuleDeleteItem `json:"targets" validate:"required,min=1,dive"`
|
||||
}
|
||||
|
||||
type FirewallRuleDeleteItem struct {
|
||||
FirewallRuleDeleteTarget
|
||||
Observed filter.ObservedRule `json:"observed" validate:"required"`
|
||||
}
|
||||
|
||||
type FirewallRuleDeleteTarget struct {
|
||||
@@ -346,13 +320,13 @@ type FirewallRuleDeleteResponse struct {
|
||||
}
|
||||
|
||||
type FirewallRuleDeleteFailure struct {
|
||||
Index int `json:"index"`
|
||||
UUID string `json:"uuid"`
|
||||
Error string `json:"error"`
|
||||
Index int `json:"index"`
|
||||
InstanceKey string `json:"instanceKey"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type FirewallRuleUpdate struct {
|
||||
UUID string `json:"uuid" validate:"required,max=64"`
|
||||
FirewallRuleDeleteTarget
|
||||
Rule *filter.FirewallRule `json:"rule,omitempty" validate:"required_without_all=Description OrderIndex Priority,excluded_with=Description OrderIndex Priority"`
|
||||
Description *string `json:"description,omitempty" validate:"excluded_with=Rule"`
|
||||
OrderIndex *int64 `json:"orderIndex,omitempty" validate:"excluded_with=Rule Priority"`
|
||||
@@ -360,26 +334,21 @@ type FirewallRuleUpdate struct {
|
||||
}
|
||||
|
||||
type FirewallRuleReorder struct {
|
||||
UUID string `json:"uuid" validate:"required,max=64"`
|
||||
FirewallRuleDeleteTarget
|
||||
TargetPosition *int64 `json:"targetPosition"`
|
||||
Priority *int `json:"priority"`
|
||||
}
|
||||
|
||||
func (p *FirewallRuleSyncPreview) Add(item FirewallRuleSyncItem) {
|
||||
p.Items = append(p.Items, item)
|
||||
switch item.Status {
|
||||
case firewallsync.StatusReady:
|
||||
p.Ready++
|
||||
p.Total++
|
||||
case firewallsync.StatusExisting:
|
||||
p.Existing++
|
||||
p.Total++
|
||||
case firewallsync.StatusRemove:
|
||||
p.Removed++
|
||||
case firewallsync.StatusBlocked:
|
||||
p.Blocked++
|
||||
if item.ReasonCode != firewallsync.ReasonReadOnlyRule {
|
||||
p.Total++
|
||||
}
|
||||
}
|
||||
type FirewallRuleExportItem struct {
|
||||
filter.FirewallRule
|
||||
Raw string `json:"raw,omitempty"`
|
||||
ParseStatus filter.ParseStatus `json:"parseStatus,omitempty"`
|
||||
}
|
||||
|
||||
type FirewallSubsystemBackup struct {
|
||||
Families []string `json:"families,omitempty"`
|
||||
Subsystem string `json:"subsystem"`
|
||||
Provider filter.Provider `json:"provider"`
|
||||
Forwarding []forwarding.Rule `json:"forwarding"`
|
||||
Docker *dockerfirewall.PolicyInventory `json:"docker,omitempty"`
|
||||
}
|
||||
|
||||
@@ -24,15 +24,11 @@ type ForwardRule struct {
|
||||
|
||||
UsedStatus string `json:"usedStatus"`
|
||||
Description string `json:"description"`
|
||||
|
||||
IsDesired bool `json:"isDesired"`
|
||||
IsRuntime bool `json:"isRuntime"`
|
||||
SyncStatus string `json:"syncStatus"`
|
||||
}
|
||||
|
||||
type ForwardRuleOperate struct {
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
Rules []ForwardRuleOperation `json:"rules" validate:"required,min=1,dive"`
|
||||
Import bool `json:"import"`
|
||||
Rules []ForwardRuleOperation `json:"rules" validate:"required,min=1,dive"`
|
||||
}
|
||||
|
||||
type ForwardRuleOperation struct {
|
||||
|
||||
+87
-18
@@ -1,6 +1,10 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
)
|
||||
|
||||
type MonitorSearch struct {
|
||||
Param string `json:"param" validate:"required,oneof=all cpu memory load io network"`
|
||||
@@ -26,24 +30,33 @@ type Process struct {
|
||||
}
|
||||
|
||||
type MonitorSetting struct {
|
||||
MonitorStatus string `json:"monitorStatus"`
|
||||
MonitorStoreDays string `json:"monitorStoreDays"`
|
||||
MonitorInterval string `json:"monitorInterval"`
|
||||
DefaultNetwork string `json:"defaultNetwork"`
|
||||
DefaultIO string `json:"defaultIO"`
|
||||
GPUMonitorStatus string `json:"gpuMonitorStatus"`
|
||||
GPUMonitorStoreDays string `json:"gpuMonitorStoreDays"`
|
||||
GPUMonitorInterval string `json:"gpuMonitorInterval"`
|
||||
VLLMMonitorStatus string `json:"vllmMonitorStatus"`
|
||||
VLLMMonitorStoreDays string `json:"vllmMonitorStoreDays"`
|
||||
VLLMMonitorInterval string `json:"vllmMonitorInterval"`
|
||||
MonitorStatus string `json:"monitorStatus"`
|
||||
MonitorStoreDays string `json:"monitorStoreDays"`
|
||||
MonitorInterval string `json:"monitorInterval"`
|
||||
DefaultNetwork string `json:"defaultNetwork"`
|
||||
DefaultIO string `json:"defaultIO"`
|
||||
}
|
||||
|
||||
type MonitorSettingUpdate struct {
|
||||
Key string `json:"key" validate:"required,oneof=MonitorStatus MonitorStoreDays MonitorInterval DefaultNetwork DefaultIO"`
|
||||
Key string `json:"key" validate:"required,oneof=MonitorStatus MonitorStoreDays MonitorInterval GPUMonitorStatus GPUMonitorStoreDays GPUMonitorInterval VLLMMonitorStatus VLLMMonitorStoreDays VLLMMonitorInterval DefaultNetwork DefaultIO"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type MonitorGPUOptions struct {
|
||||
Supported bool `json:"supported"`
|
||||
GPUType string `json:"gpuType"`
|
||||
ChartHide []GPUChartHide `json:"chartHide"`
|
||||
Options []string `json:"options"`
|
||||
}
|
||||
type GPUChartHide struct {
|
||||
DeviceID string `json:"deviceID"`
|
||||
Legacy bool `json:"legacy"`
|
||||
ProductName string `json:"productName"`
|
||||
Type string `json:"type"`
|
||||
Process bool `json:"process"`
|
||||
@@ -55,23 +68,54 @@ type GPUChartHide struct {
|
||||
Speed bool `json:"speed"`
|
||||
}
|
||||
type MonitorGPUSearch struct {
|
||||
Aggregation string `json:"aggregation" validate:"omitempty,oneof=avg max"`
|
||||
DeviceID string `json:"deviceID"`
|
||||
Legacy bool `json:"legacy"`
|
||||
ProductName string `json:"productName"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
}
|
||||
type MonitorGPUData struct {
|
||||
Date []time.Time `json:"date"`
|
||||
GPUValue []float64 `json:"gpuValue"`
|
||||
TemperatureValue []float64 `json:"temperatureValue"`
|
||||
PowerTotal []float64 `json:"powerTotal"`
|
||||
PowerUsed []float64 `json:"powerUsed"`
|
||||
PowerPercent []float64 `json:"powerPercent"`
|
||||
MemoryTotal []float64 `json:"memoryTotal"`
|
||||
MemoryUsed []float64 `json:"memoryUsed"`
|
||||
MemoryPercent []float64 `json:"memoryPercent"`
|
||||
SpeedValue []int `json:"speedValue"`
|
||||
MemoryActivity []*float64 `json:"memoryActivity"`
|
||||
EncoderUtil []*float64 `json:"encoderUtil"`
|
||||
DecoderUtil []*float64 `json:"decoderUtil"`
|
||||
JPEGUtil []*float64 `json:"jpegUtil"`
|
||||
OFAUtil []*float64 `json:"ofaUtil"`
|
||||
MediaUtil []*float64 `json:"mediaUtil"`
|
||||
ComputeUtil []*float64 `json:"computeUtil"`
|
||||
CopyUtil []*float64 `json:"copyUtil"`
|
||||
HotspotTemperature []*float64 `json:"hotspotTemperature"`
|
||||
FanRPM []*float64 `json:"fanRPM"`
|
||||
AICPUUtil []*float64 `json:"aiCPUUtil"`
|
||||
CtrlCPUUtil []*float64 `json:"ctrlCPUUtil"`
|
||||
DDRUsed []*float64 `json:"ddrUsed"`
|
||||
DDRTotal []*float64 `json:"ddrTotal"`
|
||||
HBMUsed []*float64 `json:"hbmUsed"`
|
||||
HBMTotal []*float64 `json:"hbmTotal"`
|
||||
DDRBandwidth []*float64 `json:"ddrBandwidth"`
|
||||
HBMBandwidth []*float64 `json:"hbmBandwidth"`
|
||||
MemoryBandwidth []*float64 `json:"memoryBandwidth"`
|
||||
MediaFrequency []*float64 `json:"mediaFrequency"`
|
||||
HugepagesUsed []*float64 `json:"hugepagesUsed"`
|
||||
HugepagesTotal []*float64 `json:"hugepagesTotal"`
|
||||
|
||||
ProcessCount []int `json:"processCount"`
|
||||
BucketSeconds int64 `json:"bucketSeconds"`
|
||||
SampleCount int64 `json:"sampleCount"`
|
||||
MemoryTemperatureValue []*float64 `json:"memoryTemperatureValue"`
|
||||
FrequencyValue []*float64 `json:"frequencyValue"`
|
||||
MemoryFrequencyValue []*float64 `json:"memoryFrequencyValue"`
|
||||
Date []time.Time `json:"date"`
|
||||
GPUValue []*float64 `json:"gpuValue"`
|
||||
TemperatureValue []*float64 `json:"temperatureValue"`
|
||||
PowerTotal []*float64 `json:"powerTotal"`
|
||||
PowerUsed []*float64 `json:"powerUsed"`
|
||||
PowerPercent []*float64 `json:"powerPercent"`
|
||||
MemoryTotal []*float64 `json:"memoryTotal"`
|
||||
MemoryUsed []*float64 `json:"memoryUsed"`
|
||||
MemoryPercent []*float64 `json:"memoryPercent"`
|
||||
SpeedValue []*float64 `json:"speedValue"`
|
||||
|
||||
ProcessCount []*float64 `json:"processCount"`
|
||||
GPUProcesses [][]GPUProcess `json:"gpuProcesses"`
|
||||
}
|
||||
|
||||
@@ -81,3 +125,28 @@ type GPUProcess struct {
|
||||
ProcessName string `json:"processName"`
|
||||
UsedMemory string `json:"usedMemory"`
|
||||
}
|
||||
|
||||
type MonitorVLLMSearch struct {
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
StartTime time.Time `json:"startTime" validate:"required"`
|
||||
EndTime time.Time `json:"endTime" validate:"required"`
|
||||
Aggregation string `json:"aggregation" validate:"omitempty,oneof=avg max"`
|
||||
}
|
||||
|
||||
type MonitorVLLMData struct {
|
||||
SampleCount int64 `json:"sampleCount"`
|
||||
BucketSeconds int64 `json:"bucketSeconds"`
|
||||
Points []model.MonitorVLLM `json:"points"`
|
||||
}
|
||||
|
||||
type MonitorVLLMCurrent struct {
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
}
|
||||
|
||||
type MonitorVLLMClean struct {
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
}
|
||||
|
||||
type MonitorClean struct {
|
||||
Type string `json:"type" validate:"required,oneof=host gpu"`
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ type RootCertOperate struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Mode string `json:"mode"`
|
||||
EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"`
|
||||
EncryptionMode string `json:"encryptionMode"`
|
||||
PassPhrase string `json:"passPhrase"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
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 {
|
||||
BaseModel
|
||||
|
||||
UUID string `gorm:"size:64;not null;uniqueIndex" json:"uuid"`
|
||||
ReadOnly bool `gorm:"not null;default:false;uniqueIndex:idx_docker_port_guard_endpoint" json:"-"`
|
||||
Family string `gorm:"size:16;not null;uniqueIndex:idx_docker_port_guard_endpoint" json:"family"`
|
||||
HostIP string `gorm:"size:64;not null;uniqueIndex:idx_docker_port_guard_endpoint" json:"hostIP"`
|
||||
HostPort uint16 `gorm:"not null;uniqueIndex:idx_docker_port_guard_endpoint" json:"hostPort"`
|
||||
Protocol string `gorm:"size:8;not null;uniqueIndex:idx_docker_port_guard_endpoint" json:"protocol"`
|
||||
Mode string `gorm:"size:32;not null" json:"mode"`
|
||||
Sources string `gorm:"type:text" json:"-"`
|
||||
Description string `gorm:"type:text" json:"description"`
|
||||
NativeAction string `gorm:"size:32;not null;default:''" json:"-"`
|
||||
NativeRules string `gorm:"type:text" json:"-"`
|
||||
Sequence int64 `gorm:"not null;default:0" json:"-"`
|
||||
}
|
||||
|
||||
type ForwardingRule struct {
|
||||
BaseModel
|
||||
|
||||
Family string `gorm:"size:16;not null;uniqueIndex:idx_forwarding_rule_identity" json:"family"`
|
||||
Protocol string `gorm:"size:8;not null;uniqueIndex:idx_forwarding_rule_identity" json:"protocol"`
|
||||
Port string `gorm:"size:32;not null;uniqueIndex:idx_forwarding_rule_identity" json:"port"`
|
||||
TargetIP string `gorm:"size:64;not null;uniqueIndex:idx_forwarding_rule_identity" json:"targetIP"`
|
||||
TargetPort string `gorm:"size:32;not null;uniqueIndex:idx_forwarding_rule_identity" json:"targetPort"`
|
||||
Interface string `gorm:"size:32;not null;default:'';uniqueIndex:idx_forwarding_rule_identity" json:"interface"`
|
||||
}
|
||||
|
||||
type FirewallRule struct {
|
||||
UUID string `gorm:"size:64;primaryKey" json:"uuid"`
|
||||
Family string `gorm:"size:16;not null" json:"family"`
|
||||
|
||||
Protocol string `gorm:"size:32;not null" json:"protocol"`
|
||||
SourceAddress string `gorm:"size:255" json:"sourceAddress"`
|
||||
SourcePort string `gorm:"size:64" json:"sourcePort"`
|
||||
DestinationAddress string `gorm:"size:255" json:"destinationAddress"`
|
||||
DestinationPort string `gorm:"size:64" json:"destinationPort"`
|
||||
Interface string `gorm:"size:128" json:"interface"`
|
||||
ConnectionStates string `gorm:"type:text" json:"connectionStates"`
|
||||
Action string `gorm:"size:32;not null" json:"action"`
|
||||
Description string `gorm:"type:text" json:"description"`
|
||||
CompatibilityError string `gorm:"type:text" json:"compatibilityError,omitempty"`
|
||||
Priority *int `json:"priority,omitempty"`
|
||||
Sequence *int64 `gorm:"index" json:"sequence,omitempty"`
|
||||
|
||||
Origin string `gorm:"size:32;not null" json:"origin"`
|
||||
Owner string `gorm:"size:320;not null" json:"owner"`
|
||||
Revision uint `gorm:"not null;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
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type MonitorBase struct {
|
||||
BaseModel
|
||||
Cpu float64 `json:"cpu"`
|
||||
@@ -33,14 +35,78 @@ type MonitorNetwork struct {
|
||||
}
|
||||
|
||||
type MonitorGPU struct {
|
||||
MemoryUtil *float64 `json:"memoryUtil"`
|
||||
MemoryActivity *float64 `json:"memoryActivity"`
|
||||
EncoderUtil *float64 `json:"encoderUtil"`
|
||||
DecoderUtil *float64 `json:"decoderUtil"`
|
||||
JPEGUtil *float64 `json:"jpegUtil"`
|
||||
OFAUtil *float64 `json:"ofaUtil"`
|
||||
MediaUtil *float64 `json:"mediaUtil"`
|
||||
ComputeUtil *float64 `json:"computeUtil"`
|
||||
CopyUtil *float64 `json:"copyUtil"`
|
||||
HotspotTemperature *float64 `json:"hotspotTemperature"`
|
||||
FanRPM *float64 `json:"fanRPM"`
|
||||
AICPUUtil *float64 `json:"aiCPUUtil"`
|
||||
CtrlCPUUtil *float64 `json:"ctrlCPUUtil"`
|
||||
DDRUsed *float64 `json:"ddrUsed"`
|
||||
DDRTotal *float64 `json:"ddrTotal"`
|
||||
HBMUsed *float64 `json:"hbmUsed"`
|
||||
HBMTotal *float64 `json:"hbmTotal"`
|
||||
DDRBandwidth *float64 `json:"ddrBandwidth"`
|
||||
HBMBandwidth *float64 `json:"hbmBandwidth"`
|
||||
MemoryBandwidth *float64 `json:"memoryBandwidth"`
|
||||
MediaFrequency *float64 `json:"mediaFrequency"`
|
||||
HugepagesUsed *float64 `json:"hugepagesUsed"`
|
||||
HugepagesTotal *float64 `json:"hugepagesTotal"`
|
||||
|
||||
MemoryTemperature *float64 `json:"memoryTemperature"`
|
||||
DeviceID string `json:"deviceID"`
|
||||
DeviceType string `json:"deviceType"`
|
||||
ProcessStatus string `json:"processStatus"`
|
||||
Frequency *float64 `json:"frequency"`
|
||||
MemoryFrequency *float64 `json:"memoryFrequency"`
|
||||
IntervalSeconds int `json:"intervalSeconds"`
|
||||
BaseModel
|
||||
ProductName string `json:"productName"`
|
||||
GPUUtil float64 `json:"gpuUtil"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
PowerDraw float64 `json:"powerDraw"`
|
||||
MaxPowerLimit float64 `json:"maxPowerLimit"`
|
||||
MemUsed float64 `json:"memUsed"`
|
||||
MemTotal float64 `json:"memTotal"`
|
||||
FanSpeed int `json:"fanSpeed"`
|
||||
Processes string `json:"processes"`
|
||||
ProductName string `json:"productName"`
|
||||
GPUUtil *float64 `json:"gpuUtil"`
|
||||
Temperature *float64 `json:"temperature"`
|
||||
PowerDraw *float64 `json:"powerDraw"`
|
||||
MaxPowerLimit *float64 `json:"maxPowerLimit"`
|
||||
MemUsed *float64 `json:"memUsed"`
|
||||
MemTotal *float64 `json:"memTotal"`
|
||||
FanSpeed *float64 `json:"fanSpeed"`
|
||||
Processes string `json:"processes"`
|
||||
}
|
||||
|
||||
type MonitorVLLM struct {
|
||||
ID uint `json:"-" gorm:"primarykey;autoIncrement"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
AppInstallID uint `json:"appInstallID"`
|
||||
Status string `json:"status"`
|
||||
RawMetrics string `json:"-"`
|
||||
HistogramDeltas string `json:"-"`
|
||||
|
||||
Running *float64 `json:"running"`
|
||||
Waiting *float64 `json:"waiting"`
|
||||
CacheUsage *float64 `json:"cacheUsage"`
|
||||
PromptThroughput *float64 `json:"promptThroughput"`
|
||||
GenerationThroughput *float64 `json:"generationThroughput"`
|
||||
RequestThroughput *float64 `json:"requestThroughput"`
|
||||
TimeToFirstToken *float64 `json:"timeToFirstToken"`
|
||||
TimePerOutputToken *float64 `json:"timePerOutputToken"`
|
||||
RequestLatency *float64 `json:"requestLatency"`
|
||||
PrefillTime *float64 `json:"prefillTime"`
|
||||
DecodeTime *float64 `json:"decodeTime"`
|
||||
TimeToFirstTokenP50 *float64 `json:"timeToFirstTokenP50"`
|
||||
TimeToFirstTokenP90 *float64 `json:"timeToFirstTokenP90"`
|
||||
TimeToFirstTokenP95 *float64 `json:"timeToFirstTokenP95"`
|
||||
TimeToFirstTokenP99 *float64 `json:"timeToFirstTokenP99"`
|
||||
TimePerOutputTokenP50 *float64 `json:"timePerOutputTokenP50"`
|
||||
TimePerOutputTokenP90 *float64 `json:"timePerOutputTokenP90"`
|
||||
TimePerOutputTokenP95 *float64 `json:"timePerOutputTokenP95"`
|
||||
TimePerOutputTokenP99 *float64 `json:"timePerOutputTokenP99"`
|
||||
RequestLatencyP50 *float64 `json:"requestLatencyP50"`
|
||||
RequestLatencyP90 *float64 `json:"requestLatencyP90"`
|
||||
RequestLatencyP95 *float64 `json:"requestLatencyP95"`
|
||||
RequestLatencyP99 *float64 `json:"requestLatencyP99"`
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type IDockerPortGuardRepo interface {
|
||||
ListManaged(context.Context) ([]model.DockerPortGuardPolicy, error)
|
||||
ListRuntimeReadOnly(context.Context) ([]model.DockerPortGuardPolicy, error)
|
||||
DeleteBatch(context.Context, []string) error
|
||||
UpsertBatch(context.Context, []model.DockerPortGuardPolicy) error
|
||||
ReplaceRuntimeReadOnly(context.Context, []model.DockerPortGuardPolicy) error
|
||||
}
|
||||
|
||||
type DockerPortGuardRepo struct{}
|
||||
|
||||
func NewIDockerPortGuardRepo() IDockerPortGuardRepo { return &DockerPortGuardRepo{} }
|
||||
|
||||
func (r *DockerPortGuardRepo) ListManaged(ctx context.Context) ([]model.DockerPortGuardPolicy, error) {
|
||||
var policies []model.DockerPortGuardPolicy
|
||||
err := global.DB.WithContext(ctx).
|
||||
Where("read_only = ?", false).
|
||||
Order("family, host_ip, host_port, protocol").
|
||||
Find(&policies).Error
|
||||
return policies, err
|
||||
}
|
||||
|
||||
func (r *DockerPortGuardRepo) 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 {
|
||||
return global.DB.WithContext(ctx).
|
||||
Where("read_only = ? AND uuid IN ?", false, uuids).
|
||||
Delete(&model.DockerPortGuardPolicy{}).Error
|
||||
}
|
||||
|
||||
func (r *DockerPortGuardRepo) UpsertBatch(ctx context.Context, policies []model.DockerPortGuardPolicy) error {
|
||||
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
for i := range policies {
|
||||
policies[i].ReadOnly = false
|
||||
if err := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "read_only"}, {Name: "family"}, {Name: "host_ip"}, {Name: "host_port"}, {Name: "protocol"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"mode", "sources", "description", "updated_at"}),
|
||||
}).Create(&policies[i]).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrFirewallRuleRevisionConflict = errors.New("firewall rule revision conflict")
|
||||
ErrFirewallPersistenceInvalid = errors.New("invalid firewall persistence record")
|
||||
)
|
||||
|
||||
type IFirewallRuleRepo interface {
|
||||
Create(context.Context, *model.FirewallRule) error
|
||||
GetByUUID(context.Context, string) (model.FirewallRule, error)
|
||||
List(context.Context, ...DBOption) ([]model.FirewallRule, error)
|
||||
UpdateWithRevision(context.Context, string, uint, map[string]interface{}) error
|
||||
DeleteWithRevision(context.Context, string, uint) error
|
||||
}
|
||||
|
||||
type FirewallRuleRepo struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewIFirewallRuleRepo() IFirewallRuleRepo {
|
||||
return &FirewallRuleRepo{}
|
||||
}
|
||||
|
||||
func NewFirewallRuleRepo(db *gorm.DB) *FirewallRuleRepo {
|
||||
return &FirewallRuleRepo{db: db}
|
||||
}
|
||||
|
||||
func (r *FirewallRuleRepo) Create(ctx context.Context, rule *model.FirewallRule) error {
|
||||
if err := prepareFirewallRule(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.dbFor(ctx).Create(rule).Error
|
||||
}
|
||||
|
||||
func (r *FirewallRuleRepo) GetByUUID(ctx context.Context, ruleUUID string) (model.FirewallRule, error) {
|
||||
var rule model.FirewallRule
|
||||
err := r.dbFor(ctx).Where("uuid = ?", ruleUUID).First(&rule).Error
|
||||
return rule, err
|
||||
}
|
||||
|
||||
func (r *FirewallRuleRepo) List(ctx context.Context, opts ...DBOption) ([]model.FirewallRule, error) {
|
||||
var rules []model.FirewallRule
|
||||
db := r.dbFor(ctx).Model(&model.FirewallRule{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return rules, db.Find(&rules).Error
|
||||
}
|
||||
|
||||
func (r *FirewallRuleRepo) UpdateWithRevision(ctx context.Context, ruleUUID string, expectedRevision uint, updates map[string]interface{}) error {
|
||||
updates = sanitizeRuleUpdates(updates)
|
||||
updates["revision"] = gorm.Expr("revision + 1")
|
||||
result := r.dbFor(ctx).Model(&model.FirewallRule{}).
|
||||
Where("uuid = ? AND revision = ?", ruleUUID, expectedRevision).
|
||||
Updates(updates)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return ErrFirewallRuleRevisionConflict
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *FirewallRuleRepo) DeleteWithRevision(ctx context.Context, ruleUUID string, expectedRevision uint) error {
|
||||
result := r.dbFor(ctx).
|
||||
Where("uuid = ? AND revision = ?", ruleUUID, expectedRevision).
|
||||
Delete(&model.FirewallRule{})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return ErrFirewallRuleRevisionConflict
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *FirewallRuleRepo) dbFor(ctx context.Context) *gorm.DB {
|
||||
return firewallDB(ctx, r.db)
|
||||
}
|
||||
|
||||
func firewallDB(ctx context.Context, fallback *gorm.DB) *gorm.DB {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
if tx, ok := ctx.Value(constant.DB).(*gorm.DB); ok && tx != nil {
|
||||
return tx.WithContext(ctx)
|
||||
}
|
||||
if fallback == nil {
|
||||
fallback = global.DB
|
||||
}
|
||||
return fallback.WithContext(ctx)
|
||||
}
|
||||
|
||||
func prepareFirewallRule(rule *model.FirewallRule) error {
|
||||
if rule == nil {
|
||||
return fmt.Errorf("%w: rule is nil", ErrFirewallPersistenceInvalid)
|
||||
}
|
||||
if rule.Family == "" || rule.Protocol == "" || rule.Action == "" {
|
||||
return fmt.Errorf("%w: atomic rule identity fields are required", ErrFirewallPersistenceInvalid)
|
||||
}
|
||||
if rule.UUID == "" {
|
||||
rule.UUID = uuid.NewString()
|
||||
}
|
||||
if rule.Revision == 0 {
|
||||
rule.Revision = 1
|
||||
}
|
||||
if rule.Origin == "" {
|
||||
rule.Origin = constant.FirewallRuleOriginCreated
|
||||
}
|
||||
if rule.Owner == "" {
|
||||
rule.Owner = constant.FirewallRuleSourceUser
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sanitizeRuleUpdates(updates map[string]interface{}) map[string]interface{} {
|
||||
result := cloneUpdates(updates)
|
||||
delete(result, "id")
|
||||
delete(result, "uuid")
|
||||
delete(result, "revision")
|
||||
delete(result, "created_at")
|
||||
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
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type IForwardingRuleRepo interface {
|
||||
List(context.Context) ([]model.ForwardingRule, error)
|
||||
ReplaceAll(context.Context, []model.ForwardingRule) error
|
||||
}
|
||||
|
||||
type ForwardingRuleRepo struct{}
|
||||
|
||||
func NewIForwardingRuleRepo() IForwardingRuleRepo { return &ForwardingRuleRepo{} }
|
||||
|
||||
func (r *ForwardingRuleRepo) List(ctx context.Context) ([]model.ForwardingRule, error) {
|
||||
var rules []model.ForwardingRule
|
||||
err := global.DB.WithContext(ctx).Order("id ASC").Find(&rules).Error
|
||||
return rules, err
|
||||
}
|
||||
|
||||
func (r *ForwardingRuleRepo) ReplaceAll(ctx context.Context, rules []model.ForwardingRule) error {
|
||||
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&model.ForwardingRule{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
return tx.Create(&rules).Error
|
||||
})
|
||||
}
|
||||
+103
-13
@@ -1,6 +1,8 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
@@ -10,11 +12,25 @@ import (
|
||||
|
||||
type MonitorRepo struct{}
|
||||
|
||||
type GPUHistoryPoint struct {
|
||||
model.MonitorGPU
|
||||
Bucket int64
|
||||
PowerPercent *float64
|
||||
MemoryPercent *float64
|
||||
ProcessCount *float64
|
||||
}
|
||||
|
||||
type IMonitorRepo interface {
|
||||
CleanHost() error
|
||||
CleanGPU() error
|
||||
GetBase(opts ...DBOption) ([]model.MonitorBase, error)
|
||||
GetGPU(opts ...DBOption) ([]model.MonitorGPU, error)
|
||||
CountGPU(opts ...DBOption) (int64, error)
|
||||
GetGPUHistory(start time.Time, bucketSeconds int64, aggregation string, opts ...DBOption) ([]GPUHistoryPoint, error)
|
||||
GetGPUDevices() ([]model.MonitorGPU, error)
|
||||
GetIO(opts ...DBOption) ([]model.MonitorIO, error)
|
||||
GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error)
|
||||
GetIONames() ([]string, error)
|
||||
GetNetworkNames() ([]string, error)
|
||||
|
||||
CreateMonitorBase(model model.MonitorBase) error
|
||||
BatchCreateMonitorGPU(list []model.MonitorGPU) error
|
||||
@@ -25,13 +41,26 @@ type IMonitorRepo interface {
|
||||
DelMonitorIO(timeForDelete time.Time) error
|
||||
DelMonitorNet(timeForDelete time.Time) error
|
||||
|
||||
WithByProductName(name string) DBOption
|
||||
WithByGPUDevice(deviceID, name string, legacy bool) DBOption
|
||||
}
|
||||
|
||||
func NewIMonitorRepo() IMonitorRepo {
|
||||
return &MonitorRepo{}
|
||||
}
|
||||
|
||||
func (s *MonitorRepo) CleanHost() error {
|
||||
for _, item := range []interface{}{&model.MonitorBase{}, &model.MonitorIO{}, &model.MonitorNetwork{}} {
|
||||
if err := global.MonitorDB.Where("1 = 1").Delete(item).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MonitorRepo) CleanGPU() error {
|
||||
return global.GPUMonitorDB.Where("1 = 1").Delete(&model.MonitorGPU{}).Error
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) GetBase(opts ...DBOption) ([]model.MonitorBase, error) {
|
||||
var data []model.MonitorBase
|
||||
db := global.MonitorDB
|
||||
@@ -59,14 +88,16 @@ func (u *MonitorRepo) GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, erro
|
||||
err := db.Find(&data).Error
|
||||
return data, err
|
||||
}
|
||||
func (u *MonitorRepo) GetGPU(opts ...DBOption) ([]model.MonitorGPU, error) {
|
||||
var data []model.MonitorGPU
|
||||
db := global.GPUMonitorDB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&data).Error
|
||||
return data, err
|
||||
func (u *MonitorRepo) GetIONames() ([]string, error) {
|
||||
var names []string
|
||||
err := global.MonitorDB.Model(&model.MonitorIO{}).Distinct().Pluck("name", &names).Error
|
||||
return names, err
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) GetNetworkNames() ([]string, error) {
|
||||
var names []string
|
||||
err := global.MonitorDB.Model(&model.MonitorNetwork{}).Distinct().Pluck("name", &names).Error
|
||||
return names, err
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) CreateMonitorBase(model model.MonitorBase) error {
|
||||
@@ -97,8 +128,67 @@ func (s *MonitorRepo) DelMonitorGPU(timeForDelete time.Time) error {
|
||||
return global.GPUMonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorGPU{}).Error
|
||||
}
|
||||
|
||||
func (s *MonitorRepo) WithByProductName(name string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("product_name = ?", name)
|
||||
func (u *MonitorRepo) GetGPUDevices() ([]model.MonitorGPU, error) {
|
||||
var data []model.MonitorGPU
|
||||
err := global.GPUMonitorDB.Model(&model.MonitorGPU{}).Select("device_id, product_name, device_type").Group("device_id, product_name, device_type").Order("product_name, device_id").Find(&data).Error
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) WithByGPUDevice(deviceID, name string, legacy bool) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
if deviceID != "" {
|
||||
return db.Where("device_id = ?", deviceID)
|
||||
}
|
||||
db = db.Where("product_name = ?", name)
|
||||
if legacy {
|
||||
db = db.Where("device_id IS NULL OR device_id = ''")
|
||||
}
|
||||
return db
|
||||
}
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) CountGPU(opts ...DBOption) (int64, error) {
|
||||
db := global.GPUMonitorDB.Model(&model.MonitorGPU{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
var count int64
|
||||
err := db.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) GetGPUHistory(start time.Time, bucketSeconds int64, aggregation string, opts ...DBOption) ([]GPUHistoryPoint, error) {
|
||||
db := global.GPUMonitorDB.Model(&model.MonitorGPU{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
expressions := []string{
|
||||
"CASE WHEN max_power_limit > 0 THEN 100.0 * power_draw / max_power_limit END",
|
||||
"CASE WHEN mem_total > 0 AND mem_used IS NOT NULL THEN 100.0 * mem_used / mem_total ELSE memory_util END",
|
||||
"CASE WHEN (process_status = 'ok' OR process_status IS NULL OR process_status = '') AND json_valid(processes) THEN CASE WHEN json_type(processes) = 'array' THEN json_array_length(processes) END END",
|
||||
}
|
||||
aliases := []string{"power_percent", "memory_percent", "process_count"}
|
||||
columns := []string{"*"}
|
||||
if bucketSeconds > 0 {
|
||||
operation := "AVG"
|
||||
if aggregation == "max" {
|
||||
operation = "MAX"
|
||||
}
|
||||
columns = []string{fmt.Sprintf("(CAST(strftime('%%s', created_at) AS INTEGER) - %d) / %d AS bucket", start.Unix(), bucketSeconds)}
|
||||
for _, column := range []string{"memory_activity", "encoder_util", "decoder_util", "jpeg_util", "ofa_util", "media_util", "compute_util", "copy_util", "hotspot_temperature", "fan_rpm", "ai_cpu_util", "ctrl_cpu_util", "ddr_used", "ddr_total", "hbm_used", "hbm_total", "ddr_bandwidth", "hbm_bandwidth", "memory_bandwidth", "media_frequency", "hugepages_used", "hugepages_total", "gpu_util", "temperature", "memory_temperature", "power_draw", "max_power_limit", "mem_used", "mem_total", "frequency", "memory_frequency", "fan_speed"} {
|
||||
columns = append(columns, operation+"("+column+") AS "+column)
|
||||
}
|
||||
for i := range expressions {
|
||||
expressions[i] = operation + "(" + expressions[i] + ")"
|
||||
}
|
||||
db = db.Group("bucket").Order("bucket ASC")
|
||||
} else {
|
||||
db = db.Order("created_at ASC, id ASC")
|
||||
}
|
||||
for i, expression := range expressions {
|
||||
columns = append(columns, expression+" AS "+aliases[i])
|
||||
}
|
||||
var data []GPUHistoryPoint
|
||||
err := db.Select(strings.Join(columns, ", ")).Scan(&data).Error
|
||||
return data, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type VLLMMonitorRepo struct{}
|
||||
|
||||
type VLLMHistoryPoint struct {
|
||||
model.MonitorVLLM
|
||||
Bucket int64
|
||||
HistogramSamples string
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) Create(point *model.MonitorVLLM) error {
|
||||
return global.VLLMMonitorDB.Create(point).Error
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) Latest(id uint) (model.MonitorVLLM, error) {
|
||||
var point model.MonitorVLLM
|
||||
db := global.VLLMMonitorDB.Where("app_install_id = ?", id)
|
||||
err := db.Order("created_at DESC, id DESC").First(&point).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return point, nil
|
||||
}
|
||||
return point, err
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) CleanTarget(id uint) error {
|
||||
return global.VLLMMonitorDB.Where("app_install_id = ?", id).Delete(&model.MonitorVLLM{}).Error
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) DeleteBefore(before time.Time) error {
|
||||
return global.VLLMMonitorDB.Where("created_at < ?", before).Delete(&model.MonitorVLLM{}).Error
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) Count(id uint, start, end time.Time) (int64, error) {
|
||||
var count int64
|
||||
db := global.VLLMMonitorDB.Model(&model.MonitorVLLM{}).Where("app_install_id = ? AND created_at >= ? AND created_at <= ?", id, start, end)
|
||||
err := db.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) History(id uint, start, end time.Time, seconds int64, aggregation string) ([]VLLMHistoryPoint, error) {
|
||||
db := global.VLLMMonitorDB.Model(&model.MonitorVLLM{}).Where("app_install_id = ? AND created_at >= ? AND created_at <= ?", id, start, end)
|
||||
metrics := []string{"running", "waiting", "cache_usage", "prompt_throughput", "generation_throughput", "request_throughput", "time_to_first_token", "time_per_output_token", "request_latency", "prefill_time", "decode_time", "time_to_first_token_p50", "time_to_first_token_p90", "time_to_first_token_p95", "time_to_first_token_p99", "time_per_output_token_p50", "time_per_output_token_p90", "time_per_output_token_p95", "time_per_output_token_p99", "request_latency_p50", "request_latency_p90", "request_latency_p95", "request_latency_p99"}
|
||||
var columns []string
|
||||
if seconds > 0 {
|
||||
operation := "AVG"
|
||||
if aggregation == "max" {
|
||||
operation = "MAX"
|
||||
}
|
||||
columns = []string{fmt.Sprintf("(CAST(strftime('%%s', created_at) AS INTEGER) - %d) / %d AS bucket", start.Unix(), seconds)}
|
||||
for _, column := range metrics {
|
||||
if aggregation != "max" && (strings.HasPrefix(column, "time_to_first_token_p") || strings.HasPrefix(column, "time_per_output_token_p") || strings.HasPrefix(column, "request_latency_p")) {
|
||||
continue
|
||||
}
|
||||
columns = append(columns, operation+"("+column+") AS "+column)
|
||||
}
|
||||
if aggregation != "max" {
|
||||
columns = append(columns, "json_group_array(json(NULLIF(histogram_deltas, ''))) AS histogram_samples")
|
||||
}
|
||||
db = db.Group("bucket").Order("bucket ASC")
|
||||
} else {
|
||||
columns = append([]string{"id", "created_at", "app_install_id", "status"}, metrics...)
|
||||
db = db.Order("created_at ASC, id ASC")
|
||||
}
|
||||
var points []VLLMHistoryPoint
|
||||
err := db.Select(strings.Join(columns, ", ")).Scan(&points).Error
|
||||
return points, err
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type SettingRepo struct{}
|
||||
@@ -19,12 +21,16 @@ type ISettingRepo interface {
|
||||
WithByKey(key string) DBOption
|
||||
|
||||
UpdateOrCreate(key, value string) error
|
||||
UpdateValues(map[string]string) error
|
||||
|
||||
GetDescription(opts ...DBOption) (model.CommonDescription, error)
|
||||
GetDescriptionList(opts ...DBOption) ([]model.CommonDescription, error)
|
||||
CreateDescription(data *model.CommonDescription) error
|
||||
SaveDescriptions(context.Context, []model.CommonDescription) error
|
||||
UpdateDescription(id string, val map[string]interface{}) error
|
||||
DelDescription(id string) error
|
||||
DeleteDescriptions(context.Context, string, []string, bool) (int64, error)
|
||||
WithDescriptionIDs(ids []string) DBOption
|
||||
WithByDescriptionID(id string) DBOption
|
||||
}
|
||||
|
||||
@@ -90,6 +96,25 @@ func (s *SettingRepo) UpdateOrCreate(key, value string) error {
|
||||
return global.DB.Model(&setting).UpdateColumn("value", value).Error
|
||||
}
|
||||
|
||||
func (s *SettingRepo) UpdateValues(values map[string]string) error {
|
||||
return global.DB.Transaction(func(tx *gorm.DB) error {
|
||||
for key, value := range values {
|
||||
var setting model.Setting
|
||||
err := tx.Where("key = ?", key).First(&setting).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if err := tx.Create(&model.Setting{Key: key, Value: value}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
} else if err := tx.Model(&setting).UpdateColumn("value", value).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SettingRepo) GetDescriptionList(opts ...DBOption) ([]model.CommonDescription, error) {
|
||||
var lists []model.CommonDescription
|
||||
db := global.DB.Model(&model.CommonDescription{})
|
||||
@@ -111,14 +136,42 @@ func (s *SettingRepo) GetDescription(opts ...DBOption) (model.CommonDescription,
|
||||
func (s *SettingRepo) CreateDescription(data *model.CommonDescription) error {
|
||||
return global.DB.Create(data).Error
|
||||
}
|
||||
|
||||
func (s *SettingRepo) SaveDescriptions(ctx context.Context, descriptions []model.CommonDescription) error {
|
||||
return global.DB.WithContext(ctx).Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"description"}),
|
||||
}).CreateInBatches(&descriptions, 100).Error
|
||||
}
|
||||
|
||||
func (s *SettingRepo) UpdateDescription(id string, val map[string]interface{}) error {
|
||||
return global.DB.Model(&model.CommonDescription{}).Where("id = ?", id).Updates(val).Error
|
||||
}
|
||||
func (s *SettingRepo) DelDescription(id string) error {
|
||||
return global.DB.Where("id = ?", id).Delete(&model.CommonDescription{}).Error
|
||||
}
|
||||
|
||||
func (s *SettingRepo) DeleteDescriptions(ctx context.Context, kind string, ids []string, emptyOnly bool) (int64, error) {
|
||||
var deleted int64
|
||||
for start := 0; start < len(ids); start += 500 {
|
||||
query := global.DB.WithContext(ctx).Where("type = ? AND id IN ?", kind, ids[start:min(start+500, len(ids))])
|
||||
if emptyOnly {
|
||||
query = query.Where("description = ? AND is_pinned = ?", "", false)
|
||||
}
|
||||
result := query.Delete(&model.CommonDescription{})
|
||||
deleted += result.RowsAffected
|
||||
if result.Error != nil {
|
||||
return deleted, result.Error
|
||||
}
|
||||
}
|
||||
return deleted, nil
|
||||
}
|
||||
func (s *SettingRepo) WithByDescriptionID(id string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("id = ?", id)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SettingRepo) WithDescriptionIDs(ids []string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB { return db.Where("id IN ?", ids) }
|
||||
}
|
||||
|
||||
+102
-117
@@ -4,11 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
@@ -17,14 +14,13 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
alertconfig "github.com/1Panel-dev/1Panel/agent/utils/alert_config"
|
||||
alertwebhook "github.com/1Panel-dev/1Panel/agent/utils/alert_webhook"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/email"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack/providers"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
)
|
||||
|
||||
type AlertService struct{}
|
||||
@@ -109,7 +105,38 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
cronjobProjects := make(map[string]uint)
|
||||
var cronjobIDs []uint
|
||||
for _, item := range alerts {
|
||||
if alertUtil.GetCronJobType(item.Type) != "cronJob" {
|
||||
continue
|
||||
}
|
||||
if _, exists := cronjobProjects[item.Project]; exists {
|
||||
continue
|
||||
}
|
||||
id, parseErr := strconv.ParseUint(item.Project, 10, strconv.IntSize)
|
||||
if parseErr != nil || id == 0 {
|
||||
continue
|
||||
}
|
||||
cronjobProjects[item.Project] = uint(id)
|
||||
cronjobIDs = append(cronjobIDs, uint(id))
|
||||
}
|
||||
cronjobsByID := make(map[uint]model.Cronjob)
|
||||
if len(cronjobIDs) > 0 {
|
||||
cronjobs, err := cronjobRepo.List(repo.WithByIDs(cronjobIDs))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
for _, cronjob := range cronjobs {
|
||||
cronjobsByID[cronjob.ID] = cronjob
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range alerts {
|
||||
var taskName string
|
||||
if cronjob, exists := cronjobsByID[cronjobProjects[item.Project]]; exists && cronjob.Type == item.Type {
|
||||
taskName = cronjob.Name
|
||||
}
|
||||
|
||||
result = append(result, dto.AlertDTO{
|
||||
ID: item.ID,
|
||||
@@ -119,6 +146,7 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
||||
Method: item.Method,
|
||||
Title: item.Title,
|
||||
Project: item.Project,
|
||||
TaskName: taskName,
|
||||
Status: item.Status,
|
||||
SendCount: item.SendCount,
|
||||
AdvancedParams: item.AdvancedParams,
|
||||
@@ -190,6 +218,16 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
advanced, err := prepareCronJobAlertParams(create.Type, "", create.AdvancedParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
create.AdvancedParams = advanced
|
||||
if create.Status != constant.AlertDisable {
|
||||
if err := a.validateCronJobAlertChannels(create.Type, advanced, create.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
alertInfo.Status = constant.AlertEnable
|
||||
if err := copier.Copy(&alertInfo, &create); err != nil {
|
||||
return buserr.WithErr("ErrStructTransform", err)
|
||||
@@ -207,11 +245,24 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, 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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Status != constant.AlertDisable {
|
||||
if err := a.validateCronJobAlertChannels(req.Type, req.AdvancedParams, req.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -278,6 +329,9 @@ func (a AlertService) UpdateStatus(id uint, status string) error {
|
||||
return err
|
||||
}
|
||||
if status == constant.AlertEnable {
|
||||
if err := a.validateCronJobAlertChannels(alertInfo.Type, alertInfo.AdvancedParams, alertInfo.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -299,122 +353,14 @@ func (a AlertService) UpdateStatus(id uint, status string) error {
|
||||
}
|
||||
|
||||
func (a AlertService) GetDisks() ([]dto.DiskDTO, error) {
|
||||
var disks []dto.DiskDTO
|
||||
excludes := map[string]struct{}{
|
||||
"/mnt/cdrom": {}, "/boot": {}, "/boot/efi": {}, "/dev": {}, "/dev/shm": {},
|
||||
"/run/lock": {}, "/run": {}, "/run/shm": {}, "/run/user": {},
|
||||
infos := loadDiskInfo(true)
|
||||
disks := make([]dto.DiskDTO, 0, len(infos))
|
||||
for _, item := range infos {
|
||||
disks = append(disks, dto.DiskDTO(item))
|
||||
}
|
||||
stdout, err := executeDiskCommand()
|
||||
if err != nil {
|
||||
return disks, nil
|
||||
}
|
||||
|
||||
lines := strings.Split(stdout, "\n")
|
||||
var mounts []dto.AlertDiskInfo
|
||||
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 7 {
|
||||
continue
|
||||
}
|
||||
mountPoint := strings.Join(fields[6:], " ")
|
||||
if shouldExclude(fields, mountPoint, excludes) {
|
||||
continue
|
||||
}
|
||||
mounts = append(mounts, dto.AlertDiskInfo{Type: fields[1], Device: fields[0], Mount: mountPoint})
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
)
|
||||
wg.Add(len(mounts))
|
||||
for i := 0; i < len(mounts); i++ {
|
||||
go func(timeoutCh <-chan time.Time, mount dto.AlertDiskInfo) {
|
||||
defer wg.Done()
|
||||
|
||||
var itemData dto.DiskDTO
|
||||
itemData.Path = mount.Mount
|
||||
itemData.Type = mount.Type
|
||||
itemData.Device = mount.Device
|
||||
select {
|
||||
case <-timeoutCh:
|
||||
mu.Lock()
|
||||
disks = append(disks, itemData)
|
||||
mu.Unlock()
|
||||
global.LOG.Errorf("load disk info from %s failed, err: timeout", mount.Mount)
|
||||
default:
|
||||
state, err := disk.Usage(mount.Mount)
|
||||
if err != nil {
|
||||
mu.Lock()
|
||||
disks = append(disks, itemData)
|
||||
mu.Unlock()
|
||||
global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, err)
|
||||
return
|
||||
}
|
||||
itemData.Total = state.Total
|
||||
itemData.Free = state.Free
|
||||
itemData.Used = state.Used
|
||||
itemData.UsedPercent = state.UsedPercent
|
||||
itemData.InodesTotal = state.InodesTotal
|
||||
itemData.InodesUsed = state.InodesUsed
|
||||
itemData.InodesFree = state.InodesFree
|
||||
itemData.InodesUsedPercent = state.InodesUsedPercent
|
||||
mu.Lock()
|
||||
disks = append(disks, itemData)
|
||||
mu.Unlock()
|
||||
}
|
||||
}(time.After(5*time.Second), mounts[i])
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
sort.Slice(disks, func(i, j int) bool {
|
||||
return disks[i].Path < disks[j].Path
|
||||
})
|
||||
return disks, nil
|
||||
}
|
||||
|
||||
func executeDiskCommand() (string, error) {
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second))
|
||||
stdout, err := cmdMgr.RunWithStdout("df", "-hT", "-P")
|
||||
if err != nil {
|
||||
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
|
||||
stdout, err = cmdMgr2.RunWithStdout("df", "-lhT", "-P")
|
||||
}
|
||||
if err != nil {
|
||||
return stdout, err
|
||||
}
|
||||
var lines []string
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
if !strings.Contains(line, "/") || strings.Contains(line, "tmpfs") || strings.Contains(line, "snap/core") || strings.Contains(line, "udev") {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
if len(lines) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return strings.Join(lines, "\n"), nil
|
||||
}
|
||||
|
||||
func shouldExclude(fields []string, mountPoint string, excludes map[string]struct{}) bool {
|
||||
if strings.HasPrefix(mountPoint, "/snap") || len(strings.Split(mountPoint, "/")) > 10 {
|
||||
return true
|
||||
}
|
||||
if strings.TrimSpace(fields[1]) == "tmpfs" {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(fields[2], "K") {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(mountPoint, "docker") {
|
||||
return true
|
||||
}
|
||||
_, excluded := excludes[mountPoint]
|
||||
return excluded
|
||||
}
|
||||
|
||||
func (a AlertService) PageAlertLogs(search dto.AlertLogSearch) (int64, []dto.AlertLogDTO, error) {
|
||||
var (
|
||||
opts []repo.DBOption
|
||||
@@ -1021,6 +967,23 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
||||
alertRepo.WithByType(updateAlert.Type),
|
||||
alertRepo.WithByProject(updateAlert.Project),
|
||||
)
|
||||
advanced, err := prepareCronJobAlertParams(updateAlert.Type, alertInfo.AdvancedParams, updateAlert.AdvancedParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateAlert.AdvancedParams = advanced
|
||||
if alertUtil.GetCronJobType(updateAlert.Type) == "cronJob" {
|
||||
upMap["advanced_params"] = advanced
|
||||
}
|
||||
if newStatus == constant.AlertEnable {
|
||||
method := updateAlert.Method
|
||||
if method == "" {
|
||||
method = alertInfo.Method
|
||||
}
|
||||
if err := a.validateCronJobAlertChannels(updateAlert.Type, advanced, method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if alertInfo.ID > 0 {
|
||||
shouldUpdate := false
|
||||
@@ -1034,6 +997,9 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
||||
if val, ok := upMap["method"]; ok && val != "" && val != alertInfo.Method {
|
||||
shouldUpdate = true
|
||||
}
|
||||
if val, ok := upMap["advanced_params"]; ok && val != alertInfo.AdvancedParams {
|
||||
shouldUpdate = true
|
||||
}
|
||||
|
||||
if shouldUpdate {
|
||||
if err := alertRepo.Update(
|
||||
@@ -1055,3 +1021,22 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareCronJobAlertParams(alertType, previous, incoming string) (string, error) {
|
||||
if alertUtil.GetCronJobType(alertType) != "cronJob" {
|
||||
return incoming, nil
|
||||
}
|
||||
return alertUtil.MergeCronJobAlertParams(previous, incoming)
|
||||
}
|
||||
|
||||
func (a AlertService) validateCronJobAlertChannels(alertType, advanced, method string) error {
|
||||
if alertUtil.GetCronJobType(alertType) != "cronJob" {
|
||||
return nil
|
||||
}
|
||||
mode, err := alertUtil.CronJobAlertTriggerMode(advanced)
|
||||
if err != nil || mode != alertUtil.CronJobAlertSuccess {
|
||||
return err
|
||||
}
|
||||
_, err = a.validateAlertMethodReferences(method)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -30,10 +30,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ResourceAlertInterval = 30
|
||||
CheckIntervalSec = 3
|
||||
LoadCheckIntervalMin = 5
|
||||
sshIPLoginWindow = 30 * time.Minute
|
||||
ResourceAlertInterval = 30
|
||||
CheckIntervalSec = 3
|
||||
LoadCheckIntervalMin = 5
|
||||
sshIPLoginWindow = 30 * time.Minute
|
||||
sslAutoRenewAlertSkipDays = 31
|
||||
)
|
||||
|
||||
type AlertTaskHelper struct {
|
||||
@@ -529,11 +530,15 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to load ssh login logs: %v", err)
|
||||
}
|
||||
interfaceAddrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
global.LOG.Warnf("Failed to load local IP addresses for ssh login alert: %v", err)
|
||||
}
|
||||
count, records := summarizeSSHLoginHistories(
|
||||
histories,
|
||||
now,
|
||||
failedWindow,
|
||||
strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n"),
|
||||
sshSuccessLoginWhitelist(alert.AdvancedParams, interfaceAddrs),
|
||||
)
|
||||
isAlert := count >= int(alert.Count)
|
||||
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 {
|
||||
filtered := make([]model.LoginLog, 0, len(records))
|
||||
for _, record := range records {
|
||||
@@ -864,7 +882,7 @@ func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]s
|
||||
daysDiff := int(math.Ceil(
|
||||
ssl.ExpireDate.Sub(currentDate).Hours() / 24,
|
||||
))
|
||||
if daysDiff > 0 && int(cycle) >= daysDiff {
|
||||
if daysDiff > 0 && int(cycle) >= daysDiff && !shouldSuppressSSLExpiryAlert(ssl, daysDiff) {
|
||||
daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], ssl.PrimaryDomain)
|
||||
projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate)
|
||||
}
|
||||
@@ -872,6 +890,10 @@ func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]s
|
||||
return daysDiffMap, projectMap
|
||||
}
|
||||
|
||||
func shouldSuppressSSLExpiryAlert(ssl model.WebsiteSSL, remainingDays int) bool {
|
||||
return ssl.AutoRenew && remainingDays < sslAutoRenewAlertSkipDays
|
||||
}
|
||||
|
||||
func calculateWebsiteExpiryDays(websites []model.Website, cycle uint) (map[int][]string, map[uint][]time.Time) {
|
||||
currentDate := time.Now()
|
||||
daysDiffMap := make(map[int][]string)
|
||||
@@ -1044,50 +1066,37 @@ func processAllDisks(alert dto.AlertDTO) error {
|
||||
global.LOG.Errorf("error getting disk list, err: %v", err)
|
||||
return err
|
||||
}
|
||||
var errMsgs []string
|
||||
for _, item := range diskList {
|
||||
err := checkAndCreateDiskAlert(alert, item.Path)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("disk path %s process failed: %v", item.Path, err)
|
||||
errMsgs = append(errMsgs, errMsg)
|
||||
global.LOG.Errorf("%s", errMsg)
|
||||
if item.Total == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(errMsgs) > 0 {
|
||||
return fmt.Errorf("batch process disks failed, error count: %d, details: %s", len(errMsgs), strings.Join(errMsgs, "; "))
|
||||
checkAndCreateDiskAlert(alert, item.Path, &disk.UsageStat{Used: item.Used, UsedPercent: item.UsedPercent})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func processSingleDisk(alert dto.AlertDTO) error {
|
||||
err := checkAndCreateDiskAlert(alert, alert.Project)
|
||||
usageStat, err := loadDiskUsageWithTimeout(alert.Project, true)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s", err.Error())
|
||||
global.LOG.Errorf("error getting disk usage for %s, err: %v", alert.Project, err)
|
||||
return err
|
||||
}
|
||||
checkAndCreateDiskAlert(alert, alert.Project, usageStat)
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) error {
|
||||
usageStat, err := psutil.DISK.GetUsage(path, false)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("error getting disk usage for %s, err: %v", path, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func checkAndCreateDiskAlert(alert dto.AlertDTO, path string, usageStat *disk.UsageStat) {
|
||||
usedTotal, usedStr := calculateUsedTotal(alert.Cycle, usageStat)
|
||||
commonTotal := float64(alert.Count)
|
||||
if alert.Cycle == 1 {
|
||||
commonTotal *= 1024 * 1024 * 1024
|
||||
}
|
||||
if usedTotal < commonTotal {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
params := createAlertDiskParams(path, usedStr)
|
||||
sender := NewAlertSender(alert, alert.Project)
|
||||
sender.ResourceSend(path, params)
|
||||
return nil
|
||||
}
|
||||
|
||||
func calculateUsedTotal(cycle uint, usageStat *disk.UsageStat) (float64, string) {
|
||||
|
||||
@@ -40,14 +40,13 @@ const (
|
||||
appUpgradeDown
|
||||
appUpgradeMutated
|
||||
appUpgradeStarted
|
||||
appUpgradeReady
|
||||
appUpgradeCommitted
|
||||
)
|
||||
|
||||
const composeServiceLabel = "com.docker.compose.service"
|
||||
|
||||
var appUpgradeLocks sync.Map
|
||||
|
||||
const composeServiceLabel = "com.docker.compose.service"
|
||||
|
||||
type appUpgradeSnapshot interface {
|
||||
Restore() error
|
||||
Cleanup()
|
||||
@@ -439,15 +438,14 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
|
||||
t.LogSuccess(logStr)
|
||||
u.phase = appUpgradeStarted
|
||||
|
||||
t.LogStart(i18n.GetMsgByKey("UpgradeWaitReady"))
|
||||
containerNames, err := waitAppContainersReady(context.Background(), u.candidate)
|
||||
if err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("UpgradeWaitReady"), err)
|
||||
return err
|
||||
containerNames, discoverErr := discoverUpgradeContainerNames(u.candidate, u.envContent)
|
||||
if discoverErr != nil {
|
||||
t.Logf("WARNING: discover upgraded application containers failed: %v", discoverErr)
|
||||
} else if len(containerNames) > 0 {
|
||||
u.candidate.ContainerName = strings.Join(containerNames, ",")
|
||||
} else {
|
||||
t.Log("WARNING: no containers found for the upgraded application")
|
||||
}
|
||||
t.LogSuccess(i18n.GetMsgByKey("UpgradeWaitReady"))
|
||||
u.phase = appUpgradeReady
|
||||
u.candidate.ContainerName = strings.Join(containerNames, ",")
|
||||
u.candidate.Status = constant.StatusRunning
|
||||
u.candidate.Message = ""
|
||||
|
||||
@@ -472,6 +470,11 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
|
||||
} else if err = appInstallRepo.Save(context.Background(), &u.candidate); err != nil {
|
||||
return err
|
||||
}
|
||||
if discoverErr == nil && len(containerNames) > 0 {
|
||||
if syncErr := syncAppInstallStatus(&u.candidate, true); syncErr != nil {
|
||||
t.Logf("WARNING: sync upgraded application status failed: %v", syncErr)
|
||||
}
|
||||
}
|
||||
u.phase = appUpgradeCommitted
|
||||
u.deleteOldImages(t)
|
||||
return nil
|
||||
@@ -591,9 +594,6 @@ func (u *appUpgradeContext) rollback(t *task.Task) (rollbackErr error) {
|
||||
}
|
||||
|
||||
func (u *appUpgradeContext) finishRollback() error {
|
||||
if _, err := waitAppContainersReady(context.Background(), u.original); err != nil {
|
||||
return err
|
||||
}
|
||||
restored := u.original
|
||||
if err := appInstallRepo.Save(context.Background(), &restored); err != nil {
|
||||
return err
|
||||
@@ -881,28 +881,7 @@ func (s *upgradeFileSnapshot) Cleanup() {
|
||||
}
|
||||
}
|
||||
|
||||
type appContainerReadinessClient interface {
|
||||
ContainerList(context.Context, container.ListOptions) ([]container.Summary, error)
|
||||
ContainerInspect(context.Context, string) (container.InspectResponse, error)
|
||||
}
|
||||
|
||||
func waitAppContainersReady(ctx context.Context, install model.AppInstall) ([]string, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
return waitAppContainersReadyWithClient(ctx, client, install)
|
||||
}
|
||||
|
||||
func waitAppContainersReadyWithClient(ctx context.Context, client appContainerReadinessClient, install model.AppInstall) ([]string, error) {
|
||||
envContent, err := os.ReadFile(install.GetEnvPath())
|
||||
if err != nil {
|
||||
envContent, err = renderUpgradeEnv(&install, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
func discoverUpgradeContainerNames(install model.AppInstall, envContent []byte) ([]string, error) {
|
||||
project, err := docker.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), envContent, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -916,36 +895,24 @@ func waitAppContainersReadyWithClient(ctx context.Context, client appContainerRe
|
||||
if len(expectedServices) == 0 {
|
||||
return strings.Split(install.ContainerName, ","), nil
|
||||
}
|
||||
options := container.ListOptions{
|
||||
All: true,
|
||||
Filters: filters.NewArgs(
|
||||
filters.Arg("label", composeWorkdirLabel+"="+install.GetPath()),
|
||||
),
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
containers, err := client.ContainerList(ctx, options)
|
||||
defer client.Close()
|
||||
containers, err := client.ContainerList(context.Background(), container.ListOptions{
|
||||
All: true,
|
||||
Filters: filters.NewArgs(filters.Arg("label", composeWorkdirLabel+"="+install.GetPath())),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
foundServices := make(map[string]bool, len(expectedServices))
|
||||
containerNames := make([]string, 0, len(containers))
|
||||
for _, item := range containers {
|
||||
serviceName := item.Labels[composeServiceLabel]
|
||||
if _, ok := expectedServices[serviceName]; !ok {
|
||||
continue
|
||||
}
|
||||
if err = waitContainerReady(ctx, client, item.ID); err != nil {
|
||||
return nil, fmt.Errorf("container %s is not ready: %w", serviceName, err)
|
||||
}
|
||||
foundServices[serviceName] = true
|
||||
if len(item.Names) > 0 {
|
||||
if _, ok := expectedServices[item.Labels[composeServiceLabel]]; ok && len(item.Names) > 0 {
|
||||
containerNames = append(containerNames, strings.TrimPrefix(item.Names[0], "/"))
|
||||
}
|
||||
}
|
||||
for serviceName := range expectedServices {
|
||||
if !foundServices[serviceName] {
|
||||
return nil, fmt.Errorf("container for service %s was not created", serviceName)
|
||||
}
|
||||
}
|
||||
sort.Strings(containerNames)
|
||||
return containerNames, nil
|
||||
}
|
||||
|
||||
@@ -352,22 +352,34 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
||||
if dir != nil {
|
||||
logStr := i18n.GetMsgByKey("Stop") + i18n.GetMsgByKey("App")
|
||||
t.Log(logStr)
|
||||
cleanupFailed := false
|
||||
|
||||
if deleteReq.UseLifecycleScripts {
|
||||
if err = runScript(t, &install, "uninstall"); err != nil {
|
||||
return err
|
||||
if scriptErr := runScript(t, &install, "uninstall"); scriptErr != nil {
|
||||
cleanupFailed = true
|
||||
if !deleteReq.ForceDelete {
|
||||
return scriptErr
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out, err := compose.Down(install.GetComposePath())
|
||||
if err != nil && !deleteReq.ForceDelete {
|
||||
return handleErr(install, err, out)
|
||||
out, downErr := compose.Down(install.GetComposePath())
|
||||
if downErr != nil {
|
||||
cleanupFailed = true
|
||||
if !deleteReq.ForceDelete {
|
||||
return handleErr(install, downErr, out)
|
||||
}
|
||||
}
|
||||
if err = runScript(t, &install, "uninstall"); err != nil {
|
||||
_, _ = compose.Up(install.GetComposePath())
|
||||
return err
|
||||
if scriptErr := runScript(t, &install, "uninstall"); scriptErr != nil {
|
||||
cleanupFailed = true
|
||||
if !deleteReq.ForceDelete {
|
||||
_, _ = compose.Up(install.GetComposePath())
|
||||
return scriptErr
|
||||
}
|
||||
}
|
||||
}
|
||||
t.LogSuccess(logStr)
|
||||
if !cleanupFailed {
|
||||
t.LogSuccess(logStr)
|
||||
}
|
||||
if deleteReq.DeleteImage {
|
||||
content, err := op.GetContent(install.GetEnvPath())
|
||||
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)
|
||||
go func() {
|
||||
if err := uninstallTask.Execute(); err != nil && !deleteReq.ForceDelete {
|
||||
if err := uninstallTask.Execute(); err != nil {
|
||||
install.Status = constant.StatusError
|
||||
install.Message = err.Error()
|
||||
_ = appInstallRepo.Save(context.Background(), &install)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
@@ -75,6 +76,7 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
|
||||
EntryID: cronjob.ID,
|
||||
}
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
|
||||
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||
if alertInfo.SendCount != 0 {
|
||||
item.AlertCount = alertInfo.SendCount
|
||||
} else {
|
||||
@@ -98,9 +100,11 @@ func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, err
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
}
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))))
|
||||
item.AlertMethod = alertInfo.Method
|
||||
if alertInfo.SendCount != 0 {
|
||||
item.AlertTitle = alertInfo.Title
|
||||
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||
if alertInfo.Status == constant.AlertEnable {
|
||||
item.AlertCount = alertInfo.SendCount
|
||||
} else {
|
||||
item.AlertCount = 0
|
||||
@@ -195,11 +199,12 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
||||
}
|
||||
}
|
||||
item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID)
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))), repo.WithByStatus(constant.AlertEnable))
|
||||
if alertInfo.SendCount != 0 {
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))))
|
||||
item.AlertTitle = alertInfo.Title
|
||||
item.AlertMethod = alertInfo.Method
|
||||
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||
if alertInfo.Status == constant.AlertEnable {
|
||||
item.AlertCount = alertInfo.SendCount
|
||||
item.AlertTitle = alertInfo.Title
|
||||
item.AlertMethod = alertInfo.Method
|
||||
} else {
|
||||
item.AlertCount = 0
|
||||
}
|
||||
@@ -213,6 +218,17 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
||||
}
|
||||
|
||||
func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
||||
for _, item := range req {
|
||||
advanced, err := cronJobAlertAdvancedParams(item.AlertTriggerMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if item.AlertCount != 0 {
|
||||
if err := (AlertService{}).validateCronJobAlertChannels(item.Type, advanced, item.AlertMethod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, item := range req {
|
||||
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
|
||||
if cronjobItem.ID != 0 {
|
||||
@@ -395,17 +411,27 @@ func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
||||
} else {
|
||||
cronjob.Status = constant.StatusDisable
|
||||
}
|
||||
_ = cronjobRepo.Create(&cronjob)
|
||||
if item.AlertCount != 0 && item.AlertTitle != "" && item.AlertMethod != "" {
|
||||
if err := cronjobRepo.Create(&cronjob); err != nil {
|
||||
return err
|
||||
}
|
||||
if item.AlertTitle != "" && item.AlertMethod != "" {
|
||||
advanced, _ := cronJobAlertAdvancedParams(item.AlertTriggerMode)
|
||||
status := constant.AlertEnable
|
||||
if item.AlertCount == 0 {
|
||||
status = constant.AlertDisable
|
||||
}
|
||||
createAlert := dto.AlertCreate{
|
||||
Title: item.AlertTitle,
|
||||
SendCount: item.AlertCount,
|
||||
Method: item.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
Title: item.AlertTitle,
|
||||
SendCount: item.AlertCount,
|
||||
Method: item.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: status,
|
||||
AdvancedParams: advanced,
|
||||
}
|
||||
if err := NewIAlertService().CreateAlert(createAlert, operator); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = NewIAlertService().CreateAlert(createAlert, operator)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -562,6 +588,15 @@ func (u *CronjobService) HandleOnce(id uint) error {
|
||||
}
|
||||
|
||||
func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
|
||||
advanced, err := cronJobAlertAdvancedParams(req.AlertTriggerMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.AlertCount != 0 {
|
||||
if err := (AlertService{}).validateCronJobAlertChannels(req.Type, advanced, req.AlertMethod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name))
|
||||
if cronjob.ID != 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
@@ -603,12 +638,13 @@ func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
|
||||
}
|
||||
if req.AlertCount != 0 && req.AlertTitle != "" && req.AlertMethod != "" {
|
||||
createAlert := dto.AlertCreate{
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
AdvancedParams: advanced,
|
||||
}
|
||||
err := NewIAlertService().CreateAlert(createAlert, operator)
|
||||
if err != nil {
|
||||
@@ -682,6 +718,10 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
|
||||
}
|
||||
|
||||
func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string) error {
|
||||
advanced, err := cronJobAlertAdvancedParams(req.AlertTriggerMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var cronjob model.Cronjob
|
||||
if err := copier.Copy(&cronjob, &req); err != nil {
|
||||
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
@@ -697,6 +737,20 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
||||
if err != nil {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if req.AlertCount != 0 {
|
||||
previous, _ := alertRepo.Get(alertRepo.WithByType(cronModel.Type), alertRepo.WithByProject(strconv.Itoa(int(id))))
|
||||
merged, err := prepareCronJobAlertParams(cronModel.Type, previous.AdvancedParams, advanced)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
method := req.AlertMethod
|
||||
if method == "" {
|
||||
method = previous.Method
|
||||
}
|
||||
if err := (AlertService{}).validateCronJobAlertChannels(cronModel.Type, merged, method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
upMap := make(map[string]interface{})
|
||||
cronjob.EntryIDs = cronModel.EntryIDs
|
||||
cronjob.Type = cronModel.Type
|
||||
@@ -753,11 +807,12 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
||||
return err
|
||||
}
|
||||
updateAlert := dto.AlertCreate{
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronModel.ID)),
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronModel.ID)),
|
||||
AdvancedParams: advanced,
|
||||
}
|
||||
err = NewIAlertService().ExternalUpdateAlert(updateAlert, operator)
|
||||
if err != nil {
|
||||
@@ -766,6 +821,17 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
||||
return nil
|
||||
}
|
||||
|
||||
func cronJobAlertAdvancedParams(mode string) (string, error) {
|
||||
if mode == "" {
|
||||
return "", nil
|
||||
}
|
||||
data, err := json.Marshal(map[string]string{"alertTriggerMode": mode})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return alertUtil.MergeCronJobAlertParams("", string(data))
|
||||
}
|
||||
|
||||
func (u *CronjobService) UpdateStatus(id uint, status string) error {
|
||||
cronjob, _ := cronjobRepo.Get(repo.WithByID(id))
|
||||
if cronjob.ID == 0 {
|
||||
|
||||
@@ -412,6 +412,7 @@ func addSkipTask(source string, taskItem *task.Task) {
|
||||
taskItem.Log(i18n.GetMsgByKey("NoSuchResource"))
|
||||
return nil
|
||||
}, nil)
|
||||
taskItem.SubTasks[len(taskItem.SubTasks)-1].StepAlias = cronJobSkippedStep
|
||||
}
|
||||
|
||||
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ntp"
|
||||
@@ -56,10 +58,11 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
_ = taskRepo.Save(context.Background(), taskItem.Task)
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||
return
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, nil))
|
||||
}()
|
||||
return
|
||||
}
|
||||
@@ -70,19 +73,20 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
record.TaskID = ""
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
if err := taskItem.Execute(); err != nil {
|
||||
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||
if len(taskItem.ID) == 0 {
|
||||
storedTask, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||
if len(storedTask.ID) == 0 {
|
||||
record.TaskID = ""
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||
} else {
|
||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, nil))
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -482,8 +486,33 @@ func hasBackup(cronjobType string) bool {
|
||||
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log" || cronjobType == "cutWebsiteLog"
|
||||
}
|
||||
|
||||
func handleCronJobAlert(cronjob *model.Cronjob) {
|
||||
const cronJobSkippedStep = "cronjob-skipped"
|
||||
|
||||
func cronJobAlertResult(taskItem *task.Task, err error) string {
|
||||
if errors.Is(err, context.Canceled) || taskItem.Task.Status == constant.StatusCanceled ||
|
||||
(taskItem.TaskCtx != nil && taskItem.TaskCtx.Err() != nil) {
|
||||
return ""
|
||||
}
|
||||
if err != nil {
|
||||
return alertUtil.CronJobAlertFailed
|
||||
}
|
||||
if taskItem.Task.Status != constant.StatusSuccess {
|
||||
return ""
|
||||
}
|
||||
for _, subTask := range taskItem.SubTasks {
|
||||
if subTask.StepAlias != cronJobSkippedStep {
|
||||
return alertUtil.CronJobAlertSuccess
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func handleCronJobAlert(cronjob *model.Cronjob, result string) {
|
||||
if result == "" {
|
||||
return
|
||||
}
|
||||
pushAlert := dto.PushAlert{
|
||||
Result: result,
|
||||
TaskName: cronjob.Name,
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
|
||||
@@ -242,7 +242,7 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
|
||||
currentInfo.SwapMemoryUsed = swapInfo.Used
|
||||
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
|
||||
|
||||
currentInfo.DiskData = loadDiskInfo()
|
||||
currentInfo.DiskData = loadDiskInfo(false)
|
||||
currentInfo.GPUData, currentInfo.NPUData, currentInfo.XPUData = loadAcceleratorInfo()
|
||||
|
||||
if ioOption == "all" {
|
||||
@@ -455,24 +455,9 @@ type diskInfo struct {
|
||||
Device string
|
||||
}
|
||||
|
||||
func loadDiskInfo() []dto.DiskInfo {
|
||||
func loadDiskInfo(forceRefresh bool) []dto.DiskInfo {
|
||||
var datas []dto.DiskInfo
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second))
|
||||
format := `NR>1 && !/tmpfs|snap\/core|udev/ {printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $1, $2, $3, $4, $5, $6, $7}`
|
||||
stdout, err := cmdMgr.RunPipe(
|
||||
cmd.PipeCommand{Name: "df", Args: []string{"-hT", "-P"}},
|
||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||
)
|
||||
if err != nil {
|
||||
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
|
||||
stdout, err = cmdMgr2.RunPipe(
|
||||
cmd.PipeCommand{Name: "df", Args: []string{"-lhT", "-P"}},
|
||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||
)
|
||||
if err != nil {
|
||||
return datas
|
||||
}
|
||||
}
|
||||
stdout := loadDiskMounts()
|
||||
lines := strings.Split(stdout, "\n")
|
||||
|
||||
var mounts []diskInfo
|
||||
@@ -520,43 +505,22 @@ func loadDiskInfo() []dto.DiskInfo {
|
||||
itemData.Type = mount.Type
|
||||
itemData.Device = mount.Device
|
||||
|
||||
type diskResult struct {
|
||||
state *disk.UsageStat
|
||||
err error
|
||||
}
|
||||
resultCh := make(chan diskResult, 1)
|
||||
|
||||
go func() {
|
||||
state, err := psutil.DISK.GetUsage(mount.Mount, false)
|
||||
resultCh <- diskResult{state: state, err: err}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
global.LOG.Errorf("load disk info from %s failed, err: timeout", mount.Mount)
|
||||
case result := <-resultCh:
|
||||
if result.err != nil {
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, result.err)
|
||||
return
|
||||
}
|
||||
itemData.Total = result.state.Total
|
||||
itemData.Free = result.state.Free
|
||||
itemData.Used = result.state.Used
|
||||
itemData.UsedPercent = result.state.UsedPercent
|
||||
itemData.InodesTotal = result.state.InodesTotal
|
||||
itemData.InodesUsed = result.state.InodesUsed
|
||||
itemData.InodesFree = result.state.InodesFree
|
||||
itemData.InodesUsedPercent = result.state.InodesUsedPercent
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
state, err := loadDiskUsageWithTimeout(mount.Mount, forceRefresh)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, err)
|
||||
} else {
|
||||
itemData.Total = state.Total
|
||||
itemData.Free = state.Free
|
||||
itemData.Used = state.Used
|
||||
itemData.UsedPercent = state.UsedPercent
|
||||
itemData.InodesTotal = state.InodesTotal
|
||||
itemData.InodesUsed = state.InodesUsed
|
||||
itemData.InodesFree = state.InodesFree
|
||||
itemData.InodesUsedPercent = state.InodesUsedPercent
|
||||
}
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
}(mounts[i])
|
||||
}
|
||||
wg.Wait()
|
||||
@@ -567,6 +531,69 @@ func loadDiskInfo() []dto.DiskInfo {
|
||||
return datas
|
||||
}
|
||||
|
||||
var diskMountsMu sync.Mutex
|
||||
|
||||
func loadDiskMounts() string {
|
||||
if !diskMountsMu.TryLock() {
|
||||
return ""
|
||||
}
|
||||
resultCh := make(chan string, 1)
|
||||
go func() {
|
||||
var stdout string
|
||||
defer func() {
|
||||
diskMountsMu.Unlock()
|
||||
resultCh <- stdout
|
||||
}()
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second))
|
||||
format := `NR>1 && !/tmpfs|snap\/core|udev/ {printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $1, $2, $3, $4, $5, $6, $7}`
|
||||
output, err := cmdMgr.RunPipe(
|
||||
cmd.PipeCommand{Name: "df", Args: []string{"-hT", "-P"}},
|
||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||
)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load disk info with df -hT -P failed, err: %v", err)
|
||||
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
|
||||
output, err = cmdMgr2.RunPipe(
|
||||
cmd.PipeCommand{Name: "df", Args: []string{"-lhT", "-P"}},
|
||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||
)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load disk info with df -lhT -P failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
stdout = output
|
||||
}()
|
||||
timer := time.NewTimer(3 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case stdout := <-resultCh:
|
||||
return stdout
|
||||
case <-timer.C:
|
||||
global.LOG.Error("load disk mounts timed out; df collection is still running")
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func loadDiskUsageWithTimeout(path string, forceRefresh bool) (*disk.UsageStat, error) {
|
||||
type diskResult struct {
|
||||
state *disk.UsageStat
|
||||
err error
|
||||
}
|
||||
resultCh := make(chan diskResult, 1)
|
||||
go func() {
|
||||
state, err := psutil.DISK.GetUsage(path, forceRefresh)
|
||||
resultCh <- diskResult{state: state, err: err}
|
||||
}()
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
return nil, fmt.Errorf("load disk usage from %s: timeout", path)
|
||||
case result := <-resultCh:
|
||||
return result.state, result.err
|
||||
}
|
||||
}
|
||||
|
||||
func loadAcceleratorInfo() ([]dto.GPUInfo, []dto.NPUInfo, []dto.XPUInfo) {
|
||||
ok, client := accelerator.New()
|
||||
if !ok {
|
||||
@@ -586,6 +613,9 @@ func loadAcceleratorInfo() ([]dto.GPUInfo, []dto.NPUInfo, []dto.XPUInfo) {
|
||||
xpuData []dto.XPUInfo
|
||||
)
|
||||
for _, device := range snapshot.Devices {
|
||||
if device.ParentID != "" {
|
||||
continue
|
||||
}
|
||||
switch device.Kind {
|
||||
case accelerator.KindGPU:
|
||||
if device.GPU == nil {
|
||||
@@ -595,7 +625,11 @@ func loadAcceleratorInfo() ([]dto.GPUInfo, []dto.NPUInfo, []dto.XPUInfo) {
|
||||
if err := copier.Copy(&dataItem, device.GPU); err != nil {
|
||||
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
|
||||
gpuData = append(gpuData, dataItem)
|
||||
case accelerator.KindNPU:
|
||||
|
||||
@@ -73,7 +73,7 @@ func (u *DeviceService) LoadBaseInfo() (dto.DeviceBaseInfo, error) {
|
||||
if baseInfo.SwapMemoryTotal != 0 {
|
||||
baseInfo.SwapDetails = loadSwap()
|
||||
}
|
||||
disks := loadDiskInfo()
|
||||
disks := loadDiskInfo(false)
|
||||
for _, item := range disks {
|
||||
baseInfo.MaxSize += item.Free
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
|
||||
|
||||
dockerfirewall "github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
|
||||
)
|
||||
|
||||
const dockerNftablesMinVersion = "29.0.0"
|
||||
@@ -84,7 +85,7 @@ func (u *DockerService) UpdateFirewallBackend(backend string) error {
|
||||
return fmt.Errorf("Docker Engine %s or later is required for the nftables firewall backend", dockerNftablesMinVersion)
|
||||
}
|
||||
if backend == constant.FirewallProviderNftables {
|
||||
if err := docker_guard.CheckIPv4Forwarding(); err != nil {
|
||||
if err := dockerfirewall.CheckIPv4Forwarding(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -282,7 +283,8 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate, withRestart bool) erro
|
||||
delete(daemonMap, "ipv6")
|
||||
delete(daemonMap, "fixed-cidr-v6")
|
||||
delete(daemonMap, "ip6tables")
|
||||
if configuredDockerFirewallBackend() != constant.FirewallProviderNftables {
|
||||
backend, _ := settingRepo.GetValueByKey(constant.FirewallDockerBackendKey)
|
||||
if !strings.EqualFold(strings.TrimSpace(backend), constant.FirewallProviderNftables) {
|
||||
delete(daemonMap, "experimental")
|
||||
}
|
||||
}
|
||||
@@ -485,6 +487,9 @@ func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
|
||||
if err := controller.Handle(req.Operation, service); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Operation == "start" || req.Operation == "restart" {
|
||||
return RestoreDockerPortGuard(context.Background())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -32,10 +32,11 @@ var (
|
||||
scriptRepo = repo.NewIScriptRepo()
|
||||
cronjobRepo = repo.NewICronjobRepo()
|
||||
|
||||
hostRepo = repo.NewIHostRepo()
|
||||
ftpRepo = repo.NewIFtpRepo()
|
||||
clamRepo = repo.NewIClamRepo()
|
||||
monitorRepo = repo.NewIMonitorRepo()
|
||||
hostRepo = repo.NewIHostRepo()
|
||||
ftpRepo = repo.NewIFtpRepo()
|
||||
clamRepo = repo.NewIClamRepo()
|
||||
monitorRepo = repo.NewIMonitorRepo()
|
||||
vllmMonitorRepo = &repo.VLLMMonitorRepo{}
|
||||
|
||||
settingRepo = repo.NewISettingRepo()
|
||||
backupRepo = repo.NewIBackupRepo()
|
||||
|
||||
+29
-10
@@ -439,13 +439,15 @@ func (f *FileService) Compress(c request.FileCompress) error {
|
||||
if err := preflightCompressTool(files.CompressType(c.Type)); err != nil {
|
||||
return err
|
||||
}
|
||||
taskItem, err := task.NewTask(c.Name, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||
taskName := i18n.GetMsgWithMap("FileTaskCompress", map[string]interface{}{"dst": strconv.Quote(filepath.Join(c.Dst, c.Name))})
|
||||
taskItem, err := task.NewTask(taskName, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
taskItem.AddSubTask(c.Name, func(t *task.Task) error {
|
||||
t.LogStart(c.Name)
|
||||
taskItem.AddSubTask(taskName, func(t *task.Task) error {
|
||||
logFileTaskSources(t, c.Files)
|
||||
t.Log(i18n.GetMsgWithMap("FileTaskFormat", map[string]interface{}{"format": strconv.Quote(c.Type)}))
|
||||
compressType := files.CompressType(c.Type)
|
||||
dstFile := filepath.Join(c.Dst, c.Name)
|
||||
success := false
|
||||
@@ -516,13 +518,15 @@ func (f *FileService) DeCompress(c request.FileDeCompress) error {
|
||||
if err := preflightDecompressTool(files.CompressType(c.Type)); err != nil {
|
||||
return err
|
||||
}
|
||||
taskItem, err := task.NewTask(c.Path, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||
taskName := i18n.GetMsgWithMap("FileTaskDecompress", map[string]interface{}{"dst": strconv.Quote(c.Dst)})
|
||||
taskItem, err := task.NewTask(taskName, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
taskItem.AddSubTask(c.Path, func(t *task.Task) error {
|
||||
t.LogStart(c.Path)
|
||||
taskItem.AddSubTask(taskName, func(t *task.Task) error {
|
||||
logFileTaskSources(t, []string{c.Path})
|
||||
t.Log(i18n.GetMsgWithMap("FileTaskFormat", map[string]interface{}{"format": strconv.Quote(c.Type)}))
|
||||
dstExisted := fo.Stat(c.Dst)
|
||||
parentDir := filepath.Dir(c.Dst)
|
||||
if !fo.Stat(parentDir) {
|
||||
@@ -914,6 +918,12 @@ func (f *FileService) Wget(w request.FileWget) (string, error) {
|
||||
return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key, options)
|
||||
}
|
||||
|
||||
func logFileTaskSources(t *task.Task, sources []string) {
|
||||
for _, source := range sources {
|
||||
t.Log(i18n.GetMsgWithMap("FileTaskSource", map[string]interface{}{"path": strconv.Quote(source)}))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FileService) MvFile(m request.FileMove) error {
|
||||
fo := files.NewFileOp()
|
||||
if err := validateFileMove(fo, m); err != nil {
|
||||
@@ -925,15 +935,24 @@ func (f *FileService) MvFile(m request.FileMove) error {
|
||||
if !fileTransferLocks.Acquire(m.TaskID, getFileTransferPaths(m)) {
|
||||
return buserr.New("TaskIsExecuting")
|
||||
}
|
||||
taskItem, err := task.NewTask(m.NewPath, task.TaskExec, task.TaskScopeTask, m.TaskID, 1)
|
||||
nameKey := "FileTaskCopy"
|
||||
if m.Type == "cut" {
|
||||
nameKey = "FileTaskMove"
|
||||
}
|
||||
taskName := i18n.GetMsgWithMap(nameKey, map[string]interface{}{"dst": strconv.Quote(m.NewPath)})
|
||||
taskItem, err := task.NewTask(taskName, task.TaskExec, task.TaskScopeTask, m.TaskID, 1)
|
||||
if err != nil {
|
||||
fileTransferLocks.Release(m.TaskID)
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
defer fileTransferLocks.Release(m.TaskID)
|
||||
taskItem.AddSubTaskWithOps(m.NewPath, func(t *task.Task) error {
|
||||
t.LogStart(m.NewPath)
|
||||
taskItem.AddSubTaskWithOps(taskName, func(t *task.Task) error {
|
||||
logFileTaskSources(t, m.OldPaths)
|
||||
logFileTaskSources(t, m.CoverPaths)
|
||||
if m.Name != "" {
|
||||
t.Log(i18n.GetMsgWithMap("FileTaskRename", map[string]interface{}{"name": strconv.Quote(m.Name)}))
|
||||
}
|
||||
err := f.moveFileWithContext(t.TaskCtx, m)
|
||||
if err != nil && t.TaskCtx.Err() != nil {
|
||||
return t.TaskCtx.Err()
|
||||
@@ -1291,7 +1310,7 @@ func (f *FileService) BatchCheckFiles(req request.FilePathsCheck) []response.Exi
|
||||
}
|
||||
|
||||
func (f *FileService) GetHostMount() []dto.DiskInfo {
|
||||
return loadDiskInfo()
|
||||
return loadDiskInfo(false)
|
||||
}
|
||||
|
||||
func (f *FileService) GetUsersAndGroups() (*response.UserGroupResponse, error) {
|
||||
|
||||
+932
-2689
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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,20 +5,20 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"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"
|
||||
filterruntime "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter/runtime"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
|
||||
"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/nftables_helper"
|
||||
@@ -31,211 +31,124 @@ type IFirewallSettingService interface {
|
||||
DeletePortWhitelist(context.Context, dto.FirewallPortWhitelistDelete) error
|
||||
Load(context.Context) (dto.FirewallSettings, error)
|
||||
Operate(context.Context, dto.FirewallBackendOperation) error
|
||||
OperateFamily(dto.FirewallFamilyOperation) (dto.FilterChainOperationResponse, error)
|
||||
OperateIPv6(dto.FirewallIPv6Operation) (dto.FilterChainOperationResponse, error)
|
||||
}
|
||||
|
||||
type FirewallSettingService struct{}
|
||||
|
||||
var firewallWhitelistMu sync.Mutex
|
||||
|
||||
var ErrFirewallBackendCleanupRequired = errors.New("firewall backend cleanup required")
|
||||
|
||||
func firewallBackendCleanupRequired(current, target string) error {
|
||||
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) CreatePortWhitelist(ctx context.Context, request dto.FirewallPortWhitelistCreate) error {
|
||||
return savePortWhitelist(ctx, func(current []firewall.PortWhitelist) ([]firewall.PortWhitelist, error) {
|
||||
return append(current, request.Rule), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) UpdatePortWhitelist(ctx context.Context, request dto.FirewallPortWhitelistUpdate) error {
|
||||
return savePortWhitelist(ctx, func(current []firewall.PortWhitelist) ([]firewall.PortWhitelist, error) {
|
||||
index, err := findPortWhitelistRule(current, request.OldRule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (s *FirewallSettingService) CreatePortWhitelist(ctx context.Context, request dto.FirewallPortWhitelistCreate) (result error) {
|
||||
firewallWhitelistMu.Lock()
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer func() {
|
||||
firewallRuleMutationMu.Unlock()
|
||||
firewallWhitelistMu.Unlock()
|
||||
if result == nil {
|
||||
result = newFirewallService().SyncPortWhitelist(ctx)
|
||||
}
|
||||
current[index] = request.Rule
|
||||
return current, nil
|
||||
})
|
||||
}()
|
||||
current, err := loadFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
current = append(current, request.Rule)
|
||||
current, err = firewall.ValidatePortWhitelist(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateFirewallWhitelistFamilies(current); err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := json.Marshal(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return settingRepo.UpdateOrCreate(constant.FirewallPortWhiteList, string(value))
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) UpdatePortWhitelist(ctx context.Context, request dto.FirewallPortWhitelistUpdate) (result error) {
|
||||
firewallWhitelistMu.Lock()
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer func() {
|
||||
firewallRuleMutationMu.Unlock()
|
||||
firewallWhitelistMu.Unlock()
|
||||
if result == nil {
|
||||
result = newFirewallService().SyncPortWhitelist(ctx)
|
||||
}
|
||||
}()
|
||||
current, err := loadFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
index, err := findPortWhitelistRule(current, request.OldRule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
current[index] = request.Rule
|
||||
current, err = firewall.ValidatePortWhitelist(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateFirewallWhitelistFamilies(current); err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := json.Marshal(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return settingRepo.UpdateOrCreate(constant.FirewallPortWhiteList, string(value))
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) DeletePortWhitelist(ctx context.Context, request dto.FirewallPortWhitelistDelete) error {
|
||||
if request.Rule == nil {
|
||||
return fmt.Errorf("select one firewall port whitelist rule to delete")
|
||||
}
|
||||
return savePortWhitelist(ctx, func(current []firewall.PortWhitelist) ([]firewall.PortWhitelist, error) {
|
||||
index, err := findPortWhitelistRule(current, *request.Rule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return slices.Delete(current, index, index+1), nil
|
||||
})
|
||||
}
|
||||
|
||||
func findPortWhitelistRule(rules []firewall.PortWhitelist, target firewall.PortWhitelist) (int, error) {
|
||||
index := slices.IndexFunc(rules, func(rule firewall.PortWhitelist) bool {
|
||||
return samePortWhitelistRule(rule, target)
|
||||
})
|
||||
if index < 0 {
|
||||
return -1, fmt.Errorf("firewall port whitelist rule has changed or no longer exists; refresh and retry")
|
||||
}
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func samePortWhitelistRule(left, right firewall.PortWhitelist) bool {
|
||||
if reflect.DeepEqual(left, right) {
|
||||
return true
|
||||
}
|
||||
normalizedLeft, err := firewall.ValidatePortWhitelist([]firewall.PortWhitelist{left})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
normalizedRight, err := firewall.ValidatePortWhitelist([]firewall.PortWhitelist{right})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
slices.Sort(normalizedLeft[0].Sources)
|
||||
slices.Sort(normalizedRight[0].Sources)
|
||||
return reflect.DeepEqual(normalizedLeft[0], normalizedRight[0])
|
||||
}
|
||||
|
||||
func savePortWhitelist(ctx context.Context, change func([]firewall.PortWhitelist) ([]firewall.PortWhitelist, error)) error {
|
||||
firewallWhitelistMu.Lock()
|
||||
defer firewallWhitelistMu.Unlock()
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
defer filterruntime.InvalidateInventory()
|
||||
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
current, err := loadPortWhitelistSetting(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
desired, err := change(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
desired, err = firewall.ValidatePortWhitelist(desired)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := json.Marshal(desired)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Where("key = ?", constant.FirewallPortWhiteList).Assign(map[string]interface{}{"value": string(value)}).
|
||||
FirstOrCreate(&model.Setting{Key: constant.FirewallPortWhiteList}).Error
|
||||
})
|
||||
}
|
||||
|
||||
func checkFirewallRuleWhitelistProtection(provider filter.Provider, record model.FirewallRule) error {
|
||||
ports, err := loadFirewallPortWhiteList()
|
||||
defer func() {
|
||||
firewallRuleMutationMu.Unlock()
|
||||
firewallWhitelistMu.Unlock()
|
||||
}()
|
||||
current, err := loadFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rules, err := record.RulesForProvider(provider)
|
||||
if request.Rule == nil {
|
||||
return filter.ErrInvalidRule
|
||||
}
|
||||
index, err := findPortWhitelistRule(current, *request.Rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if filter.RuleMatchesPortWhitelist(rule, ports) {
|
||||
return filter.ErrProtectedRule
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadPortWhitelistSetting(db *gorm.DB) ([]firewall.PortWhitelist, error) {
|
||||
var setting model.Setting
|
||||
if err := db.Where("key = ?", constant.FirewallPortWhiteList).First(&setting).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
setting.Value = constant.FirewallPortWhiteListValue
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rules []firewall.PortWhitelist
|
||||
err := json.Unmarshal([]byte(setting.Value), &rules)
|
||||
return rules, err
|
||||
}
|
||||
|
||||
func loadSSHWhitelistPortFrom(path string) (string, error) {
|
||||
directives, _, err := parseSSHConfigTree(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return defaultSSHPort, nil
|
||||
}
|
||||
current = slices.Delete(current, index, index+1)
|
||||
current, err = firewall.ValidatePortWhitelist(current)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
return loadSSHPortValues(directives)[0], nil
|
||||
}
|
||||
|
||||
func customWhitelist(entries []firewall.PortWhitelist) []firewall.PortWhitelist {
|
||||
result := make([]firewall.PortWhitelist, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if entry.Type == "" {
|
||||
result = append(result, entry)
|
||||
}
|
||||
if err := validateFirewallWhitelistFamilies(current); err != nil {
|
||||
return err
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func InitializeFirewallWhitelistPorts(entries []firewall.PortWhitelist) ([]firewall.PortWhitelist, error) {
|
||||
entries = slices.Clone(entries)
|
||||
var sshPort string
|
||||
for i := range entries {
|
||||
entry := &entries[i]
|
||||
if entry.Type == "" || entry.Port != "" {
|
||||
continue
|
||||
}
|
||||
switch entry.Type {
|
||||
case firewall.PortWhitelistTypePanel:
|
||||
entry.Port = LoadPanelPort()
|
||||
case firewall.PortWhitelistTypeSSH:
|
||||
if sshPort == "" {
|
||||
var err error
|
||||
sshPort, err = loadSSHWhitelistPortFrom(sshPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
entry.Port = sshPort
|
||||
}
|
||||
value, err := json.Marshal(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return firewall.ValidatePortWhitelist(entries)
|
||||
}
|
||||
|
||||
func updateSystemAccessPortWhitelist(ctx context.Context, serviceType string, ports []string) error {
|
||||
return savePortWhitelist(ctx, func(entries []firewall.PortWhitelist) ([]firewall.PortWhitelist, error) {
|
||||
for i := range entries {
|
||||
if entries[i].Type == serviceType {
|
||||
if len(ports) == 0 {
|
||||
return nil, fmt.Errorf("firewall whitelist %s requires a port", serviceType)
|
||||
}
|
||||
entries[i].Port = ports[0]
|
||||
}
|
||||
}
|
||||
return entries, nil
|
||||
})
|
||||
return settingRepo.UpdateOrCreate(constant.FirewallPortWhiteList, string(value))
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings, error) {
|
||||
result := dto.FirewallSettings{PingStatus: firewall.LoadPingStatus()}
|
||||
families, err := loadFirewallFamilies()
|
||||
if err != nil {
|
||||
return dto.FirewallSettings{}, err
|
||||
}
|
||||
result := dto.FirewallSettings{PingStatus: firewall.LoadPingStatus(), IPv6Enabled: slices.Contains(families, constant.FirewallFamilyIPv6)}
|
||||
|
||||
installed := make(map[string]bool)
|
||||
for _, name := range lifecycle.InstalledProviders() {
|
||||
installed[name] = true
|
||||
}
|
||||
result.System.Selected = configuredSystemFirewallBackend()
|
||||
systemBackend, _ := settingRepo.GetValueByKey(constant.FirewallSystemBackendKey)
|
||||
result.System.Selected = strings.TrimSpace(systemBackend)
|
||||
if result.System.Selected == "" {
|
||||
if client, err := lifecycle.NewClient(); err == nil {
|
||||
if client, err := lifecycle.NewClient(""); err == nil {
|
||||
result.System.Selected = client.Name()
|
||||
}
|
||||
}
|
||||
@@ -248,16 +161,16 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
|
||||
} {
|
||||
option := dto.FirewallBackendOption{Name: name, Installed: installed[name], Supported: true}
|
||||
if option.Installed && name == result.System.Selected {
|
||||
client, err := lifecycle.NewClientFor(name)
|
||||
client, err := lifecycle.NewClient(name)
|
||||
if err != nil {
|
||||
option.Message = err.Error()
|
||||
} else if supportsManagedFilterChains(name) {
|
||||
option.Initialized, option.Bound, err = loadFirewallInitStatus(name, "base")
|
||||
} else if name == constant.FirewallProviderIptables || name == constant.FirewallProviderNftables {
|
||||
overview, err := loadSystemFirewallOverview(name, "base", families)
|
||||
if err != nil {
|
||||
option.Message = err.Error()
|
||||
}
|
||||
option.IPv4 = loadSystemFirewallFamilyInfo(name, constant.FirewallFamilyIPv4)
|
||||
option.IPv6 = loadSystemFirewallFamilyInfo(name, constant.FirewallFamilyIPv6)
|
||||
option.Initialized, option.Bound = overview.IsInit, overview.IsBind
|
||||
option.IPv4, option.IPv6 = overview.IPv4, overview.IPv6
|
||||
} else if option.Active, err = client.Status(); err != nil {
|
||||
option.Message = err.Error()
|
||||
}
|
||||
@@ -270,32 +183,29 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
|
||||
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
|
||||
for _, name := range []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables} {
|
||||
option := dto.FirewallBackendOption{Name: name, Installed: installed[name], Supported: true}
|
||||
if option.Installed && name == result.Forwarding.Selected {
|
||||
manager, err := newForwardingManagerFor(name)
|
||||
manager, err := newForwardingAdapterFor(ctx, name)
|
||||
if err != nil {
|
||||
option.Message = err.Error()
|
||||
} else if status, err := manager.Status(); err != nil {
|
||||
option.Message = err.Error()
|
||||
} else {
|
||||
option.Initialized, option.Bound = status.IsInit, status.IsBind
|
||||
ipv4Init, ipv4Bound, ipv4Err := manager.FamilyStatus(constant.FirewallFamilyIPv4)
|
||||
ipv6Init, ipv6Bound, ipv6Err := manager.FamilyStatus(constant.FirewallFamilyIPv6)
|
||||
option.IPv4 = dto.FirewallBackendFamilyStatus{
|
||||
Available: ipv4Err == nil, Initialized: ipv4Init, Bound: ipv4Bound,
|
||||
status, statusErr := loadForwardingFirewallOverview(manager, families)
|
||||
option.IPv4, option.IPv6 = status.IPv4, status.IPv6
|
||||
if statusErr != nil {
|
||||
option.Message = statusErr.Error()
|
||||
} else {
|
||||
option.Initialized, option.Bound = status.IsInit, status.IsBind
|
||||
}
|
||||
option.IPv6 = dto.FirewallBackendFamilyStatus{
|
||||
Available: ipv6Err == nil, Initialized: ipv6Init, Bound: ipv6Bound,
|
||||
}
|
||||
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
|
||||
}
|
||||
if result.IPv6Enabled && name == constant.FirewallProviderIptables && !option.IPv6.Available {
|
||||
if commands, err := lifecycle.ResolveIptablesCommands(); err == nil && !commands.IPv6Available() {
|
||||
option.IPv6.Reason = dockerfirewall.ReasonCommandMissing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,7 +223,11 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
|
||||
if dockerInstalled {
|
||||
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
|
||||
for _, name := range []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables} {
|
||||
option := dto.FirewallBackendOption{
|
||||
@@ -326,20 +240,25 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
|
||||
option.Active = false
|
||||
}
|
||||
if option.Active {
|
||||
guard := docker_guard.NewRuntime(name)
|
||||
ipv4, ipv6 := guard.Status(docker_guard.FamilyIPv4), guard.Status(docker_guard.FamilyIPv6)
|
||||
option.Initialized = ipv4.Initialized || ipv6.Initialized
|
||||
option.Bound = ipv4.Bound || ipv6.Bound
|
||||
option.IPv4.Initialized, option.IPv4.Bound = ipv4.Initialized, ipv4.Bound
|
||||
option.IPv6.Initialized, option.IPv6.Bound = ipv6.Initialized, ipv6.Bound
|
||||
option.IPv4.Available = ipv4.Reason != docker_guard.ReasonCommandMissing
|
||||
option.IPv6.Available = ipv6.Reason != docker_guard.ReasonCommandMissing
|
||||
option.IPv4.Reason, option.IPv6.Reason = ipv4.Reason, ipv6.Reason
|
||||
guard := newDockerFirewallRuntime(ctx, name)
|
||||
for _, family := range families {
|
||||
status := guard.Status(family)
|
||||
option.Initialized = option.Initialized || status.Initialized
|
||||
option.Bound = option.Bound || status.Bound
|
||||
info := dto.FirewallBackendFamilyStatus{
|
||||
Available: status.Reason != dockerfirewall.ReasonCommandMissing,
|
||||
Initialized: status.Initialized, Bound: status.Bound, Reason: status.Reason,
|
||||
}
|
||||
if family == constant.FirewallFamilyIPv4 {
|
||||
option.IPv4 = info
|
||||
} else {
|
||||
option.IPv6 = info
|
||||
}
|
||||
}
|
||||
}
|
||||
result.Docker.Options = append(result.Docker.Options, option)
|
||||
}
|
||||
var err error
|
||||
result.PortWhitelist, err = loadPortWhitelistSetting(global.DB.WithContext(ctx))
|
||||
result.PortWhitelist, err = loadPortWhitelistSetting()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
@@ -353,33 +272,11 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func loadSystemFirewallFamilyInfo(provider, family string) dto.FirewallBackendFamilyStatus {
|
||||
if provider == constant.FirewallProviderIptables && family == constant.FirewallFamilyIPv6 {
|
||||
commands, err := lifecycle.ResolveIptablesCommands()
|
||||
if err != nil || !commands.IPv6Available() {
|
||||
return dto.FirewallBackendFamilyStatus{Reason: docker_guard.ReasonCommandMissing}
|
||||
}
|
||||
}
|
||||
initialized, bound, err := loadSystemFirewallFamilyStatus(provider, family)
|
||||
return dto.FirewallBackendFamilyStatus{
|
||||
Available: err == nil,
|
||||
Initialized: initialized,
|
||||
Bound: bound,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) Operate(ctx context.Context, request dto.FirewallBackendOperation) error {
|
||||
if request.Operation == "cleanup" {
|
||||
_, err := newFirewallService().Reset(ctx, dto.FirewallRuleReset{Subsystem: request.Subsystem, Provider: filter.Provider(request.Backend)})
|
||||
return err
|
||||
}
|
||||
if err := lockFirewallLifecycleIdle(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -387,7 +284,7 @@ func (s *FirewallSettingService) Operate(ctx context.Context, request dto.Firewa
|
||||
if request.Subsystem != "system" && request.Backend != constant.FirewallProviderIptables && request.Backend != constant.FirewallProviderNftables {
|
||||
return fmt.Errorf("%s only supports iptables or nftables", request.Subsystem)
|
||||
}
|
||||
if request.Subsystem == "system" && !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)
|
||||
}
|
||||
switch request.Subsystem {
|
||||
@@ -397,13 +294,12 @@ func (s *FirewallSettingService) Operate(ctx context.Context, request dto.Firewa
|
||||
}
|
||||
if request.Operation == "initialize" {
|
||||
service := newFirewallService()
|
||||
rulesErr := service.restoreStoredFirewallRules(ctx, filter.Provider(request.Backend), nil)
|
||||
whitelistErr := service.SyncPortWhitelist(ctx)
|
||||
return errors.Join(rulesErr, whitelistErr)
|
||||
return whitelistErr
|
||||
}
|
||||
return nil
|
||||
case "forwarding":
|
||||
return s.operateForwarding(request)
|
||||
return s.operateForwarding(ctx, request)
|
||||
case "docker":
|
||||
return s.operateDocker(ctx, request)
|
||||
default:
|
||||
@@ -411,72 +307,224 @@ func (s *FirewallSettingService) Operate(ctx context.Context, request dto.Firewa
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) operateDocker(ctx context.Context, request dto.FirewallBackendOperation) error {
|
||||
guard := docker_guard.NewRuntime(request.Backend)
|
||||
if request.Operation == "cleanup" {
|
||||
if err := guard.Cleanup(); err != nil {
|
||||
func (s *FirewallSettingService) OperateFamily(request dto.FirewallFamilyOperation) (dto.FilterChainOperationResponse, error) {
|
||||
if request.Family != constant.FirewallFamilyIPv4 && request.Family != constant.FirewallFamilyIPv6 {
|
||||
return dto.FilterChainOperationResponse{}, filter.ErrInvalidScope
|
||||
}
|
||||
if request.Backend != constant.FirewallProviderIptables && request.Backend != constant.FirewallProviderNftables {
|
||||
return dto.FilterChainOperationResponse{}, filter.ErrUnsupportedScope
|
||||
}
|
||||
if request.Operation != "initialize" && request.Operation != "repair" && request.Operation != "bind" {
|
||||
return dto.FilterChainOperationResponse{}, filter.ErrRuleOperation
|
||||
}
|
||||
subsystem := ""
|
||||
switch request.Subsystem {
|
||||
case "system":
|
||||
subsystem = firewallTaskHost
|
||||
case "forwarding":
|
||||
subsystem = firewallTaskForwarding
|
||||
case "docker":
|
||||
subsystem = firewallTaskDocker
|
||||
default:
|
||||
return dto.FilterChainOperationResponse{}, filter.ErrInvalidScope
|
||||
}
|
||||
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeFirewall, 0); err != nil {
|
||||
return dto.FilterChainOperationResponse{}, err
|
||||
}
|
||||
return queueFirewallRuleTask(subsystem, task.TaskExec, "", nil, func(t *task.Task) error {
|
||||
if err := lockFirewallLifecycleIdle(); 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)
|
||||
defer firewallLifecycleTaskMu.Unlock()
|
||||
t.Logf("backend=%s family=%s operation=%s", request.Backend, request.Family, request.Operation)
|
||||
families, err := loadFirewallFamilies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if current != request.Backend && initialized {
|
||||
return firewallBackendCleanupRequired(current, request.Backend)
|
||||
if !slices.Contains(families, request.Family) {
|
||||
return fmt.Errorf("IPv6 firewall support is disabled")
|
||||
}
|
||||
}
|
||||
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
|
||||
initialize := request.Operation != "bind"
|
||||
switch request.Subsystem {
|
||||
case "system":
|
||||
firewallWhitelistMu.Lock()
|
||||
defer firewallWhitelistMu.Unlock()
|
||||
if err := newFirewallService().checkSelectedProvider(t.TaskCtx, filter.Provider(request.Backend)); err != nil {
|
||||
return err
|
||||
}
|
||||
ports, err := loadFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
required, err := firewall.RequiredPortWhitelist(ports)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
firewallRuleMutationMu.Lock()
|
||||
if request.Backend == constant.FirewallProviderIptables {
|
||||
err = iptables_helper.OperateFamily(request.Family, initialize, required)
|
||||
} else {
|
||||
err = nftables_helper.OperateFamily(filter.Family(request.Family), initialize, required)
|
||||
}
|
||||
firewallRuleMutationMu.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if initialize {
|
||||
if err := newFirewallService().applyPortWhitelist(t.TaskCtx, ports, nil, filter.Family(request.Family)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return settingRepo.UpdateOrCreate("IptablesStatus", constant.StatusEnable)
|
||||
case "forwarding":
|
||||
forwardingMutationMu.Lock()
|
||||
defer forwardingMutationMu.Unlock()
|
||||
manager, err := newForwardingAdapter(t.TaskCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if manager.Name() != request.Backend {
|
||||
return filter.ErrProviderUnavailable
|
||||
}
|
||||
if err := manager.OperateFamily(request.Family, initialize); err != nil {
|
||||
return err
|
||||
}
|
||||
rules, err := manager.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := persistForwardingRules(manager, rules); err != nil {
|
||||
return err
|
||||
}
|
||||
return settingRepo.UpdateOrCreate(constant.FirewallForwardingInitializedKey, constant.StatusEnable)
|
||||
default:
|
||||
dockerPortGuardServiceMu.Lock()
|
||||
defer dockerPortGuardServiceMu.Unlock()
|
||||
runtime, backend, err := newDockerPortGuardService().runtimeForDocker(t.TaskCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if backend != request.Backend {
|
||||
return filter.ErrProviderUnavailable
|
||||
}
|
||||
if err := runtime.OperateFamily(request.Family, initialize); err != nil {
|
||||
return err
|
||||
}
|
||||
inventory, err := runtime.ListPolicies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := persistDockerRules(backend, inventory); err != nil {
|
||||
return err
|
||||
}
|
||||
return settingRepo.UpdateOrCreate(constant.FirewallDockerPortGuardStatusKey, constant.StatusEnable)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
func (s *FirewallSettingService) OperateIPv6(request dto.FirewallIPv6Operation) (dto.FilterChainOperationResponse, error) {
|
||||
if request.Status != constant.StatusEnable && request.Status != constant.StatusDisable {
|
||||
return dto.FilterChainOperationResponse{}, filter.ErrInvalidRule
|
||||
}
|
||||
return false, nil
|
||||
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeFirewall, 0); err != nil {
|
||||
return dto.FilterChainOperationResponse{}, err
|
||||
}
|
||||
return queueFirewallRuleTask(firewallTaskHost, task.TaskExec, "", nil, func(t *task.Task) error {
|
||||
if err := lockFirewallLifecycleIdle(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer firewallLifecycleTaskMu.Unlock()
|
||||
firewallWhitelistMu.Lock()
|
||||
defer firewallWhitelistMu.Unlock()
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
forwardingMutationMu.Lock()
|
||||
defer forwardingMutationMu.Unlock()
|
||||
dockerPortGuardServiceMu.Lock()
|
||||
defer dockerPortGuardServiceMu.Unlock()
|
||||
if request.Status == constant.StatusEnable {
|
||||
return settingRepo.UpdateOrCreate(constant.FirewallIPv6SupportKey, request.Status)
|
||||
}
|
||||
ports, err := loadFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := json.Marshal(ipv4PortWhitelist(ports))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installed := lifecycle.InstalledProviders()
|
||||
for _, selection := range []struct{ subsystem, key string }{
|
||||
{"system", constant.FirewallSystemBackendKey},
|
||||
{"forwarding", constant.FirewallForwardingBackendKey},
|
||||
{"docker", constant.FirewallDockerBackendKey},
|
||||
} {
|
||||
backend, err := settingRepo.GetValueByKey(selection.key)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
backend = strings.ToLower(strings.TrimSpace(backend))
|
||||
if backend == "" {
|
||||
if selection.subsystem == "system" {
|
||||
if len(installed) == 0 {
|
||||
continue
|
||||
}
|
||||
client, err := NewSelectedSystemFirewallClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backend = client.Name()
|
||||
} else {
|
||||
backend = constant.FirewallProviderIptables
|
||||
}
|
||||
}
|
||||
if !slices.Contains(installed, backend) || (backend != constant.FirewallProviderIptables && backend != constant.FirewallProviderNftables) {
|
||||
continue
|
||||
}
|
||||
t.Logf("disable IPv6 firewall bindings: subsystem=%s backend=%s", selection.subsystem, backend)
|
||||
switch selection.subsystem {
|
||||
case "system":
|
||||
if backend == constant.FirewallProviderIptables {
|
||||
err = iptables_helper.UnbindIPv6BaseChains()
|
||||
} else {
|
||||
err = nftables_helper.SetTableDormant(t.TaskCtx, "ip6", nftables_helper.TableName)
|
||||
if err == nil {
|
||||
err = nftables_helper.PersistRuleset(t.TaskCtx)
|
||||
}
|
||||
}
|
||||
case "forwarding":
|
||||
var manager forwarding.Adapter
|
||||
manager, err = newForwardingAdapterFor(t.TaskCtx, backend)
|
||||
if err == nil {
|
||||
err = manager.UnbindFamily(constant.FirewallFamilyIPv6)
|
||||
}
|
||||
case "docker":
|
||||
err = newDockerFirewallRuntime(t.TaskCtx, backend).Unbind(constant.FirewallFamilyIPv6)
|
||||
}
|
||||
if err != nil && !errors.Is(err, filter.ErrFamilyUnavailable) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := t.TaskCtx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return settingRepo.UpdateValues(map[string]string{constant.FirewallIPv6SupportKey: request.Status, constant.FirewallPortWhiteList: string(value)})
|
||||
})
|
||||
}
|
||||
|
||||
func NewIFirewallSettingService() IFirewallSettingService {
|
||||
return &FirewallSettingService{}
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperation) error {
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
if _, err := lifecycle.NewClientFor(request.Backend); err != nil {
|
||||
if _, err := lifecycle.NewClient(request.Backend); err != nil {
|
||||
return err
|
||||
}
|
||||
if request.Operation == "cleanup" {
|
||||
return cleanupSystemBackend(request.Backend)
|
||||
}
|
||||
previous, _ := settingRepo.GetValueByKey(constant.FirewallSystemBackendKey)
|
||||
if previous == "" {
|
||||
if client, err := lifecycle.NewClient(); err == nil {
|
||||
if client, err := lifecycle.NewClient(""); err == nil {
|
||||
previous = client.Name()
|
||||
}
|
||||
}
|
||||
@@ -486,7 +534,7 @@ func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperat
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
@@ -512,21 +560,14 @@ func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperat
|
||||
}
|
||||
|
||||
func systemFirewallBackendInitialized(backend string) (bool, error) {
|
||||
return systemFirewallBackendInitializedWithClientFactory(backend, lifecycle.NewClientFor)
|
||||
}
|
||||
|
||||
func systemFirewallBackendInitializedWithClientFactory(
|
||||
backend string,
|
||||
newClient func(string) (lifecycle.Client, error),
|
||||
) (bool, error) {
|
||||
client, err := newClient(backend)
|
||||
client, err := lifecycle.NewClient(backend)
|
||||
if err != nil {
|
||||
if errors.Is(err, lifecycle.ErrNotInstalled) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if supportsManagedFilterChains(backend) {
|
||||
if backend == constant.FirewallProviderIptables || backend == constant.FirewallProviderNftables {
|
||||
for _, family := range []string{constant.FirewallFamilyIPv4, constant.FirewallFamilyIPv6} {
|
||||
initialized, _, err := loadSystemFirewallFamilyStatus(backend, family)
|
||||
if family == constant.FirewallFamilyIPv6 && errors.Is(err, filter.ErrFamilyUnavailable) {
|
||||
@@ -544,73 +585,39 @@ func systemFirewallBackendInitializedWithClientFactory(
|
||||
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 {
|
||||
manager, err := newForwardingManagerFor(request.Backend)
|
||||
if err != nil {
|
||||
func (s *FirewallSettingService) operateForwarding(ctx context.Context, request dto.FirewallBackendOperation) error {
|
||||
if _, err := newForwardingAdapterFor(ctx, request.Backend); err != nil {
|
||||
return err
|
||||
}
|
||||
if request.Operation == "cleanup" {
|
||||
if err := manager.Cleanup(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.UpdateOrCreate(constant.FirewallForwardingInitializedKey, constant.StatusDisable); err != nil {
|
||||
return err
|
||||
}
|
||||
recordForwardingSyncError(nil)
|
||||
return nil
|
||||
}
|
||||
previous, _ := settingRepo.GetValueByKey(constant.FirewallForwardingBackendKey)
|
||||
if request.Operation == "select" {
|
||||
current := previous
|
||||
if current == "" {
|
||||
detected, err := newForwardingManager()
|
||||
detected, err := newForwardingAdapter(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
current = detected.Name()
|
||||
}
|
||||
initialized, err := forwardingBackendInitialized(current)
|
||||
initialized, err := forwardingBackendInitialized(ctx, current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
if request.Operation == "initialize" {
|
||||
return newForwardingService().Enable()
|
||||
return newForwardingService().Enable(ctx)
|
||||
}
|
||||
recordForwardingSyncError(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func forwardingBackendInitialized(backend string) (bool, error) {
|
||||
manager, err := newForwardingManagerFor(backend)
|
||||
func forwardingBackendInitialized(ctx context.Context, backend string) (bool, error) {
|
||||
manager, err := newForwardingAdapterFor(ctx, backend)
|
||||
if err != nil {
|
||||
if errors.Is(err, lifecycle.ErrNotInstalled) {
|
||||
return false, nil
|
||||
@@ -629,9 +636,53 @@ func forwardingBackendInitialized(backend string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func alternateDirectBackend(backend string) string {
|
||||
if backend == constant.FirewallProviderNftables {
|
||||
return constant.FirewallProviderIptables
|
||||
func (s *FirewallSettingService) operateDocker(ctx context.Context, request dto.FirewallBackendOperation) error {
|
||||
|
||||
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(ctx, current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if current != request.Backend && initialized {
|
||||
return buserr.WithMap("ErrFirewallBackendCleanupRequired", map[string]interface{}{"current": current, "target": request.Backend}, nil)
|
||||
}
|
||||
}
|
||||
return constant.FirewallProviderNftables
|
||||
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(ctx context.Context, backend string) (bool, error) {
|
||||
guard := newDockerFirewallRuntime(ctx, 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
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+224
-468
@@ -4,78 +4,59 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
|
||||
)
|
||||
|
||||
type IForwardingService interface {
|
||||
LoadBaseInfo() (dto.FirewallSubsystemStatus, error)
|
||||
SearchRules(request dto.ForwardRuleSearch) (int64, []dto.ForwardRule, error)
|
||||
LoadBaseInfo(ctx context.Context) (dto.FirewallSubsystemStatus, error)
|
||||
ExportBackup(context.Context, filter.Provider) (dto.FirewallSubsystemBackup, error)
|
||||
SearchRules(ctx context.Context, request dto.ForwardRuleSearch) (int64, []dto.ForwardRule, error)
|
||||
OperateRules(dto.ForwardRuleOperate) (dto.FilterChainOperationResponse, error)
|
||||
Enable() error
|
||||
Enable(ctx context.Context) error
|
||||
QueueInitialization(dto.FirewallInitializationTask) (dto.FilterChainOperationResponse, error)
|
||||
Restore(context.Context) error
|
||||
}
|
||||
|
||||
type ForwardingService struct {
|
||||
managerFactory func() (*forwarding.Manager, error)
|
||||
rules repo.IForwardingRuleRepo
|
||||
enabled func() (bool, error)
|
||||
persistBackend func(string) error
|
||||
markEnabled func() error
|
||||
clientFactory func(context.Context) (forwarding.Adapter, error)
|
||||
}
|
||||
|
||||
var errForwardingBackendUnavailable = errors.New("no supported forwarding backend detected")
|
||||
|
||||
var forwardingMutationMu sync.Mutex
|
||||
|
||||
const (
|
||||
forwardingSyncConverged = "converged"
|
||||
forwardingSyncMissing = "missing"
|
||||
forwardingSyncRuntimeOnly = "runtime_only"
|
||||
)
|
||||
|
||||
var (
|
||||
forwardingSyncStateMu sync.RWMutex
|
||||
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(ctx context.Context) (dto.FirewallSubsystemStatus, error) {
|
||||
families, err := loadFirewallFamilies()
|
||||
if err != nil {
|
||||
return dto.FirewallSubsystemStatus{}, err
|
||||
}
|
||||
selected, _ := settingRepo.GetValueByKey(constant.FirewallForwardingBackendKey)
|
||||
selected = strings.TrimSpace(selected)
|
||||
if selected == "" {
|
||||
selected = constant.FirewallProviderIptables
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ForwardingService) LoadBaseInfo() (dto.FirewallSubsystemStatus, error) {
|
||||
selected := configuredForwardingBackend()
|
||||
baseInfo := dto.FirewallSubsystemStatus{
|
||||
Version: "-", Name: forwardingDisplayName(selected), Backend: selected, SyncError: lastForwardingSyncError(),
|
||||
Version: "-", Name: selected, Backend: selected, IPv6Enabled: len(families) > 1,
|
||||
}
|
||||
manager, err := s.managerFactory()
|
||||
if selected == constant.FirewallProviderIptables || selected == constant.FirewallProviderNftables {
|
||||
baseInfo.Name += "-forward"
|
||||
}
|
||||
manager, err := s.clientFactory(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, errForwardingBackendUnavailable) {
|
||||
baseInfo.Reason = constant.FirewallBackendNotInstalled
|
||||
@@ -83,119 +64,100 @@ func (s *ForwardingService) LoadBaseInfo() (dto.FirewallSubsystemStatus, error)
|
||||
}
|
||||
return baseInfo, err
|
||||
}
|
||||
status, err := manager.Status()
|
||||
client, err := lifecycle.NewClient(manager.Name())
|
||||
if err != nil {
|
||||
return baseInfo, err
|
||||
}
|
||||
version, versionErr := client.Version()
|
||||
status, statusErr := loadForwardingFirewallOverview(manager, families)
|
||||
if err := errors.Join(versionErr, statusErr); err != nil {
|
||||
return baseInfo, err
|
||||
}
|
||||
baseInfo.IsExist = true
|
||||
baseInfo.Name, baseInfo.Backend = forwardingDisplayName(status.Name), status.Name
|
||||
baseInfo.Version = status.Version
|
||||
baseInfo.Name, baseInfo.Backend = manager.Name(), manager.Name()
|
||||
if baseInfo.Backend == constant.FirewallProviderIptables || baseInfo.Backend == constant.FirewallProviderNftables {
|
||||
baseInfo.Name += "-forward"
|
||||
}
|
||||
baseInfo.Version = version
|
||||
baseInfo.PingStatus = firewall.LoadPingStatus()
|
||||
baseInfo.IsInit, baseInfo.IsBind = status.IsInit, status.IsBind
|
||||
baseInfo.IPv4 = loadForwardingFamilyInfo(manager, status.Name, constant.FirewallFamilyIPv4)
|
||||
baseInfo.IPv6 = loadForwardingFamilyInfo(manager, status.Name, constant.FirewallFamilyIPv6)
|
||||
baseInfo.IPv4, baseInfo.IPv6 = status.IPv4, status.IPv6
|
||||
for _, family := range []struct {
|
||||
command string
|
||||
status *dto.FirewallBackendFamilyStatus
|
||||
}{
|
||||
{"iptables", &baseInfo.IPv4},
|
||||
{"ip6tables", &baseInfo.IPv6},
|
||||
} {
|
||||
if family.command == "ip6tables" && !baseInfo.IPv6Enabled {
|
||||
continue
|
||||
}
|
||||
policy, err := loadForwardPolicy(ctx, family.command)
|
||||
if err != nil {
|
||||
global.LOG.Warnf("inspect %s FORWARD policy: %v", family.command, err)
|
||||
continue
|
||||
}
|
||||
family.status.ForwardPolicy = policy
|
||||
}
|
||||
return baseInfo, nil
|
||||
}
|
||||
|
||||
func 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()
|
||||
func (s *ForwardingService) ExportBackup(ctx context.Context, provider filter.Provider) (dto.FirewallSubsystemBackup, error) {
|
||||
var manager forwarding.Adapter
|
||||
var err error
|
||||
if provider == "" {
|
||||
manager, err = s.clientFactory(ctx)
|
||||
} else {
|
||||
manager, err = newForwardingAdapterFor(ctx, string(provider))
|
||||
}
|
||||
return dto.FirewallBackendFamilyStatus{Available: available, Initialized: initialized, Bound: bound}
|
||||
if err != nil {
|
||||
return dto.FirewallSubsystemBackup{}, err
|
||||
}
|
||||
rules, err := manager.List()
|
||||
if err != nil {
|
||||
return dto.FirewallSubsystemBackup{}, err
|
||||
}
|
||||
return forwardingBackup(manager, rules)
|
||||
}
|
||||
|
||||
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) {
|
||||
if request.Strategy != "" {
|
||||
return 0, nil, nil
|
||||
}
|
||||
stored, err := s.rules.List(context.Background())
|
||||
func (s *ForwardingService) SearchRules(ctx context.Context, request dto.ForwardRuleSearch) (int64, []dto.ForwardRule, error) {
|
||||
manager, err := s.clientFactory(ctx)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
manager, err := s.managerFactory()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
runtime, err := manager.List("", "")
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
inventory, err := mergeForwardingInventory(stored, runtime)
|
||||
rules, err := manager.List()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
items := make([]dto.ForwardRule, 0, len(rules))
|
||||
keyword := strings.ToLower(strings.TrimSpace(request.Info))
|
||||
filtered := inventory[:0]
|
||||
for _, item := range inventory {
|
||||
if keyword == "" || forwardingRuleMatchesKeyword(item, keyword) {
|
||||
filtered = append(filtered, item)
|
||||
for _, rule := range rules {
|
||||
if keyword != "" && !strings.Contains(strings.ToLower(strings.Join([]string{rule.Family, rule.Protocol, rule.Port, rule.TargetIP, rule.TargetPort, rule.Interface}, " ")), keyword) {
|
||||
continue
|
||||
}
|
||||
items = append(items, dto.ForwardRule{Num: strconv.Itoa(len(items) + 1), Family: rule.Family, Protocol: rule.Protocol, Port: rule.Port, TargetIP: rule.TargetIP, TargetPort: rule.TargetPort, Interface: rule.Interface})
|
||||
}
|
||||
inventory = filtered
|
||||
total := len(inventory)
|
||||
start, end := (request.Page-1)*request.PageSize, request.Page*request.PageSize
|
||||
total := len(items)
|
||||
if request.All {
|
||||
start, end = 0, total
|
||||
return int64(total), items, nil
|
||||
}
|
||||
if start > total {
|
||||
return int64(total), make([]dto.ForwardRule, 0), nil
|
||||
}
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
pageRules := inventory[start:end]
|
||||
var items []dto.ForwardRule
|
||||
if pageRules != nil {
|
||||
items = make([]dto.ForwardRule, 0, len(pageRules))
|
||||
}
|
||||
for index, item := range pageRules {
|
||||
items = append(items, dto.ForwardRule{
|
||||
ID: item.ID,
|
||||
Num: strconv.Itoa(start + index + 1),
|
||||
Family: item.Rule.Family,
|
||||
Protocol: item.Rule.Protocol,
|
||||
Port: item.Rule.Port,
|
||||
TargetIP: item.Rule.TargetIP,
|
||||
TargetPort: item.Rule.TargetPort,
|
||||
Interface: item.Rule.Interface,
|
||||
IsDesired: item.IsDesired,
|
||||
IsRuntime: item.IsRuntime,
|
||||
SyncStatus: item.SyncStatus(),
|
||||
})
|
||||
}
|
||||
return int64(total), items, nil
|
||||
}
|
||||
|
||||
func forwardingRuleMatchesKeyword(item forwardingInventoryItem, keyword string) bool {
|
||||
values := []string{
|
||||
item.Rule.Family, item.Rule.Protocol, item.Rule.Port, item.Rule.TargetIP,
|
||||
item.Rule.TargetPort, item.Rule.Interface, item.SyncStatus(),
|
||||
}
|
||||
for _, value := range values {
|
||||
if strings.Contains(strings.ToLower(value), keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
start := min(max(request.Page-1, 0)*max(request.PageSize, 1), total)
|
||||
end := min(start+max(request.PageSize, 1), total)
|
||||
return int64(total), items[start:end], nil
|
||||
}
|
||||
|
||||
func (s *ForwardingService) OperateRules(request dto.ForwardRuleOperate) (dto.FilterChainOperationResponse, error) {
|
||||
labels := make([]string, len(request.Rules))
|
||||
count := 0
|
||||
for _, rule := range request.Rules {
|
||||
if rule.Operation == "add" {
|
||||
count += strings.Count(rule.Protocol, "/") + 1
|
||||
}
|
||||
if count > filter.MaxAtomicExpansion {
|
||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("create or import at most %d rules per batch (after expansion)", filter.MaxAtomicExpansion)
|
||||
}
|
||||
}
|
||||
operation := task.TaskCreate
|
||||
for i, 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)
|
||||
for _, rule := range request.Rules {
|
||||
if rule.Operation != "add" {
|
||||
operation = task.TaskUpdate
|
||||
}
|
||||
@@ -203,375 +165,169 @@ func (s *ForwardingService) OperateRules(request dto.ForwardRuleOperate) (dto.Fi
|
||||
if forwardingOperationsOnlyRemove(request.Rules) {
|
||||
operation = task.TaskDelete
|
||||
}
|
||||
return queueFirewallRuleTask(firewallTaskForwarding, operation, labels, func(ctx context.Context) error {
|
||||
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)
|
||||
taskItem, err := task.NewTask(firewallTaskName(operation, firewallTaskForwarding, ""), operation, task.TaskScopeFirewall, "", 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
desired, err := applyForwardingOperations(forwardingRulesFromModels(stored), request.Rules)
|
||||
if errors.Is(err, forwarding.ErrRuleExists) {
|
||||
return buserr.New("ErrRecordExist")
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.rules.ReplaceAll(ctx, forwardingRuleModels(desired)); err != nil {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
forwardingMutationMu.Lock()
|
||||
defer forwardingMutationMu.Unlock()
|
||||
manager, err := s.managerFactory()
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
if err := s.persistForwardingEnabled(); err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
if err := s.activateManager(manager); err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
rules, err := s.rules.List(context.Background())
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
err = manager.Reconcile(forwardingRulesFromModels(rules))
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ForwardingService) QueueInitialization(
|
||||
request dto.FirewallInitializationTask,
|
||||
) (dto.FilterChainOperationResponse, error) {
|
||||
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeFirewall, 0); err != nil {
|
||||
return dto.FilterChainOperationResponse{}, err
|
||||
}
|
||||
taskItem, err := task.NewTask(firewallTaskName(task.TaskExec, firewallTaskForwarding, ""), task.TaskExec, task.TaskScopeFirewall, request.TaskID, 0)
|
||||
if err != nil {
|
||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("create forwarding initialization task: %w", err)
|
||||
}
|
||||
var manager *forwarding.Manager
|
||||
var backend string
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("FirewallEnableForwardingStep"), func(t *task.Task) error {
|
||||
forwardingMutationMu.Lock()
|
||||
defer forwardingMutationMu.Unlock()
|
||||
var err error
|
||||
manager, err = s.managerFactory()
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
backend = manager.Name()
|
||||
t.Logf("backend=%s", backend)
|
||||
if err := s.persistForwardingEnabled(); err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
if err := s.activateManager(manager); err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("FirewallRestoreForwardingRulesStep"), func(t *task.Task) error {
|
||||
forwardingMutationMu.Lock()
|
||||
defer forwardingMutationMu.Unlock()
|
||||
rules, err := s.rules.List(t.TaskCtx)
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
err = manager.Reconcile(forwardingRulesFromModels(rules))
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}, nil)
|
||||
if err := repo.NewITaskRepo().Save(context.Background(), taskItem.Task); err != nil {
|
||||
return dto.FilterChainOperationResponse{}, fmt.Errorf("save forwarding initialization task: %w", err)
|
||||
taskItem.AddSubTaskWithOps(taskItem.Name, func(t *task.Task) error {
|
||||
return s.operateRules(t.TaskCtx, request, t, false)
|
||||
}, nil, 0, 0)
|
||||
if err := taskRepo.Save(context.Background(), taskItem.Task); err != nil {
|
||||
taskItem.LogFailedWithErr(taskItem.Name, err)
|
||||
closeUnstartedFirewallTask(taskItem)
|
||||
return dto.FilterChainOperationResponse{}, err
|
||||
}
|
||||
go func() { _ = taskItem.Execute() }()
|
||||
return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil
|
||||
}
|
||||
|
||||
func (s *ForwardingService) Enable(ctx context.Context) error {
|
||||
forwardingMutationMu.Lock()
|
||||
defer forwardingMutationMu.Unlock()
|
||||
manager, err := s.clientFactory(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rules, err := manager.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
families, err := loadFirewallFamilies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := initializeForwarding(manager, families); err != nil {
|
||||
return err
|
||||
}
|
||||
return persistForwardingRules(manager, rules)
|
||||
}
|
||||
|
||||
func (s *ForwardingService) QueueInitialization(request dto.FirewallInitializationTask) (dto.FilterChainOperationResponse, error) {
|
||||
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeFirewall, 0); err != nil {
|
||||
return dto.FilterChainOperationResponse{}, err
|
||||
}
|
||||
var backup dto.FirewallSubsystemBackup
|
||||
if request.BackupFile != "" {
|
||||
var err error
|
||||
backup, err = readFirewallSubsystemBackup(request.BackupFile, "forwarding")
|
||||
if err != nil {
|
||||
return dto.FilterChainOperationResponse{}, err
|
||||
}
|
||||
}
|
||||
operations := dto.ForwardRuleOperate{Rules: make([]dto.ForwardRuleOperation, 0, len(backup.Forwarding))}
|
||||
for _, rule := range backup.Forwarding {
|
||||
operations.Rules = append(operations.Rules, dto.ForwardRuleOperation{Operation: "add", Family: rule.Family, Protocol: rule.Protocol, Port: rule.Port, TargetIP: rule.TargetIP, TargetPort: rule.TargetPort, Interface: rule.Interface})
|
||||
}
|
||||
return queueFirewallRuleTask(firewallTaskForwarding, task.TaskExec, request.TaskID, nil, func(t *task.Task) error {
|
||||
return s.operateRules(t.TaskCtx, operations, t, true, backup.Families...)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ForwardingService) Restore(ctx context.Context) error {
|
||||
forwardingMutationMu.Lock()
|
||||
defer forwardingMutationMu.Unlock()
|
||||
enabled, err := s.forwardingEnabled()
|
||||
if err != nil || !enabled {
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
manager, err := s.managerFactory()
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
stored, err := s.rules.List(ctx)
|
||||
if err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
if err := s.activateManager(manager); err != nil {
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
err = manager.Reconcile(forwardingRulesFromModels(stored))
|
||||
recordForwardingSyncError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ForwardingService) reconcile(rules []forwarding.Rule) error {
|
||||
manager, err := s.managerFactory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.reconcileWithManager(manager, rules)
|
||||
}
|
||||
|
||||
func (s *ForwardingService) reconcileWithManager(manager *forwarding.Manager, rules []forwarding.Rule) 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,
|
||||
})
|
||||
if err != nil || status != constant.StatusEnable {
|
||||
return err
|
||||
}
|
||||
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,
|
||||
})
|
||||
manager, err := s.clientFactory(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
backup, err := readFirewallSubsystemBackup("forwarding-"+manager.Name()+".rules", "forwarding")
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func mergeForwardingInventory(
|
||||
stored []model.ForwardingRule,
|
||||
runtime []forwarding.Rule,
|
||||
) ([]forwardingInventoryItem, error) {
|
||||
items := make([]forwardingInventoryItem, 0, len(stored)+len(runtime))
|
||||
byIdentity := make(map[string]int, len(stored)+len(runtime))
|
||||
for _, record := range stored {
|
||||
rule, err := forwarding.NormalizeRule(forwarding.Rule{
|
||||
Family: record.Family, Protocol: record.Protocol, Port: record.Port, TargetIP: record.TargetIP,
|
||||
TargetPort: record.TargetPort, Interface: record.Interface,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
families, err := loadFirewallFamilies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !slices.Contains(families, constant.FirewallFamilyIPv6) {
|
||||
before := len(backup.Forwarding)
|
||||
backup.Forwarding = slices.DeleteFunc(backup.Forwarding, func(rule forwarding.Rule) bool { return rule.Family == constant.FirewallFamilyIPv6 })
|
||||
logFirewallIPv6Skipped(nil, "forwarding startup", before-len(backup.Forwarding))
|
||||
}
|
||||
missing := make(map[string]bool)
|
||||
needsBind := false
|
||||
for _, family := range families {
|
||||
initialized, bound, err := manager.FamilyStatus(family)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("normalize desired forwarding rule: %w", err)
|
||||
return err
|
||||
}
|
||||
key := rule.Identity()
|
||||
byIdentity[key] = len(items)
|
||||
items = append(items, forwardingInventoryItem{ID: record.ID, Rule: rule, IsDesired: true})
|
||||
missing[family] = !initialized
|
||||
needsBind = needsBind || (initialized && !bound)
|
||||
}
|
||||
for _, observed := range runtime {
|
||||
rule, err := forwarding.NormalizeRule(observed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("normalize runtime forwarding rule: %w", err)
|
||||
}
|
||||
key := rule.Identity()
|
||||
if index, exists := byIdentity[key]; exists {
|
||||
items[index].IsRuntime = true
|
||||
restoreIPv6 := slices.Contains(backup.Families, forwarding.FamilyIPv6)
|
||||
for _, rule := range backup.Forwarding {
|
||||
restoreIPv6 = restoreIPv6 || rule.Family == forwarding.FamilyIPv6
|
||||
}
|
||||
if !missing[forwarding.FamilyIPv4] && (!missing[forwarding.FamilyIPv6] || !restoreIPv6) && !needsBind {
|
||||
return nil
|
||||
}
|
||||
current, err := manager.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seen := make(map[string]int, len(current))
|
||||
for _, rule := range current {
|
||||
seen[rule.Identity()]++
|
||||
}
|
||||
for _, family := range families {
|
||||
if family == forwarding.FamilyIPv6 && missing[family] && !restoreIPv6 {
|
||||
continue
|
||||
}
|
||||
byIdentity[key] = len(items)
|
||||
items = append(items, forwardingInventoryItem{Rule: rule, IsRuntime: true})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func recordForwardingSyncError(err error) {
|
||||
forwardingSyncStateMu.Lock()
|
||||
forwardingLastSyncErr = err
|
||||
forwardingSyncStateMu.Unlock()
|
||||
}
|
||||
|
||||
func lastForwardingSyncError() string {
|
||||
forwardingSyncStateMu.RLock()
|
||||
defer forwardingSyncStateMu.RUnlock()
|
||||
if forwardingLastSyncErr == nil {
|
||||
return ""
|
||||
}
|
||||
return forwardingLastSyncErr.Error()
|
||||
}
|
||||
|
||||
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)
|
||||
if err := manager.OperateFamily(family, missing[family]); err != nil {
|
||||
return err
|
||||
}
|
||||
desired = append(desired, normalized)
|
||||
}
|
||||
for _, operation := range requested {
|
||||
for _, protocol := range strings.Split(operation.Protocol, "/") {
|
||||
rule, err := forwarding.NormalizeRule(forwarding.Rule{
|
||||
Family: operation.Family, Protocol: protocol, Port: operation.Port, TargetIP: operation.TargetIP,
|
||||
TargetPort: operation.TargetPort, Interface: operation.Interface,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
for _, family := range families {
|
||||
if !missing[family] {
|
||||
continue
|
||||
}
|
||||
rules := make([]forwarding.Rule, 0)
|
||||
for _, rule := range backup.Forwarding {
|
||||
if rule.Family != family {
|
||||
continue
|
||||
}
|
||||
index := forwardingRuleIndex(desired, rule)
|
||||
switch forwarding.OperationType(operation.Operation) {
|
||||
case forwarding.OperationAdd:
|
||||
if index >= 0 {
|
||||
return nil, forwarding.ErrRuleExists
|
||||
}
|
||||
desired = append(desired, rule)
|
||||
case forwarding.OperationRemove:
|
||||
if index >= 0 {
|
||||
desired = append(desired[:index], desired[index+1:]...)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported forwarding operation %q", operation.Operation)
|
||||
if seen[rule.Identity()] > 0 {
|
||||
seen[rule.Identity()]--
|
||||
continue
|
||||
}
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
if err := manager.CreateRules(ctx, rules); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return desired, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func forwardingRuleIndex(rules []forwarding.Rule, wanted forwarding.Rule) int {
|
||||
wantedIdentity := wanted.Identity()
|
||||
for index, rule := range rules {
|
||||
if rule.Identity() == wantedIdentity {
|
||||
return index
|
||||
func NewIForwardingService() IForwardingService {
|
||||
return newForwardingService()
|
||||
}
|
||||
|
||||
func loadForwardPolicy(ctx context.Context, command string) (string, error) {
|
||||
if !cmd.Which(command) {
|
||||
command += "-nft"
|
||||
if !cmd.Which(command) {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func forwardingOperationsOnlyRemove(operations []dto.ForwardRuleOperation) bool {
|
||||
if len(operations) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, operation := range operations {
|
||||
if operation.Operation != string(forwarding.OperationRemove) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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)
|
||||
output, err := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(5*time.Second)).RunWithOptionalSudoAndStdout(command, "-t", "filter", "-w", "2", "-S", "FORWARD")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"%w: selected forwarding backend %s: %w",
|
||||
errForwardingBackendUnavailable, backend, err,
|
||||
)
|
||||
return "", err
|
||||
}
|
||||
adapter, err := forwarding.New(client.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 3 && fields[0] == "-P" && fields[1] == "FORWARD" {
|
||||
if fields[2] != "ACCEPT" && fields[2] != "DROP" {
|
||||
return "", fmt.Errorf("unexpected FORWARD policy: %s", fields[2])
|
||||
}
|
||||
return fields[2], nil
|
||||
}
|
||||
}
|
||||
return forwarding.NewManager(adapter, client), nil
|
||||
return "", errors.New("FORWARD default policy was not found")
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/moby/go-archive"
|
||||
)
|
||||
|
||||
type ImageService struct{}
|
||||
|
||||
+437
-138
@@ -33,11 +33,14 @@ import (
|
||||
type MonitorService struct {
|
||||
DiskIO chan ([]disk.IOCountersStat)
|
||||
NetIO chan ([]net.IOCountersStat)
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
var (
|
||||
monitorCancel context.CancelFunc
|
||||
hostSysPath = loadHostSysPath()
|
||||
monitorSettingMutex sync.Mutex
|
||||
gpuMonitorMutex sync.Mutex
|
||||
monitorCancel context.CancelFunc
|
||||
hostSysPath = loadHostSysPath()
|
||||
|
||||
blockDevicePartitionCache sync.Map
|
||||
)
|
||||
@@ -47,8 +50,13 @@ type IMonitorService interface {
|
||||
LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorData, error)
|
||||
LoadSetting() (*dto.MonitorSetting, error)
|
||||
UpdateSetting(key, value string) error
|
||||
CleanData() error
|
||||
CleanData(monitorType string) error
|
||||
|
||||
LoadIOOptions() []string
|
||||
LoadNetworkOptions() []string
|
||||
LoadVLLMMonitorData(req dto.MonitorVLLMSearch) (dto.MonitorVLLMData, error)
|
||||
LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error)
|
||||
CleanVLLMMonitor(req dto.MonitorVLLMClean) error
|
||||
LoadGPUOptions() dto.MonitorGPUOptions
|
||||
LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error)
|
||||
|
||||
@@ -60,6 +68,7 @@ func NewIMonitorService() IMonitorService {
|
||||
return &MonitorService{
|
||||
DiskIO: make(chan []disk.IOCountersStat, 2),
|
||||
NetIO: make(chan []net.IOCountersStat, 2),
|
||||
ctx: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +94,7 @@ func (m *MonitorService) LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorDa
|
||||
base.TopCPUItems = processes
|
||||
base.TopCPU = ""
|
||||
}
|
||||
if req.Param == "all" || req.Param == "mem" {
|
||||
if req.Param == "all" || req.Param == "memory" {
|
||||
var processes []dto.Process
|
||||
_ = json.Unmarshal([]byte(base.TopMem), &processes)
|
||||
base.TopMemItems = processes
|
||||
@@ -96,7 +105,11 @@ func (m *MonitorService) LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorDa
|
||||
data = append(data, itemData)
|
||||
}
|
||||
if req.Param == "all" || req.Param == "io" {
|
||||
bases, err := monitorRepo.GetIO(repo.WithByName(req.IO), repo.WithByCreatedAt(req.StartTime, req.EndTime))
|
||||
ioOpts := []repo.DBOption{repo.WithByCreatedAt(req.StartTime, req.EndTime)}
|
||||
if len(req.IO) != 0 {
|
||||
ioOpts = append(ioOpts, repo.WithByName(req.IO))
|
||||
}
|
||||
bases, err := monitorRepo.GetIO(ioOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -110,7 +123,11 @@ func (m *MonitorService) LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorDa
|
||||
data = append(data, itemData)
|
||||
}
|
||||
if req.Param == "all" || req.Param == "network" {
|
||||
bases, err := monitorRepo.GetNetwork(repo.WithByName(req.Network), repo.WithByCreatedAt(req.StartTime, req.EndTime))
|
||||
netOpts := []repo.DBOption{repo.WithByCreatedAt(req.StartTime, req.EndTime)}
|
||||
if len(req.Network) != 0 {
|
||||
netOpts = append(netOpts, repo.WithByName(req.Network))
|
||||
}
|
||||
bases, err := monitorRepo.GetNetwork(netOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -126,117 +143,171 @@ func (m *MonitorService) LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorDa
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (m *MonitorService) LoadIOOptions() []string {
|
||||
optionSet := make(map[string]struct{})
|
||||
if diskStat, err := disk.IOCounters(); err == nil {
|
||||
for _, item := range diskStat {
|
||||
optionSet[item.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
// union with names recorded in the monitor db so removed devices stay selectable
|
||||
if names, err := monitorRepo.GetIONames(); err == nil {
|
||||
for _, name := range names {
|
||||
optionSet[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
return sortedMonitorOptions(optionSet)
|
||||
}
|
||||
|
||||
func (m *MonitorService) LoadNetworkOptions() []string {
|
||||
optionSet := make(map[string]struct{})
|
||||
if netStat, err := net.IOCounters(true); err == nil {
|
||||
for _, item := range netStat {
|
||||
optionSet[item.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
if names, err := monitorRepo.GetNetworkNames(); err == nil {
|
||||
for _, name := range names {
|
||||
optionSet[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
return sortedMonitorOptions(optionSet)
|
||||
}
|
||||
|
||||
func (m *MonitorService) LoadGPUOptions() dto.MonitorGPUOptions {
|
||||
var data dto.MonitorGPUOptions
|
||||
exist, client := accelerator.New()
|
||||
if !exist {
|
||||
return data
|
||||
}
|
||||
seen := make(map[string]bool)
|
||||
snapshot, err := client.Collect(context.Background())
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Load accelerator info failed, err: %v", err)
|
||||
global.LOG.Warnf("Load accelerator options failed: %v", err)
|
||||
} else {
|
||||
data = loadGPUOptions(snapshot)
|
||||
for _, item := range data.ChartHide {
|
||||
seen[item.DeviceID] = true
|
||||
}
|
||||
}
|
||||
data.Supported = true
|
||||
devices, err := monitorRepo.GetGPUDevices()
|
||||
if err != nil {
|
||||
global.LOG.Warnf("Load accelerator history options failed: %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 {
|
||||
hasGPUOrNPU = true
|
||||
for _, device := range devices {
|
||||
key := device.DeviceID
|
||||
if key == "" {
|
||||
key = "legacy:" + device.ProductName
|
||||
}
|
||||
}
|
||||
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 seen[key] {
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
seen[key] = true
|
||||
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)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
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
|
||||
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 {
|
||||
return data, err
|
||||
}
|
||||
|
||||
for _, gpu := range gpuList {
|
||||
data.Date = append(data.Date, gpu.CreatedAt)
|
||||
data.GPUValue = append(data.GPUValue, gpu.GPUUtil)
|
||||
data.TemperatureValue = append(data.TemperatureValue, gpu.Temperature)
|
||||
data.PowerUsed = append(data.PowerUsed, gpu.PowerDraw)
|
||||
data.PowerTotal = append(data.PowerTotal, gpu.MaxPowerLimit)
|
||||
if gpu.MaxPowerLimit != 0 {
|
||||
data.PowerPercent = append(data.PowerPercent, gpu.PowerDraw/gpu.MaxPowerLimit*100)
|
||||
} else {
|
||||
data.PowerPercent = append(data.PowerPercent, float64(0))
|
||||
req.StartTime, req.EndTime = req.StartTime.In(loc), req.EndTime.In(loc)
|
||||
opts := []repo.DBOption{repo.WithByCreatedAt(req.StartTime, req.EndTime), monitorRepo.WithByGPUDevice(req.DeviceID, req.ProductName, req.Legacy)}
|
||||
data.SampleCount, err = monitorRepo.CountGPU(opts...)
|
||||
if err != nil || data.SampleCount == 0 {
|
||||
return data, err
|
||||
}
|
||||
if data.SampleCount > 1200 {
|
||||
seconds := req.EndTime.Unix() - req.StartTime.Unix() + 1
|
||||
data.BucketSeconds = (seconds + 599) / 600
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
data.MemoryTotal = append(data.MemoryTotal, gpu.MemTotal)
|
||||
data.MemoryUsed = append(data.MemoryUsed, gpu.MemUsed)
|
||||
if gpu.MemTotal != 0 {
|
||||
data.MemoryPercent = append(data.MemoryPercent, gpu.MemUsed/gpu.MemTotal*100)
|
||||
} else {
|
||||
data.MemoryPercent = append(data.MemoryPercent, float64(0))
|
||||
} else {
|
||||
for i, point := range points {
|
||||
if i > 0 && points[i-1].IntervalSeconds > 0 {
|
||||
interval := time.Duration(points[i-1].IntervalSeconds) * time.Second
|
||||
if point.CreatedAt.Sub(points[i-1].CreatedAt) > 2*interval {
|
||||
samples = append(samples, repo.GPUHistoryPoint{MonitorGPU: model.MonitorGPU{BaseModel: model.BaseModel{CreatedAt: points[i-1].CreatedAt.Add(interval)}}})
|
||||
}
|
||||
}
|
||||
samples = append(samples, point)
|
||||
}
|
||||
var process []dto.GPUProcess
|
||||
if err := json.Unmarshal([]byte(gpu.Processes), &process); err == nil {
|
||||
data.ProcessCount = append(data.ProcessCount, len(process))
|
||||
data.GPUProcesses = append(data.GPUProcesses, process)
|
||||
} else {
|
||||
data.ProcessCount = append(data.ProcessCount, 0)
|
||||
data.GPUProcesses = append(data.GPUProcesses, []dto.GPUProcess{})
|
||||
}
|
||||
for _, point := range samples {
|
||||
data.Date = append(data.Date, point.CreatedAt)
|
||||
data.MemoryActivity = append(data.MemoryActivity, point.MemoryActivity)
|
||||
data.EncoderUtil = append(data.EncoderUtil, point.EncoderUtil)
|
||||
data.DecoderUtil = append(data.DecoderUtil, point.DecoderUtil)
|
||||
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
|
||||
}
|
||||
@@ -262,9 +333,27 @@ func (m *MonitorService) LoadSetting() (*dto.MonitorSetting, error) {
|
||||
}
|
||||
|
||||
func (m *MonitorService) UpdateSetting(key, value string) error {
|
||||
monitorSettingMutex.Lock()
|
||||
defer monitorSettingMutex.Unlock()
|
||||
switch key {
|
||||
case "MonitorStatus", "GPUMonitorStatus", "VLLMMonitorStatus":
|
||||
if value != constant.StatusEnable && value != constant.StatusDisable {
|
||||
return fmt.Errorf("invalid monitoring status")
|
||||
}
|
||||
case "MonitorInterval", "GPUMonitorInterval", "VLLMMonitorInterval":
|
||||
interval, err := strconv.Atoi(value)
|
||||
if err != nil || interval < 10 || interval > 43200 {
|
||||
return fmt.Errorf("monitoring interval must be between 10 and 43200 seconds")
|
||||
}
|
||||
case "MonitorStoreDays", "GPUMonitorStoreDays", "VLLMMonitorStoreDays":
|
||||
days, err := strconv.Atoi(value)
|
||||
if err != nil || days < 1 {
|
||||
return fmt.Errorf("monitoring retention must be a positive integer")
|
||||
}
|
||||
}
|
||||
switch key {
|
||||
case "MonitorStatus":
|
||||
if value == constant.StatusEnable && global.MonitorCronID == 0 {
|
||||
if value == constant.StatusEnable && monitorCancel == nil {
|
||||
interval, err := settingRepo.Get(settingRepo.WithByKey("MonitorInterval"))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -273,8 +362,9 @@ func (m *MonitorService) UpdateSetting(key, value string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if value == constant.StatusDisable && global.MonitorCronID != 0 {
|
||||
if value == constant.StatusDisable && monitorCancel != nil {
|
||||
monitorCancel()
|
||||
monitorCancel = nil
|
||||
global.Cron.Remove(cron.EntryID(global.MonitorCronID))
|
||||
global.MonitorCronID = 0
|
||||
}
|
||||
@@ -283,31 +373,80 @@ func (m *MonitorService) UpdateSetting(key, value string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status.Value == constant.StatusEnable && global.MonitorCronID != 0 {
|
||||
if status.Value == constant.StatusEnable && monitorCancel != nil {
|
||||
if err := StartMonitor(true, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case "GPUMonitorStatus":
|
||||
if value == constant.StatusEnable && global.GPUMonitorCronID == 0 {
|
||||
interval, err := settingRepo.GetValueByKey("GPUMonitorInterval")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := StartGPUMonitor(interval); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if value == constant.StatusDisable && global.GPUMonitorCronID != 0 {
|
||||
global.Cron.Remove(global.GPUMonitorCronID)
|
||||
global.GPUMonitorCronID = 0
|
||||
}
|
||||
case "GPUMonitorInterval":
|
||||
status, err := settingRepo.GetValueByKey("GPUMonitorStatus")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status == constant.StatusEnable {
|
||||
if err := StartGPUMonitor(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case "VLLMMonitorStatus":
|
||||
if value == constant.StatusEnable && global.VLLMMonitorCronID == 0 {
|
||||
interval, err := settingRepo.GetValueByKey("VLLMMonitorInterval")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := StartVLLMMonitor(interval); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if value == constant.StatusDisable && global.VLLMMonitorCronID != 0 {
|
||||
global.Cron.Remove(global.VLLMMonitorCronID)
|
||||
global.VLLMMonitorCronID = 0
|
||||
}
|
||||
case "VLLMMonitorInterval":
|
||||
status, err := settingRepo.GetValueByKey("VLLMMonitorStatus")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status == constant.StatusEnable {
|
||||
if err := StartVLLMMonitor(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return settingRepo.Update(key, value)
|
||||
}
|
||||
|
||||
func (m *MonitorService) CleanData() error {
|
||||
if err := global.MonitorDB.Exec("DELETE FROM monitor_bases").Error; err != nil {
|
||||
return err
|
||||
func (m *MonitorService) CleanData(monitorType string) error {
|
||||
switch monitorType {
|
||||
case "host":
|
||||
return monitorRepo.CleanHost()
|
||||
case "gpu":
|
||||
gpuMonitorMutex.Lock()
|
||||
defer gpuMonitorMutex.Unlock()
|
||||
return monitorRepo.CleanGPU()
|
||||
default:
|
||||
return fmt.Errorf("unsupported monitoring cleanup type: %s", monitorType)
|
||||
}
|
||||
if err := global.MonitorDB.Exec("DELETE FROM monitor_ios").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := global.MonitorDB.Exec("DELETE FROM monitor_networks").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
_ = global.GPUMonitorDB.Exec("DELETE FROM monitor_gpus").Error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MonitorService) Run() {
|
||||
saveAcceleratorDataToDB()
|
||||
if m.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
var itemModel model.MonitorBase
|
||||
totalPercent, _ := cpu.Percent(3*time.Second, false)
|
||||
if len(totalPercent) == 1 {
|
||||
@@ -353,30 +492,34 @@ func (m *MonitorService) Run() {
|
||||
_ = monitorRepo.DelMonitorBase(timeForDelete)
|
||||
_ = monitorRepo.DelMonitorIO(timeForDelete)
|
||||
_ = monitorRepo.DelMonitorNet(timeForDelete)
|
||||
_ = monitorRepo.DelMonitorGPU(timeForDelete)
|
||||
}
|
||||
|
||||
func (m *MonitorService) loadDiskIO() {
|
||||
ioStat, _ := disk.IOCounters()
|
||||
ioStat, _ := disk.IOCountersWithContext(m.ctx)
|
||||
var diskIOList []disk.IOCountersStat
|
||||
for _, io := range ioStat {
|
||||
diskIOList = append(diskIOList, io)
|
||||
}
|
||||
diskIOList = append(diskIOList, sumDiskIOCounters(ioStat))
|
||||
m.DiskIO <- diskIOList
|
||||
select {
|
||||
case <-m.ctx.Done():
|
||||
case m.DiskIO <- diskIOList:
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MonitorService) loadNetIO() {
|
||||
netStat, _ := net.IOCounters(true)
|
||||
netStatAll, _ := net.IOCounters(false)
|
||||
netStat, _ := net.IOCountersWithContext(m.ctx, true)
|
||||
netStatAll, _ := net.IOCountersWithContext(m.ctx, false)
|
||||
var netList []net.IOCountersStat
|
||||
netList = append(netList, netStat...)
|
||||
netList = append(netList, netStatAll...)
|
||||
m.NetIO <- netList
|
||||
select {
|
||||
case <-m.ctx.Done():
|
||||
case m.NetIO <- netList:
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MonitorService) saveIODataToDB(ctx context.Context, interval float64) {
|
||||
defer close(m.DiskIO)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -426,14 +569,17 @@ func (m *MonitorService) saveIODataToDB(ctx context.Context, interval float64) {
|
||||
}
|
||||
}
|
||||
_ = monitorRepo.BatchCreateMonitorIO(ioList)
|
||||
m.DiskIO <- ioStat2
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case m.DiskIO <- ioStat2:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MonitorService) saveNetDataToDB(ctx context.Context, interval float64) {
|
||||
defer close(m.NetIO)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -463,7 +609,11 @@ func (m *MonitorService) saveNetDataToDB(ctx context.Context, interval float64)
|
||||
}
|
||||
|
||||
_ = monitorRepo.BatchCreateMonitorNet(netList)
|
||||
m.NetIO <- netStat2
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case m.NetIO <- netStat2:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -572,27 +722,38 @@ func loadTopMem() []dto.Process {
|
||||
}
|
||||
|
||||
func StartMonitor(removeBefore bool, interval string) error {
|
||||
intervalItem, err := strconv.Atoi(interval)
|
||||
if err != nil || intervalItem < 10 || intervalItem > 43200 {
|
||||
return fmt.Errorf("invalid host monitoring interval: %s", interval)
|
||||
}
|
||||
if removeBefore {
|
||||
monitorCancel()
|
||||
global.Cron.Remove(cron.EntryID(global.MonitorCronID))
|
||||
}
|
||||
intervalItem, err := strconv.Atoi(interval)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service := NewIMonitorService()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
monitorCancel = cancel
|
||||
service := &MonitorService{
|
||||
DiskIO: make(chan []disk.IOCountersStat, 2),
|
||||
NetIO: make(chan []net.IOCountersStat, 2),
|
||||
ctx: ctx,
|
||||
}
|
||||
now := time.Now()
|
||||
nextMinute := now.Truncate(time.Minute).Add(time.Minute)
|
||||
time.AfterFunc(time.Until(nextMinute), func() {
|
||||
timer := time.AfterFunc(time.Until(nextMinute), func() {
|
||||
monitorSettingMutex.Lock()
|
||||
defer monitorSettingMutex.Unlock()
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %ss", interval), service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
global.MonitorCronID = monitorID
|
||||
})
|
||||
monitorCancel = func() {
|
||||
cancel()
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
service.Run()
|
||||
|
||||
@@ -602,7 +763,97 @@ func StartMonitor(removeBefore bool, interval string) error {
|
||||
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 StartGPUMonitor(interval string) error {
|
||||
seconds, err := strconv.Atoi(interval)
|
||||
if err != nil || seconds < 10 || seconds > 43200 {
|
||||
return fmt.Errorf("invalid GPU monitoring interval: %s", interval)
|
||||
}
|
||||
service := &MonitorService{}
|
||||
job := cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(cron.FuncJob(service.saveGPUData))
|
||||
id, err := global.Cron.AddFunc(fmt.Sprintf("@every %ds", seconds), func() { go job.Run() })
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if global.GPUMonitorCronID != 0 {
|
||||
global.Cron.Remove(global.GPUMonitorCronID)
|
||||
}
|
||||
global.GPUMonitorCronID = id
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MonitorService) saveGPUData() {
|
||||
if !gpuMonitorMutex.TryLock() {
|
||||
return
|
||||
}
|
||||
defer gpuMonitorMutex.Unlock()
|
||||
status, err := settingRepo.GetValueByKey("GPUMonitorStatus")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Load GPU monitoring status failed: %v", err)
|
||||
return
|
||||
}
|
||||
if status != constant.StatusEnable {
|
||||
return
|
||||
}
|
||||
retention, err := settingRepo.GetValueByKey("GPUMonitorStoreDays")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Load GPU monitoring retention failed: %v", err)
|
||||
return
|
||||
}
|
||||
days, err := strconv.Atoi(retention)
|
||||
if err != nil || days < 1 {
|
||||
global.LOG.Errorf("Invalid GPU monitoring retention: %s", retention)
|
||||
return
|
||||
}
|
||||
if err := monitorRepo.DelMonitorGPU(time.Now().AddDate(0, 0, -days)); err != nil {
|
||||
global.LOG.Errorf("Clean GPU monitoring data failed: %v", err)
|
||||
}
|
||||
exist, client := accelerator.New()
|
||||
if !exist {
|
||||
return
|
||||
@@ -615,27 +866,64 @@ func saveAcceleratorDataToDB() {
|
||||
if warning := snapshot.Warning(); warning != nil {
|
||||
global.LOG.Warnf("load accelerator monitor data partially failed, err: %v", warning)
|
||||
}
|
||||
intervalSeconds := 0
|
||||
if setting, err := settingRepo.Get(settingRepo.WithByKey("GPUMonitorInterval")); err == nil {
|
||||
intervalSeconds, _ = strconv.Atoi(setting.Value)
|
||||
}
|
||||
list := make([]model.MonitorGPU, 0, len(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)
|
||||
}
|
||||
}
|
||||
|
||||
func newMonitorGPU(device accelerator.Device) model.MonitorGPU {
|
||||
item := model.MonitorGPU{
|
||||
ProductName: device.Label,
|
||||
GPUUtil: device.Metrics.Utilization.ValueOrZero(),
|
||||
Temperature: device.Metrics.Temperature.ValueOrZero(),
|
||||
PowerDraw: device.Metrics.Power.ValueOrZero(),
|
||||
MaxPowerLimit: device.Metrics.PowerLimit.ValueOrZero(),
|
||||
MemUsed: device.Metrics.MemoryUsed.ValueOrZero(),
|
||||
MemTotal: device.Metrics.MemoryTotal.ValueOrZero(),
|
||||
FanSpeed: int(device.Metrics.FanSpeed.ValueOrZero()),
|
||||
MemoryUtil: device.Metrics.MemoryUtil.Value,
|
||||
MemoryActivity: device.Metrics.MemoryActivity.Value,
|
||||
EncoderUtil: device.Metrics.EncoderUtil.Value,
|
||||
DecoderUtil: device.Metrics.DecoderUtil.Value,
|
||||
JPEGUtil: device.Metrics.JPEGUtil.Value,
|
||||
OFAUtil: device.Metrics.OFAUtil.Value,
|
||||
MediaUtil: device.Metrics.MediaUtil.Value,
|
||||
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
|
||||
}
|
||||
processes := make([]dto.GPUProcess, 0, len(device.Processes))
|
||||
@@ -695,3 +983,14 @@ func loadHostSysPath() string {
|
||||
}
|
||||
return hostSys
|
||||
}
|
||||
|
||||
func sortedMonitorOptions(optionSet map[string]struct{}) []string {
|
||||
options := make([]string, 0, len(optionSet))
|
||||
for name := range optionSet {
|
||||
if len(name) != 0 && name != "all" {
|
||||
options = append(options, name)
|
||||
}
|
||||
}
|
||||
sort.Strings(options)
|
||||
return append([]string{"all"}, options...)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/vllm"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/robfig/cron/v3"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var vllmMonitorMutex sync.Mutex
|
||||
var vllmMetricsClient = &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
Transport: &http.Transport{DialContext: (&net.Dialer{Timeout: 3 * time.Second}).DialContext, IdleConnTimeout: 30 * time.Second},
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
|
||||
}
|
||||
|
||||
func (m *MonitorService) LoadVLLMMonitorData(req dto.MonitorVLLMSearch) (dto.MonitorVLLMData, error) {
|
||||
data := dto.MonitorVLLMData{Points: []model.MonitorVLLM{}}
|
||||
if !req.EndTime.After(req.StartTime) {
|
||||
return data, fmt.Errorf("invalid vLLM history request")
|
||||
}
|
||||
install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID))
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
if install.App.Key != "vllm" {
|
||||
return data, fmt.Errorf("not a vLLM installation")
|
||||
}
|
||||
|
||||
loc, err := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
req.StartTime, req.EndTime = req.StartTime.In(loc), req.EndTime.In(loc)
|
||||
data.SampleCount, err = vllmMonitorRepo.Count(req.AppInstallID, req.StartTime, req.EndTime)
|
||||
if err != nil || data.SampleCount == 0 {
|
||||
return data, err
|
||||
}
|
||||
if data.SampleCount > 1200 {
|
||||
data.BucketSeconds = (req.EndTime.Unix() - req.StartTime.Unix() + 600) / 600
|
||||
}
|
||||
points, err := vllmMonitorRepo.History(req.AppInstallID, req.StartTime, req.EndTime, data.BucketSeconds, req.Aggregation)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
if data.BucketSeconds > 0 {
|
||||
if req.Aggregation != "max" {
|
||||
for i := range points {
|
||||
if err := vllm.AggregateHistograms(points[i].HistogramSamples, &points[i].MonitorVLLM); err != nil {
|
||||
return data, err
|
||||
}
|
||||
}
|
||||
}
|
||||
next := 0
|
||||
for bucket := int64(0); bucket <= (req.EndTime.Unix()-req.StartTime.Unix())/data.BucketSeconds; bucket++ {
|
||||
var point model.MonitorVLLM
|
||||
if next < len(points) && points[next].Bucket == bucket {
|
||||
point = points[next].MonitorVLLM
|
||||
next++
|
||||
}
|
||||
point.AppInstallID = req.AppInstallID
|
||||
point.CreatedAt = time.Unix(req.StartTime.Unix()+bucket*data.BucketSeconds, 0).In(loc)
|
||||
if bucket == 0 {
|
||||
point.CreatedAt = req.StartTime
|
||||
}
|
||||
data.Points = append(data.Points, point)
|
||||
}
|
||||
} else {
|
||||
setting, err := settingRepo.GetValueByKey("VLLMMonitorInterval")
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
intervalSeconds, err := strconv.Atoi(setting)
|
||||
if err != nil || intervalSeconds <= 0 {
|
||||
return data, fmt.Errorf("invalid vLLM monitoring interval: %s", setting)
|
||||
}
|
||||
interval := time.Duration(intervalSeconds) * time.Second
|
||||
for i, point := range points {
|
||||
if i > 0 && point.CreatedAt.Sub(points[i-1].CreatedAt) > 2*interval {
|
||||
data.Points = append(data.Points, model.MonitorVLLM{CreatedAt: points[i-1].CreatedAt.Add(interval), AppInstallID: req.AppInstallID})
|
||||
}
|
||||
data.Points = append(data.Points, point.MonitorVLLM)
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (m *MonitorService) LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error) {
|
||||
install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID))
|
||||
if err != nil {
|
||||
return model.MonitorVLLM{}, err
|
||||
}
|
||||
if install.App.Key != "vllm" {
|
||||
return model.MonitorVLLM{}, fmt.Errorf("not a vLLM installation")
|
||||
}
|
||||
previous, err := collectVLLMMetrics(ctx, install, model.MonitorVLLM{})
|
||||
if err != nil {
|
||||
global.LOG.Debugf("Collect vLLM metrics on port %d failed: %v", install.HttpPort, err)
|
||||
return previous, nil
|
||||
}
|
||||
timer := time.NewTimer(time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return model.MonitorVLLM{}, ctx.Err()
|
||||
case <-timer.C:
|
||||
}
|
||||
point, err := collectVLLMMetrics(ctx, install, previous)
|
||||
if err != nil {
|
||||
global.LOG.Debugf("Collect vLLM metrics on port %d failed: %v", install.HttpPort, err)
|
||||
}
|
||||
return point, nil
|
||||
}
|
||||
|
||||
func (m *MonitorService) CleanVLLMMonitor(req dto.MonitorVLLMClean) error {
|
||||
install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if install.App.Key != "vllm" {
|
||||
return fmt.Errorf("not a vLLM installation")
|
||||
}
|
||||
vllmMonitorMutex.Lock()
|
||||
defer vllmMonitorMutex.Unlock()
|
||||
return vllmMonitorRepo.CleanTarget(req.AppInstallID)
|
||||
}
|
||||
|
||||
func StartVLLMMonitor(interval string) error {
|
||||
seconds, err := strconv.Atoi(interval)
|
||||
if err != nil || seconds < 10 || seconds > 43200 {
|
||||
return fmt.Errorf("invalid vLLM monitoring interval: %s", interval)
|
||||
}
|
||||
service := &MonitorService{}
|
||||
job := cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(cron.FuncJob(service.saveVLLMData))
|
||||
id, err := global.Cron.AddFunc(fmt.Sprintf("@every %ds", seconds), func() { go job.Run() })
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if global.VLLMMonitorCronID != 0 {
|
||||
global.Cron.Remove(global.VLLMMonitorCronID)
|
||||
}
|
||||
global.VLLMMonitorCronID = id
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MonitorService) saveVLLMData() {
|
||||
if !vllmMonitorMutex.TryLock() {
|
||||
return
|
||||
}
|
||||
defer vllmMonitorMutex.Unlock()
|
||||
status, err := settingRepo.GetValueByKey("VLLMMonitorStatus")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Load vLLM monitoring status failed: %v", err)
|
||||
return
|
||||
}
|
||||
if status != constant.StatusEnable {
|
||||
return
|
||||
}
|
||||
retention, err := settingRepo.GetValueByKey("VLLMMonitorStoreDays")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Load vLLM monitoring retention failed: %v", err)
|
||||
return
|
||||
}
|
||||
days, err := strconv.Atoi(retention)
|
||||
if err != nil || days < 1 {
|
||||
global.LOG.Errorf("Invalid vLLM monitoring retention: %s", retention)
|
||||
return
|
||||
}
|
||||
if err := vllmMonitorRepo.DeleteBefore(time.Now().AddDate(0, 0, -days)); err != nil {
|
||||
global.LOG.Errorf("Clean vLLM monitoring data failed: %v", err)
|
||||
}
|
||||
apps, err := appRepo.GetBy(appRepo.WithKey("vllm"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Load vLLM apps for monitoring failed: %v", err)
|
||||
return
|
||||
}
|
||||
if len(apps) == 0 {
|
||||
return
|
||||
}
|
||||
ids := make([]uint, 0, len(apps))
|
||||
for _, app := range apps {
|
||||
ids = append(ids, app.ID)
|
||||
}
|
||||
installs, err := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppIdsIn(ids))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Load vLLM instances for monitoring failed: %v", err)
|
||||
return
|
||||
}
|
||||
var group errgroup.Group
|
||||
group.SetLimit(4)
|
||||
for _, install := range installs {
|
||||
group.Go(func() error {
|
||||
previous, err := vllmMonitorRepo.Latest(install.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
point, err := collectVLLMMetrics(context.Background(), install, previous)
|
||||
if err != nil {
|
||||
global.LOG.Debugf("Collect vLLM metrics for %s failed: %v", install.Name, err)
|
||||
}
|
||||
return vllmMonitorRepo.Create(&point)
|
||||
})
|
||||
}
|
||||
if err := group.Wait(); err != nil {
|
||||
global.LOG.Errorf("Save vLLM monitoring data failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func collectVLLMMetrics(ctx context.Context, install model.AppInstall, previous model.MonitorVLLM) (model.MonitorVLLM, error) {
|
||||
point := model.MonitorVLLM{AppInstallID: install.ID, CreatedAt: time.Now(), Status: "unavailable"}
|
||||
if install.HttpPort <= 0 || install.HttpPort > 65535 {
|
||||
return point, fmt.Errorf("invalid vLLM port")
|
||||
}
|
||||
var env map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(install.Env), &env); err != nil {
|
||||
return point, err
|
||||
}
|
||||
host, _ := env[constant.HostIP].(string)
|
||||
host = strings.Trim(host, "[]")
|
||||
switch host {
|
||||
case "", "0.0.0.0":
|
||||
host = "127.0.0.1"
|
||||
case "::":
|
||||
host = "::1"
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://"+net.JoinHostPort(host, strconv.Itoa(install.HttpPort))+"/metrics", nil)
|
||||
if err != nil {
|
||||
return point, err
|
||||
}
|
||||
response, err := vllmMetricsClient.Do(req)
|
||||
if err != nil {
|
||||
return point, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return point, fmt.Errorf("vLLM metrics returned HTTP %d", response.StatusCode)
|
||||
}
|
||||
body, err := io.ReadAll(io.LimitReader(response.Body, 8*1024*1024+1))
|
||||
if err != nil {
|
||||
return point, err
|
||||
}
|
||||
if len(body) > 8*1024*1024 {
|
||||
return point, fmt.Errorf("vLLM metrics response too large")
|
||||
}
|
||||
metrics, err := vllm.Parse(bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return point, err
|
||||
}
|
||||
point.CreatedAt = time.Now()
|
||||
raw, err := json.Marshal(metrics)
|
||||
if err != nil {
|
||||
return point, err
|
||||
}
|
||||
var before vllm.Metrics
|
||||
elapsed := point.CreatedAt.Sub(previous.CreatedAt).Seconds()
|
||||
if previous.Status == "ok" && elapsed > 0 {
|
||||
if err := json.Unmarshal([]byte(previous.RawMetrics), &before); err != nil {
|
||||
return point, err
|
||||
}
|
||||
}
|
||||
calculated, err := vllm.Calculate(metrics, before, elapsed)
|
||||
if err != nil {
|
||||
return point, err
|
||||
}
|
||||
calculated.AppInstallID = install.ID
|
||||
calculated.CreatedAt = point.CreatedAt
|
||||
calculated.RawMetrics = string(raw)
|
||||
calculated.Status = "ok"
|
||||
return calculated, nil
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -44,6 +45,7 @@ type ISettingService interface {
|
||||
GetLocalConnForSSH() (dto.SSHConnData, error)
|
||||
|
||||
SaveDescription(req dto.CommonDescription) error
|
||||
CleanupDescriptions(context.Context) (int64, error)
|
||||
}
|
||||
|
||||
func NewISettingService() ISettingService {
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"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/lifecycle"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
)
|
||||
|
||||
func (u *SettingService) CleanupDescriptions(ctx context.Context) (int64, error) {
|
||||
var deleted int64
|
||||
var failures []error
|
||||
for _, kind := range []string{"container", "firewall", "firewall-docker"} {
|
||||
count, err := func() (int64, error) {
|
||||
switch kind {
|
||||
case "container":
|
||||
return cleanupUnusedDescriptions(ctx, kind, loadContainerDescriptionIDs)
|
||||
case "firewall":
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
return cleanupUnusedDescriptions(ctx, kind, loadHostFirewallDescriptionIDs)
|
||||
default:
|
||||
return cleanupUnusedDescriptions(ctx, kind, nil)
|
||||
}
|
||||
}()
|
||||
deleted += count
|
||||
if err != nil {
|
||||
failures = append(failures, fmt.Errorf("%s: %w", kind, err))
|
||||
}
|
||||
}
|
||||
return deleted, errors.Join(failures...)
|
||||
}
|
||||
|
||||
func cleanupUnusedDescriptions(ctx context.Context, kind string, loadIDs func(context.Context) (map[string]bool, error)) (int64, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
descriptions, err := settingRepo.GetDescriptionList(repo.WithByType(kind))
|
||||
if err != nil || len(descriptions) == 0 {
|
||||
return 0, err
|
||||
}
|
||||
var active map[string]bool
|
||||
if loadIDs != nil {
|
||||
active, err = loadIDs(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
ids, empty := make([]string, 0), make([]string, 0)
|
||||
for _, description := range descriptions {
|
||||
if !active[description.ID] {
|
||||
ids = append(ids, description.ID)
|
||||
} else if description.Description == "" && !description.IsPinned {
|
||||
empty = append(empty, description.ID)
|
||||
}
|
||||
}
|
||||
deleted, err := settingRepo.DeleteDescriptions(ctx, kind, ids, false)
|
||||
if err != nil {
|
||||
return deleted, err
|
||||
}
|
||||
count, err := settingRepo.DeleteDescriptions(ctx, kind, empty, true)
|
||||
return deleted + count, err
|
||||
}
|
||||
|
||||
func loadContainerDescriptionIDs(ctx context.Context) (map[string]bool, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
containers, err := client.ContainerList(ctx, container.ListOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids := make(map[string]bool, len(containers))
|
||||
for _, item := range containers {
|
||||
ids[item.ID] = true
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func loadHostFirewallDescriptionIDs(ctx context.Context) (map[string]bool, error) {
|
||||
providers := lifecycle.InstalledProviders()
|
||||
if len(providers) == 0 {
|
||||
return nil, filter.ErrProviderUnavailable
|
||||
}
|
||||
service := newFirewallService()
|
||||
ids := make(map[string]bool)
|
||||
for _, name := range providers {
|
||||
provider := filter.Provider(name)
|
||||
inventory, err := service.readFirewallInventory(ctx, provider, filter.ManagedInputScopes(provider))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, notice := range inventory.Notices {
|
||||
if notice.Code == filter.ScopeNoticeFamilyUnavailable || notice.Code == filter.ScopeNoticeManagedScopeInactive {
|
||||
return nil, fmt.Errorf("%w: %s %s", filter.ErrInventoryUnavailable, name, notice.Code)
|
||||
}
|
||||
}
|
||||
for _, item := range inventory.Items {
|
||||
id, err := filter.DescriptionID(*item.Observed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids[id] = true
|
||||
}
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
+73
-12
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -37,6 +38,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
const sshPath = "/etc/ssh/sshd_config"
|
||||
@@ -228,7 +230,7 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
|
||||
return err
|
||||
}
|
||||
if req.Key == "Port" {
|
||||
if err := updateSystemAccessPortWhitelist(context.Background(), firewall.PortWhitelistTypeSSH, splitSSHPorts(req.NewValue)); err != nil {
|
||||
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)
|
||||
}
|
||||
@@ -438,15 +440,33 @@ func (u *SSHService) SyncRootCert() error {
|
||||
global.LOG.Errorf("read pubic key of %s for sync failed, err: %v", item, err)
|
||||
continue
|
||||
}
|
||||
cert.EncryptionMode = loadEncryptioMode(string(pubItem))
|
||||
publicKey, _, _, rest, err := ssh.ParseAuthorizedKey(pubItem)
|
||||
if err != nil || len(bytes.TrimSpace(rest)) != 0 {
|
||||
continue
|
||||
}
|
||||
cert.EncryptionMode = loadSSHKeyType(publicKey)
|
||||
if cert.EncryptionMode == "" {
|
||||
continue
|
||||
}
|
||||
rootCerts = append(rootCerts, cert)
|
||||
}
|
||||
return hostRepo.SyncCert(rootCerts)
|
||||
}
|
||||
|
||||
func (u *SSHService) CreateRootCert(req dto.RootCertOperate) error {
|
||||
if cmd.CheckIllegal(req.EncryptionMode, req.PassPhrase) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
switch req.Mode {
|
||||
case "generate":
|
||||
switch req.EncryptionMode {
|
||||
case "rsa", "ed25519", "ecdsa", "dsa":
|
||||
default:
|
||||
return buserr.WithName("ErrNotSupportType", req.EncryptionMode)
|
||||
}
|
||||
case "input", "import":
|
||||
if err := validateSSHKeyPair(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return buserr.WithName("ErrNotSupportType", req.Mode)
|
||||
}
|
||||
certItem, _ := hostRepo.GetCert(repo.WithByName(req.Name))
|
||||
if certItem.ID != 0 {
|
||||
@@ -543,6 +563,9 @@ func (u *SSHService) CreateRootCert(req dto.RootCertOperate) error {
|
||||
}
|
||||
|
||||
func (u *SSHService) EditRootCert(req dto.RootCertOperate) error {
|
||||
if err := validateSSHKeyPair(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
return fmt.Errorf("load current user failed, err: %v", err)
|
||||
@@ -1681,22 +1704,60 @@ func loadDate(currentYear int, DateStr string, nyc *time.Location) time.Time {
|
||||
return itemDate
|
||||
}
|
||||
|
||||
func loadEncryptioMode(content string) string {
|
||||
if strings.HasPrefix(content, "ssh-rsa") {
|
||||
func loadSSHKeyType(publicKey ssh.PublicKey) string {
|
||||
switch publicKey.Type() {
|
||||
case ssh.KeyAlgoRSA:
|
||||
return "rsa"
|
||||
}
|
||||
if strings.HasPrefix(content, "ssh-ed25519") {
|
||||
case ssh.KeyAlgoED25519:
|
||||
return "ed25519"
|
||||
}
|
||||
if strings.HasPrefix(content, "ssh-ecdsa") {
|
||||
case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521:
|
||||
return "ecdsa"
|
||||
}
|
||||
if strings.HasPrefix(content, "ssh-dsa") {
|
||||
case ssh.KeyAlgoDSA:
|
||||
return "dsa"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func validateSSHKeyPair(req *dto.RootCertOperate) error {
|
||||
req.PublicKey = strings.TrimSpace(req.PublicKey)
|
||||
req.PrivateKey = strings.TrimSpace(req.PrivateKey)
|
||||
if req.PublicKey == "" || strings.ContainsAny(req.PublicKey, "\r\n") {
|
||||
return buserr.New("ErrSSHPublicKey")
|
||||
}
|
||||
publicKey, comment, options, rest, err := ssh.ParseAuthorizedKey([]byte(req.PublicKey))
|
||||
if err != nil || len(options) != 0 || len(bytes.TrimSpace(rest)) != 0 {
|
||||
return buserr.New("ErrSSHPublicKey")
|
||||
}
|
||||
req.EncryptionMode = loadSSHKeyType(publicKey)
|
||||
if req.EncryptionMode == "" {
|
||||
return buserr.WithName("ErrNotSupportType", publicKey.Type())
|
||||
}
|
||||
block, rest := pem.Decode([]byte(req.PrivateKey))
|
||||
if !strings.HasPrefix(req.PrivateKey, "-----BEGIN ") || block == nil || len(bytes.TrimSpace(rest)) != 0 {
|
||||
return buserr.New("ErrSSHPrivateKey")
|
||||
}
|
||||
signer, err := ssh.ParsePrivateKey([]byte(req.PrivateKey))
|
||||
var missingPassphrase *ssh.PassphraseMissingError
|
||||
if errors.As(err, &missingPassphrase) {
|
||||
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(req.PrivateKey), []byte(req.PassPhrase))
|
||||
} else if err == nil {
|
||||
req.PassPhrase = ""
|
||||
}
|
||||
if err != nil {
|
||||
return buserr.New("ErrSSHPrivateKey")
|
||||
}
|
||||
if !bytes.Equal(signer.PublicKey().Marshal(), publicKey.Marshal()) {
|
||||
return buserr.New("ErrSSHKeyMismatch")
|
||||
}
|
||||
req.PublicKey = strings.TrimSpace(string(ssh.MarshalAuthorizedKey(publicKey)))
|
||||
if comment != "" {
|
||||
req.PublicKey += " " + comment
|
||||
}
|
||||
req.PublicKey += "\n"
|
||||
req.PrivateKey += "\n"
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateLocalConn(newPort uint) error {
|
||||
conn, _ := settingRepo.GetValueByKey("LocalSSHConn")
|
||||
if len(conn) == 0 {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"path"
|
||||
"strconv"
|
||||
@@ -32,7 +33,9 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
|
||||
return nil, err
|
||||
}
|
||||
go func() {
|
||||
_ = ensureFirewallPorts(addPorts)
|
||||
if err := ensureFirewallPorts(addPorts); err != nil {
|
||||
global.LOG.Errorf("allow website firewall ports failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
|
||||
@@ -99,6 +99,7 @@ const (
|
||||
TaskScopeTamper = "Tamper"
|
||||
TaskScopeFileConvert = "Convert"
|
||||
TaskScopeTask = "Task"
|
||||
TaskScopeVm = "VirtualMachine"
|
||||
)
|
||||
|
||||
func GetTaskName(resourceName, operate, scope string) string {
|
||||
@@ -154,7 +155,7 @@ func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, e
|
||||
logPath := path.Join(global.Dir.TaskDir, taskScope, taskID+".log")
|
||||
logger := logrus.New()
|
||||
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 {
|
||||
return nil, fmt.Errorf("failed to open log file: %w", err)
|
||||
}
|
||||
|
||||
+122
-107
@@ -265,8 +265,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "应用端口修改 [key]-[name] =\u003e [port]",
|
||||
"formatEN": "Application port update [key]-[name] =\u003e [port]"
|
||||
"formatZH": "应用端口修改 [key]-[name] => [port]",
|
||||
"formatEN": "Application port update [key]-[name] => [port]"
|
||||
},
|
||||
"/apps/installed/sort/update": {
|
||||
"bodyKeys": [],
|
||||
@@ -679,8 +679,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "容器重命名 [name] =\u003e [newName]",
|
||||
"formatEN": "rename container [name] =\u003e [newName]"
|
||||
"formatZH": "容器重命名 [name] => [newName]",
|
||||
"formatEN": "rename container [name] => [newName]"
|
||||
},
|
||||
"/containers/repo": {
|
||||
"bodyKeys": [
|
||||
@@ -831,8 +831,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
|
||||
"formatEN": "update api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
|
||||
"formatZH": "更新 API 接口配置 => IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
|
||||
"formatEN": "update api config => IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
|
||||
},
|
||||
"/core/auth/expired/reset": {
|
||||
"bodyKeys": [],
|
||||
@@ -1716,8 +1716,8 @@
|
||||
"output_value": "name"
|
||||
}
|
||||
],
|
||||
"formatZH": "更新用户 [name] API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
|
||||
"formatEN": "update user [name] api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
|
||||
"formatZH": "更新用户 [name] API 接口配置 => IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
|
||||
"formatEN": "update user [name] api config => IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
|
||||
},
|
||||
"/core/enterprise/users/del": {
|
||||
"bodyKeys": [
|
||||
@@ -1918,8 +1918,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "修改系统监听信息 =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]",
|
||||
"formatEN": "update system bind info =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]"
|
||||
"formatZH": "修改系统监听信息 => ipv6: [ipv6], 监听 IP: [bindAddress]",
|
||||
"formatEN": "update system bind info => ipv6: [ipv6], 监听 IP: [bindAddress]"
|
||||
},
|
||||
"/core/settings/memo": {
|
||||
"bodyKeys": [],
|
||||
@@ -1948,8 +1948,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "修改系统端口 =\u003e [serverPort]",
|
||||
"formatEN": "update system port =\u003e [serverPort]"
|
||||
"formatZH": "修改系统端口 => [serverPort]",
|
||||
"formatEN": "update system port => [serverPort]"
|
||||
},
|
||||
"/core/settings/proxy/update": {
|
||||
"bodyKeys": [
|
||||
@@ -1974,8 +1974,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "修改系统 ssl =\u003e [ssl]",
|
||||
"formatEN": "update system ssl =\u003e [ssl]"
|
||||
"formatZH": "修改系统 ssl => [ssl]",
|
||||
"formatEN": "update system ssl => [ssl]"
|
||||
},
|
||||
"/core/settings/terminal/update": {
|
||||
"bodyKeys": [],
|
||||
@@ -1991,8 +1991,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "修改系统配置 [key] =\u003e [value]",
|
||||
"formatEN": "update system setting [key] =\u003e [value]"
|
||||
"formatZH": "修改系统配置 [key] => [value]",
|
||||
"formatEN": "update system setting [key] => [value]"
|
||||
},
|
||||
"/core/settings/upgrade": {
|
||||
"bodyKeys": [
|
||||
@@ -2000,8 +2000,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "更新系统 =\u003e [version]",
|
||||
"formatEN": "upgrade system =\u003e [version]"
|
||||
"formatZH": "更新系统 => [version]",
|
||||
"formatEN": "upgrade system => [version]"
|
||||
},
|
||||
"/core/xapp/appQRCode": {
|
||||
"bodyKeys": [
|
||||
@@ -2459,7 +2459,7 @@
|
||||
"formatZH": "同步 SSL 证书 [primaryDomain]",
|
||||
"formatEN": "sync SSL certificate [primaryDomain]"
|
||||
},
|
||||
"/core/xpack/vms": {
|
||||
"/xpack/vms": {
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
],
|
||||
@@ -2468,7 +2468,7 @@
|
||||
"formatZH": "创建虚拟机 [name]",
|
||||
"formatEN": "create VM [name]"
|
||||
},
|
||||
"/core/xpack/vms/del": {
|
||||
"/xpack/vms/del": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
@@ -2486,14 +2486,25 @@
|
||||
"formatZH": "删除虚拟机 [name]",
|
||||
"formatEN": "delete VM [name]"
|
||||
},
|
||||
"/core/xpack/vms/environment/enable": {
|
||||
"/xpack/vms/disks/resize": {
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
"target",
|
||||
"sizeGiB"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "扩容虚拟机 [name] 磁盘 [target] 至 [sizeGiB] GiB",
|
||||
"formatEN": "expand VM [name] disk [target] to [sizeGiB] GiB"
|
||||
},
|
||||
"/xpack/vms/environment/enable": {
|
||||
"bodyKeys": [],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "启用虚拟机运行环境",
|
||||
"formatEN": "enable VM runtime environment"
|
||||
},
|
||||
"/core/xpack/vms/iso": {
|
||||
"/xpack/vms/iso": {
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
"type"
|
||||
@@ -2503,7 +2514,7 @@
|
||||
"formatZH": "创建虚拟机镜像 [name] [type]",
|
||||
"formatEN": "create VM ISO [name] [type]"
|
||||
},
|
||||
"/core/xpack/vms/iso/del": {
|
||||
"/xpack/vms/iso/del": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
@@ -2521,7 +2532,7 @@
|
||||
"formatZH": "删除虚拟机镜像 [name]",
|
||||
"formatEN": "delete VM ISO [name]"
|
||||
},
|
||||
"/core/xpack/vms/iso/update": {
|
||||
"/xpack/vms/iso/update": {
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
"type"
|
||||
@@ -2531,7 +2542,7 @@
|
||||
"formatZH": "更新虚拟机镜像 [name] [type]",
|
||||
"formatEN": "update VM ISO [name] [type]"
|
||||
},
|
||||
"/core/xpack/vms/networks": {
|
||||
"/xpack/vms/networks": {
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
"type"
|
||||
@@ -2541,7 +2552,7 @@
|
||||
"formatZH": "创建虚拟机网络 [name] [type]",
|
||||
"formatEN": "create VM network [name] [type]"
|
||||
},
|
||||
"/core/xpack/vms/networks/del": {
|
||||
"/xpack/vms/networks/del": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
@@ -2559,7 +2570,7 @@
|
||||
"formatZH": "删除虚拟机网络 [name]",
|
||||
"formatEN": "delete VM network [name]"
|
||||
},
|
||||
"/core/xpack/vms/networks/update": {
|
||||
"/xpack/vms/networks/update": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
@@ -2577,7 +2588,7 @@
|
||||
"formatZH": "更新虚拟机网络 [name]",
|
||||
"formatEN": "update VM network [name]"
|
||||
},
|
||||
"/core/xpack/vms/operate": {
|
||||
"/xpack/vms/operate": {
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
"operate"
|
||||
@@ -2587,14 +2598,14 @@
|
||||
"formatZH": "操作虚拟机 [name] [operate]",
|
||||
"formatEN": "operate VM [name] [operate]"
|
||||
},
|
||||
"/core/xpack/vms/orphans/clean": {
|
||||
"/xpack/vms/orphans/clean": {
|
||||
"bodyKeys": [],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "清理虚拟机孤立数据",
|
||||
"formatEN": "clean VM orphaned data"
|
||||
},
|
||||
"/core/xpack/vms/rename": {
|
||||
"/xpack/vms/rename": {
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
"newName"
|
||||
@@ -2604,7 +2615,7 @@
|
||||
"formatZH": "重命名虚拟机 [name] 为 [newName]",
|
||||
"formatEN": "rename VM [name] to [newName]"
|
||||
},
|
||||
"/core/xpack/vms/snapshots": {
|
||||
"/xpack/vms/snapshots": {
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
"snapshotName"
|
||||
@@ -2614,7 +2625,7 @@
|
||||
"formatZH": "创建虚拟机 [name] 快照 [snapshotName]",
|
||||
"formatEN": "create VM [name] snapshot [snapshotName]"
|
||||
},
|
||||
"/core/xpack/vms/snapshots/del": {
|
||||
"/xpack/vms/snapshots/del": {
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
"snapshotName"
|
||||
@@ -2624,7 +2635,7 @@
|
||||
"formatZH": "删除虚拟机 [name] 快照 [snapshotName]",
|
||||
"formatEN": "delete VM [name] snapshot [snapshotName]"
|
||||
},
|
||||
"/core/xpack/vms/snapshots/recover": {
|
||||
"/xpack/vms/snapshots/recover": {
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
"snapshotName"
|
||||
@@ -2634,7 +2645,7 @@
|
||||
"formatZH": "恢复虚拟机 [name] 快照 [snapshotName]",
|
||||
"formatEN": "recover VM [name] snapshot [snapshotName]"
|
||||
},
|
||||
"/core/xpack/vms/storages": {
|
||||
"/xpack/vms/storages": {
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
"type"
|
||||
@@ -2644,7 +2655,7 @@
|
||||
"formatZH": "创建虚拟机存储 [name] [type]",
|
||||
"formatEN": "create VM storage [name] [type]"
|
||||
},
|
||||
"/core/xpack/vms/storages/del": {
|
||||
"/xpack/vms/storages/del": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
@@ -2662,7 +2673,7 @@
|
||||
"formatZH": "删除虚拟机存储 [name]",
|
||||
"formatEN": "delete VM storage [name]"
|
||||
},
|
||||
"/core/xpack/vms/storages/update": {
|
||||
"/xpack/vms/storages/update": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
@@ -2680,14 +2691,14 @@
|
||||
"formatZH": "更新虚拟机存储 [name]",
|
||||
"formatEN": "update VM storage [name]"
|
||||
},
|
||||
"/core/xpack/vms/sync": {
|
||||
"/xpack/vms/sync": {
|
||||
"bodyKeys": [],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "同步虚拟机",
|
||||
"formatEN": "sync virtual machines"
|
||||
},
|
||||
"/core/xpack/vms/templates": {
|
||||
"/xpack/vms/templates": {
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
"templateName"
|
||||
@@ -2697,7 +2708,7 @@
|
||||
"formatZH": "创建虚拟机 [name] 模板 [templateName]",
|
||||
"formatEN": "create VM [name] template [templateName]"
|
||||
},
|
||||
"/core/xpack/vms/templates/del": {
|
||||
"/xpack/vms/templates/del": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
@@ -2715,7 +2726,7 @@
|
||||
"formatZH": "删除虚拟机模板 [name]",
|
||||
"formatEN": "delete VM template [name]"
|
||||
},
|
||||
"/core/xpack/vms/update": {
|
||||
"/xpack/vms/update": {
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
],
|
||||
@@ -2877,8 +2888,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "首页应用 [key] =\u003e 显示:[value]",
|
||||
"formatEN": "app launcher [key] =\u003e show: [value]"
|
||||
"formatZH": "首页应用 [key] => 显示:[value]",
|
||||
"formatEN": "app launcher [key] => show: [value]"
|
||||
},
|
||||
"/dashboard/quick/change": {
|
||||
"bodyKeys": [],
|
||||
@@ -3006,7 +3017,7 @@
|
||||
}
|
||||
],
|
||||
"formatZH": "mysql 数据库 [name] 描述信息修改 [description]",
|
||||
"formatEN": "The description of the mysql database [name] is modified =\u003e [description]"
|
||||
"formatEN": "The description of the mysql database [name] is modified => [description]"
|
||||
},
|
||||
"/databases/grants": {
|
||||
"bodyKeys": [
|
||||
@@ -3087,7 +3098,7 @@
|
||||
}
|
||||
],
|
||||
"formatZH": "mongodb 数据库 [name] 描述信息修改 [description]",
|
||||
"formatEN": "The description of the mongodb database [name] is modified =\u003e [description]"
|
||||
"formatEN": "The description of the mongodb database [name] is modified => [description]"
|
||||
},
|
||||
"/databases/mongodb/password": {
|
||||
"bodyKeys": [
|
||||
@@ -3172,7 +3183,7 @@
|
||||
}
|
||||
],
|
||||
"formatZH": "postgresql 数据库 [name] 描述信息修改 [description]",
|
||||
"formatEN": "The description of the postgresql database [name] is modified =\u003e [description]"
|
||||
"formatEN": "The description of the postgresql database [name] is modified => [description]"
|
||||
},
|
||||
"/databases/pg/password": {
|
||||
"bodyKeys": [
|
||||
@@ -3277,8 +3288,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "更新 mysql 数据库 [database] 用户 [username] 访问权限 [host] =\u003e [newHost] 描述 [description]",
|
||||
"formatEN": "update mysql database [database] user [username] access [host] =\u003e [newHost] description [description]"
|
||||
"formatZH": "更新 mysql 数据库 [database] 用户 [username] 访问权限 [host] => [newHost] 描述 [description]",
|
||||
"formatEN": "update mysql database [database] user [username] access [host] => [newHost] description [description]"
|
||||
},
|
||||
"/databases/variables/update": {
|
||||
"bodyKeys": [],
|
||||
@@ -3314,8 +3325,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "批量修改文件权限和用户/组 [paths] =\u003e [mode]/[user]/[group]",
|
||||
"formatEN": "Batch change file mode and owner [paths] =\u003e [mode]/[user]/[group]"
|
||||
"formatZH": "批量修改文件权限和用户/组 [paths] => [mode]/[user]/[group]",
|
||||
"formatEN": "Batch change file mode and owner [paths] => [mode]/[user]/[group]"
|
||||
},
|
||||
"/files/chunkdownload": {
|
||||
"bodyKeys": [
|
||||
@@ -3405,8 +3416,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "修改权限 [path] =\u003e [mode]",
|
||||
"formatEN": "Change mode [path] =\u003e [mode]"
|
||||
"formatZH": "修改权限 [path] => [mode]",
|
||||
"formatEN": "Change mode [path] => [mode]"
|
||||
},
|
||||
"/files/move": {
|
||||
"bodyKeys": [
|
||||
@@ -3415,8 +3426,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "移动文件 [oldPaths] =\u003e [newPath]",
|
||||
"formatEN": "Move [oldPaths] =\u003e [newPath]"
|
||||
"formatZH": "移动文件 [oldPaths] => [newPath]",
|
||||
"formatEN": "Move [oldPaths] => [newPath]"
|
||||
},
|
||||
"/files/owner": {
|
||||
"bodyKeys": [
|
||||
@@ -3426,8 +3437,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "修改用户/组 [path] =\u003e [user]/[group]",
|
||||
"formatEN": "Change owner [path] =\u003e [user]/[group]"
|
||||
"formatZH": "修改用户/组 [path] => [user]/[group]",
|
||||
"formatEN": "Change owner [path] => [user]/[group]"
|
||||
},
|
||||
"/files/preview": {
|
||||
"bodyKeys": [
|
||||
@@ -3461,8 +3472,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "重命名 [oldName] =\u003e [newName]",
|
||||
"formatEN": "Rename [oldName] =\u003e [newName]"
|
||||
"formatZH": "重命名 [oldName] => [newName]",
|
||||
"formatEN": "Rename [oldName] => [newName]"
|
||||
},
|
||||
"/files/save": {
|
||||
"bodyKeys": [
|
||||
@@ -3519,8 +3530,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "下载 url =\u003e [path]/[name]",
|
||||
"formatEN": "Download url =\u003e [path]/[name]"
|
||||
"formatZH": "下载 url => [path]/[name]",
|
||||
"formatEN": "Download url => [path]/[name]"
|
||||
},
|
||||
"/files/wget/process/remove": {
|
||||
"bodyKeys": [
|
||||
@@ -3670,12 +3681,16 @@
|
||||
"formatZH": "删除 Docker 端口防护策略 [uuids]",
|
||||
"formatEN": "delete Docker port guard policies [uuids]"
|
||||
},
|
||||
"/hosts/firewall/docker/sync": {
|
||||
"bodyKeys": [],
|
||||
"/hosts/firewall/family/operate": {
|
||||
"bodyKeys": [
|
||||
"subsystem",
|
||||
"family",
|
||||
"operation"
|
||||
],
|
||||
"formatEN": "[operation] [subsystem] [family] firewall chains",
|
||||
"formatZH": "[operation] [subsystem] [family] 防火墙链",
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "同步 Docker 端口防护规则",
|
||||
"formatEN": "sync Docker port guard rules"
|
||||
"beforeFunctions": []
|
||||
},
|
||||
"/hosts/firewall/filter/operate": {
|
||||
"bodyKeys": [
|
||||
@@ -3716,13 +3731,6 @@
|
||||
"formatZH": "添加防火墙规则",
|
||||
"formatEN": "create firewall rules"
|
||||
},
|
||||
"/hosts/firewall/rules/adopt": {
|
||||
"bodyKeys": [],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "纳管防火墙规则",
|
||||
"formatEN": "adopt firewall rule"
|
||||
},
|
||||
"/hosts/firewall/rules/delete": {
|
||||
"bodyKeys": [],
|
||||
"paramKeys": [],
|
||||
@@ -3732,12 +3740,12 @@
|
||||
},
|
||||
"/hosts/firewall/rules/reorder": {
|
||||
"bodyKeys": [
|
||||
"uuid"
|
||||
"instanceKey"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "调整防火墙规则顺序 [uuid]",
|
||||
"formatEN": "reorder firewall rule [uuid]"
|
||||
"formatZH": "调整防火墙规则顺序 [instanceKey]",
|
||||
"formatEN": "reorder firewall rule [instanceKey]"
|
||||
},
|
||||
"/hosts/firewall/rules/reset": {
|
||||
"bodyKeys": [],
|
||||
@@ -3746,25 +3754,23 @@
|
||||
"formatZH": "重置防火墙规则",
|
||||
"formatEN": "reset firewall rules"
|
||||
},
|
||||
"/hosts/firewall/rules/sync": {
|
||||
"bodyKeys": [
|
||||
"subsystem",
|
||||
"sourceProvider",
|
||||
"targetProvider"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "同步 [subsystem] 防火墙规则到 [targetProvider]",
|
||||
"formatEN": "sync [subsystem] firewall rules to [targetProvider]"
|
||||
},
|
||||
"/hosts/firewall/rules/update": {
|
||||
"bodyKeys": [
|
||||
"uuid"
|
||||
"instanceKey"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "更新防火墙规则 [uuid]",
|
||||
"formatEN": "update firewall rule [uuid]"
|
||||
"formatZH": "更新防火墙规则 [instanceKey]",
|
||||
"formatEN": "update firewall rule [instanceKey]"
|
||||
},
|
||||
"/hosts/firewall/settings/ipv6": {
|
||||
"bodyKeys": [
|
||||
"status"
|
||||
],
|
||||
"formatEN": "Set firewall IPv6 support to [status]",
|
||||
"formatZH": "设置防火墙 IPv6 支持为 [status]",
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": []
|
||||
},
|
||||
"/hosts/firewall/settings/operate": {
|
||||
"bodyKeys": [
|
||||
@@ -3882,8 +3888,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "修改 SSH 配置 [key] =\u003e [newValue]",
|
||||
"formatEN": "update SSH setting [key] =\u003e [newValue]"
|
||||
"formatZH": "修改 SSH 配置 [key] => [newValue]",
|
||||
"formatEN": "update SSH setting [key] => [newValue]"
|
||||
},
|
||||
"/hosts/tool/config/set": {
|
||||
"bodyKeys": [
|
||||
@@ -4028,12 +4034,21 @@
|
||||
},
|
||||
"/runtimes/operate": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
"ID"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "操作运行环境 [id]",
|
||||
"formatEN": "Operate runtime [id]"
|
||||
"beforeFunctions": [
|
||||
{
|
||||
"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": {
|
||||
"bodyKeys": [
|
||||
@@ -4135,7 +4150,7 @@
|
||||
}
|
||||
],
|
||||
"formatZH": "快照 [name] 描述信息修改 [description]",
|
||||
"formatEN": "The description of the snapshot [name] is modified =\u003e [description]"
|
||||
"formatEN": "The description of the snapshot [name] is modified => [description]"
|
||||
},
|
||||
"/settings/snapshot/import": {
|
||||
"bodyKeys": [
|
||||
@@ -4227,8 +4242,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "修改系统配置 [key] =\u003e [value]",
|
||||
"formatEN": "update system setting [key] =\u003e [value]"
|
||||
"formatZH": "修改系统配置 [key] => [value]",
|
||||
"formatEN": "update system setting [key] => [value]"
|
||||
},
|
||||
"/toolbox/clam": {
|
||||
"bodyKeys": [
|
||||
@@ -4346,8 +4361,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "修改主机参数 [key] =\u003e [value]",
|
||||
"formatEN": "update device conf [key] =\u003e [value]"
|
||||
"formatZH": "修改主机参数 [key] => [value]",
|
||||
"formatEN": "update device conf [key] => [value]"
|
||||
},
|
||||
"/toolbox/device/update/host": {
|
||||
"bodyKeys": [
|
||||
@@ -4356,8 +4371,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "修改主机 Host [key] =\u003e [value]",
|
||||
"formatEN": "update device host [key] =\u003e [value]"
|
||||
"formatZH": "修改主机 Host [key] => [value]",
|
||||
"formatEN": "update device host [key] => [value]"
|
||||
},
|
||||
"/toolbox/device/update/swap": {
|
||||
"bodyKeys": [
|
||||
@@ -4385,8 +4400,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "修改 Fail2ban 配置 [key] =\u003e [value]",
|
||||
"formatEN": "update fail2ban conf [key] =\u003e [value]"
|
||||
"formatZH": "修改 Fail2ban 配置 [key] => [value]",
|
||||
"formatEN": "update fail2ban conf [key] => [value]"
|
||||
},
|
||||
"/toolbox/ftp": {
|
||||
"bodyKeys": [
|
||||
@@ -4645,8 +4660,8 @@
|
||||
"output_value": "domain"
|
||||
}
|
||||
],
|
||||
"formatZH": "修改默认 server =\u003e [domain]",
|
||||
"formatEN": "Change default server =\u003e [domain]"
|
||||
"formatZH": "修改默认 server => [domain]",
|
||||
"formatEN": "Change default server => [domain]"
|
||||
},
|
||||
"/websites/del": {
|
||||
"bodyKeys": [
|
||||
@@ -5409,8 +5424,8 @@
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "更新防篡改信息 [website][path] =\u003e [status]",
|
||||
"formatEN": "update tamper info [website][path] =\u003e [status]"
|
||||
"formatZH": "更新防篡改信息 [website][path] => [status]",
|
||||
"formatEN": "update tamper info [website][path] => [status]"
|
||||
},
|
||||
"/xpack/vllm/command-template/create": {
|
||||
"bodyKeys": [
|
||||
|
||||
@@ -17,6 +17,7 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
FirewallIPv6SupportKey = "FirewallIPv6Support"
|
||||
FirewallSystemBackendKey = "FirewallProvider"
|
||||
FirewallForwardingBackendKey = "ForwardingBackend"
|
||||
FirewallDockerBackendKey = "DockerFirewallBackend"
|
||||
@@ -33,12 +34,7 @@ const (
|
||||
const (
|
||||
FirewallSystemAcceptedPortSourcePrefix = "accepted-port:"
|
||||
|
||||
FirewallRuleOriginCreated = "created"
|
||||
FirewallRuleOriginAdopted = "adopted"
|
||||
|
||||
FirewallRuleSourceUser = "user"
|
||||
FirewallRuleSourceImported = "imported"
|
||||
FirewallRuleSourcePanel = "panel"
|
||||
FirewallRuleSourceSecurity = "security"
|
||||
FirewallRuleSourceApp = "application"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
StatusInactive = "Inactive"
|
||||
StatusUnknown = "Unknown"
|
||||
StatusDegraded = "Degraded"
|
||||
StatusInaccessible = "Inaccessible"
|
||||
StatusRunning = "Running"
|
||||
StatusCanceled = "Canceled"
|
||||
StatusDone = "Done"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cron
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -38,6 +39,28 @@ func Run() {
|
||||
}
|
||||
}
|
||||
|
||||
var gpuStatus, gpuInterval model.Setting
|
||||
if err := global.DB.Where("key = ?", "GPUMonitorStatus").First(&gpuStatus).Error; err != nil {
|
||||
global.LOG.Errorf("Load GPU monitoring status failed: %v", err)
|
||||
} else if gpuStatus.Value == constant.StatusEnable {
|
||||
if err := global.DB.Where("key = ?", "GPUMonitorInterval").First(&gpuInterval).Error; err != nil {
|
||||
global.LOG.Errorf("Load GPU monitoring interval failed: %v", err)
|
||||
} else if err := service.StartGPUMonitor(gpuInterval.Value); err != nil {
|
||||
global.LOG.Errorf("Start GPU monitoring failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var vllmStatus, vllmInterval model.Setting
|
||||
if err := global.DB.Where("key = ?", "VLLMMonitorStatus").First(&vllmStatus).Error; err != nil {
|
||||
global.LOG.Errorf("Load vLLM monitoring status failed: %v", err)
|
||||
} else if vllmStatus.Value == constant.StatusEnable {
|
||||
if err := global.DB.Where("key = ?", "VLLMMonitorInterval").First(&vllmInterval).Error; err != nil {
|
||||
global.LOG.Errorf("Load vLLM monitoring interval failed: %v", err)
|
||||
} else if err := service.StartVLLMMonitor(vllmInterval.Value); err != nil {
|
||||
global.LOG.Errorf("Start vLLM monitoring failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := global.Cron.AddJob("@daily", job.NewWebsiteJob()); err != nil {
|
||||
global.LOG.Errorf("can not add website corn job: %s", err.Error())
|
||||
}
|
||||
@@ -60,6 +83,17 @@ func Run() {
|
||||
if _, err := global.Cron.AddJob("0 3 */31 * *", job.NewBackupJob()); err != nil {
|
||||
global.LOG.Errorf("can not add backup token refresh corn job: %s", err.Error())
|
||||
}
|
||||
if _, err := global.Cron.AddFunc("@every 240h", func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
defer cancel()
|
||||
deleted, err := service.NewISettingService().CleanupDescriptions(ctx)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("clean unused common descriptions: %v", err)
|
||||
}
|
||||
global.LOG.Infof("common description cleanup completed, deleted %d records", deleted)
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("add common description cleanup job: %v", err)
|
||||
}
|
||||
|
||||
var cronJobs []model.Cronjob
|
||||
if err := global.DB.Where("status = ?", constant.StatusEnable).Find(&cronJobs).Error; err != nil {
|
||||
|
||||
+11
-8
@@ -14,12 +14,13 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
DB *gorm.DB
|
||||
MonitorDB *gorm.DB
|
||||
GPUMonitorDB *gorm.DB
|
||||
TaskDB *gorm.DB
|
||||
CoreDB *gorm.DB
|
||||
AlertDB *gorm.DB
|
||||
DB *gorm.DB
|
||||
MonitorDB *gorm.DB
|
||||
GPUMonitorDB *gorm.DB
|
||||
VLLMMonitorDB *gorm.DB
|
||||
TaskDB *gorm.DB
|
||||
CoreDB *gorm.DB
|
||||
AlertDB *gorm.DB
|
||||
|
||||
LOG *logrus.Logger
|
||||
CONF ServerConfig
|
||||
@@ -29,8 +30,10 @@ var (
|
||||
|
||||
Dir SystemDir
|
||||
|
||||
Cron *cron.Cron
|
||||
MonitorCronID cron.EntryID
|
||||
Cron *cron.Cron
|
||||
MonitorCronID cron.EntryID
|
||||
VLLMMonitorCronID cron.EntryID
|
||||
GPUMonitorCronID cron.EntryID
|
||||
|
||||
IsMaster bool
|
||||
|
||||
|
||||
+17
-20
@@ -1,14 +1,12 @@
|
||||
module github.com/1Panel-dev/1Panel/agent
|
||||
|
||||
go 1.26.1
|
||||
|
||||
replace github.com/moby/go-archive => github.com/moby/go-archive v0.1.0
|
||||
go 1.26.6
|
||||
|
||||
replace github.com/go-acme/lego/v5 => github.com/1Panel-dev/lego/v5 v5.3.1
|
||||
|
||||
require (
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/compose-spec/compose-go/v2 v2.14.0
|
||||
github.com/compose-spec/compose-go/v2 v2.15.0
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/docker/cli v29.7.2+incompatible
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
@@ -33,6 +31,7 @@ require (
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
||||
github.com/miekg/dns v1.1.73
|
||||
github.com/minio/minio-go/v7 v7.3.0
|
||||
github.com/moby/go-archive v0.3.0
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/oschwald/maxminddb-golang v1.13.1
|
||||
@@ -52,13 +51,13 @@ require (
|
||||
github.com/tklauser/go-sysconf v0.4.0
|
||||
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19
|
||||
github.com/upyun/go-sdk v2.1.0+incompatible
|
||||
go.mongodb.org/mongo-driver/v2 v2.8.2
|
||||
golang.org/x/crypto v0.55.0
|
||||
go.mongodb.org/mongo-driver/v2 v2.9.0
|
||||
golang.org/x/crypto v0.56.0
|
||||
golang.org/x/net v0.58.0
|
||||
golang.org/x/sync v0.22.0
|
||||
golang.org/x/sync v0.23.0
|
||||
golang.org/x/sys v0.47.0
|
||||
golang.org/x/text v0.41.0
|
||||
golang.org/x/time v0.15.0
|
||||
golang.org/x/time v0.16.0
|
||||
google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478
|
||||
gopkg.in/ini.v1 v1.67.3
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -121,7 +120,7 @@ require (
|
||||
github.com/go-acme/alidns-20150109/v5 v5.5.0 // indirect
|
||||
github.com/go-acme/esa-20240910/v3 v3.4.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
@@ -132,7 +131,6 @@ require (
|
||||
github.com/gofrs/flock v0.13.0 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/go-querystring v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.205 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
@@ -155,11 +153,10 @@ require (
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.2.0 // indirect
|
||||
github.com/moby/patternmatcher v0.6.1 // indirect
|
||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/sequential v0.7.0 // indirect
|
||||
github.com/moby/sys/user v0.4.1 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
@@ -209,18 +206,18 @@ require (
|
||||
go.mongodb.org/mongo-driver v1.17.9 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
||||
go.opentelemetry.io/otel v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.45.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect
|
||||
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
||||
golang.org/x/arch v0.26.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
google.golang.org/grpc v1.83.1 // indirect
|
||||
google.golang.org/grpc v1.83.2 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
modernc.org/fileutil v1.4.0 // indirect
|
||||
modernc.org/libc v1.72.0 // indirect
|
||||
|
||||
+41
-40
@@ -205,8 +205,8 @@ github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gE
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/compose-spec/compose-go/v2 v2.14.0 h1:uaJeo5B3+OVlu+Rx2qLBcAdXPEUUzm5nQrRiGJafRAQ=
|
||||
github.com/compose-spec/compose-go/v2 v2.14.0/go.mod h1:ZU6zlcweCZKyiB7BVfCizQT9XmkEIMFE+PRZydVcsZg=
|
||||
github.com/compose-spec/compose-go/v2 v2.15.0 h1:tdQw+eMyT+P6ZIb09JfcIVvbMmIa+PjST7cWezVLf00=
|
||||
github.com/compose-spec/compose-go/v2 v2.15.0/go.mod h1:Q1+qtN4vhzEjGrnqRtzx1xa8raDZQlMUe3WJxndYNiQ=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
@@ -307,8 +307,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
@@ -438,6 +438,7 @@ github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||
@@ -597,16 +598,16 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||
github.com/moby/go-archive v0.3.0 h1:nos4BtzzUIqB406BgQnWGMI4qib9BZ8XUHU+ucv/n1c=
|
||||
github.com/moby/go-archive v0.3.0/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE=
|
||||
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
|
||||
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8=
|
||||
github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o=
|
||||
github.com/moby/sys/user v0.4.1 h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0=
|
||||
github.com/moby/sys/user v0.4.1/go.mod h1:E9QsW5WRe1kUAf7kW8hXKwu1uhsZEAdPLYHYSDudF4Y=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
@@ -877,8 +878,8 @@ go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lL
|
||||
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
|
||||
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
|
||||
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
|
||||
go.mongodb.org/mongo-driver/v2 v2.8.2 h1:b6o2m7zL8g2URuO8urBedAylxojybKXNZTxgkOcl+2w=
|
||||
go.mongodb.org/mongo-driver/v2 v2.8.2/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.mongodb.org/mongo-driver/v2 v2.9.0 h1:e2mQdOmbkiYz+dj3faM7lVDwl7WdnRD+g5VicafMhL0=
|
||||
go.mongodb.org/mongo-driver/v2 v2.9.0/go.mod h1:SHKN0IWkKmEVGHLjXnni6s4wPKX4v86FTgOeJJFuXcA=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@@ -889,23 +890,23 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
|
||||
go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 h1:QBajQ2SrwQijzHyZbQlPsuIzpl/ll8DY6wPWsajeGcI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0/go.mod h1:08ZQLjrPLQ6R4kAXvuOvODEer5Yh4CoFvll5qB2BCI8=
|
||||
go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
|
||||
go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
|
||||
go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=
|
||||
go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA=
|
||||
go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
|
||||
go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk=
|
||||
go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
@@ -951,8 +952,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
|
||||
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -1076,8 +1077,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
||||
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1193,8 +1194,8 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE=
|
||||
golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -1315,10 +1316,10 @@ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxH
|
||||
google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478 h1:aLsVTW0lZ8+IY5u/ERjZSCvAmhuR7slKzyha3YikDNA=
|
||||
google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478/go.mod h1:YJAzKjfHIUHb9T+bfu8L7mthAp7VVXQBUs1PLdBWS7M=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7 h1:eM/YSd5bBFagF51o1E745Ta7RwzpW0h+z+QDNZOgmQ8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -1336,8 +1337,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y=
|
||||
google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
|
||||
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
|
||||
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
||||
+61
-16
@@ -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."
|
||||
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."
|
||||
CronJobSuccessAlert: "Panel {{ .node }}{{ .ip }}: scheduled task {{ .name }} completed successfully. Log in to view details."
|
||||
|
||||
# disk
|
||||
DeviceNotFound: "Device {{ .name }} not found"
|
||||
@@ -682,8 +683,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
|
||||
# Firewall
|
||||
ErrDockerIptablesChainUnavailable: 'The Docker DOCKER-USER chain was not found. Restart Docker and try again'
|
||||
ErrDockerNftablesChainUnavailable: 'The Docker nftables IPv4 firewall chain was not found. Verify that the current Docker version supports the nftables firewall backend, restart Docker, and try again'
|
||||
ErrDockerForwardPolicyDrop: '{{ .family }} FORWARD defaults to DROP. Adjust the policy and retry'
|
||||
ErrUFWRuleAdopt: 'Failed to adopt the UFW rule: {{ .detail }}. If the installed UFW version is earlier than 0.35, upgrade UFW and try again'
|
||||
ErrDockerForwardPolicyDrop: 'This server restricts network forwarding. Container port protection cannot be enabled at this time.'
|
||||
Firewall: 'Firewall'
|
||||
FirewallTaskHost: 'Host firewall'
|
||||
FirewallTaskForwarding: 'Port forwarding'
|
||||
@@ -697,25 +697,70 @@ FirewallCreateRulesStep: 'Create firewall rules'
|
||||
FirewallCreateRulesResult: 'Creation result: {{ .succeeded }} succeeded, {{ .failed }} failed, {{ .skipped }} not executed'
|
||||
FirewallRuleOperationResult: 'Operation result: {{ .succeeded }} succeeded, {{ .failed }} failed'
|
||||
FirewallCreateRuleSkipped: 'Not executed'
|
||||
FirewallCreateBatchStep: 'Submit {{ .count }} rules as a batch to {{ .backend }}'
|
||||
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; converted to {{ .count }} rules'
|
||||
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'
|
||||
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, {{ .unchanged }} already matching (no changes needed)'
|
||||
FirewallSyncRuleUnchanged: 'Already matching; no changes needed'
|
||||
FirewallSyncStep: 'Synchronize rules to {{ .name }}'
|
||||
FirewallSyncFailed: '{{ .failed }} firewall rules failed to synchronize'
|
||||
FirewallResetSourceStep: 'Reset and disable source firewall {{ .name }}'
|
||||
FirewallResetSourceResult: 'Source firewall reset completed; {{ .removed }} database rules were deleted'
|
||||
FirewallCreateRuleExecutionFailed: 'Rule creation failed; executed commands were not rolled back'
|
||||
FirewallInitializeChainsStep: 'Initialize and bind {{ .name }} base chains'
|
||||
FirewallRestoreRulesStep: 'Restore database rules to {{ .name }}'
|
||||
FirewallSyncWhitelistStep: 'Synchronize firewall port whitelist'
|
||||
FirewallEnableForwardingStep: 'Enable and bind port forwarding chains'
|
||||
FirewallRestoreForwardingRulesStep: 'Restore database port forwarding rules'
|
||||
FirewallInspectDockerGuardStep: 'Inspect Docker firewall backend and policies'
|
||||
FirewallInitializeDockerGuardStep: 'Initialize and bind {{ .name }} port guard chains'
|
||||
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."
|
||||
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"
|
||||
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 }}"
|
||||
ErrFirewallRuleConflict: "A UFW rule with the same match conditions and a different action already exists. Edit the existing rule instead."
|
||||
ErrForwardInterfaceNotFound: "Network interface {{ .name }} specified by the forwarding rule does not exist. Check the interface name and retry."
|
||||
|
||||
ErrSSHPublicKey: "Enter a valid SSH public key"
|
||||
ErrSSHPrivateKey: "Invalid SSH private key or incorrect passphrase; encrypted keys require the original passphrase"
|
||||
ErrSSHKeyMismatch: "The SSH public and private keys do not match"
|
||||
|
||||
# virtual machine
|
||||
ErrVMNotFound: "Virtual machine does not exist"
|
||||
ErrVMAlreadyExists: "Virtual machine already exists"
|
||||
ErrVMCountLimit: "The Professional edition supports up to {{ .limit }} virtual machines. Delete an existing VM or upgrade to Enterprise to create more."
|
||||
ErrVMImageAlreadyExists: "Virtual machine image already exists: {{ .detail }}"
|
||||
ErrVMStoragePathInUse: "Storage pool path is already used by [{{ .name }}]"
|
||||
ErrVMHelperNotFound: "1panel-kvm not found"
|
||||
ErrVMBridgeManaged: "Bridge name is already managed by libvirt or Docker"
|
||||
ErrVMNetworkInUse: "Network is in use and cannot be deleted"
|
||||
ErrVMStorageInUse: "Storage pool is in use and cannot be deleted"
|
||||
ErrVMStorageNameCannotChange: "Storage pool name cannot be changed"
|
||||
ErrVMStoragePathCannotChange: "Storage pool path cannot be changed"
|
||||
ErrVMRunning: "Virtual machine is running"
|
||||
ErrVMLibvirtConnect: "Failed to connect to libvirt: {{ .detail }}"
|
||||
ErrVMPermission: "No permission to operate virtual machine: {{ .detail }}"
|
||||
ErrVMNetworkNotFound: "Virtual machine network does not exist: {{ .detail }}"
|
||||
ErrVMInvalidPath: "Invalid virtual machine path: {{ .detail }}"
|
||||
ErrVMInvalidInput: "Invalid virtual machine parameter: {{ .detail }}"
|
||||
ErrVMCommandFailed: "Virtual machine command failed: {{ .err }}"
|
||||
ErrVMDiskResizeBlocked: "The disk does not meet expansion requirements or is referenced by another device. Check the disk status."
|
||||
ErrVMDiskReferenced: "This virtual disk is used by another virtual machine or device and cannot be expanded yet."
|
||||
ErrVMDiskReferenceCheck: "Cannot complete virtual disk reference checks. Expansion is unavailable; check the disks of other virtual machines."
|
||||
ErrVMSnapshotMetadataSync: "The snapshot was restored, but disk information could not be synced. Refresh and check; do not restore the snapshot again."
|
||||
VMCreateTask: "Create virtual machine [{{ .name }}]"
|
||||
VMCreateFromTemplateTask: "Create virtual machine [{{ .name }}] from template"
|
||||
VMUpdateTask: "Update virtual machine [{{ .name }}]"
|
||||
VMDiskResizeTask: "Expand disk [{{ .disk }}] of virtual machine [{{ .name }}]"
|
||||
VMRenameTask: "Rename virtual machine [{{ .name }}] to [{{ .newName }}]"
|
||||
VMDeleteTask: "Delete virtual machine [{{ .name }}]"
|
||||
VMTemplateCreateTask: "Save virtual machine [{{ .name }}] as template [{{ .template }}]"
|
||||
VMTemplateDeleteTask: "Delete virtual machine template [{{ .name }}]"
|
||||
VMForceDeleteMetadataOnly: "1panel-kvm not found, force deleting virtual machine [{{ .name }}] metadata only"
|
||||
VMForceDeleteMetadataAfterLibvirtFailed: "Failed to delete virtual machine [{{ .name }}] from libvirt, force deleting metadata: {{ .err }}"
|
||||
VMForceDeleteDiskFailed: "Force delete virtual machine disk file [{{ .path }}] failed: {{ .err }}"
|
||||
VMForceDeleteDiskSuccess: "Force deleted virtual machine disk file [{{ .path }}]"
|
||||
VMSnapshotCreateTask: "Create snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]"
|
||||
VMSnapshotCreateStep: "Create virtual machine snapshot [{{ .snapshot }}]"
|
||||
VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]"
|
||||
VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]"
|
||||
|
||||
+61
-16
@@ -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.'
|
||||
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.'
|
||||
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: la tarea programada {{ .name }} se completó correctamente. Inicie sesión para ver los detalles.'
|
||||
|
||||
# disco
|
||||
DeviceNotFound: 'Dispositivo {{ .name }} no encontrado'
|
||||
@@ -682,8 +683,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
|
||||
# Firewall
|
||||
ErrDockerIptablesChainUnavailable: 'No se encontró la cadena DOCKER-USER de Docker. Reinicie Docker y vuelva a intentarlo'
|
||||
ErrDockerNftablesChainUnavailable: 'No se encontró la cadena de firewall IPv4 de nftables de Docker. Compruebe que la versión actual de Docker admita el backend de firewall nftables, reinicie Docker y vuelva a intentarlo'
|
||||
ErrDockerForwardPolicyDrop: 'La política predeterminada de FORWARD para {{ .family }} es DROP. Ajústela y vuelva a intentarlo'
|
||||
ErrUFWRuleAdopt: 'No se pudo administrar la regla UFW: {{ .detail }}. Si la versión actual de UFW es anterior a 0.35, actualice UFW y vuelva a intentarlo'
|
||||
ErrDockerForwardPolicyDrop: 'Este servidor restringe el reenvío de red. Por ahora no se puede habilitar la protección de puertos de contenedores.'
|
||||
Firewall: 'Firewall'
|
||||
FirewallTaskHost: 'Cortafuegos del host'
|
||||
FirewallTaskForwarding: 'Reenvío de puertos'
|
||||
@@ -697,25 +697,70 @@ FirewallCreateRulesStep: 'Crear reglas de cortafuegos'
|
||||
FirewallCreateRulesResult: 'Resultado de creación: {{ .succeeded }} correctas, {{ .failed }} fallidas, {{ .skipped }} sin ejecutar'
|
||||
FirewallRuleOperationResult: 'Resultado de la operación: {{ .succeeded }} correctas, {{ .failed }} fallidas'
|
||||
FirewallCreateRuleSkipped: 'Sin ejecutar'
|
||||
FirewallCreateBatchStep: 'Enviar {{ .count }} reglas en un lote a {{ .backend }}'
|
||||
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; convertido en {{ .count }} reglas'
|
||||
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'
|
||||
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, {{ .unchanged }} ya coinciden (sin cambios necesarios)'
|
||||
FirewallSyncRuleUnchanged: 'Ya coincide; no requiere cambios'
|
||||
FirewallSyncStep: 'Sincronizar reglas con {{ .name }}'
|
||||
FirewallSyncFailed: 'No se pudieron sincronizar {{ .failed }} reglas del firewall'
|
||||
FirewallResetSourceStep: 'Restablecer y desactivar el firewall de origen {{ .name }}'
|
||||
FirewallResetSourceResult: 'Firewall de origen restablecido; se eliminaron {{ .removed }} reglas de la base de datos'
|
||||
FirewallCreateRuleExecutionFailed: 'Error al crear la regla. Los comandos ejecutados no se revirtieron'
|
||||
FirewallInitializeChainsStep: 'Inicializar y vincular las cadenas base de {{ .name }}'
|
||||
FirewallRestoreRulesStep: 'Restaurar las reglas de la base de datos en {{ .name }}'
|
||||
FirewallSyncWhitelistStep: 'Sincronizar la lista de puertos permitidos del firewall'
|
||||
FirewallEnableForwardingStep: 'Habilitar y vincular las cadenas de reenvío de puertos'
|
||||
FirewallRestoreForwardingRulesStep: 'Restaurar las reglas de reenvío de puertos de la base de datos'
|
||||
FirewallInspectDockerGuardStep: 'Inspeccionar el backend del firewall de Docker y las políticas'
|
||||
FirewallInitializeDockerGuardStep: 'Inicializar y vincular las cadenas de protección de puertos de {{ .name }}'
|
||||
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."
|
||||
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"
|
||||
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 }}"
|
||||
ErrFirewallRuleConflict: "Ya existe una regla UFW con las mismas condiciones y una acción diferente. Edite la regla existente."
|
||||
ErrForwardInterfaceNotFound: "La interfaz de red {{ .name }} indicada en la regla de reenvío no existe. Compruebe el nombre e inténtelo de nuevo."
|
||||
|
||||
ErrSSHPublicKey: "Introduce una clave pública SSH válida"
|
||||
ErrSSHPrivateKey: "Clave privada SSH no válida o contraseña incorrecta; las claves cifradas requieren la contraseña original"
|
||||
ErrSSHKeyMismatch: "Las claves pública y privada SSH no coinciden"
|
||||
|
||||
# máquina virtual
|
||||
ErrVMNotFound: "La máquina virtual no existe"
|
||||
ErrVMAlreadyExists: "La máquina virtual ya existe"
|
||||
ErrVMCountLimit: "La edición Professional permite crear hasta {{ .limit }} máquinas virtuales. Elimina una máquina virtual existente o actualiza a Enterprise y vuelve a intentarlo."
|
||||
ErrVMImageAlreadyExists: "La imagen de la máquina virtual ya existe: {{ .detail }}"
|
||||
ErrVMStoragePathInUse: "La ruta del pool de almacenamiento ya está usada por [{{ .name }}]"
|
||||
ErrVMHelperNotFound: "No se encontró 1panel-kvm"
|
||||
ErrVMBridgeManaged: "El nombre del bridge ya está administrado por libvirt o Docker"
|
||||
ErrVMNetworkInUse: "La red está en uso y no se puede eliminar"
|
||||
ErrVMStorageInUse: "El pool de almacenamiento está en uso y no se puede eliminar"
|
||||
ErrVMStorageNameCannotChange: "El nombre del pool de almacenamiento no se puede cambiar"
|
||||
ErrVMStoragePathCannotChange: "La ruta del pool de almacenamiento no se puede cambiar"
|
||||
ErrVMRunning: "La máquina virtual está en ejecución"
|
||||
ErrVMLibvirtConnect: "Error al conectar con libvirt: {{ .detail }}"
|
||||
ErrVMPermission: "Sin permiso para operar la máquina virtual: {{ .detail }}"
|
||||
ErrVMNetworkNotFound: "La red de la máquina virtual no existe: {{ .detail }}"
|
||||
ErrVMInvalidPath: "Ruta de máquina virtual no válida: {{ .detail }}"
|
||||
ErrVMInvalidInput: "Parámetro de máquina virtual no válido: {{ .detail }}"
|
||||
ErrVMCommandFailed: "Error al ejecutar el comando de la máquina virtual: {{ .err }}"
|
||||
ErrVMDiskResizeBlocked: "El disco no cumple los requisitos de ampliación o está referenciado por otro dispositivo. Compruebe su estado."
|
||||
ErrVMDiskReferenced: "Otro equipo virtual o dispositivo utiliza este disco virtual; la ampliación aún no se admite."
|
||||
ErrVMDiskReferenceCheck: "No se pueden comprobar las referencias al disco virtual. La ampliación no está disponible; revise los discos de otras máquinas virtuales."
|
||||
ErrVMSnapshotMetadataSync: "La instantánea se restauró, pero falló la sincronización de los datos del disco. Actualice y compruebe; no restaure de nuevo la instantánea."
|
||||
VMCreateTask: "Crear máquina virtual [{{ .name }}]"
|
||||
VMCreateFromTemplateTask: "Crear máquina virtual [{{ .name }}] desde plantilla"
|
||||
VMUpdateTask: "Actualizar máquina virtual [{{ .name }}]"
|
||||
VMDiskResizeTask: "Ampliar disco [{{ .disk }}] de la máquina virtual [{{ .name }}]"
|
||||
VMRenameTask: "Renombrar máquina virtual [{{ .name }}] a [{{ .newName }}]"
|
||||
VMDeleteTask: "Eliminar máquina virtual [{{ .name }}]"
|
||||
VMTemplateCreateTask: "Guardar máquina virtual [{{ .name }}] como plantilla [{{ .template }}]"
|
||||
VMTemplateDeleteTask: "Eliminar plantilla de máquina virtual [{{ .name }}]"
|
||||
VMForceDeleteMetadataOnly: "1panel-kvm no encontrado, eliminando solo los metadatos de la máquina virtual [{{ .name }}]"
|
||||
VMForceDeleteMetadataAfterLibvirtFailed: "Error al eliminar la máquina virtual [{{ .name }}] de libvirt, eliminando metadatos: {{ .err }}"
|
||||
VMForceDeleteDiskFailed: "Error al forzar la eliminación del archivo de disco de la máquina virtual [{{ .path }}]: {{ .err }}"
|
||||
VMForceDeleteDiskSuccess: "Archivo de disco de la máquina virtual [{{ .path }}] eliminado forzosamente"
|
||||
VMSnapshotCreateTask: "Crear instantánea [{{ .snapshot }}] para la máquina virtual [{{ .vm }}]"
|
||||
VMSnapshotCreateStep: "Crear instantánea de máquina virtual [{{ .snapshot }}]"
|
||||
VMSnapshotRecoverTask: "Restaurar instantánea [{{ .snapshot }}] para la máquina virtual [{{ .vm }}]"
|
||||
VMSnapshotRecoverStep: "Restaurar instantánea de máquina virtual [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "Eliminar instantánea [{{ .snapshot }}] de la máquina virtual [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "Eliminar instantánea de máquina virtual [{{ .snapshot }}]"
|
||||
|
||||
+62
-16
@@ -649,6 +649,8 @@ CommonAlert: "پنل {{ .node }}{{ .ip }}: {{ .msg }}. برای مشاهده ج
|
||||
NodeExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} گره غیرعادی هستند. برای مشاهده جزئیات وارد شوید."
|
||||
LicenseExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} مجوز غیرعادی است. برای مشاهده جزئیات وارد شوید."
|
||||
SSHAndPanelLoginAlert: "پنل {{ .node }}{{ .ip }}: ورود غیرعادی {{ .name }} از {{ .loginIp }}. برای مشاهده جزئیات وارد شوید."
|
||||
CronJobSuccessAlert: "پنل {{ .node }}{{ .ip }}: وظیفه زمانبندیشده {{ .name }} با موفقیت تکمیل شد. برای مشاهده جزئیات وارد شوید."
|
||||
|
||||
|
||||
# دیسک
|
||||
DeviceNotFound: "دستگاه {{ .name }} یافت نشد"
|
||||
@@ -682,8 +684,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
|
||||
# Firewall
|
||||
ErrDockerIptablesChainUnavailable: 'زنجیره DOCKER-USER داکر یافت نشد. داکر را راهاندازی مجدد کنید و دوباره تلاش کنید'
|
||||
ErrDockerNftablesChainUnavailable: 'زنجیره فایروال IPv4 مربوط به nftables داکر یافت نشد. بررسی کنید نسخه فعلی داکر از بکاند فایروال nftables پشتیبانی کند، سپس داکر را راهاندازی مجدد کرده و دوباره تلاش کنید'
|
||||
ErrDockerForwardPolicyDrop: 'سیاست پیشفرض FORWARD برای {{ .family }} برابر DROP است. آن را تغییر دهید و دوباره تلاش کنید'
|
||||
ErrUFWRuleAdopt: 'مدیریت قانون UFW ناموفق بود: {{ .detail }}. اگر نسخه فعلی UFW قدیمیتر از 0.35 است، ابتدا UFW را ارتقا دهید و دوباره تلاش کنید'
|
||||
ErrDockerForwardPolicyDrop: 'این سرور هدایت ترافیک شبکه را محدود کرده است. در حال حاضر امکان فعالسازی محافظت از پورت کانتینرها وجود ندارد.'
|
||||
Firewall: 'فایروال'
|
||||
FirewallTaskHost: 'دیواره آتش میزبان'
|
||||
FirewallTaskForwarding: 'هدایت پورت'
|
||||
@@ -697,25 +698,70 @@ FirewallCreateRulesStep: 'ایجاد قوانین دیوار آتش'
|
||||
FirewallCreateRulesResult: 'نتیجه ایجاد: {{ .succeeded }} موفق، {{ .failed }} ناموفق، {{ .skipped }} اجرا نشده'
|
||||
FirewallRuleOperationResult: 'نتیجه عملیات: {{ .succeeded }} موفق، {{ .failed }} ناموفق'
|
||||
FirewallCreateRuleSkipped: 'اجرا نشده'
|
||||
FirewallCreateBatchStep: 'ارسال {{ .count }} قانون به صورت دستهای به {{ .backend }}'
|
||||
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}؛ به {{ .count }} قانون تبدیل شد'
|
||||
FirewallCreateRuleExecutionFailed: 'ایجاد قانون ناموفق بود. هیچ رکوردی در پایگاه داده ذخیره نشد و دستورات اجراشده بازگردانی نشدند'
|
||||
FirewallCreateRulePersistenceFailed: 'قانون ایجاد شد، اما ذخیره رکورد مدیریتی آن ناموفق بود'
|
||||
FirewallAdoptRulePersistenceFailed: 'دستور پذیرش قانون برای مدیریت اجرا شد، اما اطلاعات مدیریت ذخیره نشد'
|
||||
FirewallSyncOperationsResult: 'عملیات همگامسازی: {{ .removed }} حذفشده، {{ .created }} ایجادشده، {{ .failed }} ناموفق، {{ .skipped }} اجرانشده، {{ .unchanged }} از قبل مطابق (بدون نیاز به تغییر)'
|
||||
FirewallSyncRuleUnchanged: 'از قبل مطابق است؛ نیازی به تغییر نیست'
|
||||
FirewallSyncStep: 'همگامسازی قوانین با {{ .name }}'
|
||||
FirewallSyncFailed: 'همگامسازی {{ .failed }} قانون فایروال ناموفق بود'
|
||||
FirewallResetSourceStep: 'بازنشانی و غیرفعالکردن فایروال مبدأ {{ .name }}'
|
||||
FirewallResetSourceResult: 'فایروال مبدأ بازنشانی شد و {{ .removed }} قانون پایگاه داده حذف شد'
|
||||
FirewallCreateRuleExecutionFailed: 'ایجاد قانون ناموفق بود. دستورات اجراشده بازگردانی نشدند'
|
||||
FirewallInitializeChainsStep: 'راهاندازی و اتصال زنجیرههای پایه {{ .name }}'
|
||||
FirewallRestoreRulesStep: 'بازیابی قوانین پایگاه داده در {{ .name }}'
|
||||
FirewallSyncWhitelistStep: 'همگامسازی فهرست مجاز پورتهای فایروال'
|
||||
FirewallEnableForwardingStep: 'فعالسازی و اتصال زنجیرههای هدایت پورت'
|
||||
FirewallRestoreForwardingRulesStep: 'بازیابی قوانین هدایت پورت پایگاه داده'
|
||||
FirewallInspectDockerGuardStep: 'بررسی پشتیبان فایروال Docker و سیاستها'
|
||||
FirewallInitializeDockerGuardStep: 'راهاندازی و اتصال زنجیرههای محافظت پورت {{ .name }}'
|
||||
FirewallPersistDockerGuardStep: 'ذخیره وضعیت محافظت پورت Docker'
|
||||
ErrFirewallRuleScopeChange: "فایروال فعلی از تغییر محدودهٔ قانون (مانند خانوادهٔ آدرس IPv4/IPv6) پشتیبانی نمیکند. لطفاً یک قانون جدید ایجاد کنید."
|
||||
FirewallWhitelistReleased: "{{ .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 }}"
|
||||
ErrFirewallRuleConflict: "یک قانون UFW با شرایط تطبیق یکسان و عملکرد متفاوت از قبل وجود دارد. قانون موجود را ویرایش کنید."
|
||||
ErrForwardInterfaceNotFound: "رابط شبکه {{ .name }} مشخصشده در قانون انتقال وجود ندارد. نام رابط را بررسی کرده و دوباره تلاش کنید."
|
||||
|
||||
ErrSSHPublicKey: "یک کلید عمومی SSH معتبر وارد کنید"
|
||||
ErrSSHPrivateKey: "کلید خصوصی SSH نامعتبر یا عبارت عبور نادرست است؛ کلیدهای رمزگذاریشده به عبارت عبور اصلی نیاز دارند"
|
||||
ErrSSHKeyMismatch: "کلید عمومی و خصوصی SSH مطابقت ندارند"
|
||||
|
||||
# ماشین مجازی
|
||||
ErrVMNotFound: "ماشین مجازی وجود ندارد"
|
||||
ErrVMAlreadyExists: "ماشین مجازی از قبل وجود دارد"
|
||||
ErrVMCountLimit: "در نسخه Professional میتوانید حداکثر {{ .limit }} ماشین مجازی ایجاد کنید. یک ماشین مجازی موجود را حذف کنید یا به نسخه Enterprise ارتقا دهید و دوباره تلاش کنید."
|
||||
ErrVMImageAlreadyExists: "فایل ایمیج ماشین مجازی از قبل وجود دارد: {{ .detail }}"
|
||||
ErrVMStoragePathInUse: "مسیر استخر ذخیرهسازی قبلاً توسط [{{ .name }}] استفاده شده است"
|
||||
ErrVMHelperNotFound: "1panel-kvm یافت نشد"
|
||||
ErrVMBridgeManaged: "نام bridge از قبل توسط libvirt یا Docker مدیریت میشود"
|
||||
ErrVMNetworkInUse: "شبکه در حال استفاده است و قابل حذف نیست"
|
||||
ErrVMStorageInUse: "مخزن ذخیرهسازی در حال استفاده است و قابل حذف نیست"
|
||||
ErrVMStorageNameCannotChange: "نام مخزن ذخیرهسازی قابل تغییر نیست"
|
||||
ErrVMStoragePathCannotChange: "مسیر مخزن ذخیرهسازی قابل تغییر نیست"
|
||||
ErrVMRunning: "ماشین مجازی در حال اجرا است"
|
||||
ErrVMLibvirtConnect: "اتصال به libvirt ناموفق بود: {{ .detail }}"
|
||||
ErrVMPermission: "مجوز عملیات ماشین مجازی وجود ندارد: {{ .detail }}"
|
||||
ErrVMNetworkNotFound: "شبکه ماشین مجازی وجود ندارد: {{ .detail }}"
|
||||
ErrVMInvalidPath: "مسیر ماشین مجازی نامعتبر است: {{ .detail }}"
|
||||
ErrVMInvalidInput: "پارامتر ماشین مجازی نامعتبر است: {{ .detail }}"
|
||||
ErrVMCommandFailed: "اجرای دستور ماشین مجازی ناموفق بود: {{ .err }}"
|
||||
ErrVMDiskResizeBlocked: "دیسک شرایط افزایش ظرفیت را ندارد یا دستگاه دیگری به آن ارجاع میدهد. وضعیت دیسک را بررسی کنید."
|
||||
ErrVMDiskReferenced: "این دیسک مجازی توسط ماشین مجازی یا دستگاه دیگری استفاده میشود؛ افزایش ظرفیت فعلاً پشتیبانی نمیشود."
|
||||
ErrVMDiskReferenceCheck: "بررسی ارجاعهای دیسک مجازی کامل نشد. افزایش ظرفیت ممکن نیست؛ دیسکهای سایر ماشینهای مجازی را بررسی کنید."
|
||||
ErrVMSnapshotMetadataSync: "اسنپشات بازیابی شد، اما همگامسازی اطلاعات دیسک ناموفق بود. تازهسازی و بررسی کنید؛ نیازی به بازیابی دوباره اسنپشات نیست."
|
||||
VMCreateTask: "ایجاد ماشین مجازی [{{ .name }}]"
|
||||
VMCreateFromTemplateTask: "ایجاد ماشین مجازی [{{ .name }}] از قالب"
|
||||
VMUpdateTask: "بهروزرسانی ماشین مجازی [{{ .name }}]"
|
||||
VMDiskResizeTask: "افزایش ظرفیت دیسک [{{ .disk }}] ماشین مجازی [{{ .name }}]"
|
||||
VMRenameTask: "تغییر نام ماشین مجازی [{{ .name }}] به [{{ .newName }}]"
|
||||
VMDeleteTask: "حذف ماشین مجازی [{{ .name }}]"
|
||||
VMTemplateCreateTask: "ذخیره ماشین مجازی [{{ .name }}] به عنوان قالب [{{ .template }}]"
|
||||
VMTemplateDeleteTask: "حذف قالب ماشین مجازی [{{ .name }}]"
|
||||
VMForceDeleteMetadataOnly: "1panel-kvm پیدا نشد، فقط فراداده ماشین مجازی [{{ .name }}] به اجبار حذف میشود"
|
||||
VMForceDeleteMetadataAfterLibvirtFailed: "حذف ماشین مجازی [{{ .name }}] از libvirt ناموفق بود، فراداده به اجبار حذف میشود: {{ .err }}"
|
||||
VMForceDeleteDiskFailed: "حذف اجباری فایل دیسک ماشین مجازی [{{ .path }}] ناموفق بود: {{ .err }}"
|
||||
VMForceDeleteDiskSuccess: "فایل دیسک ماشین مجازی [{{ .path }}] به اجبار حذف شد"
|
||||
VMSnapshotCreateTask: "ایجاد اسنپشات [{{ .snapshot }}] برای ماشین مجازی [{{ .vm }}]"
|
||||
VMSnapshotCreateStep: "ایجاد اسنپشات ماشین مجازی [{{ .snapshot }}]"
|
||||
VMSnapshotRecoverTask: "بازیابی اسنپشات [{{ .snapshot }}] برای ماشین مجازی [{{ .vm }}]"
|
||||
VMSnapshotRecoverStep: "بازیابی اسنپشات ماشین مجازی [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "حذف اسنپشات [{{ .snapshot }}] برای ماشین مجازی [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "حذف اسنپشات ماشین مجازی [{{ .snapshot }}]"
|
||||
|
||||
+61
-16
@@ -649,6 +649,7 @@ CommonAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .msg }}。詳細
|
||||
NodeExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のノードに異常が発生しています。詳細はパネルにログインして'
|
||||
LicenseExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のライセンスに異常が発生しています。詳細はパネルにログインして'
|
||||
SSHAndPanelLoginAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .loginIp }} からの {{ .name }} ログインに異常があります。詳細はパネルにログインして'
|
||||
CronJobSuccessAlert: 'パネル {{ .node }}{{ .ip }}: スケジュールタスク {{ .name }} が正常に完了しました。詳細はパネルにログインして確認してください。'
|
||||
|
||||
# ディスク
|
||||
DeviceNotFound: 'デバイス {{ .name }} が見つかりません'
|
||||
@@ -682,8 +683,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
|
||||
# Firewall
|
||||
ErrDockerIptablesChainUnavailable: 'Docker の DOCKER-USER チェーンが見つかりません。Docker を再起動してから再試行してください'
|
||||
ErrDockerNftablesChainUnavailable: 'Docker の nftables IPv4 ファイアウォールチェーンが見つかりません。現在の Docker バージョンが nftables ファイアウォールバックエンドをサポートしていることを確認し、Docker を再起動してから再試行してください'
|
||||
ErrDockerForwardPolicyDrop: '{{ .family }} FORWARD の既定ポリシーが DROP です。変更してから再試行してください'
|
||||
ErrUFWRuleAdopt: 'UFW ルールの管理に失敗しました:{{ .detail }}。現在の UFW バージョンが 0.35 未満の場合は、UFW をアップグレードしてから再試行してください'
|
||||
ErrDockerForwardPolicyDrop: 'このサーバーではネットワーク転送が制限されているため、現在コンテナのポート保護を有効にできません。'
|
||||
Firewall: 'ファイアウォール'
|
||||
FirewallTaskHost: 'ホストファイアウォール'
|
||||
FirewallTaskForwarding: 'ポート転送'
|
||||
@@ -697,25 +697,70 @@ FirewallCreateRulesStep: 'ファイアウォールルールを作成'
|
||||
FirewallCreateRulesResult: '作成結果:成功 {{ .succeeded }} 件、失敗 {{ .failed }} 件、未実行 {{ .skipped }} 件'
|
||||
FirewallRuleOperationResult: '操作結果:成功 {{ .succeeded }} 件、失敗 {{ .failed }} 件'
|
||||
FirewallCreateRuleSkipped: '未実行'
|
||||
FirewallCreateBatchStep: '{{ .backend }} に {{ .count }} 件のルールを一括送信'
|
||||
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }};{{ .count }} 件のルールに変換'
|
||||
FirewallCreateRuleExecutionFailed: 'ルールの作成に失敗しました。データベースには保存せず、実行済みのコマンドはロールバックしません'
|
||||
FirewallCreateRulePersistenceFailed: 'ルールは作成されましたが、管理情報の保存に失敗しました'
|
||||
FirewallAdoptRulePersistenceFailed: 'ルールの管理対象への取り込みコマンドは実行されましたが、管理情報の保存に失敗しました'
|
||||
FirewallSyncOperationsResult: '同期操作:削除成功 {{ .removed }} 件、作成成功 {{ .created }} 件、失敗 {{ .failed }} 件、未実行 {{ .skipped }} 件、一致済みで変更不要 {{ .unchanged }} 件'
|
||||
FirewallSyncRuleUnchanged: '一致済み、変更不要'
|
||||
FirewallSyncStep: '{{ .name }} にルールを同期'
|
||||
FirewallSyncFailed: '{{ .failed }} 件のファイアウォールルールを同期できませんでした'
|
||||
FirewallResetSourceStep: '移行元ファイアウォール {{ .name }} をリセットして無効化'
|
||||
FirewallResetSourceResult: '移行元ファイアウォールをリセットし、データベースルール {{ .removed }} 件を削除しました'
|
||||
FirewallCreateRuleExecutionFailed: 'ルールの作成に失敗しました。実行済みのコマンドはロールバックしません'
|
||||
FirewallInitializeChainsStep: '{{ .name }} のベースチェーンを初期化してバインド'
|
||||
FirewallRestoreRulesStep: 'データベースルールを {{ .name }} に復元'
|
||||
FirewallSyncWhitelistStep: 'ファイアウォールポート許可リストを同期'
|
||||
FirewallEnableForwardingStep: 'ポート転送チェーンを有効化してバインド'
|
||||
FirewallRestoreForwardingRulesStep: 'データベースのポート転送ルールを復元'
|
||||
FirewallInspectDockerGuardStep: 'Docker ファイアウォールバックエンドと保護ポリシーを確認'
|
||||
FirewallInitializeDockerGuardStep: '{{ .name }} のポート保護チェーンを初期化してバインド'
|
||||
FirewallPersistDockerGuardStep: 'Docker ポート保護状態を保存'
|
||||
ErrFirewallRuleScopeChange: "現在のファイアウォールでは、ルールの適用範囲(IPv4/IPv6 アドレスファミリーなど)を変更できません。新しいルールを作成してください。"
|
||||
FirewallWhitelistReleased: "{{ .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 }}"
|
||||
ErrFirewallRuleConflict: "UFW に同じ一致条件でアクションが異なるルールが既に存在します。既存のルールを編集してください。"
|
||||
ErrForwardInterfaceNotFound: "転送ルールで指定されたネットワークインターフェース {{ .name }} は存在しません。名前を確認して再試行してください。"
|
||||
|
||||
ErrSSHPublicKey: "有効な SSH 公開鍵を入力してください"
|
||||
ErrSSHPrivateKey: "SSH 秘密鍵の形式が無効か、パスフレーズが間違っています。暗号化された鍵には元のパスフレーズが必要です"
|
||||
ErrSSHKeyMismatch: "SSH 公開鍵と秘密鍵が一致しません"
|
||||
|
||||
# 仮想マシン
|
||||
ErrVMNotFound: "仮想マシンが存在しません"
|
||||
ErrVMAlreadyExists: "仮想マシンは既に存在します"
|
||||
ErrVMCountLimit: "Professional 版では最大 {{ .limit }} 台の仮想マシンを作成できます。既存の仮想マシンを削除するか、Enterprise 版にアップグレードしてから再試行してください。"
|
||||
ErrVMImageAlreadyExists: "仮想マシンのイメージファイルは既に存在します: {{ .detail }}"
|
||||
ErrVMStoragePathInUse: "ストレージプールのパスは既に [{{ .name }}] で使用されています"
|
||||
ErrVMHelperNotFound: "1panel-kvm が見つかりません"
|
||||
ErrVMBridgeManaged: "ブリッジ名はすでに libvirt または Docker によって管理されています"
|
||||
ErrVMNetworkInUse: "ネットワークは使用中のため削除できません"
|
||||
ErrVMStorageInUse: "ストレージプールは使用中のため削除できません"
|
||||
ErrVMStorageNameCannotChange: "ストレージプール名は変更できません"
|
||||
ErrVMStoragePathCannotChange: "ストレージプールのパスは変更できません"
|
||||
ErrVMRunning: "仮想マシンは実行中です"
|
||||
ErrVMLibvirtConnect: "libvirt への接続に失敗しました: {{ .detail }}"
|
||||
ErrVMPermission: "仮想マシンを操作する権限がありません: {{ .detail }}"
|
||||
ErrVMNetworkNotFound: "仮想マシンネットワークが存在しません: {{ .detail }}"
|
||||
ErrVMInvalidPath: "仮想マシンのパスが無効です: {{ .detail }}"
|
||||
ErrVMInvalidInput: "仮想マシンのパラメータが無効です: {{ .detail }}"
|
||||
ErrVMCommandFailed: "仮想マシンコマンドの実行に失敗しました: {{ .err }}"
|
||||
ErrVMDiskResizeBlocked: "ディスクが拡張条件を満たしていないか、他のデバイスから参照されています。ディスクの状態を確認してください。"
|
||||
ErrVMDiskReferenced: "この仮想ディスクは他の仮想マシンまたはデバイスで使用されているため、現在は拡張できません。"
|
||||
ErrVMDiskReferenceCheck: "仮想ディスクの参照を確認できないため拡張できません。他の仮想マシンのディスク状態を確認してください。"
|
||||
ErrVMSnapshotMetadataSync: "スナップショットは復元されましたが、ディスク情報の同期に失敗しました。更新して確認してください。再度復元する必要はありません。"
|
||||
VMCreateTask: "仮想マシン [{{ .name }}] を作成"
|
||||
VMCreateFromTemplateTask: "テンプレートから仮想マシン [{{ .name }}] を作成"
|
||||
VMUpdateTask: "仮想マシン [{{ .name }}] を更新"
|
||||
VMDiskResizeTask: "仮想マシン [{{ .name }}] のディスク [{{ .disk }}] を拡張"
|
||||
VMRenameTask: "仮想マシン [{{ .name }}] を [{{ .newName }}] にリネーム"
|
||||
VMDeleteTask: "仮想マシン [{{ .name }}] を削除"
|
||||
VMTemplateCreateTask: "仮想マシン [{{ .name }}] をテンプレート [{{ .template }}] として保存"
|
||||
VMTemplateDeleteTask: "仮想マシンテンプレート [{{ .name }}] を削除"
|
||||
VMForceDeleteMetadataOnly: "1panel-kvm が見つかりません。仮想マシン [{{ .name }}] のメタデータのみを強制削除します"
|
||||
VMForceDeleteMetadataAfterLibvirtFailed: "libvirt から仮想マシン [{{ .name }}] を削除できませんでした。メタデータを強制削除します: {{ .err }}"
|
||||
VMForceDeleteDiskFailed: "仮想マシンディスクファイル [{{ .path }}] の強制削除に失敗しました: {{ .err }}"
|
||||
VMForceDeleteDiskSuccess: "仮想マシンディスクファイル [{{ .path }}] を強制削除しました"
|
||||
VMSnapshotCreateTask: "仮想マシン [{{ .vm }}] のスナップショット [{{ .snapshot }}] を作成"
|
||||
VMSnapshotCreateStep: "仮想マシンのスナップショット [{{ .snapshot }}] を作成"
|
||||
VMSnapshotRecoverTask: "仮想マシン [{{ .vm }}] のスナップショット [{{ .snapshot }}] を復元"
|
||||
VMSnapshotRecoverStep: "仮想マシンのスナップショット [{{ .snapshot }}] を復元"
|
||||
VMSnapshotDeleteTask: "仮想マシン [{{ .vm }}] のスナップショット [{{ .snapshot }}] を削除"
|
||||
VMSnapshotDeleteStep: "仮想マシンのスナップショット [{{ .snapshot }}] を削除"
|
||||
|
||||
+61
-16
@@ -649,6 +649,7 @@ CommonAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .msg }}。자세한 내
|
||||
NodeExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
||||
LicenseExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 라이센스에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
||||
SSHAndPanelLoginAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .loginIp }}에서의 {{ .name }} 로그인에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
||||
CronJobSuccessAlert: '패널 {{ .node }}{{ .ip }}: 예약 작업 {{ .name }}이(가) 성공적으로 완료되었습니다. 로그인하여 자세한 내용을 확인하십시오.'
|
||||
|
||||
# 디스크
|
||||
DeviceNotFound: '장치 {{ .name }} 을(를) 찾을 수 없습니다'
|
||||
@@ -682,8 +683,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
|
||||
# Firewall
|
||||
ErrDockerIptablesChainUnavailable: 'Docker DOCKER-USER 체인을 찾을 수 없습니다. Docker를 다시 시작한 후 재시도하세요'
|
||||
ErrDockerNftablesChainUnavailable: 'Docker nftables IPv4 방화벽 체인을 찾을 수 없습니다. 현재 Docker 버전이 nftables 방화벽 백엔드를 지원하는지 확인하고 Docker를 다시 시작한 후 재시도하세요'
|
||||
ErrDockerForwardPolicyDrop: '{{ .family }} FORWARD 기본 정책이 DROP입니다. 정책을 조정한 후 다시 시도하세요'
|
||||
ErrUFWRuleAdopt: 'UFW 규칙 관리에 실패했습니다: {{ .detail }}. 현재 UFW 버전이 0.35 미만이면 UFW를 업그레이드한 후 다시 시도하세요'
|
||||
ErrDockerForwardPolicyDrop: '현재 서버에서 네트워크 전달이 제한되어 컨테이너 포트 보호를 활성화할 수 없습니다.'
|
||||
Firewall: '방화벽'
|
||||
FirewallTaskHost: '호스트 방화벽'
|
||||
FirewallTaskForwarding: '포트 포워딩'
|
||||
@@ -697,25 +697,70 @@ FirewallCreateRulesStep: '방화벽 규칙 생성'
|
||||
FirewallCreateRulesResult: '생성 결과: 성공 {{ .succeeded }}개, 실패 {{ .failed }}개, 미실행 {{ .skipped }}개'
|
||||
FirewallRuleOperationResult: '작업 결과: 성공 {{ .succeeded }}개, 실패 {{ .failed }}개'
|
||||
FirewallCreateRuleSkipped: '미실행'
|
||||
FirewallCreateBatchStep: '{{ .backend }}에 규칙 {{ .count }}개 일괄 제출'
|
||||
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; 규칙 {{ .count }}개로 변환'
|
||||
FirewallCreateRuleExecutionFailed: '규칙 생성에 실패했습니다. 데이터베이스에 저장하지 않았으며 실행된 명령은 롤백하지 않습니다'
|
||||
FirewallCreateRulePersistenceFailed: '규칙은 생성되었지만 관리 정보를 저장하지 못했습니다'
|
||||
FirewallAdoptRulePersistenceFailed: '규칙 관리 등록 명령은 실행되었지만 관리 정보를 저장하지 못했습니다'
|
||||
FirewallSyncOperationsResult: '동기화 작업: 삭제 성공 {{ .removed }}개, 생성 성공 {{ .created }}개, 실패 {{ .failed }}개, 미실행 {{ .skipped }}개, 이미 일치하여 변경 불필요 {{ .unchanged }}개'
|
||||
FirewallSyncRuleUnchanged: '이미 일치하여 변경이 필요하지 않음'
|
||||
FirewallSyncStep: '{{ .name }}에 규칙 동기화'
|
||||
FirewallSyncFailed: '방화벽 규칙 {{ .failed }}개를 동기화하지 못했습니다'
|
||||
FirewallResetSourceStep: '원본 방화벽 {{ .name }} 초기화 및 비활성화'
|
||||
FirewallResetSourceResult: '원본 방화벽을 초기화하고 데이터베이스 규칙 {{ .removed }}개를 삭제했습니다'
|
||||
FirewallCreateRuleExecutionFailed: '규칙 생성에 실패했습니다. 실행된 명령은 롤백하지 않습니다'
|
||||
FirewallInitializeChainsStep: '{{ .name }} 기본 체인 초기화 및 바인딩'
|
||||
FirewallRestoreRulesStep: '데이터베이스 규칙을 {{ .name }}에 복원'
|
||||
FirewallSyncWhitelistStep: '방화벽 포트 허용 목록 동기화'
|
||||
FirewallEnableForwardingStep: '포트 전달 체인 활성화 및 바인딩'
|
||||
FirewallRestoreForwardingRulesStep: '데이터베이스 포트 전달 규칙 복원'
|
||||
FirewallInspectDockerGuardStep: 'Docker 방화벽 백엔드 및 보호 정책 확인'
|
||||
FirewallInitializeDockerGuardStep: '{{ .name }} 포트 보호 체인 초기화 및 바인딩'
|
||||
FirewallPersistDockerGuardStep: 'Docker 포트 보호 상태 저장'
|
||||
ErrFirewallRuleScopeChange: "현재 방화벽에서는 규칙의 적용 범위(예: IPv4/IPv6 주소 패밀리)를 변경할 수 없습니다. 새 규칙을 생성하세요."
|
||||
FirewallWhitelistReleased: "{{ .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 }}"
|
||||
ErrFirewallRuleConflict: "UFW에 일치 조건이 같지만 동작이 다른 규칙이 이미 있습니다. 기존 규칙을 편집하세요."
|
||||
ErrForwardInterfaceNotFound: "전달 규칙에 지정된 네트워크 인터페이스 {{ .name }}이 존재하지 않습니다. 이름을 확인한 후 다시 시도하세요."
|
||||
|
||||
ErrSSHPublicKey: "유효한 SSH 공개 키를 입력하세요"
|
||||
ErrSSHPrivateKey: "SSH 개인 키 형식이 잘못되었거나 암호가 틀립니다. 암호화된 키에는 원래 암호가 필요합니다"
|
||||
ErrSSHKeyMismatch: "SSH 공개 키와 개인 키가 일치하지 않습니다"
|
||||
|
||||
# 가상 머신
|
||||
ErrVMNotFound: "가상 머신이 존재하지 않습니다"
|
||||
ErrVMAlreadyExists: "가상 머신이 이미 존재합니다"
|
||||
ErrVMCountLimit: "Professional 버전에서는 가상 머신을 최대 {{ .limit }}대까지 생성할 수 있습니다. 기존 가상 머신을 삭제하거나 Enterprise 버전으로 업그레이드한 후 다시 시도하세요."
|
||||
ErrVMImageAlreadyExists: "가상 머신 이미지 파일이 이미 존재합니다: {{ .detail }}"
|
||||
ErrVMStoragePathInUse: "스토리지 풀 경로가 이미 [{{ .name }}]에서 사용 중입니다"
|
||||
ErrVMHelperNotFound: "1panel-kvm을 찾을 수 없습니다"
|
||||
ErrVMBridgeManaged: "브리지 이름이 이미 libvirt 또는 Docker에서 관리되고 있습니다"
|
||||
ErrVMNetworkInUse: "네트워크가 사용 중이므로 삭제할 수 없습니다"
|
||||
ErrVMStorageInUse: "스토리지 풀이 사용 중이므로 삭제할 수 없습니다"
|
||||
ErrVMStorageNameCannotChange: "스토리지 풀 이름은 변경할 수 없습니다"
|
||||
ErrVMStoragePathCannotChange: "스토리지 풀 경로는 변경할 수 없습니다"
|
||||
ErrVMRunning: "가상 머신이 실행 중입니다"
|
||||
ErrVMLibvirtConnect: "libvirt 연결 실패: {{ .detail }}"
|
||||
ErrVMPermission: "가상 머신 작업 권한이 없습니다: {{ .detail }}"
|
||||
ErrVMNetworkNotFound: "가상 머신 네트워크가 존재하지 않습니다: {{ .detail }}"
|
||||
ErrVMInvalidPath: "가상 머신 경로가 유효하지 않습니다: {{ .detail }}"
|
||||
ErrVMInvalidInput: "가상 머신 매개변수가 유효하지 않습니다: {{ .detail }}"
|
||||
ErrVMCommandFailed: "가상 머신 명령 실행 실패: {{ .err }}"
|
||||
ErrVMDiskResizeBlocked: "디스크가 확장 조건을 충족하지 않거나 다른 장치에서 참조하고 있습니다. 디스크 상태를 확인하세요."
|
||||
ErrVMDiskReferenced: "다른 가상 머신 또는 장치가 이 가상 디스크를 사용 중이므로 현재 확장할 수 없습니다."
|
||||
ErrVMDiskReferenceCheck: "가상 디스크 참조를 확인할 수 없어 확장할 수 없습니다. 다른 가상 머신의 디스크 상태를 확인하세요."
|
||||
ErrVMSnapshotMetadataSync: "스냅샷은 복원되었지만 디스크 정보 동기화에 실패했습니다. 새로 고쳐 확인하세요. 스냅샷을 다시 복원할 필요는 없습니다."
|
||||
VMCreateTask: "가상 머신 [{{ .name }}] 생성"
|
||||
VMCreateFromTemplateTask: "템플릿에서 가상 머신 [{{ .name }}] 생성"
|
||||
VMUpdateTask: "가상 머신 [{{ .name }}] 업데이트"
|
||||
VMDiskResizeTask: "가상 머신 [{{ .name }}]의 디스크 [{{ .disk }}] 확장"
|
||||
VMRenameTask: "가상 머신 [{{ .name }}]을(를) [{{ .newName }}](으)로 이름 변경"
|
||||
VMDeleteTask: "가상 머신 [{{ .name }}] 삭제"
|
||||
VMTemplateCreateTask: "가상 머신 [{{ .name }}]을(를) 템플릿 [{{ .template }}](으)로 저장"
|
||||
VMTemplateDeleteTask: "가상 머신 템플릿 [{{ .name }}] 삭제"
|
||||
VMForceDeleteMetadataOnly: "1panel-kvm을 찾을 수 없어 가상 머신 [{{ .name }}] 메타데이터만 강제 삭제합니다"
|
||||
VMForceDeleteMetadataAfterLibvirtFailed: "libvirt에서 가상 머신 [{{ .name }}] 삭제 실패, 메타데이터 강제 삭제: {{ .err }}"
|
||||
VMForceDeleteDiskFailed: "가상 머신 디스크 파일 [{{ .path }}] 강제 삭제 실패: {{ .err }}"
|
||||
VMForceDeleteDiskSuccess: "가상 머신 디스크 파일 [{{ .path }}]을(를) 강제 삭제했습니다"
|
||||
VMSnapshotCreateTask: "가상 머신 [{{ .vm }}] 스냅샷 [{{ .snapshot }}] 생성"
|
||||
VMSnapshotCreateStep: "가상 머신 스냅샷 [{{ .snapshot }}] 생성"
|
||||
VMSnapshotRecoverTask: "가상 머신 [{{ .vm }}] 스냅샷 [{{ .snapshot }}] 복구"
|
||||
VMSnapshotRecoverStep: "가상 머신 스냅샷 [{{ .snapshot }}] 복구"
|
||||
VMSnapshotDeleteTask: "가상 머신 [{{ .vm }}] 스냅샷 [{{ .snapshot }}] 삭제"
|
||||
VMSnapshotDeleteStep: "가상 머신 스냅샷 [{{ .snapshot }}] 삭제"
|
||||
|
||||
+61
-16
@@ -639,6 +639,7 @@ CommonAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: {{ .msg }}. ເ
|
||||
NodeExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໂນດ {{ .num }} ແຫ່ງຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||
LicenseExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໃບອະນຸຍາດ {{ .num }} ສະບັບຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||
SSHAndPanelLoginAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ມີການເຂົ້າສູ່ລະບົບ {{ .name }} ທີ່ຜິດປົກກະຕິຈາກ {{ .loginIp }}. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||
CronJobSuccessAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ວຽກທີ່ຕັ້ງເວລາ {{ .name }} ສຳເລັດແລ້ວ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "ບໍ່ພົບອຸປະກອນ {{ .name }}"
|
||||
@@ -673,8 +674,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
|
||||
# Firewall
|
||||
ErrDockerIptablesChainUnavailable: 'ບໍ່ພົບ chain DOCKER-USER ຂອງ Docker. ກະລຸນາເລີ່ມ Docker ໃໝ່ແລ້ວລອງອີກຄັ້ງ'
|
||||
ErrDockerNftablesChainUnavailable: 'ບໍ່ພົບ chain ໄຟວໍ nftables IPv4 ຂອງ Docker. ກະລຸນາກວດສອບວ່າ Docker ລຸ້ນປັດຈຸບັນຮອງຮັບ backend ໄຟວໍ nftables, ເລີ່ມ Docker ໃໝ່ແລ້ວລອງອີກຄັ້ງ'
|
||||
ErrDockerForwardPolicyDrop: 'ນະໂຍບາຍເລີ່ມຕົ້ນ FORWARD ຂອງ {{ .family }} ແມ່ນ DROP. ກະລຸນາປັບແລ້ວລອງອີກຄັ້ງ'
|
||||
ErrUFWRuleAdopt: 'ຈັດການກົດ UFW ບໍ່ສຳເລັດ: {{ .detail }}. ຖ້າ UFW ລຸ້ນປັດຈຸບັນຕ່ຳກວ່າ 0.35, ກະລຸນາອັບເກຣດ UFW ແລ້ວລອງໃໝ່'
|
||||
ErrDockerForwardPolicyDrop: 'ເຊີບເວີນີ້ຈຳກັດການສົ່ງຕໍ່ເຄືອຂ່າຍ. ຍັງບໍ່ສາມາດເປີດໃຊ້ການປ້ອງກັນພອດຄອນເທນເນີໄດ້ໃນຕອນນີ້.'
|
||||
Firewall: 'ໄຟວອລ'
|
||||
FirewallTaskHost: 'ໄຟວໍໂຮສ'
|
||||
FirewallTaskForwarding: 'ການສົ່ງຕໍ່ພອດ'
|
||||
@@ -688,25 +688,70 @@ FirewallCreateRulesStep: 'ສ້າງກົດໄຟວໍ'
|
||||
FirewallCreateRulesResult: 'ຜົນການສ້າງ: ສຳເລັດ {{ .succeeded }}, ລົ້ມເຫຼວ {{ .failed }}, ບໍ່ໄດ້ດຳເນີນການ {{ .skipped }}'
|
||||
FirewallRuleOperationResult: 'ຜົນການດຳເນີນການ: ສຳເລັດ {{ .succeeded }} ລາຍການ, ລົ້ມເຫຼວ {{ .failed }} ລາຍການ'
|
||||
FirewallCreateRuleSkipped: 'ບໍ່ໄດ້ດຳເນີນການ'
|
||||
FirewallCreateBatchStep: 'ສົ່ງ {{ .count }} ກົດເປັນຊຸດໄປຫາ {{ .backend }}'
|
||||
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; ແປງເປັນ {{ .count }} ກົດ'
|
||||
FirewallCreateRuleExecutionFailed: 'ການສ້າງກົດລົ້ມເຫຼວ. ບໍ່ໄດ້ບັນທຶກໃນຖານຂໍ້ມູນ ແລະ ບໍ່ໄດ້ຍ້ອນກັບຄຳສັ່ງທີ່ດຳເນີນການແລ້ວ'
|
||||
FirewallCreateRulePersistenceFailed: 'ສ້າງກົດແລ້ວ ແຕ່ບັນທຶກຂໍ້ມູນການຈັດການບໍ່ສຳເລັດ'
|
||||
FirewallAdoptRulePersistenceFailed: 'ຄຳສັ່ງນຳກົດເຂົ້າການຈັດການໄດ້ດຳເນີນການແລ້ວ ແຕ່ບໍ່ສາມາດບັນທຶກຂໍ້ມູນການຈັດການໄດ້'
|
||||
FirewallSyncOperationsResult: 'ການດຳເນີນການຊິງຄ໌: ລຶບ {{ .removed }}, ສ້າງ {{ .created }}, ລົ້ມເຫຼວ {{ .failed }}, ບໍ່ໄດ້ດຳເນີນການ {{ .skipped }}, ກົງກັນແລ້ວ {{ .unchanged }} (ບໍ່ຕ້ອງປ່ຽນແປງ)'
|
||||
FirewallSyncRuleUnchanged: 'ກົງກັນແລ້ວ; ບໍ່ຕ້ອງປ່ຽນແປງ'
|
||||
FirewallSyncStep: 'ຊິງຄ໌ກົດໄປຫາ {{ .name }}'
|
||||
FirewallSyncFailed: 'ຊິງຄ໌ກົດໄຟວອລ {{ .failed }} ລາຍການບໍ່ສຳເລັດ'
|
||||
FirewallResetSourceStep: 'ຣີເຊັດ ແລະ ປິດໃຊ້ໄຟວອລຕົ້ນທາງ {{ .name }}'
|
||||
FirewallResetSourceResult: 'ຣີເຊັດໄຟວອລຕົ້ນທາງແລ້ວ ແລະ ລຶບກົດຖານຂໍ້ມູນ {{ .removed }} ລາຍການ'
|
||||
FirewallCreateRuleExecutionFailed: 'ການສ້າງກົດລົ້ມເຫຼວ. ຄຳສັ່ງທີ່ດຳເນີນການແລ້ວບໍ່ໄດ້ຖືກຍ້ອນກັບ'
|
||||
FirewallInitializeChainsStep: 'ເລີ່ມຕົ້ນ ແລະ ຜູກ chain ພື້ນຖານ {{ .name }}'
|
||||
FirewallRestoreRulesStep: 'ກູ້ຄືນກົດຖານຂໍ້ມູນໄປຫາ {{ .name }}'
|
||||
FirewallSyncWhitelistStep: 'ຊິງຄ໌ລາຍການພອດໄຟວໍທີ່ອະນຸຍາດ'
|
||||
FirewallEnableForwardingStep: 'ເປີດໃຊ້ ແລະ ຜູກ chain ສົ່ງຕໍ່ພອດ'
|
||||
FirewallRestoreForwardingRulesStep: 'ກູ້ຄືນກົດສົ່ງຕໍ່ພອດຈາກຖານຂໍ້ມູນ'
|
||||
FirewallInspectDockerGuardStep: 'ກວດສອບ backend firewall Docker ແລະ ນະໂຍບາຍ'
|
||||
FirewallInitializeDockerGuardStep: 'ເລີ່ມຕົ້ນ ແລະ ຜູກ chain ປ້ອງກັນພອດ {{ .name }}'
|
||||
FirewallPersistDockerGuardStep: 'ບັນທຶກສະຖານະປ້ອງກັນພອດ Docker'
|
||||
ErrFirewallRuleScopeChange: "ໄຟວໍປັດຈຸບັນບໍ່ຮອງຮັບການປ່ຽນຂອບເຂດຂອງກົດ (ເຊັ່ນ ຕະກູນທີ່ຢູ່ IPv4/IPv6). ກະລຸນາສ້າງກົດໃໝ່."
|
||||
FirewallWhitelistReleased: "{{ .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 }}"
|
||||
ErrFirewallRuleConflict: "ມີກົດ UFW ທີ່ມີເງື່ອນໄຂກົງກັນແຕ່ການດຳເນີນການຕ່າງກັນແລ້ວ. ກະລຸນາແກ້ໄຂກົດທີ່ມີຢູ່."
|
||||
ErrForwardInterfaceNotFound: "ບໍ່ພົບອິນເຕີເຟດເຄືອຂ່າຍ {{ .name }} ທີ່ລະບຸໃນກົດສົ່ງຕໍ່. ກະລຸນາກວດສອບຊື່ ແລ້ວລອງໃໝ່."
|
||||
|
||||
ErrSSHPublicKey: "ກະລຸນາປ້ອນຄີສາທາລະນະ SSH ທີ່ຖືກຕ້ອງ"
|
||||
ErrSSHPrivateKey: "ຄີສ່ວນຕົວ SSH ບໍ່ຖືກຕ້ອງ ຫຼື ລະຫັດຜ່ານຜິດ; ຄີທີ່ເຂົ້າລະຫັດຕ້ອງໃຊ້ລະຫັດຜ່ານເດີມ"
|
||||
ErrSSHKeyMismatch: "ຄີສາທາລະນະ ແລະ ຄີສ່ວນຕົວ SSH ບໍ່ກົງກັນ"
|
||||
|
||||
# virtual machine
|
||||
ErrVMNotFound: "ບໍ່ມີເຄື່ອງສະເໝືອນ"
|
||||
ErrVMAlreadyExists: "ມີເຄື່ອງສະເໝືອນຢູ່ແລ້ວ"
|
||||
ErrVMCountLimit: "ລຸ້ນ Professional ສາມາດສ້າງເຄື່ອງສະເໝືອນໄດ້ສູງສຸດ {{ .limit }} ເຄື່ອງ. ກະລຸນາລຶບເຄື່ອງສະເໝືອນທີ່ມີຢູ່ ຫຼື ອັບເກຣດເປັນລຸ້ນ Enterprise ແລ້ວລອງອີກຄັ້ງ."
|
||||
ErrVMImageAlreadyExists: "ມີອິມເມຈເຄື່ອງສະເໝືອນຢູ່ແລ້ວ: {{ .detail }}"
|
||||
ErrVMStoragePathInUse: "ເສັ້ນທາງພູນຈັດເກັບຖືກໃຊ້ໂດຍ [{{ .name }}] ແລ້ວ"
|
||||
ErrVMHelperNotFound: "ບໍ່ພົບ 1panel-kvm"
|
||||
ErrVMBridgeManaged: "ຊື່ບຣິດຈ໌ຖືກຈັດການໂດຍ libvirt ຫຼື Docker ແລ້ວ"
|
||||
ErrVMNetworkInUse: "ເຄືອຂ່າຍກຳລັງຖືກໃຊ້ງານ ແລະ ບໍ່ສາມາດລຶບໄດ້"
|
||||
ErrVMStorageInUse: "ພູນຈັດເກັບກຳລັງຖືກໃຊ້ງານ ແລະ ບໍ່ສາມາດລຶບໄດ້"
|
||||
ErrVMStorageNameCannotChange: "ບໍ່ສາມາດປ່ຽນຊື່ພູນຈັດເກັບໄດ້"
|
||||
ErrVMStoragePathCannotChange: "ບໍ່ສາມາດປ່ຽນເສັ້ນທາງພູນຈັດເກັບໄດ້"
|
||||
ErrVMRunning: "ເຄື່ອງສະເໝືອນກຳລັງເຮັດວຽກ"
|
||||
ErrVMLibvirtConnect: "ເຊື່ອມຕໍ່ກັບ libvirt ບໍ່ສຳເລັດ: {{ .detail }}"
|
||||
ErrVMPermission: "ບໍ່ມີສິດດຳເນີນການກັບເຄື່ອງສະເໝືອນ: {{ .detail }}"
|
||||
ErrVMNetworkNotFound: "ບໍ່ມີເຄືອຂ່າຍເຄື່ອງສະເໝືອນ: {{ .detail }}"
|
||||
ErrVMInvalidPath: "ເສັ້ນທາງເຄື່ອງສະເໝືອນບໍ່ຖືກຕ້ອງ: {{ .detail }}"
|
||||
ErrVMInvalidInput: "ພາລາມິເຕີເຄື່ອງສະເໝືອນບໍ່ຖືກຕ້ອງ: {{ .detail }}"
|
||||
ErrVMCommandFailed: "ຄຳສັ່ງເຄື່ອງສະເໝືອນລົ້ມເຫຼວ: {{ .err }}"
|
||||
ErrVMDiskResizeBlocked: "ດິສກ໌ບໍ່ຕອບສະໜອງເງື່ອນໄຂການຂະຫຍາຍ ຫຼື ຖືກອ້າງອີງໂດຍອຸປະກອນອື່ນ. ກະລຸນາກວດສອບສະຖານະດິສກ໌."
|
||||
ErrVMDiskReferenced: "ດິສກ໌ເສມືອນນີ້ຖືກໃຊ້ໂດຍເຄື່ອງເສມືອນ ຫຼື ອຸປະກອນອື່ນ; ຍັງບໍ່ຮອງຮັບການຂະຫຍາຍ."
|
||||
ErrVMDiskReferenceCheck: "ບໍ່ສາມາດກວດສອບການອ້າງອີງດິສກ໌ເສມືອນໄດ້. ຍັງຂະຫຍາຍບໍ່ໄດ້; ກະລຸນາກວດສອບດິສກ໌ຂອງເຄື່ອງເສມືອນອື່ນ."
|
||||
ErrVMSnapshotMetadataSync: "ກູ້ຄືນສະແນັບຊັອດແລ້ວ ແຕ່ຊິງຂໍ້ມູນດິສກ໌ບໍ່ສຳເລັດ. ກະລຸນາໂຫຼດໃໝ່ ແລະ ກວດສອບ; ບໍ່ຈຳເປັນຕ້ອງກູ້ຄືນສະແນັບຊັອດອີກ."
|
||||
VMCreateTask: "ສ້າງເຄື່ອງສະເໝືອນ [{{ .name }}]"
|
||||
VMCreateFromTemplateTask: "ສ້າງເຄື່ອງສະເໝືອນ [{{ .name }}] ຈາກແມ່ແບບ"
|
||||
VMUpdateTask: "ອັບເດດເຄື່ອງສະເໝືອນ [{{ .name }}]"
|
||||
VMDiskResizeTask: "ຂະຫຍາຍດິສກ໌ [{{ .disk }}] ຂອງເຄື່ອງເສມືອນ [{{ .name }}]"
|
||||
VMRenameTask: "ປ່ຽນຊື່ເຄື່ອງສະເໝືອນ [{{ .name }}] ເປັນ [{{ .newName }}]"
|
||||
VMDeleteTask: "ລຶບເຄື່ອງສະເໝືອນ [{{ .name }}]"
|
||||
VMTemplateCreateTask: "ບັນທຶກເຄື່ອງສະເໝືອນ [{{ .name }}] ເປັນແມ່ແບບ [{{ .template }}]"
|
||||
VMTemplateDeleteTask: "ລຶບແມ່ແບບເຄື່ອງສະເໝືອນ [{{ .name }}]"
|
||||
VMForceDeleteMetadataOnly: "ບໍ່ພົບ 1panel-kvm, ຈະບັງຄັບລຶບສະເພາະເມຕາດາຕາຂອງເຄື່ອງສະເໝືອນ [{{ .name }}]"
|
||||
VMForceDeleteMetadataAfterLibvirtFailed: "ລຶບເຄື່ອງສະເໝືອນ [{{ .name }}] ຈາກ libvirt ບໍ່ສຳເລັດ, ຈະບັງຄັບລຶບເມຕາດາຕາ: {{ .err }}"
|
||||
VMForceDeleteDiskFailed: "ບັງຄັບລຶບໄຟລ໌ດິສກ໌ເຄື່ອງສະເໝືອນ [{{ .path }}] ບໍ່ສຳເລັດ: {{ .err }}"
|
||||
VMForceDeleteDiskSuccess: "ບັງຄັບລຶບໄຟລ໌ດິສກ໌ເຄື່ອງສະເໝືອນ [{{ .path }}] ສຳເລັດແລ້ວ"
|
||||
VMSnapshotCreateTask: "ສ້າງສະແນັບຊັອດ [{{ .snapshot }}] ໃຫ້ເຄື່ອງສະເໝືອນ [{{ .vm }}]"
|
||||
VMSnapshotCreateStep: "ສ້າງສະແນັບຊັອດເຄື່ອງສະເໝືອນ [{{ .snapshot }}]"
|
||||
VMSnapshotRecoverTask: "ກູ້ຄືນສະແນັບຊັອດ [{{ .snapshot }}] ໃຫ້ເຄື່ອງສະເໝືອນ [{{ .vm }}]"
|
||||
VMSnapshotRecoverStep: "ກູ້ຄືນສະແນັບຊັອດເຄື່ອງສະເໝືອນ [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "ລຶບສະແນັບຊັອດ [{{ .snapshot }}] ຂອງເຄື່ອງສະເໝືອນ [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "ລຶບສະແນັບຊັອດເຄື່ອງສະເໝືອນ [{{ .snapshot }}]"
|
||||
|
||||
+61
-16
@@ -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.'
|
||||
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.'
|
||||
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: tugas berjadual {{ .name }} berjaya diselesaikan. Log masuk untuk melihat butiran.'
|
||||
|
||||
# cakera
|
||||
DeviceNotFound: 'Peranti {{ .name }} tidak ditemui'
|
||||
@@ -682,8 +683,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
|
||||
# Firewall
|
||||
ErrDockerIptablesChainUnavailable: 'Rantaian DOCKER-USER Docker tidak ditemui. Mulakan semula Docker dan cuba lagi'
|
||||
ErrDockerNftablesChainUnavailable: 'Rantaian firewall IPv4 nftables Docker tidak ditemui. Pastikan versi Docker semasa menyokong bahagian belakang firewall nftables, mulakan semula Docker dan cuba lagi'
|
||||
ErrDockerForwardPolicyDrop: 'Dasar lalai FORWARD {{ .family }} ialah DROP. Laraskan dasar dan cuba lagi'
|
||||
ErrUFWRuleAdopt: 'Gagal mengurus peraturan UFW: {{ .detail }}. Jika versi UFW semasa lebih lama daripada 0.35, tingkatkan UFW dan cuba lagi'
|
||||
ErrDockerForwardPolicyDrop: 'Pelayan ini mengehadkan pemajuan rangkaian. Perlindungan port kontena tidak dapat diaktifkan buat masa ini.'
|
||||
Firewall: 'Firewall'
|
||||
FirewallTaskHost: 'Tembok api hos'
|
||||
FirewallTaskForwarding: 'Pemajuan port'
|
||||
@@ -697,25 +697,70 @@ FirewallCreateRulesStep: 'Cipta peraturan tembok api'
|
||||
FirewallCreateRulesResult: 'Hasil penciptaan: {{ .succeeded }} berjaya, {{ .failed }} gagal, {{ .skipped }} tidak dilaksanakan'
|
||||
FirewallRuleOperationResult: 'Hasil operasi: {{ .succeeded }} berjaya, {{ .failed }} gagal'
|
||||
FirewallCreateRuleSkipped: 'Tidak dilaksanakan'
|
||||
FirewallCreateBatchStep: 'Hantar {{ .count }} peraturan secara kelompok ke {{ .backend }}'
|
||||
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; ditukar kepada {{ .count }} peraturan'
|
||||
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'
|
||||
FirewallAdoptRulePersistenceFailed: 'Arahan pengambilalihan peraturan telah dilaksanakan, tetapi rekod pengurusannya tidak dapat disimpan'
|
||||
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 }}'
|
||||
FirewallSyncFailed: '{{ .failed }} peraturan firewall gagal disegerakkan'
|
||||
FirewallResetSourceStep: 'Tetapkan semula dan nyahdayakan firewall sumber {{ .name }}'
|
||||
FirewallResetSourceResult: 'Firewall sumber ditetapkan semula; {{ .removed }} peraturan pangkalan data dipadamkan'
|
||||
FirewallCreateRuleExecutionFailed: 'Penciptaan peraturan gagal. Arahan yang dilaksanakan tidak dibatalkan'
|
||||
FirewallInitializeChainsStep: 'Mulakan dan ikat rantai asas {{ .name }}'
|
||||
FirewallRestoreRulesStep: 'Pulihkan peraturan pangkalan data ke {{ .name }}'
|
||||
FirewallSyncWhitelistStep: 'Segerakkan senarai putih port firewall'
|
||||
FirewallEnableForwardingStep: 'Aktifkan dan ikat rantai pemajuan port'
|
||||
FirewallRestoreForwardingRulesStep: 'Pulihkan peraturan pemajuan port pangkalan data'
|
||||
FirewallInspectDockerGuardStep: 'Periksa backend firewall Docker dan polisi'
|
||||
FirewallInitializeDockerGuardStep: 'Mulakan dan ikat rantai perlindungan port {{ .name }}'
|
||||
FirewallPersistDockerGuardStep: 'Simpan status perlindungan port Docker'
|
||||
ErrFirewallRuleScopeChange: "Tembok api semasa tidak menyokong perubahan skop peraturan (seperti keluarga alamat IPv4/IPv6). Sila cipta peraturan baharu."
|
||||
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"
|
||||
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 }}"
|
||||
ErrFirewallRuleConflict: "Peraturan UFW dengan syarat padanan yang sama tetapi tindakan berbeza sudah wujud. Sila edit peraturan sedia ada."
|
||||
ErrForwardInterfaceNotFound: "Antara muka rangkaian {{ .name }} yang dinyatakan dalam peraturan pemajuan tidak wujud. Semak namanya dan cuba lagi."
|
||||
|
||||
ErrSSHPublicKey: "Masukkan kunci awam SSH yang sah"
|
||||
ErrSSHPrivateKey: "Kunci peribadi SSH tidak sah atau frasa laluan salah; kunci yang disulitkan memerlukan frasa laluan asal"
|
||||
ErrSSHKeyMismatch: "Kunci awam dan peribadi SSH tidak sepadan"
|
||||
|
||||
# mesin maya
|
||||
ErrVMNotFound: "Mesin maya tidak wujud"
|
||||
ErrVMAlreadyExists: "Mesin maya sudah wujud"
|
||||
ErrVMCountLimit: "Edisi Professional membenarkan penciptaan sehingga {{ .limit }} mesin maya. Padamkan mesin maya sedia ada atau naik taraf kepada Enterprise dan cuba lagi."
|
||||
ErrVMImageAlreadyExists: "Fail imej mesin maya sudah wujud: {{ .detail }}"
|
||||
ErrVMStoragePathInUse: "Laluan kolam storan sudah digunakan oleh [{{ .name }}]"
|
||||
ErrVMHelperNotFound: "1panel-kvm tidak ditemui"
|
||||
ErrVMBridgeManaged: "Nama bridge telah diurus oleh libvirt atau Docker"
|
||||
ErrVMNetworkInUse: "Rangkaian sedang digunakan dan tidak boleh dipadamkan"
|
||||
ErrVMStorageInUse: "Kumpulan storan sedang digunakan dan tidak boleh dipadamkan"
|
||||
ErrVMStorageNameCannotChange: "Nama kumpulan storan tidak boleh diubah"
|
||||
ErrVMStoragePathCannotChange: "Laluan kumpulan storan tidak boleh diubah"
|
||||
ErrVMRunning: "Mesin maya sedang berjalan"
|
||||
ErrVMLibvirtConnect: "Gagal menyambung ke libvirt: {{ .detail }}"
|
||||
ErrVMPermission: "Tiada kebenaran untuk mengendalikan mesin maya: {{ .detail }}"
|
||||
ErrVMNetworkNotFound: "Rangkaian mesin maya tidak wujud: {{ .detail }}"
|
||||
ErrVMInvalidPath: "Laluan mesin maya tidak sah: {{ .detail }}"
|
||||
ErrVMInvalidInput: "Parameter mesin maya tidak sah: {{ .detail }}"
|
||||
ErrVMCommandFailed: "Arahan mesin maya gagal: {{ .err }}"
|
||||
ErrVMDiskResizeBlocked: "Cakera tidak memenuhi syarat pembesaran atau dirujuk oleh peranti lain. Semak status cakera."
|
||||
ErrVMDiskReferenced: "Cakera maya ini digunakan oleh mesin maya atau peranti lain; pembesaran belum disokong."
|
||||
ErrVMDiskReferenceCheck: "Rujukan cakera maya tidak dapat disahkan. Pembesaran tidak tersedia; semak cakera mesin maya lain."
|
||||
ErrVMSnapshotMetadataSync: "Syot kilat telah dipulihkan, tetapi maklumat cakera gagal disegerakkan. Muat semula dan semak; tidak perlu memulihkan syot kilat sekali lagi."
|
||||
VMCreateTask: "Cipta mesin maya [{{ .name }}]"
|
||||
VMCreateFromTemplateTask: "Cipta mesin maya [{{ .name }}] daripada templat"
|
||||
VMUpdateTask: "Kemas kini mesin maya [{{ .name }}]"
|
||||
VMDiskResizeTask: "Besarkan cakera [{{ .disk }}] mesin maya [{{ .name }}]"
|
||||
VMRenameTask: "Namakan semula mesin maya [{{ .name }}] kepada [{{ .newName }}]"
|
||||
VMDeleteTask: "Padam mesin maya [{{ .name }}]"
|
||||
VMTemplateCreateTask: "Simpan mesin maya [{{ .name }}] sebagai templat [{{ .template }}]"
|
||||
VMTemplateDeleteTask: "Padam templat mesin maya [{{ .name }}]"
|
||||
VMForceDeleteMetadataOnly: "1panel-kvm tidak ditemui, memadam metadata mesin maya [{{ .name }}] sahaja secara paksa"
|
||||
VMForceDeleteMetadataAfterLibvirtFailed: "Gagal memadam mesin maya [{{ .name }}] daripada libvirt, memadam metadata secara paksa: {{ .err }}"
|
||||
VMForceDeleteDiskFailed: "Gagal memadam fail cakera mesin maya [{{ .path }}] secara paksa: {{ .err }}"
|
||||
VMForceDeleteDiskSuccess: "Fail cakera mesin maya [{{ .path }}] telah dipadam secara paksa"
|
||||
VMSnapshotCreateTask: "Cipta syot kilat [{{ .snapshot }}] untuk mesin maya [{{ .vm }}]"
|
||||
VMSnapshotCreateStep: "Cipta syot kilat mesin maya [{{ .snapshot }}]"
|
||||
VMSnapshotRecoverTask: "Pulihkan syot kilat [{{ .snapshot }}] untuk mesin maya [{{ .vm }}]"
|
||||
VMSnapshotRecoverStep: "Pulihkan syot kilat mesin maya [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "Padam syot kilat [{{ .snapshot }}] untuk mesin maya [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "Padam syot kilat mesin maya [{{ .snapshot }}]"
|
||||
|
||||
+61
-16
@@ -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.'
|
||||
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.'
|
||||
CronJobSuccessAlert: 'Painel {{ .node }}{{ .ip }}: a tarefa agendada {{ .name }} foi concluída com sucesso. Faça login para ver os detalhes.'
|
||||
|
||||
# disco
|
||||
DeviceNotFound: 'Dispositivo {{ .name }} não encontrado'
|
||||
@@ -682,8 +683,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
|
||||
# Firewall
|
||||
ErrDockerIptablesChainUnavailable: 'A cadeia DOCKER-USER do Docker não foi encontrada. Reinicie o Docker e tente novamente'
|
||||
ErrDockerNftablesChainUnavailable: 'A cadeia de firewall IPv4 do nftables do Docker não foi encontrada. Confirme se a versão atual do Docker oferece suporte ao backend de firewall nftables, reinicie o Docker e tente novamente'
|
||||
ErrDockerForwardPolicyDrop: 'A política padrão de FORWARD para {{ .family }} é DROP. Ajuste-a e tente novamente'
|
||||
ErrUFWRuleAdopt: 'Falha ao gerenciar a regra UFW: {{ .detail }}. Se a versão atual do UFW for anterior à 0.35, atualize o UFW e tente novamente'
|
||||
ErrDockerForwardPolicyDrop: 'Este servidor restringe o encaminhamento de rede. Não é possível ativar a proteção de portas de contêineres no momento.'
|
||||
Firewall: 'Firewall'
|
||||
FirewallTaskHost: 'Firewall do host'
|
||||
FirewallTaskForwarding: 'Encaminhamento de portas'
|
||||
@@ -697,25 +697,70 @@ FirewallCreateRulesStep: 'Criar regras de firewall'
|
||||
FirewallCreateRulesResult: 'Resultado da criação: {{ .succeeded }} com sucesso, {{ .failed }} com falha, {{ .skipped }} não executadas'
|
||||
FirewallRuleOperationResult: 'Resultado da operação: {{ .succeeded }} com sucesso, {{ .failed }} com falha'
|
||||
FirewallCreateRuleSkipped: 'Não executada'
|
||||
FirewallCreateBatchStep: 'Enviar {{ .count }} regras em lote para {{ .backend }}'
|
||||
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; convertido em {{ .count }} regras'
|
||||
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'
|
||||
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, {{ .unchanged }} já correspondem (sem alterações necessárias)'
|
||||
FirewallSyncRuleUnchanged: 'Já corresponde; nenhuma alteração necessária'
|
||||
FirewallSyncStep: 'Sincronizar regras com {{ .name }}'
|
||||
FirewallSyncFailed: '{{ .failed }} regras de firewall falharam na sincronização'
|
||||
FirewallResetSourceStep: 'Redefinir e desativar o firewall de origem {{ .name }}'
|
||||
FirewallResetSourceResult: 'Firewall de origem redefinido; {{ .removed }} regras do banco de dados foram excluídas'
|
||||
FirewallCreateRuleExecutionFailed: 'Falha ao criar a regra. Os comandos executados não foram revertidos'
|
||||
FirewallInitializeChainsStep: 'Inicializar e vincular as cadeias base do {{ .name }}'
|
||||
FirewallRestoreRulesStep: 'Restaurar regras do banco de dados para {{ .name }}'
|
||||
FirewallSyncWhitelistStep: 'Sincronizar a lista de portas permitidas do firewall'
|
||||
FirewallEnableForwardingStep: 'Habilitar e vincular as cadeias de encaminhamento de portas'
|
||||
FirewallRestoreForwardingRulesStep: 'Restaurar regras de encaminhamento de portas do banco de dados'
|
||||
FirewallInspectDockerGuardStep: 'Inspecionar o backend do firewall Docker e as políticas'
|
||||
FirewallInitializeDockerGuardStep: 'Inicializar e vincular as cadeias de proteção de portas do {{ .name }}'
|
||||
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."
|
||||
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"
|
||||
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 }}"
|
||||
ErrFirewallRuleConflict: "Já existe uma regra UFW com as mesmas condições e uma ação diferente. Edite a regra existente."
|
||||
ErrForwardInterfaceNotFound: "A interface de rede {{ .name }} especificada na regra de encaminhamento não existe. Verifique o nome e tente novamente."
|
||||
|
||||
ErrSSHPublicKey: "Informe uma chave pública SSH válida"
|
||||
ErrSSHPrivateKey: "Chave privada SSH inválida ou senha incorreta; chaves criptografadas exigem a senha original"
|
||||
ErrSSHKeyMismatch: "As chaves pública e privada SSH não correspondem"
|
||||
|
||||
# máquina virtual
|
||||
ErrVMNotFound: "Máquina virtual não existe"
|
||||
ErrVMAlreadyExists: "Máquina virtual já existe"
|
||||
ErrVMCountLimit: "A edição Professional permite criar até {{ .limit }} máquinas virtuais. Exclua uma máquina virtual existente ou atualize para Enterprise e tente novamente."
|
||||
ErrVMImageAlreadyExists: "A imagem da máquina virtual já existe: {{ .detail }}"
|
||||
ErrVMStoragePathInUse: "O caminho do pool de armazenamento já é usado por [{{ .name }}]"
|
||||
ErrVMHelperNotFound: "1panel-kvm não encontrado"
|
||||
ErrVMBridgeManaged: "O nome da bridge já é gerenciado pelo libvirt ou Docker"
|
||||
ErrVMNetworkInUse: "A rede está em uso e não pode ser excluída"
|
||||
ErrVMStorageInUse: "O pool de armazenamento está em uso e não pode ser excluído"
|
||||
ErrVMStorageNameCannotChange: "O nome do pool de armazenamento não pode ser alterado"
|
||||
ErrVMStoragePathCannotChange: "O caminho do pool de armazenamento não pode ser alterado"
|
||||
ErrVMRunning: "Máquina virtual está em execução"
|
||||
ErrVMLibvirtConnect: "Falha ao conectar ao libvirt: {{ .detail }}"
|
||||
ErrVMPermission: "Sem permissão para operar a máquina virtual: {{ .detail }}"
|
||||
ErrVMNetworkNotFound: "Rede da máquina virtual não existe: {{ .detail }}"
|
||||
ErrVMInvalidPath: "Caminho da máquina virtual inválido: {{ .detail }}"
|
||||
ErrVMInvalidInput: "Parâmetro da máquina virtual inválido: {{ .detail }}"
|
||||
ErrVMCommandFailed: "Falha no comando da máquina virtual: {{ .err }}"
|
||||
ErrVMDiskResizeBlocked: "O disco não atende aos requisitos de expansão ou é referenciado por outro dispositivo. Verifique seu estado."
|
||||
ErrVMDiskReferenced: "Este disco virtual é usado por outra máquina virtual ou dispositivo e ainda não pode ser expandido."
|
||||
ErrVMDiskReferenceCheck: "Não foi possível verificar as referências ao disco virtual. A expansão está indisponível; verifique os discos das outras máquinas virtuais."
|
||||
ErrVMSnapshotMetadataSync: "O snapshot foi restaurado, mas a sincronização dos dados do disco falhou. Atualize e verifique; não restaure o snapshot novamente."
|
||||
VMCreateTask: "Criar máquina virtual [{{ .name }}]"
|
||||
VMCreateFromTemplateTask: "Criar máquina virtual [{{ .name }}] a partir do modelo"
|
||||
VMUpdateTask: "Atualizar máquina virtual [{{ .name }}]"
|
||||
VMDiskResizeTask: "Expandir disco [{{ .disk }}] da máquina virtual [{{ .name }}]"
|
||||
VMRenameTask: "Renomear máquina virtual [{{ .name }}] para [{{ .newName }}]"
|
||||
VMDeleteTask: "Excluir máquina virtual [{{ .name }}]"
|
||||
VMTemplateCreateTask: "Salvar máquina virtual [{{ .name }}] como modelo [{{ .template }}]"
|
||||
VMTemplateDeleteTask: "Excluir modelo de máquina virtual [{{ .name }}]"
|
||||
VMForceDeleteMetadataOnly: "1panel-kvm não encontrado, excluindo à força apenas os metadados da máquina virtual [{{ .name }}]"
|
||||
VMForceDeleteMetadataAfterLibvirtFailed: "Falha ao excluir a máquina virtual [{{ .name }}] do libvirt, excluindo metadados à força: {{ .err }}"
|
||||
VMForceDeleteDiskFailed: "Falha ao excluir à força o arquivo de disco da máquina virtual [{{ .path }}]: {{ .err }}"
|
||||
VMForceDeleteDiskSuccess: "Arquivo de disco da máquina virtual [{{ .path }}] excluído à força"
|
||||
VMSnapshotCreateTask: "Criar snapshot [{{ .snapshot }}] para a máquina virtual [{{ .vm }}]"
|
||||
VMSnapshotCreateStep: "Criar snapshot da máquina virtual [{{ .snapshot }}]"
|
||||
VMSnapshotRecoverTask: "Restaurar snapshot [{{ .snapshot }}] da máquina virtual [{{ .vm }}]"
|
||||
VMSnapshotRecoverStep: "Restaurar snapshot da máquina virtual [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "Excluir snapshot [{{ .snapshot }}] da máquina virtual [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "Excluir snapshot da máquina virtual [{{ .snapshot }}]"
|
||||
|
||||
+61
-16
@@ -649,6 +649,7 @@ CommonAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .msg }}. Войдит
|
||||
NodeExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} узлов работают с ошибками. Войдите в панель для получения деталей.'
|
||||
LicenseExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} лицензий имеют ошибки. Войдите в панель для получения деталей.'
|
||||
SSHAndPanelLoginAlert: 'Ваш панель {{ .node }}{{ .ip }}, вход {{ .name }} с адреса {{ .loginIp }} является аномальным. Войдите в панель для получения деталей.'
|
||||
CronJobSuccessAlert: 'Панель {{ .node }}{{ .ip }}: запланированная задача {{ .name }} успешно завершена. Войдите для просмотра подробностей.'
|
||||
|
||||
# диск
|
||||
DeviceNotFound: 'Устройство {{ .name }} не найдено'
|
||||
@@ -682,8 +683,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
|
||||
# Firewall
|
||||
ErrDockerIptablesChainUnavailable: 'Цепочка Docker DOCKER-USER не найдена. Перезапустите Docker и повторите попытку'
|
||||
ErrDockerNftablesChainUnavailable: 'Цепочка IPv4 брандмауэра nftables Docker не найдена. Убедитесь, что текущая версия Docker поддерживает бэкенд брандмауэра nftables, перезапустите Docker и повторите попытку'
|
||||
ErrDockerForwardPolicyDrop: 'Политика FORWARD по умолчанию для {{ .family }} — DROP. Измените её и повторите попытку'
|
||||
ErrUFWRuleAdopt: 'Не удалось принять правило UFW под управление: {{ .detail }}. Если текущая версия UFW ниже 0.35, обновите UFW и повторите попытку'
|
||||
ErrDockerForwardPolicyDrop: 'На этом сервере ограничена пересылка сетевого трафика. Сейчас невозможно включить защиту портов контейнеров.'
|
||||
Firewall: 'Межсетевой экран'
|
||||
FirewallTaskHost: 'Межсетевой экран хоста'
|
||||
FirewallTaskForwarding: 'Перенаправление портов'
|
||||
@@ -697,25 +697,70 @@ FirewallCreateRulesStep: 'Создание правил межсетевого
|
||||
FirewallCreateRulesResult: 'Результат создания: успешно — {{ .succeeded }}, ошибок — {{ .failed }}, не выполнено — {{ .skipped }}'
|
||||
FirewallRuleOperationResult: 'Результат операции: успешно — {{ .succeeded }}, ошибок — {{ .failed }}'
|
||||
FirewallCreateRuleSkipped: 'Не выполнено'
|
||||
FirewallCreateBatchStep: 'Отправка {{ .count }} правил одним пакетом в {{ .backend }}'
|
||||
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; преобразовано в {{ .count }} правил'
|
||||
FirewallCreateRuleExecutionFailed: 'Не удалось создать правило. Запись в базе данных не сохранена, выполненные команды не отменены'
|
||||
FirewallCreateRulePersistenceFailed: 'Правило создано, но не удалось сохранить запись управления'
|
||||
FirewallAdoptRulePersistenceFailed: 'Команды принятия правила под управление выполнены, но сохранить запись управления не удалось'
|
||||
FirewallSyncOperationsResult: 'Операции синхронизации: удалено {{ .removed }}, создано {{ .created }}, ошибок {{ .failed }}, не выполнено {{ .skipped }}, уже совпадают (изменения не нужны): {{ .unchanged }}'
|
||||
FirewallSyncRuleUnchanged: 'Уже совпадает; изменения не нужны'
|
||||
FirewallSyncStep: 'Синхронизировать правила с {{ .name }}'
|
||||
FirewallSyncFailed: 'Не удалось синхронизировать правил межсетевого экрана: {{ .failed }}'
|
||||
FirewallResetSourceStep: 'Сбросить и отключить исходный межсетевой экран {{ .name }}'
|
||||
FirewallResetSourceResult: 'Исходный межсетевой экран сброшен; удалено правил из базы данных: {{ .removed }}'
|
||||
FirewallCreateRuleExecutionFailed: 'Не удалось создать правило. Выполненные команды не отменены'
|
||||
FirewallInitializeChainsStep: 'Инициализировать и привязать базовые цепочки {{ .name }}'
|
||||
FirewallRestoreRulesStep: 'Восстановить правила базы данных в {{ .name }}'
|
||||
FirewallSyncWhitelistStep: 'Синхронизировать белый список портов межсетевого экрана'
|
||||
FirewallEnableForwardingStep: 'Включить и привязать цепочки перенаправления портов'
|
||||
FirewallRestoreForwardingRulesStep: 'Восстановить правила перенаправления портов из базы данных'
|
||||
FirewallInspectDockerGuardStep: 'Проверить бэкенд межсетевого экрана Docker и политики'
|
||||
FirewallInitializeDockerGuardStep: 'Инициализировать и привязать цепочки защиты портов {{ .name }}'
|
||||
FirewallPersistDockerGuardStep: 'Сохранить состояние защиты портов Docker'
|
||||
ErrFirewallRuleScopeChange: "Текущий межсетевой экран не поддерживает изменение области действия правила (например, семейства адресов IPv4/IPv6). Создайте новое правило."
|
||||
FirewallWhitelistReleased: "{{ .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 }}"
|
||||
ErrFirewallRuleConflict: "В UFW уже есть правило с такими же условиями и другим действием. Отредактируйте существующее правило."
|
||||
ErrForwardInterfaceNotFound: "Сетевой интерфейс {{ .name }}, указанный в правиле перенаправления, не существует. Проверьте имя и повторите попытку."
|
||||
|
||||
ErrSSHPublicKey: "Введите действительный открытый ключ SSH"
|
||||
ErrSSHPrivateKey: "Недопустимый закрытый ключ SSH или неверная парольная фраза; для зашифрованных ключей требуется исходная парольная фраза"
|
||||
ErrSSHKeyMismatch: "Открытый и закрытый ключи SSH не соответствуют друг другу"
|
||||
|
||||
# виртуальная машина
|
||||
ErrVMNotFound: "Виртуальная машина не существует"
|
||||
ErrVMAlreadyExists: "Виртуальная машина уже существует"
|
||||
ErrVMCountLimit: "В редакции Professional можно создать не более {{ .limit }} виртуальных машин. Удалите существующую виртуальную машину или перейдите на Enterprise и повторите попытку."
|
||||
ErrVMImageAlreadyExists: "Файл образа виртуальной машины уже существует: {{ .detail }}"
|
||||
ErrVMStoragePathInUse: "Путь пула хранения уже используется [{{ .name }}]"
|
||||
ErrVMHelperNotFound: "1panel-kvm не найден"
|
||||
ErrVMBridgeManaged: "Имя моста уже управляется libvirt или Docker"
|
||||
ErrVMNetworkInUse: "Сеть используется и не может быть удалена"
|
||||
ErrVMStorageInUse: "Пул хранилища используется и не может быть удалён"
|
||||
ErrVMStorageNameCannotChange: "Имя пула хранилища нельзя изменить"
|
||||
ErrVMStoragePathCannotChange: "Путь к пулу хранилища нельзя изменить"
|
||||
ErrVMRunning: "Виртуальная машина запущена"
|
||||
ErrVMLibvirtConnect: "Не удалось подключиться к libvirt: {{ .detail }}"
|
||||
ErrVMPermission: "Нет разрешения на операцию с виртуальной машиной: {{ .detail }}"
|
||||
ErrVMNetworkNotFound: "Сеть виртуальной машины не существует: {{ .detail }}"
|
||||
ErrVMInvalidPath: "Недопустимый путь виртуальной машины: {{ .detail }}"
|
||||
ErrVMInvalidInput: "Недопустимые параметры виртуальной машины: {{ .detail }}"
|
||||
ErrVMCommandFailed: "Ошибка выполнения команды виртуальной машины: {{ .err }}"
|
||||
ErrVMDiskResizeBlocked: "Диск не соответствует условиям расширения или используется другим устройством. Проверьте состояние диска."
|
||||
ErrVMDiskReferenced: "Этот виртуальный диск используется другой виртуальной машиной или устройством; расширение пока недоступно."
|
||||
ErrVMDiskReferenceCheck: "Не удалось проверить ссылки на виртуальный диск. Расширение недоступно; проверьте диски других виртуальных машин."
|
||||
ErrVMSnapshotMetadataSync: "Снимок восстановлен, но синхронизация данных диска не удалась. Обновите данные и проверьте их; повторно восстанавливать снимок не нужно."
|
||||
VMCreateTask: "Создать виртуальную машину [{{ .name }}]"
|
||||
VMCreateFromTemplateTask: "Создать виртуальную машину [{{ .name }}] из шаблона"
|
||||
VMUpdateTask: "Обновить виртуальную машину [{{ .name }}]"
|
||||
VMDiskResizeTask: "Расширить диск [{{ .disk }}] виртуальной машины [{{ .name }}]"
|
||||
VMRenameTask: "Переименовать виртуальную машину [{{ .name }}] в [{{ .newName }}]"
|
||||
VMDeleteTask: "Удалить виртуальную машину [{{ .name }}]"
|
||||
VMTemplateCreateTask: "Сохранить виртуальную машину [{{ .name }}] как шаблон [{{ .template }}]"
|
||||
VMTemplateDeleteTask: "Удалить шаблон виртуальной машины [{{ .name }}]"
|
||||
VMForceDeleteMetadataOnly: "1panel-kvm не найден, принудительно удаляются только метаданные виртуальной машины [{{ .name }}]"
|
||||
VMForceDeleteMetadataAfterLibvirtFailed: "Не удалось удалить виртуальную машину [{{ .name }}] из libvirt, принудительно удаляются метаданные: {{ .err }}"
|
||||
VMForceDeleteDiskFailed: "Не удалось принудительно удалить файл диска виртуальной машины [{{ .path }}]: {{ .err }}"
|
||||
VMForceDeleteDiskSuccess: "Файл диска виртуальной машины [{{ .path }}] принудительно удален"
|
||||
VMSnapshotCreateTask: "Создать снимок [{{ .snapshot }}] для виртуальной машины [{{ .vm }}]"
|
||||
VMSnapshotCreateStep: "Создать снимок виртуальной машины [{{ .snapshot }}]"
|
||||
VMSnapshotRecoverTask: "Восстановить снимок [{{ .snapshot }}] виртуальной машины [{{ .vm }}]"
|
||||
VMSnapshotRecoverStep: "Восстановить снимок виртуальной машины [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "Удалить снимок [{{ .snapshot }}] виртуальной машины [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "Удалить снимок виртуальной машины [{{ .snapshot }}]"
|
||||
|
||||
+61
-16
@@ -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.'
|
||||
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.'
|
||||
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: zamanlanmış görev {{ .name }} başarıyla tamamlandı. Ayrıntıları görmek için giriş yapın.'
|
||||
|
||||
# disk
|
||||
DeviceNotFound: 'Cihaz {{ .name }} bulunamadı'
|
||||
@@ -682,8 +683,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
|
||||
# Firewall
|
||||
ErrDockerIptablesChainUnavailable: 'Docker DOCKER-USER zinciri bulunamadı. Docker’ı yeniden başlatıp tekrar deneyin'
|
||||
ErrDockerNftablesChainUnavailable: 'Docker nftables IPv4 güvenlik duvarı zinciri bulunamadı. Geçerli Docker sürümünün nftables güvenlik duvarı arka ucunu desteklediğini doğrulayın, Docker’ı yeniden başlatıp tekrar deneyin'
|
||||
ErrDockerForwardPolicyDrop: '{{ .family }} FORWARD varsayılan ilkesi DROP. İlkeyi düzenleyip tekrar deneyin'
|
||||
ErrUFWRuleAdopt: 'UFW kuralı yönetilemedi: {{ .detail }}. Geçerli UFW sürümü 0.35’ten eskiyse UFW’yi yükseltip tekrar deneyin'
|
||||
ErrDockerForwardPolicyDrop: 'Bu sunucu ağ yönlendirmesini kısıtlıyor. Konteyner port koruması şu anda etkinleştirilemiyor.'
|
||||
Firewall: 'Güvenlik duvarı'
|
||||
FirewallTaskHost: 'Ana makine güvenlik duvarı'
|
||||
FirewallTaskForwarding: 'Port yönlendirme'
|
||||
@@ -697,25 +697,70 @@ FirewallCreateRulesStep: 'Güvenlik duvarı kuralları oluştur'
|
||||
FirewallCreateRulesResult: 'Oluşturma sonucu: {{ .succeeded }} başarılı, {{ .failed }} başarısız, {{ .skipped }} yürütülmedi'
|
||||
FirewallRuleOperationResult: 'İşlem sonucu: {{ .succeeded }} başarılı, {{ .failed }} başarısız'
|
||||
FirewallCreateRuleSkipped: 'Yürütülmedi'
|
||||
FirewallCreateBatchStep: '{{ .backend }} için {{ .count }} kuralı toplu gönder'
|
||||
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; {{ .count }} kurala dönüştürüldü'
|
||||
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'
|
||||
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, {{ .unchanged }} zaten eşleşiyor (değişiklik gerekmiyor)'
|
||||
FirewallSyncRuleUnchanged: 'Zaten eşleşiyor; değişiklik gerekmiyor'
|
||||
FirewallSyncStep: 'Kuralları {{ .name }} ile eşitle'
|
||||
FirewallSyncFailed: '{{ .failed }} güvenlik duvarı kuralı eşitlenemedi'
|
||||
FirewallResetSourceStep: 'Kaynak güvenlik duvarı {{ .name }} sıfırla ve devre dışı bırak'
|
||||
FirewallResetSourceResult: 'Kaynak güvenlik duvarı sıfırlandı; {{ .removed }} veritabanı kuralı silindi'
|
||||
FirewallCreateRuleExecutionFailed: 'Kural oluşturma başarısız. Yürütülen komutlar geri alınmadı'
|
||||
FirewallInitializeChainsStep: '{{ .name }} temel zincirlerini başlat ve bağla'
|
||||
FirewallRestoreRulesStep: 'Veritabanı kurallarını {{ .name }} üzerine geri yükle'
|
||||
FirewallSyncWhitelistStep: 'Güvenlik duvarı bağlantı noktası izin listesini eşitle'
|
||||
FirewallEnableForwardingStep: 'Bağlantı noktası yönlendirme zincirlerini etkinleştir ve bağla'
|
||||
FirewallRestoreForwardingRulesStep: 'Veritabanı bağlantı noktası yönlendirme kurallarını geri yükle'
|
||||
FirewallInspectDockerGuardStep: 'Docker güvenlik duvarı arka ucunu ve ilkelerini denetle'
|
||||
FirewallInitializeDockerGuardStep: '{{ .name }} bağlantı noktası koruma zincirlerini başlat ve bağla'
|
||||
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."
|
||||
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"
|
||||
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 }}"
|
||||
ErrFirewallRuleConflict: "UFW içinde aynı eşleşme koşullarına sahip, farklı bir eylem uygulayan bir kural zaten var. Mevcut kuralı düzenleyin."
|
||||
ErrForwardInterfaceNotFound: "Yönlendirme kuralında belirtilen {{ .name }} ağ arayüzü mevcut değil. Arayüz adını kontrol edip yeniden deneyin."
|
||||
|
||||
ErrSSHPublicKey: "Geçerli bir SSH açık anahtarı girin"
|
||||
ErrSSHPrivateKey: "SSH özel anahtarı geçersiz veya parola yanlış; şifreli anahtarlar mevcut parolayı gerektirir"
|
||||
ErrSSHKeyMismatch: "SSH açık ve özel anahtarları eşleşmiyor"
|
||||
|
||||
# sanal makine
|
||||
ErrVMNotFound: "Sanal makine mevcut değil"
|
||||
ErrVMAlreadyExists: "Sanal makine zaten mevcut"
|
||||
ErrVMCountLimit: "Professional sürümü en fazla {{ .limit }} sanal makine oluşturulmasına izin verir. Mevcut bir sanal makineyi silin veya Enterprise sürümüne yükseltip tekrar deneyin."
|
||||
ErrVMImageAlreadyExists: "Sanal makine imaj dosyası zaten mevcut: {{ .detail }}"
|
||||
ErrVMStoragePathInUse: "Depolama havuzu yolu zaten [{{ .name }}] tarafından kullanılıyor"
|
||||
ErrVMHelperNotFound: "1panel-kvm bulunamadı"
|
||||
ErrVMBridgeManaged: "Bridge adı zaten libvirt veya Docker tarafından yönetiliyor"
|
||||
ErrVMNetworkInUse: "Ağ kullanımda ve silinemez"
|
||||
ErrVMStorageInUse: "Depolama havuzu kullanımda ve silinemez"
|
||||
ErrVMStorageNameCannotChange: "Depolama havuzunun adı değiştirilemez"
|
||||
ErrVMStoragePathCannotChange: "Depolama havuzunun yolu değiştirilemez"
|
||||
ErrVMRunning: "Sanal makine çalışıyor"
|
||||
ErrVMLibvirtConnect: "libvirt bağlantısı başarısız: {{ .detail }}"
|
||||
ErrVMPermission: "Sanal makine işlemi için izin yok: {{ .detail }}"
|
||||
ErrVMNetworkNotFound: "Sanal makine ağı mevcut değil: {{ .detail }}"
|
||||
ErrVMInvalidPath: "Geçersiz sanal makine yolu: {{ .detail }}"
|
||||
ErrVMInvalidInput: "Geçersiz sanal makine parametresi: {{ .detail }}"
|
||||
ErrVMCommandFailed: "Sanal makine komutu başarısız: {{ .err }}"
|
||||
ErrVMDiskResizeBlocked: "Disk genişletme koşullarını karşılamıyor veya başka bir aygıt tarafından kullanılıyor. Disk durumunu kontrol edin."
|
||||
ErrVMDiskReferenced: "Bu sanal disk başka bir sanal makine veya aygıt tarafından kullanılıyor; genişletme henüz desteklenmiyor."
|
||||
ErrVMDiskReferenceCheck: "Sanal disk başvuruları doğrulanamadı. Genişletme kullanılamıyor; diğer sanal makinelerin disklerini kontrol edin."
|
||||
ErrVMSnapshotMetadataSync: "Anlık görüntü geri yüklendi ancak disk bilgileri eşitlenemedi. Yenileyip kontrol edin; anlık görüntüyü tekrar geri yüklemeyin."
|
||||
VMCreateTask: "Sanal makine [{{ .name }}] oluştur"
|
||||
VMCreateFromTemplateTask: "Şablondan sanal makine [{{ .name }}] oluştur"
|
||||
VMUpdateTask: "Sanal makine [{{ .name }}] güncelle"
|
||||
VMDiskResizeTask: "[{{ .name }}] sanal makinesinin [{{ .disk }}] diskini genişlet"
|
||||
VMRenameTask: "Sanal makine [{{ .name }}] adını [{{ .newName }}] olarak değiştir"
|
||||
VMDeleteTask: "Sanal makine [{{ .name }}] sil"
|
||||
VMTemplateCreateTask: "Sanal makine [{{ .name }}] öğesini şablon [{{ .template }}] olarak kaydet"
|
||||
VMTemplateDeleteTask: "Sanal makine şablonu [{{ .name }}] sil"
|
||||
VMForceDeleteMetadataOnly: "1panel-kvm bulunamadı, yalnızca sanal makine [{{ .name }}] meta verileri zorla siliniyor"
|
||||
VMForceDeleteMetadataAfterLibvirtFailed: "Sanal makine [{{ .name }}] libvirt üzerinden silinemedi, meta veriler zorla siliniyor: {{ .err }}"
|
||||
VMForceDeleteDiskFailed: "Sanal makine disk dosyası [{{ .path }}] zorla silinemedi: {{ .err }}"
|
||||
VMForceDeleteDiskSuccess: "Sanal makine disk dosyası [{{ .path }}] zorla silindi"
|
||||
VMSnapshotCreateTask: "Sanal makine [{{ .vm }}] için anlık görüntü [{{ .snapshot }}] oluştur"
|
||||
VMSnapshotCreateStep: "Sanal makine anlık görüntüsü [{{ .snapshot }}] oluştur"
|
||||
VMSnapshotRecoverTask: "Sanal makine [{{ .vm }}] anlık görüntüsü [{{ .snapshot }}] geri yükle"
|
||||
VMSnapshotRecoverStep: "Sanal makine anlık görüntüsü [{{ .snapshot }}] geri yükle"
|
||||
VMSnapshotDeleteTask: "Sanal makine [{{ .vm }}] anlık görüntüsü [{{ .snapshot }}] sil"
|
||||
VMSnapshotDeleteStep: "Sanal makine anlık görüntüsü [{{ .snapshot }}] sil"
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},詳情請登入
|
||||
NodeExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個節點出現異常,詳情請登入面板檢視。'
|
||||
LicenseExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個授權出現異常,詳情請登入面板檢視。'
|
||||
SSHAndPanelLoginAlert: '您的 {{ .node }}{{ .ip }} 面板,來自 {{ .loginIp }} 的 {{ .name }} 登入出現異常,詳情請登入面板檢視。'
|
||||
CronJobSuccessAlert: '您的 {{ .node }}{{ .ip }} 面板,排程任務 {{ .name }} 執行成功,詳情請登入面板檢視。'
|
||||
|
||||
# 磁碟
|
||||
DeviceNotFound: '裝置 {{ .name }} 未找到'
|
||||
@@ -682,8 +683,7 @@ AIProviderArkCodingPlan: '火山方舟 Coding Plan'
|
||||
# 防火牆
|
||||
ErrDockerIptablesChainUnavailable: '未偵測到 Docker 的 DOCKER-USER 鏈,請重新啟動 Docker 後再試'
|
||||
ErrDockerNftablesChainUnavailable: '未偵測到 Docker 的 nftables IPv4 防火牆鏈,請確認目前 Docker 版本支援 nftables 防火牆後端,重新啟動 Docker 後再試'
|
||||
ErrDockerForwardPolicyDrop: '偵測到 {{ .family }} FORWARD 預設策略為 DROP,請調整後重試'
|
||||
ErrUFWRuleAdopt: 'UFW 規則納管失敗:{{ .detail }}。如果目前 UFW 版本低於 0.35,請先升級 UFW 後再試'
|
||||
ErrDockerForwardPolicyDrop: '目前伺服器限制了網路轉送,暫時無法啟用容器連接埠防護。'
|
||||
Firewall: '防火牆'
|
||||
FirewallTaskHost: '主機防火牆'
|
||||
FirewallTaskForwarding: '連接埠轉發'
|
||||
@@ -697,25 +697,70 @@ FirewallCreateRulesStep: '建立防火牆規則'
|
||||
FirewallCreateRulesResult: '建立結果:成功 {{ .succeeded }} 條,失敗 {{ .failed }} 條,未執行 {{ .skipped }} 條'
|
||||
FirewallRuleOperationResult: '操作結果:成功 {{ .succeeded }} 條,失敗 {{ .failed }} 條'
|
||||
FirewallCreateRuleSkipped: '未執行'
|
||||
FirewallCreateBatchStep: '向 {{ .backend }} 批次提交 {{ .count }} 條規則'
|
||||
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}:{{ .rule }};轉換為 {{ .count }} 條規則'
|
||||
FirewallCreateRuleExecutionFailed: '規則建立失敗,未寫入資料庫,已執行的命令不回復'
|
||||
FirewallCreateRulePersistenceFailed: '規則已建立,但納管資訊儲存失敗'
|
||||
FirewallAdoptRulePersistenceFailed: '規則納管命令已執行,但納管資訊儲存失敗'
|
||||
FirewallSyncOperationsResult: '同步操作彙總:刪除成功 {{ .removed }} 條,建立成功 {{ .created }} 條,失敗 {{ .failed }} 條,未執行 {{ .skipped }} 條,已一致無需變更 {{ .unchanged }} 條'
|
||||
FirewallSyncRuleUnchanged: '已一致,無需變更'
|
||||
FirewallSyncStep: '同步規則到 {{ .name }}'
|
||||
FirewallSyncFailed: '{{ .failed }} 條防火牆規則同步失敗'
|
||||
FirewallResetSourceStep: '重設並停用來源防火牆 {{ .name }}'
|
||||
FirewallResetSourceResult: '來源防火牆重設完成,已刪除 {{ .removed }} 條資料庫規則'
|
||||
FirewallCreateRuleExecutionFailed: '規則建立失敗,已執行的命令不回復'
|
||||
FirewallInitializeChainsStep: '初始化並綁定 {{ .name }} 基礎鏈'
|
||||
FirewallRestoreRulesStep: '恢復資料庫規則至 {{ .name }}'
|
||||
FirewallSyncWhitelistStep: '同步防火牆連接埠白名單'
|
||||
FirewallEnableForwardingStep: '啟用並綁定連接埠轉發鏈'
|
||||
FirewallRestoreForwardingRulesStep: '恢復資料庫連接埠轉發規則'
|
||||
FirewallInspectDockerGuardStep: '檢查 Docker 防火牆後端與防護策略'
|
||||
FirewallInitializeDockerGuardStep: '初始化並綁定 {{ .name }} 連接埠防護鏈'
|
||||
FirewallPersistDockerGuardStep: '儲存 Docker 連接埠防護狀態'
|
||||
ErrFirewallRuleScopeChange: "目前的防火牆不支援修改規則的作用範圍(如 IPv4/IPv6 位址族),請建立新規則。"
|
||||
FirewallWhitelistReleased: "{{ .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 }}"
|
||||
ErrFirewallRuleConflict: "UFW 中已存在比對條件相同但動作不同的規則,請編輯既有規則。"
|
||||
ErrForwardInterfaceNotFound: "轉發規則指定的網卡 {{ .name }} 不存在,請檢查網卡名稱後重試。"
|
||||
|
||||
ErrSSHPublicKey: "請輸入有效的 SSH 公鑰"
|
||||
ErrSSHPrivateKey: "SSH 私鑰格式無效或密碼錯誤;加密私鑰需要填寫原密碼"
|
||||
ErrSSHKeyMismatch: "SSH 公鑰與私鑰不相符"
|
||||
|
||||
# 虛擬機
|
||||
ErrVMNotFound: "虛擬機不存在"
|
||||
ErrVMAlreadyExists: "虛擬機已存在"
|
||||
ErrVMCountLimit: "專業版最多可建立 {{ .limit }} 台虛擬機,請刪除現有虛擬機或升級企業版後重試。"
|
||||
ErrVMImageAlreadyExists: "虛擬機映像檔已存在: {{ .detail }}"
|
||||
ErrVMStoragePathInUse: "儲存池路徑已被 [{{ .name }}] 使用"
|
||||
ErrVMHelperNotFound: "1panel-kvm 不存在"
|
||||
ErrVMBridgeManaged: "橋接名稱已被 libvirt 或 Docker 管理"
|
||||
ErrVMNetworkInUse: "網路正在被使用,無法刪除"
|
||||
ErrVMStorageInUse: "儲存池正在被使用,無法刪除"
|
||||
ErrVMStorageNameCannotChange: "儲存池名稱無法修改"
|
||||
ErrVMStoragePathCannotChange: "儲存池路徑無法修改"
|
||||
ErrVMRunning: "虛擬機正在執行"
|
||||
ErrVMLibvirtConnect: "連接 libvirt 失敗: {{ .detail }}"
|
||||
ErrVMPermission: "沒有虛擬機操作權限: {{ .detail }}"
|
||||
ErrVMNetworkNotFound: "虛擬機網路不存在: {{ .detail }}"
|
||||
ErrVMInvalidPath: "虛擬機路徑非法: {{ .detail }}"
|
||||
ErrVMInvalidInput: "虛擬機參數非法: {{ .detail }}"
|
||||
ErrVMCommandFailed: "虛擬機指令執行失敗: {{ .err }}"
|
||||
ErrVMDiskResizeBlocked: "磁碟不符合擴容條件,或被其他裝置引用,請檢查磁碟狀態"
|
||||
ErrVMDiskReferenced: "此虛擬硬碟被其他虛擬機或裝置使用,暫不支援擴容"
|
||||
ErrVMDiskReferenceCheck: "無法完成虛擬硬碟引用檢查,暫不支援擴容,請檢查其他虛擬機的磁碟狀態"
|
||||
ErrVMSnapshotMetadataSync: "快照已還原,但磁碟資訊同步失敗,請重新整理檢查,無需重複還原快照"
|
||||
VMCreateTask: "建立虛擬機 [{{ .name }}]"
|
||||
VMCreateFromTemplateTask: "從範本建立虛擬機 [{{ .name }}]"
|
||||
VMUpdateTask: "更新虛擬機 [{{ .name }}]"
|
||||
VMDiskResizeTask: "擴容虛擬機 [{{ .name }}] 磁碟 [{{ .disk }}]"
|
||||
VMRenameTask: "重新命名虛擬機 [{{ .name }}] 為 [{{ .newName }}]"
|
||||
VMDeleteTask: "刪除虛擬機 [{{ .name }}]"
|
||||
VMTemplateCreateTask: "轉存虛擬機 [{{ .name }}] 為範本 [{{ .template }}]"
|
||||
VMTemplateDeleteTask: "刪除虛擬機範本 [{{ .name }}]"
|
||||
VMForceDeleteMetadataOnly: "1panel-kvm 不存在,僅強制刪除虛擬機 [{{ .name }}] 元資料"
|
||||
VMForceDeleteMetadataAfterLibvirtFailed: "從 libvirt 刪除虛擬機 [{{ .name }}] 失敗,強制刪除元資料: {{ .err }}"
|
||||
VMForceDeleteDiskFailed: "強制刪除虛擬機磁碟檔案 [{{ .path }}] 失敗: {{ .err }}"
|
||||
VMForceDeleteDiskSuccess: "已強制刪除虛擬機磁碟檔案 [{{ .path }}]"
|
||||
VMSnapshotCreateTask: "建立虛擬機 [{{ .vm }}] 快照 [{{ .snapshot }}]"
|
||||
VMSnapshotCreateStep: "建立虛擬機快照 [{{ .snapshot }}]"
|
||||
VMSnapshotRecoverTask: "復原虛擬機 [{{ .vm }}] 快照 [{{ .snapshot }}]"
|
||||
VMSnapshotRecoverStep: "復原虛擬機快照 [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "刪除虛擬機 [{{ .vm }}] 快照 [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteStep: "刪除虛擬機快照 [{{ .snapshot }}]"
|
||||
|
||||
+61
-16
@@ -649,6 +649,7 @@ CommonAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},请登录面板
|
||||
NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个节点存在异常,请登录面板查看详情。"
|
||||
LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个许可证存在异常,请登录面板查看详情。"
|
||||
SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,面板 {{ .name }} 登录 {{ .loginIp }} 异常,请登录面板查看详情。"
|
||||
CronJobSuccessAlert: "您的 {{ .node }}{{ .ip }} 面板,计划任务-{{ .name }}执行成功,请登录面板查看详情。"
|
||||
|
||||
# 磁盘
|
||||
DeviceNotFound: "设备 {{ .name }} 未找到"
|
||||
@@ -682,8 +683,7 @@ AIProviderArkCodingPlan: "火山方舟 Coding Plan"
|
||||
# 防火墙
|
||||
ErrDockerIptablesChainUnavailable: "未检测到 Docker 的 DOCKER-USER 链,请重启 Docker 后重试"
|
||||
ErrDockerNftablesChainUnavailable: "未检测到 Docker 的 nftables IPv4 防火墙链,请确认当前 Docker 版本支持 nftables 防火墙后端,重启 Docker 后重试"
|
||||
ErrDockerForwardPolicyDrop: '检测到 {{ .family }} FORWARD 默认策略为 DROP,请调整后重试'
|
||||
ErrUFWRuleAdopt: "UFW 规则纳管失败:{{ .detail }}。如果当前 UFW 版本低于 0.35,请先升级 UFW 后重试"
|
||||
ErrDockerForwardPolicyDrop: '当前服务器限制了网络转发,暂时无法启用容器端口防护。'
|
||||
Firewall: "防火墙"
|
||||
FirewallTaskHost: '主机防火墙'
|
||||
FirewallTaskForwarding: '端口转发'
|
||||
@@ -697,25 +697,70 @@ FirewallCreateRulesStep: '创建防火墙规则'
|
||||
FirewallCreateRulesResult: '创建结果:成功 {{ .succeeded }} 条,失败 {{ .failed }} 条,未执行 {{ .skipped }} 条'
|
||||
FirewallRuleOperationResult: '操作结果:成功 {{ .succeeded }} 条,失败 {{ .failed }} 条'
|
||||
FirewallCreateRuleSkipped: '未执行'
|
||||
FirewallCreateBatchStep: '向 {{ .backend }} 批量提交 {{ .count }} 条规则'
|
||||
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}:{{ .rule }};转换为 {{ .count }} 条规则'
|
||||
FirewallCreateRuleExecutionFailed: '规则创建失败,未入库,已执行的命令不回滚'
|
||||
FirewallCreateRulePersistenceFailed: '规则已创建,但纳管信息保存失败'
|
||||
FirewallAdoptRulePersistenceFailed: '规则纳管命令已执行,但纳管信息保存失败'
|
||||
FirewallSyncOperationsResult: '同步操作汇总:删除成功 {{ .removed }} 条,创建成功 {{ .created }} 条,失败 {{ .failed }} 条,未执行 {{ .skipped }} 条,已一致无需变更 {{ .unchanged }} 条'
|
||||
FirewallSyncRuleUnchanged: '已一致,无需变更'
|
||||
FirewallSyncStep: "同步规则到 {{ .name }}"
|
||||
FirewallSyncFailed: "{{ .failed }} 条防火墙规则同步失败"
|
||||
FirewallResetSourceStep: "重置并停用源防火墙 {{ .name }}"
|
||||
FirewallResetSourceResult: "源防火墙重置完成,已删除 {{ .removed }} 条数据库规则"
|
||||
FirewallCreateRuleExecutionFailed: '规则创建失败,已执行的命令不回滚'
|
||||
FirewallInitializeChainsStep: "初始化并绑定 {{ .name }} 基础链"
|
||||
FirewallRestoreRulesStep: "恢复数据库规则到 {{ .name }}"
|
||||
FirewallSyncWhitelistStep: "同步防火墙端口白名单"
|
||||
FirewallEnableForwardingStep: "启用并绑定端口转发链"
|
||||
FirewallRestoreForwardingRulesStep: "恢复数据库端口转发规则"
|
||||
FirewallInspectDockerGuardStep: "检查 Docker 防火墙后端和防护策略"
|
||||
FirewallInitializeDockerGuardStep: "初始化并绑定 {{ .name }} 端口防护链"
|
||||
FirewallPersistDockerGuardStep: "保存 Docker 端口防护状态"
|
||||
ErrFirewallRuleScopeChange: "当前防火墙不支持修改规则的作用范围(如 IPv4/IPv6 地址族),请新建规则。"
|
||||
FirewallWhitelistReleased: "{{ .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 }}"
|
||||
ErrFirewallRuleConflict: "UFW 中已存在匹配条件相同但动作不同的规则,请编辑已有规则。"
|
||||
ErrForwardInterfaceNotFound: "转发规则指定的网卡 {{ .name }} 不存在,请检查网卡名称后重试。"
|
||||
|
||||
ErrSSHPublicKey: "请输入有效的 SSH 公钥"
|
||||
ErrSSHPrivateKey: "SSH 私钥格式无效或密码错误;加密私钥需要填写原密码"
|
||||
ErrSSHKeyMismatch: "SSH 公钥与私钥不匹配"
|
||||
|
||||
# 虚拟机
|
||||
ErrVMNotFound: "虚拟机不存在"
|
||||
ErrVMAlreadyExists: "虚拟机已存在"
|
||||
ErrVMCountLimit: "专业版最多可创建 {{ .limit }} 台虚拟机,请删除已有虚拟机或升级企业版后重试。"
|
||||
ErrVMImageAlreadyExists: "虚拟机镜像文件已存在: {{ .detail }}"
|
||||
ErrVMStoragePathInUse: "存储池路径已被 [{{ .name }}] 使用"
|
||||
ErrVMHelperNotFound: "1panel-kvm 不存在"
|
||||
ErrVMBridgeManaged: "桥接名称已被 libvirt 或 Docker 管理"
|
||||
ErrVMNetworkInUse: "网络正在被使用,无法删除"
|
||||
ErrVMStorageInUse: "存储池正在被使用,无法删除"
|
||||
ErrVMStorageNameCannotChange: "存储池名称无法修改"
|
||||
ErrVMStoragePathCannotChange: "存储池路径无法修改"
|
||||
ErrVMRunning: "虚拟机正在运行"
|
||||
ErrVMLibvirtConnect: "连接 libvirt 失败: {{ .detail }}"
|
||||
ErrVMPermission: "没有虚拟机操作权限: {{ .detail }}"
|
||||
ErrVMNetworkNotFound: "虚拟机网络不存在: {{ .detail }}"
|
||||
ErrVMInvalidPath: "虚拟机路径非法: {{ .detail }}"
|
||||
ErrVMInvalidInput: "虚拟机参数非法: {{ .detail }}"
|
||||
ErrVMCommandFailed: "虚拟机命令执行失败: {{ .err }}"
|
||||
ErrVMDiskResizeBlocked: "磁盘不符合扩容条件,或被其他设备引用,请检查磁盘状态"
|
||||
ErrVMDiskReferenced: "该虚拟硬盘被其他虚拟机或设备使用,暂不支持扩容"
|
||||
ErrVMDiskReferenceCheck: "无法完成虚拟硬盘引用检查,暂不支持扩容,请检查其他虚拟机的磁盘状态"
|
||||
ErrVMSnapshotMetadataSync: "快照已恢复,但磁盘信息同步失败,请刷新检查,无需重复恢复快照"
|
||||
VMCreateTask: "创建虚拟机 [{{ .name }}]"
|
||||
VMCreateFromTemplateTask: "从模板创建虚拟机 [{{ .name }}]"
|
||||
VMUpdateTask: "更新虚拟机 [{{ .name }}]"
|
||||
VMDiskResizeTask: "扩容虚拟机 [{{ .name }}] 磁盘 [{{ .disk }}]"
|
||||
VMRenameTask: "重命名虚拟机 [{{ .name }}] 为 [{{ .newName }}]"
|
||||
VMDeleteTask: "删除虚拟机 [{{ .name }}]"
|
||||
VMTemplateCreateTask: "转存虚拟机 [{{ .name }}] 为模板 [{{ .template }}]"
|
||||
VMTemplateDeleteTask: "删除虚拟机模板 [{{ .name }}]"
|
||||
VMForceDeleteMetadataOnly: "1panel-kvm 不存在,仅强制删除虚拟机 [{{ .name }}] 元数据"
|
||||
VMForceDeleteMetadataAfterLibvirtFailed: "从 libvirt 删除虚拟机 [{{ .name }}] 失败,强制删除元数据: {{ .err }}"
|
||||
VMForceDeleteDiskFailed: "强制删除虚拟机磁盘文件 [{{ .path }}] 失败: {{ .err }}"
|
||||
VMForceDeleteDiskSuccess: "已强制删除虚拟机磁盘文件 [{{ .path }}]"
|
||||
VMSnapshotCreateTask: "创建虚拟机 [{{ .vm }}] 快照 [{{ .snapshot }}]"
|
||||
VMSnapshotCreateStep: "创建虚拟机快照 [{{ .snapshot }}]"
|
||||
VMSnapshotRecoverTask: "恢复虚拟机 [{{ .vm }}] 快照 [{{ .snapshot }}]"
|
||||
VMSnapshotRecoverStep: "恢复虚拟机快照 [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "删除虚拟机 [{{ .vm }}] 快照 [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteStep: "删除虚拟机快照 [{{ .snapshot }}]"
|
||||
|
||||
@@ -13,6 +13,7 @@ func Init() {
|
||||
global.TaskDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "task.db"), "task")
|
||||
global.MonitorDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "monitor.db"), "monitor")
|
||||
global.GPUMonitorDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "gpu_monitor.db"), "gpu_monitor")
|
||||
global.VLLMMonitorDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "vllm_monitor.db"), "vllm_monitor")
|
||||
global.AlertDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "alert.db"), "alert")
|
||||
|
||||
if _, err := os.Stat(path.Join(global.Dir.DbDir, "core.db")); err == nil {
|
||||
|
||||
@@ -6,20 +6,26 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"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/init/migration/migrations"
|
||||
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/filter"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/iptables_helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/nftables_helper"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
ctx := context.Background()
|
||||
defer initDockerPortGuard(ctx)
|
||||
if err := initForwardingRules(ctx); err != nil {
|
||||
global.LOG.Warnf("restore forwarding rules from file failed, err: %v", err)
|
||||
}
|
||||
client, err := service.NewSelectedSystemFirewallClient()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("select system firewall provider failed, err: %v", err)
|
||||
@@ -31,38 +37,33 @@ func Init() {
|
||||
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 {
|
||||
global.LOG.Errorf("transfer legacy host firewall records failed, err: %v", err)
|
||||
families := []string{constant.FirewallFamilyIPv4, constant.FirewallFamilyIPv6}
|
||||
nftFamilies := []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6}
|
||||
ipv6Status, err := repo.NewISettingRepo().GetValueByKey(constant.FirewallIPv6SupportKey)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
global.LOG.Errorf("load firewall IPv6 setting failed: %v", err)
|
||||
return
|
||||
}
|
||||
if err := migrationutils.TransferLegacyHostFirewallRuleOwnership(ctx, clientName, service.AdoptLegacyHostFirewallRuleOwnership); err != nil {
|
||||
global.LOG.Warnf("transfer legacy host firewall rule ownership failed, err: %v", err)
|
||||
}
|
||||
if err := migrationutils.TransferFirewallForwarding(ctx); err != nil {
|
||||
global.LOG.Errorf("transfer legacy forwarding rules failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
if err := initForwardingRules(ctx); err != nil {
|
||||
global.LOG.Warnf("restore forwarding rules failed, manual synchronization is available, err: %v", err)
|
||||
if ipv6Status == constant.StatusDisable {
|
||||
global.LOG.Info("IPv6 firewall support is disabled; skipping IPv6 host chains and saved rules during startup")
|
||||
families = families[:1]
|
||||
nftFamilies = nftFamilies[:1]
|
||||
}
|
||||
initialize = needInit()
|
||||
if !initialize {
|
||||
repairIptablesBaseChains(clientName)
|
||||
repairIptablesBaseChains(clientName, families...)
|
||||
return
|
||||
}
|
||||
InitPingStatus()
|
||||
global.LOG.Info("initializing firewall settings...")
|
||||
if clientName == "nftables" {
|
||||
if err := nftables_helper.Restore(); err != nil {
|
||||
if err := nftables_helper.Restore(nftFamilies...); err != nil {
|
||||
global.LOG.Errorf("restore nftables rules failed, err: %v", err)
|
||||
}
|
||||
status, _ := repo.NewISettingRepo().GetValueByKey("IptablesStatus")
|
||||
if status == constant.StatusEnable {
|
||||
if err := nftables_helper.Bind(); err != nil {
|
||||
if err := nftables_helper.Bind(nftFamilies...); err != nil {
|
||||
global.LOG.Errorf("bind nftables base chains failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -78,7 +79,7 @@ func Init() {
|
||||
global.LOG.Errorf("load required firewall ports failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
if err := iptables_helper.RestoreBaseChains(requiredPorts); err != nil {
|
||||
if err := iptables_helper.RestoreBaseChains(requiredPorts, families...); err != nil {
|
||||
global.LOG.Errorf("restore iptables base chains failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -94,7 +95,7 @@ func Init() {
|
||||
|
||||
}
|
||||
|
||||
func repairIptablesBaseChains(clientName string) {
|
||||
func repairIptablesBaseChains(clientName string, families ...string) {
|
||||
if clientName != constant.FirewallProviderIptables {
|
||||
return
|
||||
}
|
||||
@@ -103,10 +104,12 @@ func repairIptablesBaseChains(clientName string) {
|
||||
if status != constant.StatusEnable {
|
||||
return
|
||||
}
|
||||
manager := iptables_helper.Manager{
|
||||
LoadRequiredPorts: service.LoadRequiredFirewallPortWhiteList,
|
||||
ports, err := service.LoadRequiredFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
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, families...); err != nil {
|
||||
global.LOG.Warnf("repair iptables base chains failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -118,7 +121,7 @@ func initDockerPortGuard(ctx context.Context) {
|
||||
)
|
||||
var restoreErr error
|
||||
for attempt := 1; attempt <= attempts; attempt++ {
|
||||
restoreErr = service.ReconcileDockerPortGuard(ctx)
|
||||
restoreErr = service.RestoreDockerPortGuard(ctx)
|
||||
if restoreErr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
+33
-1
@@ -4,6 +4,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
@@ -174,6 +176,36 @@ func initAlertTask() {
|
||||
}
|
||||
|
||||
func initMonitorDB() {
|
||||
_ = global.MonitorDB.AutoMigrate(&model.MonitorBase{}, &model.MonitorNetwork{}, &model.MonitorGPU{}, &model.MonitorIO{})
|
||||
_ = global.MonitorDB.AutoMigrate(&model.MonitorBase{}, &model.MonitorNetwork{}, &model.MonitorIO{})
|
||||
_ = global.GPUMonitorDB.AutoMigrate(&model.MonitorGPU{})
|
||||
_ = global.TaskDB.AutoMigrate(&model.Task{})
|
||||
// building indexes on large monitor tables can take seconds, keep it off the startup path;
|
||||
// WAL mode leaves readers unblocked and busy_timeout covers the collector's inserts meanwhile
|
||||
go ensureMonitorIndexes()
|
||||
}
|
||||
|
||||
func ensureMonitorIndexes() {
|
||||
indexes := []struct {
|
||||
db *gorm.DB
|
||||
stmt string
|
||||
}{
|
||||
// created_at alone serves unfiltered range queries and retention cleanup
|
||||
{global.MonitorDB, "CREATE INDEX IF NOT EXISTS idx_monitor_bases_created ON monitor_bases(created_at)"},
|
||||
{global.MonitorDB, "CREATE INDEX IF NOT EXISTS idx_monitor_ios_created ON monitor_ios(created_at)"},
|
||||
{global.MonitorDB, "CREATE INDEX IF NOT EXISTS idx_monitor_networks_created ON monitor_networks(created_at)"},
|
||||
{global.GPUMonitorDB, "CREATE INDEX IF NOT EXISTS idx_monitor_gpus_created ON monitor_gpus(created_at)"},
|
||||
// (name, created_at) serves per-device range queries and distinct name lookups
|
||||
{global.MonitorDB, "CREATE INDEX IF NOT EXISTS idx_monitor_ios_name_created ON monitor_ios(name, created_at)"},
|
||||
{global.MonitorDB, "CREATE INDEX IF NOT EXISTS idx_monitor_networks_name_created ON monitor_networks(name, created_at)"},
|
||||
{global.GPUMonitorDB, "CREATE INDEX IF NOT EXISTS idx_monitor_gpus_product_created ON monitor_gpus(product_name, created_at)"},
|
||||
}
|
||||
start := time.Now()
|
||||
for _, index := range indexes {
|
||||
if err := index.db.Exec(index.stmt).Error; err != nil {
|
||||
global.LOG.Warnf("create monitor index failed, stmt: %s, err: %v", index.stmt, err)
|
||||
}
|
||||
}
|
||||
if elapsed := time.Since(start); elapsed > time.Second {
|
||||
global.LOG.Infof("monitor indexes ready, took %s", elapsed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,9 +108,11 @@ func agentDBMigrations() []*gormigrate.Migration {
|
||||
migrations.AddFirewallRuleTable,
|
||||
migrations.InitDockerPortGuardStatus,
|
||||
migrations.NormalizeFirewallBackendSelections,
|
||||
migrations.SimplifyFirewallRulePolicy,
|
||||
migrations.AddDockerPortGuardReadOnly,
|
||||
migrations.MigrateFirewallDescriptions,
|
||||
migrations.MigrateFirewallPortWhitelistSources,
|
||||
migrations.AddAcceleratorMetrics,
|
||||
migrations.AddVLLMMonitor,
|
||||
migrations.AddMonitorSettings,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,6 +162,9 @@ func migrateFirewallPortWhitelist(value string) ([]firewall.PortWhitelist, error
|
||||
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)
|
||||
|
||||
@@ -59,7 +59,6 @@ var AddTable = &gormigrate.Migration{
|
||||
&model.Favorite{},
|
||||
&model.FileShare{},
|
||||
&model.Host{},
|
||||
&model.FirewallRule{},
|
||||
&model.Ftp{},
|
||||
&model.ImageRepo{},
|
||||
&model.ScriptLibrary{},
|
||||
@@ -1784,7 +1783,7 @@ var AddComposePinned = &gormigrate.Migration{
|
||||
var AddFirewallRuleTable = &gormigrate.Migration{
|
||||
ID: "20260819-add-firewall-v2-tables",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.FirewallRule{}, &model.DockerPortGuardPolicy{}, &model.ForwardingRule{})
|
||||
return tx.AutoMigrate(&model.CommonDescription{})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1837,74 +1836,41 @@ var NormalizeFirewallBackendSelections = &gormigrate.Migration{
|
||||
},
|
||||
}
|
||||
|
||||
var SimplifyFirewallRulePolicy = &gormigrate.Migration{
|
||||
ID: "20260826-simplify-firewall-rule-policy",
|
||||
var MigrateFirewallDescriptions = &gormigrate.Migration{
|
||||
ID: "20260921-migrate-firewall-descriptions",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if !tx.Migrator().HasTable(&model.FirewallRule{}) {
|
||||
return nil
|
||||
}
|
||||
return tx.Transaction(func(tx *gorm.DB) error {
|
||||
if !tx.Migrator().HasColumn("firewall_rules", "compatibility_error") {
|
||||
if err := tx.Exec("ALTER TABLE firewall_rules ADD COLUMN compatibility_error text NOT NULL DEFAULT ''").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !tx.Migrator().HasColumn("firewall_rules", "sequence") {
|
||||
if err := tx.Exec("ALTER TABLE firewall_rules ADD COLUMN sequence integer").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if tx.Migrator().HasColumn("firewall_rules", "native_kind") {
|
||||
if err := tx.Exec(`UPDATE firewall_rules
|
||||
SET compatibility_error = 'legacy native firewall rule requires manual recreation: ' || native_kind
|
||||
WHERE native_kind IN ('zone_service', 'ufw_application', 'opaque')`).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if tx.Migrator().HasColumn("firewall_rules", "provider") {
|
||||
if err := tx.Exec(`UPDATE firewall_rules
|
||||
SET priority = NULL, sequence = NULL`).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tx.Exec("CREATE INDEX IF NOT EXISTS idx_firewall_rules_sequence ON firewall_rules(sequence)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, index := range []string{
|
||||
"idx_firewall_rules_scope_key",
|
||||
"uk_firewall_rules_scope_rule",
|
||||
"uk_firewall_rules_scope_match",
|
||||
} {
|
||||
if err := tx.Exec("DROP INDEX IF EXISTS " + index).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, column := range []string{
|
||||
"provider", "scope_key", "location", "native_kind", "order_index", "order_bucket", "rule_key", "match_key",
|
||||
} {
|
||||
if tx.Migrator().HasColumn("firewall_rules", column) {
|
||||
_ = tx.Exec("ALTER TABLE firewall_rules DROP COLUMN " + column).Error
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return tx.Transaction(migrationutils.MigrateHostFirewallDescriptions)
|
||||
},
|
||||
}
|
||||
|
||||
var AddDockerPortGuardReadOnly = &gormigrate.Migration{
|
||||
ID: "20260902-add-docker-port-guard-read-only",
|
||||
var AddAcceleratorMetrics = &gormigrate.Migration{
|
||||
ID: "20260928-accelerator-vendor-metrics",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if !tx.Migrator().HasTable(&model.DockerPortGuardPolicy{}) {
|
||||
return nil
|
||||
}
|
||||
if err := tx.AutoMigrate(&model.DockerPortGuardPolicy{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if tx.Migrator().HasIndex(&model.DockerPortGuardPolicy{}, "idx_docker_port_guard_endpoint") {
|
||||
if err := tx.Migrator().DropIndex(&model.DockerPortGuardPolicy{}, "idx_docker_port_guard_endpoint"); err != nil {
|
||||
return global.GPUMonitorDB.AutoMigrate(&model.MonitorGPU{})
|
||||
},
|
||||
}
|
||||
|
||||
var AddVLLMMonitor = &gormigrate.Migration{
|
||||
ID: "20260928-vllm-monitor",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return global.VLLMMonitorDB.AutoMigrate(&model.MonitorVLLM{})
|
||||
},
|
||||
}
|
||||
|
||||
var AddMonitorSettings = &gormigrate.Migration{
|
||||
ID: "20260929-monitor-settings",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
for _, key := range []string{"MonitorStatus", "MonitorInterval", "MonitorStoreDays"} {
|
||||
var setting model.Setting
|
||||
if err := tx.Where("key = ?", key).First(&setting).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, prefix := range []string{"GPU", "VLLM"} {
|
||||
if err := tx.Where("key = ?", prefix+key).FirstOrCreate(&model.Setting{Key: prefix + key, Value: setting.Value}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return tx.Migrator().CreateIndex(&model.DockerPortGuardPolicy{}, "idx_docker_port_guard_endpoint")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,373 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/iptables_helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
const firewallTransferMigrationID = "firewall-transfer"
|
||||
|
||||
type legacyFirewalldForward struct {
|
||||
rule forwarding.Rule
|
||||
spec string
|
||||
}
|
||||
|
||||
type legacyFirewalldForwardFailure struct {
|
||||
spec string
|
||||
err error
|
||||
}
|
||||
|
||||
type firewallTransferSource struct {
|
||||
rules []forwarding.Rule
|
||||
firewalld []legacyFirewalldForward
|
||||
provider string
|
||||
cleanupOld func([]legacyFirewalldForward) error
|
||||
}
|
||||
|
||||
type firewallTransfer struct {
|
||||
db *gorm.DB
|
||||
load func() (firewallTransferSource, error)
|
||||
}
|
||||
|
||||
// TransferFirewallForwarding migrates the legacy system-backed forwarding
|
||||
// inventory into the forwarding_rules table once. The migrations table is
|
||||
// also used as the completion ledger so a failed transfer can be retried on
|
||||
// the next agent start without introducing another persisted setting.
|
||||
func TransferFirewallForwarding(ctx context.Context) error {
|
||||
transfer := &firewallTransfer{
|
||||
db: global.DB,
|
||||
load: loadLegacyFirewallForwarding,
|
||||
}
|
||||
return transfer.run(ctx)
|
||||
}
|
||||
|
||||
func (t *firewallTransfer) run(ctx context.Context) error {
|
||||
if t.db == nil {
|
||||
return errors.New("firewall transfer database is required")
|
||||
}
|
||||
completed, err := firewallTransferCompleted(t.db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if completed {
|
||||
return nil
|
||||
}
|
||||
if t.load == nil {
|
||||
return errors.New("legacy firewall forwarding loader is required")
|
||||
}
|
||||
source, err := t.load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := importLegacyForwardingRules(ctx, t.db, source.rules, source.provider); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(source.firewalld) > 0 {
|
||||
if source.cleanupOld == nil {
|
||||
return errors.New("legacy firewalld cleanup is required")
|
||||
}
|
||||
if err := source.cleanupOld(source.firewalld); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return markFirewallTransferCompleted(t.db)
|
||||
}
|
||||
|
||||
func firewallTransferCompleted(db *gorm.DB) (bool, error) {
|
||||
return migrationRecordExists(db, firewallTransferMigrationID)
|
||||
}
|
||||
|
||||
func markFirewallTransferCompleted(db *gorm.DB) error {
|
||||
return markMigrationRecord(db, firewallTransferMigrationID)
|
||||
}
|
||||
|
||||
func migrationRecordExists(db *gorm.DB, migrationID string) (bool, error) {
|
||||
var count int64
|
||||
if err := db.Table("migrations").Where("id = ?", migrationID).Count(&count).Error; err != nil {
|
||||
return false, fmt.Errorf("check migration record %q: %w", migrationID, err)
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func markMigrationRecord(db *gorm.DB, migrationID string) error {
|
||||
if err := db.Table("migrations").Clauses(clause.OnConflict{DoNothing: true}).
|
||||
Create(map[string]interface{}{"id": migrationID}).Error; err != nil {
|
||||
return fmt.Errorf("record migration %q: %w", migrationID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func importLegacyForwardingRules(ctx context.Context, db *gorm.DB, rules []forwarding.Rule, provider string) error {
|
||||
models := make([]model.ForwardingRule, 0, len(rules))
|
||||
seen := make(map[string]struct{}, len(rules))
|
||||
for _, rule := range rules {
|
||||
normalized, err := forwarding.NormalizeRule(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("normalize legacy forwarding rule: %w", err)
|
||||
}
|
||||
key := normalized.Identity()
|
||||
if _, exists := seen[key]; exists {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
models = append(models, model.ForwardingRule{
|
||||
Family: normalized.Family, Protocol: normalized.Protocol, Port: normalized.Port,
|
||||
TargetIP: normalized.TargetIP, TargetPort: normalized.TargetPort, Interface: normalized.Interface,
|
||||
})
|
||||
}
|
||||
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if len(models) > 0 {
|
||||
if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&models).Error; err != nil {
|
||||
return fmt.Errorf("import legacy forwarding rules: %w", err)
|
||||
}
|
||||
if err := updateOrCreateSetting(tx, "IptablesForwardStatus", constant.StatusEnable); err != nil {
|
||||
return err
|
||||
}
|
||||
if provider != "" {
|
||||
if err := updateOrCreateSetting(tx, "ForwardingBackend", provider); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func updateOrCreateSetting(tx *gorm.DB, key, value string) error {
|
||||
result := tx.Model(&model.Setting{}).Where("key = ?", key).Update("value", value)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected > 0 {
|
||||
return nil
|
||||
}
|
||||
return tx.Create(&model.Setting{Key: key, Value: value}).Error
|
||||
}
|
||||
|
||||
func loadLegacyFirewallForwarding() (firewallTransferSource, error) {
|
||||
source := firewallTransferSource{cleanupOld: cleanupLegacyFirewalldForwarding}
|
||||
if cmd.Which("firewall-cmd") {
|
||||
firewalldRules, err := listLegacyFirewalldForwarding()
|
||||
if err != nil {
|
||||
return firewallTransferSource{}, err
|
||||
}
|
||||
supportedRules, cleanupRules, failures := selectSupportedLegacyFirewalldForwarding(firewalldRules)
|
||||
for _, failure := range failures {
|
||||
if global.LOG != nil {
|
||||
global.LOG.Warnf("skip unsupported legacy firewalld forwarding rule %q: %v", failure.spec, failure.err)
|
||||
}
|
||||
}
|
||||
source.rules = append(source.rules, supportedRules...)
|
||||
source.firewalld = append(source.firewalld, cleanupRules...)
|
||||
if len(source.rules) > 0 {
|
||||
source.provider = "iptables"
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
if _, err := lifecycle.ResolveIptablesCommands(); err != nil {
|
||||
return source, nil
|
||||
}
|
||||
rules, err := listLegacyIptablesForwarding()
|
||||
if err != nil {
|
||||
return firewallTransferSource{}, err
|
||||
}
|
||||
source.rules = append(source.rules, rules...)
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func selectSupportedLegacyFirewalldForwarding(items []legacyFirewalldForward) (
|
||||
[]forwarding.Rule, []legacyFirewalldForward, []legacyFirewalldForwardFailure,
|
||||
) {
|
||||
rules := make([]forwarding.Rule, 0, len(items))
|
||||
cleanup := make([]legacyFirewalldForward, 0, len(items))
|
||||
failures := make([]legacyFirewalldForwardFailure, 0)
|
||||
for _, item := range items {
|
||||
if _, err := forwarding.NormalizeRule(item.rule); err != nil {
|
||||
failures = append(failures, legacyFirewalldForwardFailure{spec: item.spec, err: err})
|
||||
continue
|
||||
}
|
||||
rules = append(rules, item.rule)
|
||||
cleanup = append(cleanup, item)
|
||||
}
|
||||
return rules, cleanup, failures
|
||||
}
|
||||
|
||||
func listLegacyIptablesForwarding() ([]forwarding.Rule, error) {
|
||||
exists, err := iptables_helper.CheckChainExist(iptables_helper.NatTab, forwarding.ChainPreRouting)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, nil
|
||||
}
|
||||
stdout, err := iptables_helper.RunWithStd(
|
||||
iptables_helper.NatTab, "-nvL", forwarding.ChainPreRouting, "--line-numbers",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list legacy iptables forwarding rules: %w", err)
|
||||
}
|
||||
return parseLegacyIptablesForwarding(stdout), nil
|
||||
}
|
||||
|
||||
func parseLegacyIptablesForwarding(stdout string) []forwarding.Rule {
|
||||
rules := make([]forwarding.Rule, 0)
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 13 {
|
||||
continue
|
||||
}
|
||||
rule := forwarding.Rule{
|
||||
Family: forwarding.FamilyIPv4, Protocol: loadLegacyIptablesProtocol(fields[4]),
|
||||
Interface: fields[6], Port: loadLegacyIptablesSourcePort(fields[11]),
|
||||
}
|
||||
if len(fields) == 15 && fields[13] == "ports" {
|
||||
rule.TargetPort = fields[14]
|
||||
}
|
||||
if len(fields) == 13 && strings.HasPrefix(fields[12], "to:") {
|
||||
target := strings.TrimPrefix(fields[12], "to:")
|
||||
separator := strings.LastIndex(target, ":")
|
||||
if separator > 0 {
|
||||
rule.TargetIP, rule.TargetPort = target[:separator], target[separator+1:]
|
||||
}
|
||||
}
|
||||
if rule.TargetIP == "" {
|
||||
rule.TargetIP = "127.0.0.1"
|
||||
}
|
||||
rule.TargetPort = strings.TrimPrefix(rule.TargetPort, ":")
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
func loadLegacyIptablesProtocol(protocol string) string {
|
||||
switch protocol {
|
||||
case "6":
|
||||
return "tcp"
|
||||
case "17":
|
||||
return "udp"
|
||||
default:
|
||||
return protocol
|
||||
}
|
||||
}
|
||||
|
||||
func loadLegacyIptablesSourcePort(value string) string {
|
||||
port := ""
|
||||
if strings.Contains(value, "dpt:") {
|
||||
port = strings.ReplaceAll(value, "dpt:", "")
|
||||
}
|
||||
if strings.Contains(value, "dpts:") {
|
||||
port = strings.ReplaceAll(value, "dpts:", "")
|
||||
}
|
||||
return strings.ReplaceAll(port, ":", "-")
|
||||
}
|
||||
|
||||
func listLegacyFirewalldForwarding() ([]legacyFirewalldForward, error) {
|
||||
stdout, err := cmd.NewCommandMgr().RunWithStdout(
|
||||
"firewall-cmd", "--permanent", "--zone=public", "--list-forward-ports",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list legacy firewalld forwarding rules: %w", err)
|
||||
}
|
||||
rules, failures := parseLegacyFirewalldForwarding(stdout)
|
||||
for _, failure := range failures {
|
||||
if global.LOG != nil {
|
||||
global.LOG.Warnf("skip unsupported legacy firewalld forwarding rule %q: %v", failure.spec, failure.err)
|
||||
}
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func parseLegacyFirewalldForwarding(stdout string) ([]legacyFirewalldForward, []legacyFirewalldForwardFailure) {
|
||||
result := make([]legacyFirewalldForward, 0)
|
||||
failures := make([]legacyFirewalldForwardFailure, 0)
|
||||
for _, spec := range strings.Fields(stdout) {
|
||||
item, err := parseLegacyFirewalldForward(spec)
|
||||
if err != nil {
|
||||
failures = append(failures, legacyFirewalldForwardFailure{spec: spec, err: err})
|
||||
continue
|
||||
}
|
||||
result = append(result, item)
|
||||
}
|
||||
return result, failures
|
||||
}
|
||||
|
||||
func parseLegacyFirewalldForward(spec string) (legacyFirewalldForward, error) {
|
||||
if !strings.HasPrefix(spec, "port=") {
|
||||
return legacyFirewalldForward{}, errors.New("missing port field")
|
||||
}
|
||||
port, rest, ok := strings.Cut(strings.TrimPrefix(spec, "port="), ":proto=")
|
||||
if !ok {
|
||||
return legacyFirewalldForward{}, errors.New("missing protocol field")
|
||||
}
|
||||
protocol, rest, ok := strings.Cut(rest, ":toport=")
|
||||
if !ok {
|
||||
return legacyFirewalldForward{}, errors.New("missing target port field")
|
||||
}
|
||||
targetPort, targetIP, ok := strings.Cut(rest, ":toaddr=")
|
||||
if !ok {
|
||||
return legacyFirewalldForward{}, errors.New("missing target address field")
|
||||
}
|
||||
if targetIP == "" {
|
||||
targetIP = "127.0.0.1"
|
||||
}
|
||||
return legacyFirewalldForward{
|
||||
rule: forwarding.Rule{
|
||||
Family: forwarding.FamilyIPv4, Protocol: protocol, Port: port,
|
||||
TargetIP: targetIP, TargetPort: targetPort,
|
||||
},
|
||||
spec: spec,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func cleanupLegacyFirewalldForwarding(rules []legacyFirewalldForward) error {
|
||||
manager := cmd.NewCommandMgr()
|
||||
for _, item := range rules {
|
||||
if err := manager.Run(
|
||||
"firewall-cmd", "--permanent", "--zone=public", "--remove-forward-port="+item.spec,
|
||||
); err != nil {
|
||||
return fmt.Errorf("remove legacy firewalld forwarding rule %q: %w", item.spec, err)
|
||||
}
|
||||
}
|
||||
if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := manager.Run("firewall-cmd", "--reload"); err != nil {
|
||||
return fmt.Errorf("reload firewalld after forwarding transfer: %w", err)
|
||||
}
|
||||
return restartDockerAfterFirewalldReload(cmd.Which, controller.CheckActive, controller.HandleRestart)
|
||||
}
|
||||
|
||||
func restartDockerAfterFirewalldReload(
|
||||
which func(string) bool,
|
||||
checkActive func(string) (bool, error),
|
||||
restart func(string) error,
|
||||
) error {
|
||||
const service = "docker"
|
||||
if !which(service) {
|
||||
return nil
|
||||
}
|
||||
active, err := checkActive(service)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check Docker status after reloading firewalld: %w", err)
|
||||
}
|
||||
if !active {
|
||||
return nil
|
||||
}
|
||||
if err := restart(service); err != nil {
|
||||
return fmt.Errorf("restart Docker after reloading firewalld: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||
"github.com/google/uuid"
|
||||
filterfirewalld "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter/providers/firewalld"
|
||||
filterufw "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter/providers/ufw"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const hostFirewallTransferMigrationID = "host-firewall-transfer"
|
||||
|
||||
var errUnsupportedLegacyHostFirewallRule = errors.New("unsupported legacy host firewall rule")
|
||||
|
||||
type legacyHostFirewallRecord struct {
|
||||
@@ -34,126 +33,225 @@ type legacyHostFirewallRecord struct {
|
||||
Description string
|
||||
}
|
||||
|
||||
func TransferHostFirewall(ctx context.Context, provider string) error {
|
||||
if global.DB == nil {
|
||||
return errors.New("host firewall transfer database is required")
|
||||
}
|
||||
return transferHostFirewall(ctx, global.DB, filter.Provider(strings.ToLower(strings.TrimSpace(provider))))
|
||||
type legacyFirewallRuleDescription struct {
|
||||
UUID string
|
||||
Provider string
|
||||
ScopeKey string
|
||||
Location string
|
||||
Family string
|
||||
NativeKind string
|
||||
Protocol string
|
||||
SourceAddress string
|
||||
SourcePort string
|
||||
DestinationAddress string
|
||||
DestinationPort string
|
||||
Interface string
|
||||
ConnectionStates string
|
||||
CompatibilityError string
|
||||
Action string
|
||||
Priority *int
|
||||
Description string
|
||||
Owner string
|
||||
}
|
||||
|
||||
func TransferLegacyHostFirewallRuleOwnership(ctx context.Context, provider string, transfer func(context.Context) error) error {
|
||||
if global.DB == nil {
|
||||
return errors.New("host firewall transfer database is required")
|
||||
func MigrateHostFirewallDescriptions(db *gorm.DB) error {
|
||||
providers := []filter.Provider{filter.ProviderIptables, filter.ProviderNftables, filter.ProviderFirewalld, filter.ProviderUFW}
|
||||
descriptions := make(map[string][]string)
|
||||
add := func(id, description string) {
|
||||
if description != "" && !slices.Contains(descriptions[id], description) {
|
||||
descriptions[id] = append(descriptions[id], description)
|
||||
}
|
||||
}
|
||||
return transferLegacyHostFirewallRuleOwnership(
|
||||
ctx,
|
||||
global.DB,
|
||||
filter.Provider(strings.ToLower(strings.TrimSpace(provider))),
|
||||
transfer,
|
||||
)
|
||||
}
|
||||
|
||||
func transferLegacyHostFirewallRuleOwnership(
|
||||
ctx context.Context,
|
||||
db *gorm.DB,
|
||||
provider filter.Provider,
|
||||
transfer func(context.Context) error,
|
||||
) error {
|
||||
if !legacyHostFirewallOwnershipProvider(provider) {
|
||||
return nil
|
||||
addRules := func(rules []filter.FirewallRule, description string) bool {
|
||||
matched := false
|
||||
for _, rule := range rules {
|
||||
var err error
|
||||
switch rule.Scope.Provider {
|
||||
case filter.ProviderFirewalld:
|
||||
rule, err = (&filterfirewalld.Adapter{}).PrepareRule(rule)
|
||||
case filter.ProviderUFW:
|
||||
rule, err = (&filterufw.Adapter{}).PrepareRule(rule)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
id, err := filter.DescriptionID(filter.ObservedRule{Rule: rule, ParseStatus: filter.ParseStatusSupported})
|
||||
if err == nil {
|
||||
add(id, description)
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
if db == nil {
|
||||
return errors.New("host firewall transfer database is required")
|
||||
}
|
||||
completed, err := migrationRecordExists(db, hostFirewallTransferMigrationID)
|
||||
if err != nil || completed {
|
||||
return err
|
||||
}
|
||||
if transfer == nil {
|
||||
return errors.New("legacy host firewall ownership transfer is required")
|
||||
}
|
||||
if err := transfer(ctx); err != nil {
|
||||
return fmt.Errorf("transfer legacy host firewall rule ownership: %w", err)
|
||||
}
|
||||
return markMigrationRecord(db, hostFirewallTransferMigrationID)
|
||||
}
|
||||
|
||||
func legacyHostFirewallOwnershipProvider(provider filter.Provider) bool {
|
||||
return provider == filter.ProviderIptables || provider == filter.ProviderUFW
|
||||
}
|
||||
|
||||
func transferHostFirewall(ctx context.Context, db *gorm.DB, provider filter.Provider) error {
|
||||
if db == nil {
|
||||
return errors.New("host firewall transfer database is required")
|
||||
}
|
||||
completed, err := migrationRecordExists(db, hostFirewallTransferMigrationID)
|
||||
if err != nil || completed {
|
||||
return err
|
||||
}
|
||||
if !isLegacyHostFirewallProvider(provider) {
|
||||
return fmt.Errorf("unsupported legacy host firewall provider %q", provider)
|
||||
}
|
||||
|
||||
models := make([]model.FirewallRule, 0)
|
||||
if db.Migrator().HasTable("firewalls") {
|
||||
var records []legacyHostFirewallRecord
|
||||
if err := db.WithContext(ctx).Table("firewalls").Order("id ASC").Find(&records).Error; err != nil {
|
||||
return fmt.Errorf("load legacy host firewall records: %w", err)
|
||||
}
|
||||
models = convertLegacyHostFirewallRecords(records, provider)
|
||||
}
|
||||
|
||||
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := importLegacyHostFirewallRules(tx, models); err != nil {
|
||||
if err := db.Table("firewalls").Order("id ASC").Find(&records).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if legacyHostFirewallOwnershipProvider(provider) {
|
||||
return nil
|
||||
for _, record := range records {
|
||||
if record.Description == "" {
|
||||
continue
|
||||
}
|
||||
matched := false
|
||||
for _, provider := range providers {
|
||||
rules, err := legacyHostFirewallRules(record, provider)
|
||||
if err == nil && addRules(rules, record.Description) {
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
add(fmt.Sprintf("firewall:legacy:firewalls:%d", record.ID), record.Description)
|
||||
}
|
||||
}
|
||||
return markMigrationRecord(tx, hostFirewallTransferMigrationID)
|
||||
})
|
||||
}
|
||||
|
||||
func isLegacyHostFirewallProvider(provider filter.Provider) bool {
|
||||
switch provider {
|
||||
case filter.ProviderIptables, filter.ProviderNftables, filter.ProviderFirewalld, filter.ProviderUFW:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
if db.Migrator().HasTable("firewall_rules") {
|
||||
var records []legacyFirewallRuleDescription
|
||||
if err := db.Table("firewall_rules").Order("uuid ASC").Find(&records).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, record := range records {
|
||||
if record.Description == "" {
|
||||
continue
|
||||
}
|
||||
matched := false
|
||||
for _, provider := range providers {
|
||||
rules, err := legacyFirewallDescriptionRules(record, provider)
|
||||
if err == nil && addRules(rules, record.Description) {
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
add("firewall:legacy:firewall_rules:"+record.UUID, record.Description)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(descriptions) == 0 {
|
||||
return nil
|
||||
}
|
||||
var existing []model.CommonDescription
|
||||
if err := db.Where("type = ?", "firewall").Find(&existing).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
byID := make(map[string]model.CommonDescription, len(existing))
|
||||
for _, description := range existing {
|
||||
byID[description.ID] = description
|
||||
}
|
||||
ids := make([]string, 0, len(descriptions))
|
||||
for id := range descriptions {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Strings(ids)
|
||||
for _, id := range ids {
|
||||
value := strings.Join(descriptions[id], "\n")
|
||||
if current, ok := byID[id]; ok {
|
||||
if current.Description == value {
|
||||
continue
|
||||
}
|
||||
if current.Description == "" {
|
||||
if err := db.Model(&model.CommonDescription{}).Where("id = ?", id).Update("description", value).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
id = "firewall:legacy:description:" + strings.TrimPrefix(id, "firewall:")
|
||||
if _, ok := byID[id]; ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
record := model.CommonDescription{ID: id, Type: "firewall", Description: value}
|
||||
if strings.HasPrefix(id, "firewall:legacy:") {
|
||||
record.DetailType = "legacy"
|
||||
}
|
||||
if err := db.Create(&record).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertLegacyHostFirewallRecords(records []legacyHostFirewallRecord, provider filter.Provider) []model.FirewallRule {
|
||||
converted := make([]model.FirewallRule, 0, len(records))
|
||||
byIdentity := make(map[string]int)
|
||||
for _, record := range records {
|
||||
rules, err := legacyHostFirewallRules(record, provider)
|
||||
func legacyFirewallDescriptionRules(record legacyFirewallRuleDescription, provider filter.Provider) ([]filter.FirewallRule, error) {
|
||||
if record.CompatibilityError != "" {
|
||||
return nil, errUnsupportedLegacyHostFirewallRule
|
||||
}
|
||||
source := filter.Provider(strings.ToLower(strings.TrimSpace(record.Provider)))
|
||||
scopeParts := strings.Split(record.ScopeKey, ":")
|
||||
if source == "" && len(scopeParts) > 1 {
|
||||
source = filter.Provider(scopeParts[0])
|
||||
}
|
||||
if source != "" && source != provider {
|
||||
return nil, nil
|
||||
}
|
||||
base := filter.FirewallRule{
|
||||
Protocol: record.Protocol, SourceAddress: record.SourceAddress, SourcePort: record.SourcePort,
|
||||
DestinationAddress: record.DestinationAddress, DestinationPort: record.DestinationPort,
|
||||
Interface: record.Interface, Action: filter.Action(record.Action),
|
||||
}
|
||||
if record.ConnectionStates != "" {
|
||||
base.ConnectionStates = strings.Split(record.ConnectionStates, ",")
|
||||
}
|
||||
if source != "" {
|
||||
base.NativeKind = filter.NativeKind(record.NativeKind)
|
||||
}
|
||||
switch base.NativeKind {
|
||||
case filter.NativeKindOpaque, filter.NativeKindZoneService, filter.NativeKindUFWApplication:
|
||||
return nil, errUnsupportedLegacyHostFirewallRule
|
||||
}
|
||||
if provider != filter.ProviderUFW && strings.EqualFold(base.Protocol, "all") && base.SourcePort == "" && base.DestinationPort != "" {
|
||||
base.Protocol = "tcp/udp"
|
||||
}
|
||||
if provider == filter.ProviderFirewalld {
|
||||
base.Priority = record.Priority
|
||||
}
|
||||
families := []filter.Family{filter.Family(record.Family)}
|
||||
if families[0] == "" {
|
||||
families[0] = legacyRuleFamily(base.SourceAddress, base.DestinationAddress)
|
||||
}
|
||||
if provider != filter.ProviderFirewalld && families[0] == filter.FamilyInet {
|
||||
if base.SourceAddress != "" || base.DestinationAddress != "" || base.Protocol == "icmpv6" {
|
||||
families = []filter.Family{legacyRuleFamily(base.SourceAddress, base.DestinationAddress)}
|
||||
if base.Protocol == "icmpv6" {
|
||||
families[0] = filter.FamilyIPv6
|
||||
}
|
||||
} else {
|
||||
families = []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6}
|
||||
}
|
||||
}
|
||||
var result []filter.FirewallRule
|
||||
for _, family := range families {
|
||||
rule := base
|
||||
rule.Scope = filter.Scope{Provider: provider, Family: family, Direction: filter.DirectionInput}
|
||||
switch provider {
|
||||
case filter.ProviderIptables, filter.ProviderNftables:
|
||||
rule.Scope.Table, rule.Scope.Chain = "filter", filter.IptablesInputChain
|
||||
if source != "" {
|
||||
if record.Location != "" {
|
||||
rule.Scope.Chain = record.Location
|
||||
}
|
||||
if len(scopeParts) == 5 {
|
||||
rule.Scope.Table, rule.Scope.Chain = scopeParts[2], scopeParts[3]
|
||||
}
|
||||
}
|
||||
case filter.ProviderFirewalld:
|
||||
rule.Scope.Zone = filter.FirewalldInputZone
|
||||
if source != "" && record.Location != "" {
|
||||
rule.Scope.Zone = record.Location
|
||||
}
|
||||
case filter.ProviderUFW:
|
||||
rule.Scope.Chain = filter.UFWInputChain
|
||||
}
|
||||
expanded, err := filter.ExpandAtomicRules(rule)
|
||||
if err != nil {
|
||||
if global.LOG != nil {
|
||||
global.LOG.Warnf("skip legacy host firewall record %d during transfer: %v", record.ID, err)
|
||||
}
|
||||
continue
|
||||
return nil, err
|
||||
}
|
||||
for _, rule := range rules {
|
||||
item, err := hostFirewallRuleModel(rule)
|
||||
if err != nil {
|
||||
if global.LOG != nil {
|
||||
global.LOG.Warnf("skip legacy host firewall record %d during transfer: %v", record.ID, err)
|
||||
}
|
||||
continue
|
||||
result = append(result, expanded...)
|
||||
if source == "" && (provider == filter.ProviderIptables || provider == filter.ProviderNftables) && strings.HasPrefix(record.Owner, constant.FirewallRuleSourceSecurity+":"+constant.FirewallSystemAcceptedPortSourcePrefix) {
|
||||
for _, item := range expanded {
|
||||
item.Scope.Chain = filter.BasicBeforeChain
|
||||
result = append(result, item)
|
||||
}
|
||||
identity := item.PolicyKey()
|
||||
if index, exists := byIdentity[identity]; exists {
|
||||
if item.Description != "" {
|
||||
converted[index].Description = item.Description
|
||||
}
|
||||
continue
|
||||
}
|
||||
byIdentity[identity] = len(converted)
|
||||
converted = append(converted, item)
|
||||
}
|
||||
}
|
||||
return converted
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func legacyHostFirewallRules(record legacyHostFirewallRecord, provider filter.Provider) ([]filter.FirewallRule, error) {
|
||||
@@ -193,7 +291,7 @@ func legacyHostFirewallRules(record legacyHostFirewallRecord, provider filter.Pr
|
||||
switch provider {
|
||||
case filter.ProviderIptables:
|
||||
rule.Scope = filter.Scope{
|
||||
Provider: provider, Family: filter.FamilyIPv4, Table: "filter",
|
||||
Provider: provider, Family: legacyRuleFamily(rule.SourceAddress, rule.DestinationAddress), Table: "filter",
|
||||
Chain: legacyIptablesChain(record), Direction: filter.DirectionInput,
|
||||
}
|
||||
rule.NativeKind = filter.NativeKindRule
|
||||
@@ -334,43 +432,3 @@ func legacyIPOrPrefix(value string) bool {
|
||||
_, err := netip.ParsePrefix(value)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func hostFirewallRuleModel(rule filter.FirewallRule) (model.FirewallRule, error) {
|
||||
record, err := model.FirewallRuleFromDomain(rule)
|
||||
if err != nil {
|
||||
return model.FirewallRule{}, err
|
||||
}
|
||||
record.UUID = uuid.NewString()
|
||||
record.Origin = constant.FirewallRuleOriginAdopted
|
||||
record.Owner = constant.FirewallRuleSourceUser
|
||||
record.Revision = 1
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func importLegacyHostFirewallRules(tx *gorm.DB, rules []model.FirewallRule) error {
|
||||
var existing []model.FirewallRule
|
||||
if err := tx.Find(&existing).Error; err != nil {
|
||||
return fmt.Errorf("load current host firewall rules: %w", err)
|
||||
}
|
||||
byIdentity := make(map[string]model.FirewallRule, len(existing))
|
||||
for _, item := range existing {
|
||||
byIdentity[item.PolicyKey()] = item
|
||||
}
|
||||
for _, item := range rules {
|
||||
identity := item.PolicyKey()
|
||||
if current, exists := byIdentity[identity]; exists {
|
||||
if current.Description == "" && item.Description != "" {
|
||||
if err := tx.Model(&model.FirewallRule{}).Where("uuid = ?", current.UUID).
|
||||
Update("description", item.Description).Error; err != nil {
|
||||
return fmt.Errorf("restore legacy host firewall description: %w", err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := tx.Create(&item).Error; err != nil {
|
||||
return fmt.Errorf("import legacy host firewall rule: %w", err)
|
||||
}
|
||||
byIdentity[identity] = item
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,6 +20,12 @@ func Init() {
|
||||
if err := validator.RegisterValidation("password", checkPasswordPattern); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := validator.RegisterValidation("vm_name", checkVMNamePattern); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := validator.RegisterValidation("vm_common", checkVMCommonPattern); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
global.VALID = validator
|
||||
}
|
||||
|
||||
@@ -55,3 +61,12 @@ func checkPasswordPattern(fl validator.FieldLevel) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkVMNamePattern(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
return re.GetRegex(re.VMNameValidationPattern).MatchString(value)
|
||||
}
|
||||
func checkVMCommonPattern(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
return re.GetRegex(re.VMCommonPattern).MatchString(value)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter.GET("/gpu/load", baseApi.LoadGpuInfo)
|
||||
aiToolsRouter.POST("/gpu/search", baseApi.LoadGPUMonitor)
|
||||
aiToolsRouter.GET("/gpu/options", baseApi.GetCPUOptions)
|
||||
aiToolsRouter.POST("/vllm/monitor/search", baseApi.LoadVLLMMonitor)
|
||||
aiToolsRouter.POST("/vllm/monitor/current", baseApi.LoadVLLMCurrent)
|
||||
aiToolsRouter.POST("/vllm/monitor/clean", baseApi.CleanVLLMMonitor)
|
||||
|
||||
aiToolsRouter.POST("/mcp/search", baseApi.PageMcpServers)
|
||||
aiToolsRouter.POST("/mcp/server/detail", baseApi.LoadMcpServerDetail)
|
||||
|
||||
@@ -11,6 +11,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
hostRouter := Router.Group("hosts")
|
||||
baseApi := v2.ApiGroupApp.BaseApi
|
||||
Router.POST("/internal/terminal/sessions/revoke", baseApi.RevokeTerminalSessions)
|
||||
Router.GET("/internal/terminal/capabilities", baseApi.TerminalCapabilities)
|
||||
{
|
||||
hostRouter.POST("", baseApi.CreateHost)
|
||||
hostRouter.POST("/info", baseApi.GetHostByID)
|
||||
@@ -27,6 +28,8 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
hostRouter.POST("/firewall/port", baseApi.UpdatePanelFirewallPort)
|
||||
hostRouter.GET("/firewall/settings", baseApi.LoadFirewallSettings)
|
||||
hostRouter.POST("/firewall/settings/operate", baseApi.OperateFirewallBackend)
|
||||
hostRouter.POST("/firewall/family/operate", baseApi.OperateFirewallFamily)
|
||||
hostRouter.POST("/firewall/settings/ipv6", baseApi.OperateFirewallIPv6)
|
||||
hostRouter.POST("/firewall/settings/whitelist", baseApi.CreateFirewallPortWhitelist)
|
||||
hostRouter.POST("/firewall/settings/whitelist/update", baseApi.UpdateFirewallPortWhitelist)
|
||||
hostRouter.POST("/firewall/settings/whitelist/delete", baseApi.DeleteFirewallPortWhitelist)
|
||||
@@ -35,13 +38,10 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
hostRouter.POST("/firewall/forward/operate", baseApi.OperateForwardingRules)
|
||||
hostRouter.POST("/firewall/forward/enable", baseApi.EnableForwarding)
|
||||
hostRouter.POST("/firewall/rules/search", baseApi.SearchFirewallRules)
|
||||
hostRouter.GET("/firewall/rules/backups", baseApi.ListFirewallRuleBackups)
|
||||
hostRouter.POST("/firewall/rules/reset", baseApi.ResetFirewallRules)
|
||||
hostRouter.POST("/firewall/rules/native/detail", baseApi.LoadFirewallNativeDetail)
|
||||
hostRouter.POST("/firewall/rules/adopt", baseApi.AdoptFirewallRule)
|
||||
hostRouter.POST("/firewall/rules", baseApi.CreateFirewallRules)
|
||||
hostRouter.POST("/firewall/rules/sync/preview", baseApi.PreviewFirewallRuleSync)
|
||||
hostRouter.GET("/firewall/rules/sync/task", baseApi.LoadFirewallRuleSyncTask)
|
||||
hostRouter.POST("/firewall/rules/sync", baseApi.SyncFirewallRules)
|
||||
hostRouter.POST("/firewall/rules/update", baseApi.UpdateFirewallRule)
|
||||
hostRouter.POST("/firewall/rules/delete", baseApi.DeleteFirewallRules)
|
||||
hostRouter.POST("/firewall/rules/reorder", baseApi.ReorderFirewallRule)
|
||||
@@ -49,7 +49,6 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
hostRouter.POST("/firewall/filter/operate", baseApi.OperateFilterChain)
|
||||
hostRouter.GET("/firewall/docker/ports", baseApi.ListDockerPortGuard)
|
||||
hostRouter.GET("/firewall/docker/endpoints", baseApi.ListDockerPublishedPorts)
|
||||
hostRouter.POST("/firewall/docker/sync", baseApi.SyncDockerPortGuard)
|
||||
hostRouter.POST("/firewall/docker/operate", baseApi.OperateDockerPortGuard)
|
||||
hostRouter.POST("/firewall/docker/policies/batch", baseApi.UpsertDockerPortGuardPolicies)
|
||||
hostRouter.POST("/firewall/docker/policies/delete/batch", baseApi.DeleteDockerPortGuardPolicies)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
|
||||
@@ -22,14 +23,6 @@ type providerResult struct {
|
||||
}
|
||||
|
||||
func New() (bool, Client) {
|
||||
return newClient()
|
||||
}
|
||||
|
||||
func NewAll() (bool, Client) {
|
||||
return New()
|
||||
}
|
||||
|
||||
func newClient() (bool, Client) {
|
||||
client := Client{}
|
||||
if available, gpuClient := gpu.New(); available {
|
||||
client.providers = append(client.providers, gpuProvider{client: gpuClient})
|
||||
@@ -52,6 +45,11 @@ func (c Client) LoadInfo() (*Info, 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))
|
||||
var wg sync.WaitGroup
|
||||
for index, item := range c.providers {
|
||||
@@ -66,8 +64,11 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
snapshot := &Snapshot{DriverVersions: make(map[string]string)}
|
||||
snapshot := &Snapshot{}
|
||||
var (
|
||||
errs []error
|
||||
active []*ProviderSnapshot
|
||||
@@ -78,6 +79,11 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
||||
errs = append(errs, result.err)
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
@@ -90,11 +96,8 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
||||
if item.CudaVersion != "" {
|
||||
snapshot.Info.CudaVersion = item.CudaVersion
|
||||
}
|
||||
if item.DriverVersion != "" {
|
||||
snapshot.DriverVersions[item.Type] = item.DriverVersion
|
||||
if item.Type == "xpu" {
|
||||
xpuVersion = item.DriverVersion
|
||||
}
|
||||
if item.Type == "xpu" {
|
||||
xpuVersion = item.DriverVersion
|
||||
}
|
||||
}
|
||||
snapshot.Warnings = append(snapshot.Warnings, errs...)
|
||||
@@ -102,6 +105,10 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
||||
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.Type, snapshot.Info.DriverVersion = mergeProviderMetadata(active)
|
||||
return snapshot, nil
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package accelerator
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -18,11 +19,11 @@ func metric(display, unit string) Metric {
|
||||
return result
|
||||
}
|
||||
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
|
||||
}
|
||||
normalized, ok := convertMetricUnit(parsed, matched[2], unit)
|
||||
if !ok {
|
||||
if !ok || math.IsNaN(normalized) || math.IsInf(normalized, 0) || (unit == "%" && normalized > 100) {
|
||||
return result
|
||||
}
|
||||
result.Value = &normalized
|
||||
|
||||
@@ -12,26 +12,38 @@ import (
|
||||
|
||||
func normalizeGPU(item *gpu.Device) Device {
|
||||
metrics := Metrics{
|
||||
Utilization: metric(item.GPUUtil, "%"),
|
||||
Temperature: metric(item.Temperature, "°C"),
|
||||
Power: metric(item.PowerDraw, "W"),
|
||||
PowerLimit: metric(item.MaxPowerLimit, "W"),
|
||||
MemoryUsed: memoryMetric(item.MemUsed),
|
||||
MemoryTotal: memoryMetric(item.MemTotal),
|
||||
FanSpeed: metric(item.FanSpeed, "%"),
|
||||
MemoryActivity: metric(item.MemoryActivity, "%"),
|
||||
EncoderUtil: metric(item.EncoderUtil, "%"),
|
||||
DecoderUtil: metric(item.DecoderUtil, "%"),
|
||||
JPEGUtil: metric(item.JPEGUtil, "%"),
|
||||
OFAUtil: metric(item.OFAUtil, "%"),
|
||||
MediaUtil: metric(item.MediaUtil, "%"),
|
||||
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{
|
||||
ID: stableID(item.Type, item.BusID, strconv.FormatUint(uint64(item.Index), 10)),
|
||||
Kind: KindGPU,
|
||||
Vendor: item.Type,
|
||||
Index: int(item.Index),
|
||||
Name: item.ProductName,
|
||||
Label: fmt.Sprintf("%d - %s", item.Index, item.ProductName),
|
||||
BusID: item.BusID,
|
||||
Metrics: metrics,
|
||||
GPU: item,
|
||||
ProcessStatus: item.ProcessStatus,
|
||||
ID: stableID(item.Type, item.UUID, stableID("pci", item.BusID, strconv.FormatUint(uint64(item.Index), 10))),
|
||||
Kind: KindGPU,
|
||||
Vendor: item.Type,
|
||||
Index: int(item.Index),
|
||||
Name: item.ProductName,
|
||||
Label: fmt.Sprintf("%d - %s", item.Index, item.ProductName),
|
||||
BusID: item.BusID,
|
||||
Metrics: metrics,
|
||||
GPU: item,
|
||||
}
|
||||
device.Capabilities = capabilities(metrics)
|
||||
for _, process := range item.Processes {
|
||||
device.Processes = append(device.Processes, Process{
|
||||
PID: process.PID,
|
||||
@@ -45,26 +57,36 @@ func normalizeGPU(item *gpu.Device) Device {
|
||||
|
||||
func normalizeNPU(item *npu.Device) Device {
|
||||
metrics := Metrics{
|
||||
Utilization: metric(item.AICore, "%"),
|
||||
Temperature: metric(item.Temperature, "°C"),
|
||||
Power: metric(item.PowerDraw, "W"),
|
||||
MemoryUsed: memoryMetric(item.MemUsed),
|
||||
MemoryTotal: memoryMetric(item.MemTotal),
|
||||
AICPUUtil: metric(item.AICPUUtil, "%"),
|
||||
CtrlCPUUtil: metric(item.CtrlCPUUtil, "%"),
|
||||
DDRBandwidth: metric(item.DDRBandwidth, "%"),
|
||||
HBMBandwidth: metric(item.HBMBandwidth, "%"),
|
||||
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{
|
||||
ID: fmt.Sprintf("ascend:%d:%d", item.NPUIndex, item.ChipIndex),
|
||||
Kind: KindNPU,
|
||||
Vendor: "ascend",
|
||||
Index: int(item.Index),
|
||||
NPUIndex: int(item.NPUIndex),
|
||||
ChipIndex: int(item.ChipIndex),
|
||||
Name: item.ProductName,
|
||||
Label: fmt.Sprintf("NPU %d / Chip %d - %s", item.NPUIndex, item.ChipIndex, item.ProductName),
|
||||
BusID: item.BusID,
|
||||
Metrics: metrics,
|
||||
NPU: item,
|
||||
ProcessStatus: item.ProcessStatus,
|
||||
ID: fmt.Sprintf("%s:%d", stableID("ascend", item.BusID, strconv.FormatUint(uint64(item.NPUIndex), 10)), item.ChipIndex),
|
||||
Kind: KindNPU,
|
||||
Vendor: "ascend",
|
||||
Index: int(item.Index),
|
||||
NPUIndex: int(item.NPUIndex),
|
||||
ChipIndex: int(item.ChipIndex),
|
||||
Name: item.ProductName,
|
||||
Label: fmt.Sprintf("NPU %d / Chip %d - %s", item.NPUIndex, item.ChipIndex, item.ProductName),
|
||||
BusID: item.BusID,
|
||||
Metrics: metrics,
|
||||
NPU: item,
|
||||
}
|
||||
device.Capabilities = capabilities(metrics)
|
||||
for _, process := range item.Processes {
|
||||
device.Processes = append(device.Processes, Process{
|
||||
PID: process.PID,
|
||||
@@ -78,26 +100,32 @@ func normalizeNPU(item *npu.Device) Device {
|
||||
|
||||
func normalizeXPU(item *xpu.Device) Device {
|
||||
metrics := Metrics{
|
||||
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"),
|
||||
MediaUtil: metric(item.Stats.MediaUtil, "%"),
|
||||
ComputeUtil: metric(item.Stats.ComputeUtil, "%"),
|
||||
CopyUtil: metric(item.Stats.CopyUtil, "%"),
|
||||
MediaFrequency: metric(item.Stats.MediaFrequency, "MHz"),
|
||||
MemoryTemperature: metric(item.Stats.MemoryTemperature, "°C"),
|
||||
MemoryBandwidth: metric(item.Stats.MemoryBandwidthUtil, "%"),
|
||||
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{
|
||||
ID: stableID("xpu", item.Basic.PciBdfAddress, strconv.Itoa(item.Basic.DeviceID)),
|
||||
Kind: KindXPU,
|
||||
Vendor: item.Basic.VendorName,
|
||||
Index: item.Basic.DeviceID,
|
||||
Name: item.Basic.DeviceName,
|
||||
Label: fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName),
|
||||
BusID: item.Basic.PciBdfAddress,
|
||||
Metrics: metrics,
|
||||
XPU: item,
|
||||
ProcessStatus: item.ProcessStatus,
|
||||
ID: stableID("xpu", item.Basic.UUID, stableID("pci", item.Basic.PciBdfAddress, strconv.Itoa(item.Basic.DeviceID))),
|
||||
Kind: KindXPU,
|
||||
Vendor: item.Basic.VendorName,
|
||||
Index: item.Basic.DeviceID,
|
||||
Name: item.Basic.DeviceName,
|
||||
Label: fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName),
|
||||
BusID: item.Basic.PciBdfAddress,
|
||||
Metrics: metrics,
|
||||
XPU: item,
|
||||
}
|
||||
device.Capabilities = capabilities(metrics)
|
||||
for _, process := range item.Processes {
|
||||
device.Processes = append(device.Processes, Process{
|
||||
PID: strconv.Itoa(process.PID),
|
||||
@@ -110,18 +138,6 @@ func normalizeXPU(item *xpu.Device) 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 {
|
||||
if busID != "" && !strings.EqualFold(busID, "N/A") {
|
||||
return vendor + ":" + busID
|
||||
|
||||
@@ -2,6 +2,7 @@ package accelerator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
"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{
|
||||
Type: info.Type,
|
||||
Warnings: info.Warnings,
|
||||
DriverVersion: info.DriverVersion,
|
||||
CudaVersion: info.CudaVersion,
|
||||
GPUs: info.Devices,
|
||||
@@ -49,6 +51,7 @@ func (p npuProvider) Collect(ctx context.Context) (*ProviderSnapshot, error) {
|
||||
}
|
||||
result := &ProviderSnapshot{
|
||||
Type: info.Type,
|
||||
Warnings: info.Warnings,
|
||||
DriverVersion: info.DriverVersion,
|
||||
NPUs: info.Devices,
|
||||
}
|
||||
@@ -71,11 +74,24 @@ func (p xpuProvider) Collect(ctx context.Context) (*ProviderSnapshot, error) {
|
||||
}
|
||||
result := &ProviderSnapshot{
|
||||
Type: info.Type,
|
||||
Warnings: info.Warnings,
|
||||
DriverVersion: info.DriverVersion,
|
||||
XPUs: info.Devices,
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package accelerator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
|
||||
@@ -17,6 +18,9 @@ const (
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
CollectedAt time.Time `json:"collectedAt"`
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
Type string `json:"type"`
|
||||
CudaVersion string `json:"cudaVersion"`
|
||||
DriverVersion string `json:"driverVersion"`
|
||||
@@ -44,25 +48,40 @@ func (m Metric) ValueOrZero() float64 {
|
||||
}
|
||||
|
||||
type Metrics struct {
|
||||
Utilization Metric
|
||||
Temperature Metric
|
||||
Power Metric
|
||||
PowerLimit Metric
|
||||
MemoryUsed Metric
|
||||
MemoryTotal Metric
|
||||
MemoryUtil Metric
|
||||
FanSpeed Metric
|
||||
Frequency Metric
|
||||
}
|
||||
MemoryActivity Metric
|
||||
EncoderUtil Metric
|
||||
DecoderUtil Metric
|
||||
JPEGUtil Metric
|
||||
OFAUtil Metric
|
||||
MediaUtil Metric
|
||||
ComputeUtil Metric
|
||||
CopyUtil 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 {
|
||||
Utilization bool
|
||||
Temperature bool
|
||||
Power bool
|
||||
PowerLimit bool
|
||||
Memory bool
|
||||
FanSpeed bool
|
||||
Frequency bool
|
||||
MemoryTemperature Metric
|
||||
Utilization Metric
|
||||
Temperature Metric
|
||||
Power Metric
|
||||
PowerLimit Metric
|
||||
MemoryUsed Metric
|
||||
MemoryTotal Metric
|
||||
MemoryUtil Metric
|
||||
FanSpeed Metric
|
||||
Frequency Metric
|
||||
MemoryFrequency Metric
|
||||
}
|
||||
|
||||
type Process struct {
|
||||
@@ -74,18 +93,19 @@ type Process struct {
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
ID string
|
||||
Kind Kind
|
||||
Vendor string
|
||||
Index int
|
||||
NPUIndex int
|
||||
ChipIndex int
|
||||
Name string
|
||||
Label string
|
||||
BusID string
|
||||
Metrics Metrics
|
||||
Capabilities Capabilities
|
||||
Processes []Process
|
||||
ParentID string
|
||||
ProcessStatus string
|
||||
ID string
|
||||
Kind Kind
|
||||
Vendor string
|
||||
Index int
|
||||
NPUIndex int
|
||||
ChipIndex int
|
||||
Name string
|
||||
Label string
|
||||
BusID string
|
||||
Metrics Metrics
|
||||
Processes []Process
|
||||
|
||||
GPU *gpu.Device `json:"-"`
|
||||
NPU *npu.Device `json:"-"`
|
||||
@@ -93,10 +113,9 @@ type Device struct {
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
Info Info
|
||||
Devices []Device
|
||||
DriverVersions map[string]string
|
||||
Warnings []error
|
||||
Info Info
|
||||
Devices []Device
|
||||
Warnings []error
|
||||
}
|
||||
|
||||
func (s Snapshot) Warning() error {
|
||||
@@ -104,6 +123,7 @@ func (s Snapshot) Warning() error {
|
||||
}
|
||||
|
||||
type ProviderSnapshot struct {
|
||||
Warnings []string
|
||||
Type string
|
||||
DriverVersion string
|
||||
CudaVersion string
|
||||
|
||||
@@ -3,12 +3,13 @@ package gpu
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"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) {
|
||||
var (
|
||||
staticData string
|
||||
metricData string
|
||||
processData string
|
||||
staticErr error
|
||||
metricErr error
|
||||
processErr error
|
||||
wg sync.WaitGroup
|
||||
staticData string
|
||||
metricData string
|
||||
extendedData string
|
||||
extendedErr error
|
||||
processData string
|
||||
staticErr error
|
||||
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() {
|
||||
defer wg.Done()
|
||||
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)
|
||||
}
|
||||
|
||||
if metricErr == nil {
|
||||
metricErr = applyAMDMetrics(info, metricData)
|
||||
}
|
||||
if metricErr != nil {
|
||||
global.LOG.Warnf("calling %s metric failed, metrics will be omitted: %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)
|
||||
info.Warnings = append(info.Warnings, fmt.Sprintf("%s metrics: %v", a.command, metricErr))
|
||||
}
|
||||
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 {
|
||||
global.LOG.Warnf("calling %s process failed, process information will be omitted: %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)
|
||||
info.Warnings = append(info.Warnings, fmt.Sprintf("%s processes: %v", a.command, processErr))
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
@@ -96,6 +112,9 @@ func parseAMDStatic(data string) (*Info, error) {
|
||||
}
|
||||
device := Device{
|
||||
Type: "amd",
|
||||
UUID: amdStringAt(row, "uuid", "asic.uuid"),
|
||||
DriverVersion: amdStringAt(row, "driver.version", "driver_version", "amdgpu_version"),
|
||||
ProcessStatus: "unavailable",
|
||||
Index: index,
|
||||
ProductName: amdStringAt(row, "asic.market_name", "market_name", "gpu_name"),
|
||||
PersistenceMode: "N/A",
|
||||
@@ -111,6 +130,7 @@ func parseAMDStatic(data string) (*Info, error) {
|
||||
"limit.max_power_limit",
|
||||
"limit.max_power",
|
||||
),
|
||||
PowerLimit: amdMetricAt(row, "W", "limit.ppt0.socket_power_limit", "limit.socket_power_limit"),
|
||||
MemUsed: "N/A",
|
||||
MemTotal: "N/A",
|
||||
GPUUtil: "N/A",
|
||||
@@ -150,11 +170,31 @@ func applyAMDMetrics(info *Info, data string) error {
|
||||
continue
|
||||
}
|
||||
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.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.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 != "" {
|
||||
device.PerformanceState = value
|
||||
}
|
||||
@@ -177,6 +217,7 @@ func applyAMDProcesses(info *Info, data string) error {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
device.ProcessStatus = "ok"
|
||||
processList, _ := amdValueAt(row, "process_list")
|
||||
items := amdObjectList(processList)
|
||||
if len(items) == 0 {
|
||||
|
||||
@@ -64,6 +64,7 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
|
||||
if result.info == nil {
|
||||
continue
|
||||
}
|
||||
merged.Warnings = append(merged.Warnings, result.info.Warnings...)
|
||||
if 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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
)
|
||||
|
||||
@@ -24,41 +20,10 @@ func (n nvidiaSMI) LoadInfo(ctx context.Context) (*Info, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("calling %s failed: %w", nvidiaSMICommand, err)
|
||||
}
|
||||
data := []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)
|
||||
return parseNvidiaSMI([]byte(itemData))
|
||||
}
|
||||
|
||||
func parseNvidiaSMI(buf []byte, version string) (*Info, error) {
|
||||
func parseNvidiaSMI(buf []byte) (*Info, error) {
|
||||
var (
|
||||
s nvidiaSMIResponse
|
||||
info Info
|
||||
@@ -72,42 +37,84 @@ func parseNvidiaSMI(buf []byte, version string) (*Info, error) {
|
||||
info.DriverVersion = s.DriverVersion
|
||||
for i := range s.Gpu {
|
||||
gpuItem := Device{
|
||||
Type: "nvidia",
|
||||
Index: uint(i),
|
||||
ProductName: s.Gpu[i].ProductName,
|
||||
PersistenceMode: s.Gpu[i].PersistenceMode,
|
||||
BusID: s.Gpu[i].ID,
|
||||
DisplayActive: s.Gpu[i].DisplayActive,
|
||||
ECC: s.Gpu[i].EccErrors.Volatile.DramUncorrectable,
|
||||
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,
|
||||
}
|
||||
if version == "v12" || version == "v13" {
|
||||
gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.PowerDraw
|
||||
if gpuItem.PowerDraw == "" {
|
||||
gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.InstantPowerDraw
|
||||
}
|
||||
gpuItem.MaxPowerLimit = s.Gpu[i].GpuPowerReadings.CurrentPowerLimit
|
||||
} else {
|
||||
gpuItem.PowerDraw = s.Gpu[i].PowerReadings.PowerDraw
|
||||
gpuItem.MaxPowerLimit = s.Gpu[i].PowerReadings.MaxPowerLimit
|
||||
Type: "nvidia",
|
||||
MemoryActivity: s.Gpu[i].Utilization.MemoryUtil,
|
||||
EncoderUtil: s.Gpu[i].Utilization.EncoderUtil,
|
||||
DecoderUtil: s.Gpu[i].Utilization.DecoderUtil,
|
||||
JPEGUtil: s.Gpu[i].Utilization.JpegUtil,
|
||||
OFAUtil: s.Gpu[i].Utilization.OfaUtil,
|
||||
MediaFrequency: s.Gpu[i].Clocks.VideoClock,
|
||||
UUID: s.Gpu[i].UUID,
|
||||
DriverVersion: s.DriverVersion,
|
||||
Architecture: s.Gpu[i].ProductArchitecture,
|
||||
Frequency: s.Gpu[i].Clocks.GraphicsClock,
|
||||
MemoryFrequency: s.Gpu[i].Clocks.MemClock,
|
||||
MemoryTemperature: s.Gpu[i].Temperature.MemoryTemp,
|
||||
MemoryFree: s.Gpu[i].FbMemoryUsage.Free,
|
||||
MemoryReserved: s.Gpu[i].FbMemoryUsage.Reserved,
|
||||
PCIeGeneration: firstSMIValue(s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.DeviceCurrentLinkGen, s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.CurrentLinkGen),
|
||||
PCIeMaxGeneration: firstSMIValue(s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.MaxDeviceLinkGen, s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.MaxLinkGen),
|
||||
PCIeWidth: s.Gpu[i].Pci.PciGpuLinkInfo.LinkWidths.CurrentLinkWidth,
|
||||
PCIeMaxWidth: s.Gpu[i].Pci.PciGpuLinkInfo.LinkWidths.MaxLinkWidth,
|
||||
ProcessStatus: "unavailable",
|
||||
Index: uint(i),
|
||||
ProductName: s.Gpu[i].ProductName,
|
||||
PersistenceMode: s.Gpu[i].PersistenceMode,
|
||||
BusID: s.Gpu[i].ID,
|
||||
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 {
|
||||
gpuItem.Processes = append(gpuItem.Processes, Process{
|
||||
PID: process.Pid,
|
||||
Type: process.Type,
|
||||
ProcessName: process.ProcessName,
|
||||
UsedMemory: process.UsedMemory,
|
||||
})
|
||||
{Scope: "Volatile DRAM", Correctable: s.Gpu[i].EccErrors.Volatile.DramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Volatile.DramUncorrectable},
|
||||
{Scope: "Volatile SRAM", Correctable: s.Gpu[i].EccErrors.Volatile.SramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Volatile.SramUncorrectable},
|
||||
{Scope: "Aggregate DRAM", Correctable: s.Gpu[i].EccErrors.Aggregate.DramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Aggregate.DramUncorrectable},
|
||||
{Scope: "Aggregate SRAM", Correctable: s.Gpu[i].EccErrors.Aggregate.SramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Aggregate.SramUncorrectable},
|
||||
}
|
||||
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)
|
||||
}
|
||||
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
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
type nvidiaSMIResponse struct {
|
||||
AttachedGpus string `xml:"attached_gpus"`
|
||||
CudaVersion string `xml:"cuda_version"`
|
||||
DriverVersion string `xml:"driver_version"`
|
||||
XMLName xml.Name `xml:"nvidia_smi_log"`
|
||||
AttachedGpus string `xml:"attached_gpus"`
|
||||
CudaVersion string `xml:"cuda_version"`
|
||||
DriverVersion string `xml:"driver_version"`
|
||||
Gpu []struct {
|
||||
ID string `xml:"id,attr"`
|
||||
AccountedProcesses struct{} `xml:"accounted_processes"`
|
||||
@@ -37,16 +40,17 @@ type nvidiaSMIResponse struct {
|
||||
VideoClock string `xml:"video_clock"`
|
||||
} `xml:"clocks"`
|
||||
ClocksEventReasons struct {
|
||||
ClocksEventReasonApplicationsClocksSetting string `xml:"clocks_event_reason_applications_clocks_setting"`
|
||||
ClocksEventReasonDisplayClocksSetting string `xml:"clocks_event_reason_display_clocks_setting"`
|
||||
ClocksEventReasonGpuIdle string `xml:"clocks_event_reason_gpu_idle"`
|
||||
ClocksEventReasonHwPowerBrakeSlowdown string `xml:"clocks_event_reason_hw_power_brake_slowdown"`
|
||||
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"`
|
||||
Reasons []struct {
|
||||
XMLName xml.Name
|
||||
Value string `xml:",chardata"`
|
||||
} `xml:",any"`
|
||||
} `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"`
|
||||
DefaultApplicationsClocks struct {
|
||||
GraphicsClock string `xml:"graphics_clock"`
|
||||
@@ -63,12 +67,24 @@ type nvidiaSMIResponse struct {
|
||||
} `xml:"driver_model"`
|
||||
EccErrors 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"`
|
||||
DramUncorrectable string `xml:"dram_uncorrectable"`
|
||||
SramCorrectable string `xml:"sram_correctable"`
|
||||
SramUncorrectable string `xml:"sram_uncorrectable"`
|
||||
} `xml:"aggregate"`
|
||||
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"`
|
||||
DramUncorrectable string `xml:"dram_uncorrectable"`
|
||||
SramCorrectable string `xml:"sram_correctable"`
|
||||
@@ -226,7 +242,8 @@ type nvidiaSMIResponse struct {
|
||||
MinPowerLimit string `xml:"min_power_limit"`
|
||||
MaxPowerLimit string `xml:"max_power_limit"`
|
||||
} `xml:"power_readings"`
|
||||
Processes struct {
|
||||
Processes *struct {
|
||||
Text string `xml:",chardata"`
|
||||
ProcessInfo []struct {
|
||||
Pid string `xml:"pid"`
|
||||
Type string `xml:"type"`
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package gpu
|
||||
|
||||
type Info struct {
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
CudaVersion string `json:"cudaVersion"`
|
||||
DriverVersion string `json:"driverVersion"`
|
||||
Type string `json:"type"`
|
||||
@@ -8,7 +10,42 @@ type Info struct {
|
||||
Devices []Device `json:"gpu"`
|
||||
}
|
||||
|
||||
type ECCError struct {
|
||||
Scope string `json:"scope"`
|
||||
Correctable string `json:"correctable"`
|
||||
Uncorrectable string `json:"uncorrectable"`
|
||||
}
|
||||
|
||||
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"`
|
||||
Index uint `json:"index"`
|
||||
ProductName string `json:"productName"`
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
@@ -34,7 +35,31 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
|
||||
if err != nil {
|
||||
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 {
|
||||
@@ -158,6 +183,12 @@ func parseAscendSMI(data string) *Info {
|
||||
pending = nil
|
||||
}
|
||||
|
||||
for i := range info.Devices {
|
||||
info.Devices[i].ProcessStatus = "unavailable"
|
||||
if processSection {
|
||||
info.Devices[i].ProcessStatus = "ok"
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
@@ -208,6 +239,9 @@ func ascendMemoryPools(value, header string) (string, string, string, string) {
|
||||
}
|
||||
|
||||
hbm := usage[len(usage)-1]
|
||||
if !strings.Contains(normalizedHeader, "MEMORYUSAGE") {
|
||||
memoryUsed, memoryTotal = "", ""
|
||||
}
|
||||
return memoryUsed, memoryTotal, hbm[0] + " MB", hbm[1] + " MB"
|
||||
}
|
||||
|
||||
@@ -233,3 +267,29 @@ func ascendValueWithUnit(value, unit string) string {
|
||||
}
|
||||
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
|
||||
|
||||
type Info struct {
|
||||
Warnings []string `json:"warnings"`
|
||||
Type string `json:"type"`
|
||||
DriverVersion string `json:"driverVersion"`
|
||||
Devices []Device `json:"npu"`
|
||||
}
|
||||
|
||||
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"`
|
||||
Index uint `json:"index"`
|
||||
NPUIndex uint `json:"npuIndex"`
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
package vllm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
)
|
||||
|
||||
type Metrics map[string]map[string]float64
|
||||
|
||||
type histogram struct {
|
||||
Sum float64 `json:"sum"`
|
||||
Count float64 `json:"count"`
|
||||
Buckets map[string]float64 `json:"buckets,omitempty"`
|
||||
}
|
||||
|
||||
func Parse(reader io.Reader) (Metrics, error) {
|
||||
metrics := Metrics{}
|
||||
scanner := bufio.NewScanner(reader)
|
||||
scanner.Buffer(make([]byte, 4096), 1024*1024)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if !strings.HasPrefix(line, "vllm:") {
|
||||
continue
|
||||
}
|
||||
match := re.GetRegex(re.VLLMMetricSamplePattern).FindStringSubmatch(line)
|
||||
if match == nil {
|
||||
return nil, fmt.Errorf("invalid vLLM metric sample")
|
||||
}
|
||||
name := strings.TrimPrefix(match[1], "vllm:")
|
||||
switch name {
|
||||
case "num_requests_running", "num_requests_waiting", "kv_cache_usage_perc", "gpu_cache_usage_perc", "prompt_tokens_total", "generation_tokens_total", "request_success_total":
|
||||
default:
|
||||
base := strings.TrimSuffix(strings.TrimSuffix(strings.TrimSuffix(name, "_sum"), "_count"), "_bucket")
|
||||
if base == name {
|
||||
continue
|
||||
}
|
||||
switch base {
|
||||
case "time_to_first_token_seconds", "time_per_output_token_seconds", "inter_token_latency_seconds", "e2e_request_latency_seconds", "request_prefill_time_seconds", "request_decode_time_seconds":
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
value, err := strconv.ParseFloat(match[3], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid vLLM metric value: %w", err)
|
||||
}
|
||||
if math.IsNaN(value) || math.IsInf(value, 0) || value < 0 {
|
||||
continue
|
||||
}
|
||||
if metrics[name] == nil {
|
||||
metrics[name] = map[string]float64{}
|
||||
}
|
||||
labels := re.GetRegex(re.VLLMMetricLabelPattern).FindAllStringSubmatch(match[2], -1)
|
||||
parts := make([]string, 0, len(labels))
|
||||
for _, label := range labels {
|
||||
parts = append(parts, label[1]+"="+label[2])
|
||||
}
|
||||
sort.Strings(parts)
|
||||
metrics[name]["{"+strings.Join(parts, ",")+"}"] = value
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(metrics) == 0 {
|
||||
return nil, fmt.Errorf("no supported vLLM metrics")
|
||||
}
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
func Calculate(current, previous Metrics, elapsed float64) (model.MonitorVLLM, error) {
|
||||
result := model.MonitorVLLM{}
|
||||
for name, target := range map[string]**float64{
|
||||
"num_requests_running": &result.Running,
|
||||
"num_requests_waiting": &result.Waiting,
|
||||
} {
|
||||
if len(current[name]) == 0 {
|
||||
continue
|
||||
}
|
||||
value := 0.0
|
||||
for _, sample := range current[name] {
|
||||
value += sample
|
||||
}
|
||||
*target = &value
|
||||
}
|
||||
cache := current["kv_cache_usage_perc"]
|
||||
if len(cache) == 0 {
|
||||
cache = current["gpu_cache_usage_perc"]
|
||||
}
|
||||
if len(cache) > 0 {
|
||||
value := 0.0
|
||||
for _, sample := range cache {
|
||||
value += sample
|
||||
}
|
||||
value = value / float64(len(cache)) * 100
|
||||
result.CacheUsage = &value
|
||||
}
|
||||
if elapsed <= 0 {
|
||||
return result, nil
|
||||
}
|
||||
for name, target := range map[string]**float64{
|
||||
"prompt_tokens_total": &result.PromptThroughput,
|
||||
"generation_tokens_total": &result.GenerationThroughput,
|
||||
"request_success_total": &result.RequestThroughput,
|
||||
} {
|
||||
if value := counterDelta(current[name], previous[name]); value != nil {
|
||||
rate := *value / elapsed
|
||||
*target = &rate
|
||||
}
|
||||
}
|
||||
histograms := map[string]histogram{}
|
||||
for _, name := range []string{"time_to_first_token_seconds", "inter_token_latency_seconds", "e2e_request_latency_seconds", "request_prefill_time_seconds", "request_decode_time_seconds"} {
|
||||
source := name
|
||||
if name == "inter_token_latency_seconds" && len(current[name+"_count"]) == 0 {
|
||||
source = "time_per_output_token_seconds"
|
||||
}
|
||||
sum := counterDelta(current[source+"_sum"], previous[source+"_sum"])
|
||||
count := counterDelta(current[source+"_count"], previous[source+"_count"])
|
||||
if sum == nil || count == nil {
|
||||
continue
|
||||
}
|
||||
histograms[name] = histogram{Sum: *sum, Count: *count, Buckets: bucketDeltas(current[source+"_bucket"], previous[source+"_bucket"], *count)}
|
||||
}
|
||||
applyHistograms(&result, histograms)
|
||||
encoded, err := json.Marshal(histograms)
|
||||
if err != nil {
|
||||
return model.MonitorVLLM{}, err
|
||||
}
|
||||
result.HistogramDeltas = string(encoded)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func AggregateHistograms(encoded string, result *model.MonitorVLLM) error {
|
||||
var samples []map[string]histogram
|
||||
if err := json.Unmarshal([]byte(encoded), &samples); err != nil {
|
||||
return err
|
||||
}
|
||||
merged := map[string]histogram{}
|
||||
for _, sample := range samples {
|
||||
for name, item := range sample {
|
||||
if item.Count == 0 {
|
||||
continue
|
||||
}
|
||||
total, exists := merged[name]
|
||||
if !exists {
|
||||
merged[name] = item
|
||||
continue
|
||||
}
|
||||
total.Sum += item.Sum
|
||||
total.Count += item.Count
|
||||
if len(total.Buckets) != len(item.Buckets) {
|
||||
total.Buckets = nil
|
||||
}
|
||||
for bound, count := range total.Buckets {
|
||||
addition, ok := item.Buckets[bound]
|
||||
if !ok {
|
||||
total.Buckets = nil
|
||||
break
|
||||
}
|
||||
total.Buckets[bound] = count + addition
|
||||
}
|
||||
merged[name] = total
|
||||
}
|
||||
}
|
||||
applyHistograms(result, merged)
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyHistograms(result *model.MonitorVLLM, histograms map[string]histogram) {
|
||||
for _, item := range []struct {
|
||||
name string
|
||||
mean **float64
|
||||
quantiles []**float64
|
||||
}{
|
||||
{"time_to_first_token_seconds", &result.TimeToFirstToken, []**float64{&result.TimeToFirstTokenP50, &result.TimeToFirstTokenP90, &result.TimeToFirstTokenP95, &result.TimeToFirstTokenP99}},
|
||||
{"inter_token_latency_seconds", &result.TimePerOutputToken, []**float64{&result.TimePerOutputTokenP50, &result.TimePerOutputTokenP90, &result.TimePerOutputTokenP95, &result.TimePerOutputTokenP99}},
|
||||
{"e2e_request_latency_seconds", &result.RequestLatency, []**float64{&result.RequestLatencyP50, &result.RequestLatencyP90, &result.RequestLatencyP95, &result.RequestLatencyP99}},
|
||||
{"request_prefill_time_seconds", &result.PrefillTime, nil},
|
||||
{"request_decode_time_seconds", &result.DecodeTime, nil},
|
||||
} {
|
||||
histogram, ok := histograms[item.name]
|
||||
if !ok || histogram.Count <= 0 {
|
||||
continue
|
||||
}
|
||||
mean := histogram.Sum / histogram.Count
|
||||
*item.mean = &mean
|
||||
if len(item.quantiles) == 0 {
|
||||
continue
|
||||
}
|
||||
quantiles := histogramQuantiles(histogram.Buckets)
|
||||
for i, target := range item.quantiles {
|
||||
*target = quantiles[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func counterDelta(current, previous map[string]float64) *float64 {
|
||||
if len(current) == 0 || len(current) != len(previous) {
|
||||
return nil
|
||||
}
|
||||
value := 0.0
|
||||
for label, sample := range current {
|
||||
before, ok := previous[label]
|
||||
if !ok || sample < before {
|
||||
return nil
|
||||
}
|
||||
value += sample - before
|
||||
}
|
||||
if math.IsInf(value, 0) || math.IsNaN(value) {
|
||||
return nil
|
||||
}
|
||||
return &value
|
||||
}
|
||||
|
||||
func bucketDeltas(current, previous map[string]float64, count float64) map[string]float64 {
|
||||
if len(current) == 0 || len(current) != len(previous) {
|
||||
return nil
|
||||
}
|
||||
groups := map[string]map[string]float64{}
|
||||
for labels, sample := range current {
|
||||
before, ok := previous[labels]
|
||||
if !ok || sample < before {
|
||||
return nil
|
||||
}
|
||||
bound := ""
|
||||
var identity []string
|
||||
for _, label := range re.GetRegex(re.VLLMMetricLabelPattern).FindAllStringSubmatch(labels, -1) {
|
||||
if label[1] == "le" {
|
||||
raw, err := strconv.Unquote(label[2])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
upper, err := strconv.ParseFloat(raw, 64)
|
||||
if err != nil || math.IsNaN(upper) || upper < 0 {
|
||||
return nil
|
||||
}
|
||||
bound = strconv.FormatFloat(upper, 'g', -1, 64)
|
||||
} else {
|
||||
identity = append(identity, label[1]+"="+label[2])
|
||||
}
|
||||
}
|
||||
if bound == "" {
|
||||
return nil
|
||||
}
|
||||
group := strings.Join(identity, ",")
|
||||
if groups[group] == nil {
|
||||
groups[group] = map[string]float64{}
|
||||
}
|
||||
if _, exists := groups[group][bound]; exists {
|
||||
return nil
|
||||
}
|
||||
groups[group][bound] = sample - before
|
||||
}
|
||||
result := map[string]float64{}
|
||||
for _, buckets := range groups {
|
||||
if _, ok := buckets["+Inf"]; !ok || len(buckets) < 2 {
|
||||
return nil
|
||||
}
|
||||
if len(result) > 0 && len(result) != len(buckets) {
|
||||
return nil
|
||||
}
|
||||
for bound := range result {
|
||||
if _, ok := buckets[bound]; !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
bounds := make([]float64, 0, len(buckets))
|
||||
for bound := range buckets {
|
||||
value, _ := strconv.ParseFloat(bound, 64)
|
||||
bounds = append(bounds, value)
|
||||
}
|
||||
sort.Float64s(bounds)
|
||||
previousCount := 0.0
|
||||
for _, bound := range bounds {
|
||||
key := strconv.FormatFloat(bound, 'g', -1, 64)
|
||||
value := buckets[key]
|
||||
if value < previousCount {
|
||||
if previousCount-value > 1e-12*(previousCount+value) {
|
||||
return nil
|
||||
}
|
||||
value = previousCount
|
||||
}
|
||||
result[key] += value
|
||||
previousCount = value
|
||||
}
|
||||
}
|
||||
if math.Abs(result["+Inf"]-count) > 1e-12*(result["+Inf"]+count) {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func histogramQuantiles(buckets map[string]float64) [4]*float64 {
|
||||
var quantiles [4]*float64
|
||||
if len(buckets) < 2 || buckets["+Inf"] <= 0 {
|
||||
return quantiles
|
||||
}
|
||||
bounds := make([]float64, 0, len(buckets)-1)
|
||||
for bound := range buckets {
|
||||
value, err := strconv.ParseFloat(bound, 64)
|
||||
if err != nil || math.IsNaN(value) || value < 0 {
|
||||
return quantiles
|
||||
}
|
||||
if !math.IsInf(value, 1) {
|
||||
bounds = append(bounds, value)
|
||||
}
|
||||
}
|
||||
if len(bounds) == 0 {
|
||||
return quantiles
|
||||
}
|
||||
sort.Float64s(bounds)
|
||||
for i, q := range [...]float64{0.5, 0.9, 0.95, 0.99} {
|
||||
rank := q * buckets["+Inf"]
|
||||
lower, before := 0.0, 0.0
|
||||
value := bounds[len(bounds)-1]
|
||||
for _, upper := range bounds {
|
||||
count := buckets[strconv.FormatFloat(upper, 'g', -1, 64)]
|
||||
if count >= rank && count > before {
|
||||
value = lower + (upper-lower)*(rank-before)/(count-before)
|
||||
break
|
||||
}
|
||||
lower, before = upper, count
|
||||
}
|
||||
quantiles[i] = &value
|
||||
}
|
||||
return quantiles
|
||||
}
|
||||
@@ -33,16 +33,29 @@ type discoveryInfo struct {
|
||||
}
|
||||
|
||||
type DeviceLevelMetric struct {
|
||||
MetricsType string `json:"metrics_type"`
|
||||
Value float64 `json:"value"`
|
||||
MetricsType string `json:"metrics_type"`
|
||||
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 {
|
||||
TileLevel []TileMetrics `json:"tile_level"`
|
||||
DeviceID int `json:"device_id"`
|
||||
DeviceLevel []DeviceLevelMetric `json:"device_level"`
|
||||
}
|
||||
|
||||
type Info struct {
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
Type string `json:"type"`
|
||||
DriverVersion string `json:"driverVersion"`
|
||||
|
||||
@@ -50,12 +63,17 @@ type Info struct {
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
Tiles []TileStats `json:"tiles"`
|
||||
ProcessStatus string `json:"processStatus"`
|
||||
|
||||
Basic Basic `json:"basic"`
|
||||
Stats Stats `json:"stats"`
|
||||
Processes []Process `json:"processes"`
|
||||
}
|
||||
|
||||
type Basic struct {
|
||||
UUID string `json:"uuid"`
|
||||
|
||||
DeviceID int `json:"deviceID"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
VendorName string `json:"vendorName"`
|
||||
@@ -66,6 +84,14 @@ type Basic 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"`
|
||||
GPUUtil string `json:"gpuUtil"`
|
||||
Frequency string `json:"frequency"`
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -51,12 +52,15 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
|
||||
|
||||
processData, err := cmdMgr.RunWithStdout(xpuSMICommand, "ps", "-j")
|
||||
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 {
|
||||
var psList DeviceUtilByProcList
|
||||
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 {
|
||||
for i := range res.Devices {
|
||||
res.Devices[i].ProcessStatus = "ok"
|
||||
}
|
||||
for _, ps := range psList.DeviceUtilByProcList {
|
||||
process := Process{
|
||||
PID: ps.ProcessID,
|
||||
@@ -87,7 +91,9 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
|
||||
defer wg.Done()
|
||||
|
||||
xpu := Device{
|
||||
ProcessStatus: "unavailable",
|
||||
Basic: Basic{
|
||||
UUID: device.UUID,
|
||||
DeviceID: device.DeviceID,
|
||||
DeviceName: device.DeviceName,
|
||||
VendorName: device.VendorName,
|
||||
@@ -115,13 +121,19 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
|
||||
wgCmd.Wait()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var info discoveryDevice
|
||||
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
|
||||
}
|
||||
|
||||
@@ -142,13 +154,22 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
|
||||
}
|
||||
|
||||
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 {
|
||||
var stats DeviceStats
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
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":
|
||||
stats.Power = fmt.Sprintf("%.1fW", stat.Value)
|
||||
stats.Power = fmt.Sprintf("%.1fW", value)
|
||||
case "XPUM_STATS_GPU_UTILIZATION":
|
||||
stats.GPUUtil = fmt.Sprintf("%.1f%%", stat.Value)
|
||||
stats.GPUUtil = fmt.Sprintf("%.1f%%", value)
|
||||
case "XPUM_STATS_GPU_FREQUENCY":
|
||||
stats.Frequency = fmt.Sprintf("%.1fMHz", stat.Value)
|
||||
stats.Frequency = fmt.Sprintf("%.1fMHz", value)
|
||||
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":
|
||||
stats.MemoryUsed = fmt.Sprintf("%.1f MiB", stat.Value)
|
||||
case "XPUM_STATS_MEMORY_UTILIZATION", "XPUM_STATS_MEMORY_BANDWIDTH", "XPUM_STATS_MEMORY_BANDWIDTH_UTILIZATION":
|
||||
stats.MemoryUtil = fmt.Sprintf("%.1f%%", stat.Value)
|
||||
stats.MemoryUsed = fmt.Sprintf("%.1f MiB", value)
|
||||
case "XPUM_STATS_MEMORY_UTILIZATION":
|
||||
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"
|
||||
)
|
||||
|
||||
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 {
|
||||
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
|
||||
params := CreateTaskAlertParams(pushAlert)
|
||||
alertDetail := ProcessAlertDetail(alert, pushAlert.TaskName, params, method)
|
||||
alertRule := ProcessAlertRule(alert)
|
||||
create.AlertRule = alertRule
|
||||
@@ -76,14 +76,14 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
|
||||
Encryption: emailInfo.Encryption,
|
||||
Recipient: emailInfo.Recipient,
|
||||
}
|
||||
content := GetSendContent(alert.Type, params, agentInfo)
|
||||
content := GetAlertLogContent(create, alert, params, agentInfo)
|
||||
if content == "" {
|
||||
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
|
||||
}
|
||||
msg := email.EmailMessage{
|
||||
Subject: i18n.GetMsgByKey("PanelAlertTitle"),
|
||||
Body: content,
|
||||
IsHTML: true,
|
||||
IsHTML: GetCronJobType(alert.Type) != "cronJob",
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
content := GetSendContent(alert.Type, params, agentInfo)
|
||||
content := GetAlertLogContent(create, alert, params, agentInfo)
|
||||
if content == "" {
|
||||
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{
|
||||
Type: GetCronJobType(alert.Type),
|
||||
SubType: alert.Type,
|
||||
Title: alert.Title,
|
||||
Title: TaskAlertTitle(alert.Type, alert.Title, project, params),
|
||||
Method: method,
|
||||
Project: project,
|
||||
Params: params,
|
||||
@@ -281,6 +281,14 @@ func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param,
|
||||
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 {
|
||||
marshal, err := json.Marshal(alert)
|
||||
if err != nil {
|
||||
@@ -324,6 +332,10 @@ func GetCronJobTypeName(cronJobType string) string {
|
||||
module = "系统快照"
|
||||
case "ntp":
|
||||
module = "同步服务器时间"
|
||||
case "syncIpGroup":
|
||||
module = "同步 IP 组"
|
||||
case "cleanLog":
|
||||
module = "清理日志"
|
||||
default:
|
||||
}
|
||||
return module
|
||||
@@ -444,6 +456,30 @@ func isWithinTimeRange(savedTimeString string) bool {
|
||||
}
|
||||
|
||||
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) {
|
||||
case "ssl":
|
||||
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":
|
||||
return i18n.GetMsgWithMap("DiskUsedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "used": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||
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":
|
||||
return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||
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,
|
||||
agentInfo *dto.AgentInfo,
|
||||
) error {
|
||||
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
|
||||
params := CreateTaskAlertParams(pushAlert)
|
||||
alertInfo := info
|
||||
alertInfo.Type = alertType
|
||||
create.Type = GetCronJobType(alertType)
|
||||
@@ -99,7 +99,7 @@ func customWebhookTemplateData(rawDetail string, agentInfo *dto.AgentInfo, occur
|
||||
if businessType == "" {
|
||||
return webhook_sender.TemplateData{}, errors.New("resolve custom webhook alert detail failed")
|
||||
}
|
||||
content := GetSendContent(businessType, detail.Params, agentInfo)
|
||||
content := GetAlertDetailContent(detail, agentInfo)
|
||||
if content == "" {
|
||||
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": detail.Title})
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ func PushAlert(pushAlert dto.PushAlert) error {
|
||||
}
|
||||
var alert dto.AlertDTO
|
||||
_ = copier.Copy(&alert, &alertInfo)
|
||||
if alertUtil.GetCronJobType(pushAlert.AlertType) == "cronJob" && !alertUtil.MatchCronJobAlertResult(alert.AdvancedParams, pushAlert.Result) {
|
||||
return nil
|
||||
}
|
||||
|
||||
methods := strings.Split(alert.Method, ",")
|
||||
for _, m := range methods {
|
||||
@@ -126,7 +129,7 @@ func sendAlert(alertRepo repo.IAlertRepo, alert dto.AlertDTO, pushAlert dto.Push
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
params := alertUtil.CreateAlertParams(alertUtil.GetCronJobTypeName(pushAlert.Param))
|
||||
params := alertUtil.CreateTaskAlertParams(pushAlert)
|
||||
alertDetail := alertUtil.ProcessAlertDetail(alert, pushAlert.TaskName, params, constant.Bark)
|
||||
alertRule := alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertRule = alertRule
|
||||
|
||||
@@ -97,7 +97,7 @@ func (s sftpClient) Upload(ctx context.Context, src, target string) (bool, error
|
||||
}()
|
||||
defer close(done)
|
||||
|
||||
client, err := sftp.NewClient(sshClient)
|
||||
client, err := sftp.NewClient(sshClient, sftp.UseConcurrentWrites(true))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -127,7 +127,15 @@ func (s sftpClient) Upload(ctx context.Context, src, target string) (bool, error
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
if _, err := io.Copy(dstFile, srcFile); err != nil {
|
||||
// ReadFrom uses the pipelined write path of pkg/sftp. A plain io.Copy would
|
||||
// pick os.File.WriteTo (the source is an *os.File), which issues one
|
||||
// synchronous SFTP WRITE per packet and waits for the reply every time, so
|
||||
// the throughput is bounded by packetSize/RTT.
|
||||
written, err := dstFile.ReadFrom(srcFile)
|
||||
if err != nil {
|
||||
// A failed concurrent write may leave holes in the remote file, so cut
|
||||
// it back to the number of bytes actually written.
|
||||
_ = dstFile.Truncate(written)
|
||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||
return false, ctxErr
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ type CommandHelper struct {
|
||||
outputFile string
|
||||
scriptPath string
|
||||
stdin io.Reader
|
||||
stderr io.Writer
|
||||
env []string
|
||||
timeout time.Duration
|
||||
taskItem *task.Task
|
||||
@@ -360,6 +361,9 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
}
|
||||
if c.stderr != nil {
|
||||
cmd.Stderr = io.MultiWriter(cmd.Stderr, c.stderr)
|
||||
}
|
||||
env := os.Environ()
|
||||
env = append(env, c.env...)
|
||||
cmd.Env = env
|
||||
@@ -481,6 +485,11 @@ func WithStdin(stdin io.Reader) Option {
|
||||
s.stdin = stdin
|
||||
}
|
||||
}
|
||||
func WithStderr(stderr io.Writer) Option {
|
||||
return func(s *CommandHelper) {
|
||||
s.stderr = stderr
|
||||
}
|
||||
}
|
||||
func WithEnv(env ...string) Option {
|
||||
return func(s *CommandHelper) {
|
||||
s.env = append(s.env, env...)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user