Compare commits

..
Author SHA1 Message Date
Yuki Guoandssongliu 486cd5d0ad feat(redis): enable remote terminal through tracked background tasks (#14012)
* feat(redis): enable remote terminal through tracked background tasks

* feat(redis): enable remote terminal through tracked background tasks

* refactor(redis): remove redundant CLI status tracking

* refactor(redis): retain existing task execution flow

---------

Co-authored-by: ssongliu <sloooop1x@gmail.com>
2026-10-09 18:20:51 +08:00
ssongliu 00ce5a7b36 fix(firewall): preserve IPv6 RA protection and simplify error messages (#14015) 2026-10-09 17:45:57 +08:00
ssongliu c17f055e06 fix: preserve VM task timeout cleanup and terminate command groups (#14008) 2026-10-09 17:45:41 +08:00
王贺 f22df4b29c fix(file): stabilize closable tab width to avoid layout jitter (#14013)
The closable card tabs animated the close icon width between active and inactive tabs, so each tab switch reflowed the tab bar and caused the page to jitter. Reserve the close icon slot and toggle it with opacity instead, matching the terminal tab implementation.
2026-10-09 17:16:10 +08:00
Yuki Guoandssongliu b9bab90a14 fix: fix clean network (#14009)
* fix: fix clean network

* refactor(container): simplify network cleanup

---------

Co-authored-by: ssongliu <sloooop1x@gmail.com>
2026-10-09 17:14:50 +08:00
ssongliu ce14a104cc feat(vm): move shared VM support to agent (#14007) 2026-10-09 14:09:53 +08:00
CN_CoreStebandssongliu 2eaabccf94 fix(dashboard): prevent overlapping df collection and share disk queries (#13971)
* fix(dashboard): keep disk info working when a mount is stale or broken

The dashboard, the file manager mount list and the alert disk list
enumerated mounts with df. df calls statfs on every mount, so one bad
mount took the whole disk list down: the request hung until the
frontend timed out, or the list came back empty.

- enumerate mounts from /proc/self/mountinfo instead of running df
- read each mount through a guard with a 3s timeout that keeps at most
  one call per mount in flight
- fail closed: a mount that cannot be read stays in the list with
  errorType and error, and an unreadable mount table is an error
  rather than an empty list
- show failed mounts and their reasons on the dashboard and in the
  file manager
- cmd: after killing a timed-out command, wait at most 5s for it
  instead of forever

* fix(dashboard): distinguish remounted and hidden filesystems

* fix(disk): simplify disk queries and bound timeout handling

* fix(disk): prevent overlapping df collection tasks

---------

Co-authored-by: ssongliu <sloooop1x@gmail.com>
2026-10-09 10:14:16 +08:00
ssongliu e119239d3d refactor: manage firewall rules through runtime state and file backups (#13998)
Replace database rule synchronization with native rule operations and file-based backup, import, and recovery. Update firewall management views, descriptions, whitelist handling, and translations.

Include terminal reconnect fixes and xpack build tags for Darwin build targets. Remove temporary regression test files before committing.
2026-10-08 18:19:30 +08:00
ssongliu 14a57af4e9 feat(vm): clarify network modes and add macvtap translations (#13996) 2026-10-08 18:18:17 +08:00
Yuki Guoandssongliu 2701af9054 refactor: select bind (#13993)
* refactor: select bind

* refactor: simplify table selection state handling

---------

Co-authored-by: ssongliu <sloooop1x@gmail.com>
2026-10-08 17:11:28 +08:00
Yuki Guoandssongliu 4954067736 refactor: search bind (#13992)
* refactor: search bind

* refactor: simplify search persistence

---------

Co-authored-by: ssongliu <sloooop1x@gmail.com>
2026-10-08 16:19:29 +08:00
ssongliu b962144e7d fix(ssh): validate imported key pairs and cancel stale file reads (#13991) 2026-10-08 14:06:40 +08:00
Yuki Guo 8f1be42b6b fix: fix log text (#13990) 2026-10-08 11:32:09 +08:00
Yuki Guo c6969f231b fix: fix operation log (#13989) 2026-10-08 11:08:04 +08:00
4kerccand4kercc 3571fb8d86 fix: speed up SFTP backup upload with concurrent writes (#13976)
The SFTP uploader used io.Copy(dstFile, srcFile). Since the source is an
*os.File it implements io.WriterTo, so io.Copy took the os.File.WriteTo
branch and issued one synchronous SFTP WRITE per packet, waiting for the
server reply every time. Throughput was therefore bounded by packetSize/RTT
instead of the link capacity: on a ~68ms RTT link a 440MB backup reached
only ~0.25 MB/s, while rclone / OpenSSH sftp reach 16-19 MB/s on the very
same link and target.

pkg/sftp already provides a pipelined write path (File.ReadFrom, enabled by
UseConcurrentWrites and bounded by MaxConcurrentRequestsPerFile, default 64).
Use it for uploads, and truncate the remote file to the number of bytes
actually written when the concurrent write fails, to avoid leaving holes.

Measured against the same 68ms RTT target: a 96MiB upload went from 6m40s
(~0.25 MB/s) to ~6s (~16 MB/s); a 950MB three-site backup job from ~2h40m to
~2m04s.

Co-authored-by: 4kercc <13767585+4kercc@users.noreply.github.com>
2026-10-08 09:45:51 +08:00
ssongliu d21288788e fix: harden WebSocket origin validation (#13953) 2026-09-30 15:21:21 +08:00
ssongliu f65e4b06ab feat(vm): add VNC keyboard control translations (#13951)
* feat(vm): add VNC keyboard control translations

* feat(vm): add Windows VirtIO driver guidance translations

* feat(vm): add disk expansion translations and operation logs
2026-09-30 15:20:19 +08:00
ssongliu d26bc9a3a4 chore(deps): update dependencies and address security advisories (#13945)
Update pending Go and frontend dependencies, including gRPC, OpenTelemetry
and SVGO security fixes. Raise the minimum Go version to 1.26.6 and update
x/image and x/mod to patched versions.

Use moby/go-archive directly for image build contexts so its vulnerable
v0.1.0 replacement can be removed and v0.3.0 used. Regenerate Go module
checksums with go mod tidy and refresh the npm lockfile.

Validation: Linux amd64 builds, existing Go tests and go mod verify pass
for core and agent; frontend production build passes; npm audit reports
zero vulnerabilities. Frontend type-check reports the same nine errors
with the original and updated lockfiles.

Go scanning still flags two advisories in the legacy Docker SDK, which
has no patched release on its current module path. A separate SDK
migration and host Docker daemon updates remain necessary.
2026-09-29 21:36:19 +08:00
ssongliu 7088a903fe fix(vllm): sample realtime metrics independently of history (#13944) 2026-09-29 17:29:29 +08:00
ssongliu 864ebeed82 fix: load domestic release notes from new docs (#13943) 2026-09-29 17:29:16 +08:00
CN_CoreStebandssongliu c1b2b92708 feat(monitor): add monitor data export with csv, json and xlsx formats (#13875)
- new export tab between monitor and settings with time range, metric,
  device/interface/gpu selection and format choice
- files are generated in the browser from existing monitor apis; no new
  backend endpoints
- csv with utf-8 bom, escaping and formula sanitization; multiple metrics
  are zipped; xlsx uses one worksheet per metric with real date cells and
  row-limit warnings; json emits flat row objects
- share downloadBlob in utils/file with delayed url revocation
- use the 60s monitor timeout and node header for gpu queries

Co-authored-by: ssongliu <sloooop1x@gmail.com>
2026-09-29 15:50:00 +08:00
CN_CoreStebandssongliu 2189d9229a perf(monitor): improve monitor query and chart performance for large ranges (#13874)
* perf(monitor): improve monitor query and chart performance for large ranges

- enable LTTB sampling in the shared line chart to bound rendered points
- empty io/network in monitor search now means no name filter, returning
  all device rows in one query instead of fanning out per device
- merge device names recorded in the monitor db into io/network/gpu
  options so removed devices stay selectable; move logic to service layer
- add composite indexes (name, created_at) on monitor_ios/monitor_networks
  and (product_name, created_at) on monitor_gpus
- stop migrating the unused MonitorGPU model into MonitorDB
- fix the memory param which was compared against "mem" and never matched

* chore(monitor): remove monitor test fixtures

---------

Co-authored-by: ssongliu <sloooop1x@gmail.com>
2026-09-29 15:34:16 +08:00
ssongliu d0183f460e feat: add vLLM monitoring (#13942) 2026-09-29 15:19:30 +08:00
ssongliu d27d6db1ea feat: enhance native GPU NPU and XPU monitoring (#13939) 2026-09-28 18:32:12 +08:00
蘭 d2bb3813d9 fix: enhance SSH login alert by adding local IP addresses to success login whitelist (#13920) 2026-09-24 18:23:59 +08:00
蘭 cac73e6d45 fix: add success alert message for cron jobs and enhance alert handling (#13919) 2026-09-24 18:23:39 +08:00
ssongliu 4712ceaf2c fix: allow read-only firewall APIs in demo mode (#13914) 2026-09-24 14:01:05 +08:00
ssongliu 65243c68c4 fix: allow licensed community restore progress route (#13913) 2026-09-24 10:18:01 +08:00
ssongliu 256e79ca81 fix: remove minimum upgrade backup copies limit (#13907) 2026-09-23 18:17:32 +08:00
ssongliu 4861eb69cb fix: separate firewalld batches by native option (#13896) 2026-09-22 18:20:18 +08:00
蘭 fb8cf15537 fix: Fix the automatic renewal certificate alert issue (#13895) 2026-09-22 15:06:48 +08:00
ssongliu c4a6791271 fix: allow updated SSH ports through firewall before restart (#13894) 2026-09-22 14:51:54 +08:00
蘭 f58e147636 fix: enhance file rename functionality (#13891) 2026-09-22 12:49:05 +08:00
ssongliu 387e9fbeed fix: correct firewall port allowances and whitelist sync reporting (#13892) 2026-09-22 12:48:40 +08:00
ssongliu 4eb627bc79 fix(vm): display additional virtual machine states (#13890) 2026-09-22 10:27:49 +08:00
ssongliu 850c86229c fix: log dashboard disk command failures (#13889) 2026-09-22 10:16:54 +08:00
CityFun f984917a66 fix(upgrade): resolve application upgrade failures (#13888) 2026-09-22 10:11:13 +08:00
A_Words 8588217fbf fix: prioritize HTTP-to-HTTPS redirects over website redirects (#13847) 2026-09-22 10:05:53 +08:00
ssongliu a2307c5f64 fix(firewall): improve batch operations and task log completion (#13886) 2026-09-22 09:46:46 +08:00
蘭 5923290de8 ref: add file task messages for copy, move, compress and decompress (#13885) 2026-09-21 17:32:14 +08:00
蘭 1c994fba4a ref: update documentation URL for the panel (#13884) 2026-09-21 17:28:23 +08:00
蘭 415ab96aab ref:improve the panel API key (#13883) 2026-09-21 17:18:15 +08:00
ssongliu 19bb823b05 fix(firewall): guard IPv6 forwarding against RA disruption (#13882) 2026-09-21 17:12:17 +08:00
ssongliu 3a5371652e refactor(firewall): simplify state reads and batch verification (#13881) 2026-09-21 15:09:13 +08:00
蘭 a267b4148a ref:Optimize and improve the panel API key (#13880) 2026-09-21 12:49:45 +08:00
蘭 36a01eb60d feat: The panel API key supports creating multiple tokens and enhance terminal capabilities (#13872) 2026-09-20 18:32:54 +08:00
王贺 65f6fdd045 fix(ssh): display SSH login logs in server timezone (#13869)
The SSH log page formatted dates with the browser timezone, so logs looked shifted when the client timezone differed from the server. Format the timestamp with the offset returned by the API so the page matches auth.log, the terminal and the CSV export. Refs #13860.
2026-09-20 18:02:46 +08:00
337 changed files with 24277 additions and 17803 deletions
+10 -4
View File
@@ -86,17 +86,23 @@ func (b *BaseApi) CheckHasCli(c *gin.Context) {
// @Tags Database Redis // @Tags Database Redis
// @Summary Install redis-cli // @Summary Install redis-cli
// @Success 200 // @Accept json
// @Param request body dto.RedisCliInstall true "request"
// @Success 200 {string} string
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Security Timestamp // @Security Timestamp
// @Router /databases/redis/install/cli [post] // @Router /databases/redis/install/cli [post]
func (b *BaseApi) InstallCli(c *gin.Context) { func (b *BaseApi) InstallCli(c *gin.Context) {
if err := redisService.InstallCli(); err != nil { var req dto.RedisCliInstall
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := redisService.InstallCli(req)
if err != nil {
helper.InternalServer(c, err) helper.InternalServer(c, err)
return return
} }
helper.SuccessWithData(c, data)
helper.Success(c)
} }
// @Tags Database Redis // @Tags Database Redis
+110 -159
View File
@@ -2,14 +2,14 @@ package v2
import ( import (
"errors" "errors"
"github.com/1Panel-dev/1Panel/agent/buserr"
"net/http" "net/http"
"strings"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/service"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter" "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@@ -88,7 +88,7 @@ func (b *BaseApi) OperateFirewall(c *gin.Context) {
// @Security Timestamp // @Security Timestamp
// @Router /hosts/firewall/forward/base [post] // @Router /hosts/firewall/forward/base [post]
func (b *BaseApi) LoadForwardingBaseInfo(c *gin.Context) { func (b *BaseApi) LoadForwardingBaseInfo(c *gin.Context) {
data, err := forwardingService.LoadBaseInfo() data, err := forwardingService.LoadBaseInfo(c.Request.Context())
if err != nil { if err != nil {
helper.InternalServer(c, err) helper.InternalServer(c, err)
return return
@@ -109,7 +109,7 @@ func (b *BaseApi) SearchForwardingRules(c *gin.Context) {
if err := helper.CheckBindAndValidate(&request, c); err != nil { if err := helper.CheckBindAndValidate(&request, c); err != nil {
return return
} }
total, items, err := forwardingService.SearchRules(request) total, items, err := forwardingService.SearchRules(c.Request.Context(), request)
if err != nil { if err != nil {
helper.InternalServer(c, err) helper.InternalServer(c, err)
return return
@@ -260,28 +260,6 @@ func (b *BaseApi) LoadFirewallNativeDetail(c *gin.Context) {
helper.SuccessWithData(c, info) 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 // @Tags Firewall
// @Summary Queue firewall rule creation // @Summary Queue firewall rule creation
// @Description Creation and import return a taskID immediately; validation and execution results are written to the task log. // @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) 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 // @Tags Firewall
// @Summary Queue firewall rule deletion // @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 // @Accept json
// @Param request body dto.FirewallRuleDelete true "request" // @Param request body dto.FirewallRuleDelete true "request"
// @Success 200 {object} dto.FirewallRuleDeleteResponse // @Success 200 {object} dto.FirewallRuleDeleteResponse
@@ -392,7 +310,7 @@ func (b *BaseApi) DeleteFirewallRules(c *gin.Context) {
} }
// @Tags Firewall // @Tags Firewall
// @Summary Update a managed unified firewall v2 rule // @Summary Update a firewall rule
// @Accept json // @Accept json
// @Param request body dto.FirewallRuleUpdate true "request" // @Param request body dto.FirewallRuleUpdate true "request"
// @Success 200 // @Success 200
@@ -400,16 +318,13 @@ func (b *BaseApi) DeleteFirewallRules(c *gin.Context) {
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Security Timestamp // @Security Timestamp
// @Router /hosts/firewall/rules/update [post] // @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) { func (b *BaseApi) UpdateFirewallRule(c *gin.Context) {
var request dto.FirewallRuleUpdate var request dto.FirewallRuleUpdate
if err := helper.CheckBindAndValidate(&request, c); err != nil { if err := helper.CheckBindAndValidate(&request, c); err != nil {
return return
} }
if !normalizeFirewallRuleUUID(c, &request.UUID) { if err := firewallService.Update(c.Request.Context(), request); err != nil {
return
}
if err := firewallService.Update(c.Request.Context(), c.ClientIP(), request); err != nil {
handleFirewallRuleError(c, err) handleFirewallRuleError(c, err)
return return
} }
@@ -417,7 +332,7 @@ func (b *BaseApi) UpdateFirewallRule(c *gin.Context) {
} }
// @Tags Firewall // @Tags Firewall
// @Summary Reorder a managed unified firewall v2 rule // @Summary Reorder a firewall rule
// @Accept json // @Accept json
// @Param request body dto.FirewallRuleReorder true "request" // @Param request body dto.FirewallRuleReorder true "request"
// @Success 200 // @Success 200
@@ -425,53 +340,41 @@ func (b *BaseApi) UpdateFirewallRule(c *gin.Context) {
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Security Timestamp // @Security Timestamp
// @Router /hosts/firewall/rules/reorder [post] // @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) { func (b *BaseApi) ReorderFirewallRule(c *gin.Context) {
var request dto.FirewallRuleReorder var request dto.FirewallRuleReorder
if err := helper.CheckBindAndValidate(&request, c); err != nil { if err := helper.CheckBindAndValidate(&request, c); err != nil {
return return
} }
if !normalizeFirewallRuleUUID(c, &request.UUID) { if err := firewallService.Reorder(c.Request.Context(), request); err != nil {
return
}
if err := firewallService.Reorder(c.Request.Context(), c.ClientIP(), request); err != nil {
handleFirewallRuleError(c, err) handleFirewallRuleError(c, err)
return return
} }
helper.Success(c) 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) { func handleFirewallRuleError(c *gin.Context, err error) {
var businessErr buserr.BusinessError
isBusinessError := errors.As(err, &businessErr)
switch { switch {
case errors.Is(err, filter.ErrProtectedRule): case errors.Is(err, filter.ErrProtectedRule):
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_LOCKOUT_RISK", "ErrInvalidParams", err) helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_LOCKOUT_RISK", "ErrInvalidParams", err)
case errors.Is(err, filter.ErrRuleStale): case errors.Is(err, filter.ErrRuleStale):
helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_STALE", "ErrInvalidParams", err) helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_STALE", "ErrInvalidParams", err)
case errors.Is(err, repo.ErrFirewallRuleRevisionConflict):
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), case errors.Is(err, filter.ErrUnsupportedScope), errors.Is(err, filter.ErrInvalidScope),
errors.Is(err, filter.ErrProviderUnavailable), errors.Is(err, filter.ErrAdapterUnavailable): errors.Is(err, filter.ErrProviderUnavailable), errors.Is(err, filter.ErrAdapterUnavailable):
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_SCOPE_UNSUPPORTED", "ErrInvalidParams", err) 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), case errors.Is(err, filter.ErrInvalidRule), errors.Is(err, filter.ErrRuleOperation):
errors.Is(err, repo.ErrFirewallPersistenceInvalid):
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_RULE_UNSUPPORTED", "ErrInvalidParams", err) helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_RULE_UNSUPPORTED", "ErrInvalidParams", err)
case errors.Is(err, filter.ErrVerificationFailed): case isBusinessError && businessErr.Msg == "ErrRecordExist":
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_VERIFY_FAILED", "ErrInternalServer", err) 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: default:
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_APPLY_FAILED", "ErrInternalServer", err) helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_APPLY_FAILED", "ErrInternalServer", err)
} }
@@ -494,7 +397,7 @@ func (b *BaseApi) LoadFirewallSettings(c *gin.Context) {
// @Tags Firewall // @Tags Firewall
// @Summary Create firewall port whitelist rules // @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 // @Accept json
// @Param request body dto.FirewallPortWhitelistCreate true "request" // @Param request body dto.FirewallPortWhitelistCreate true "request"
// @Success 200 // @Success 200
@@ -516,7 +419,7 @@ func (b *BaseApi) CreateFirewallPortWhitelist(c *gin.Context) {
// @Tags Firewall // @Tags Firewall
// @Summary Update firewall port whitelist rules // @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 // @Accept json
// @Param request body dto.FirewallPortWhitelistUpdate true "request" // @Param request body dto.FirewallPortWhitelistUpdate true "request"
// @Success 200 // @Success 200
@@ -538,7 +441,7 @@ func (b *BaseApi) UpdateFirewallPortWhitelist(c *gin.Context) {
// @Tags Firewall // @Tags Firewall
// @Summary Delete firewall port whitelist rules // @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 // @Accept json
// @Param request body dto.FirewallPortWhitelistDelete true "request" // @Param request body dto.FirewallPortWhitelistDelete true "request"
// @Success 200 // @Success 200
@@ -573,14 +476,10 @@ func (b *BaseApi) OperateFirewallBackend(c *gin.Context) {
return return
} }
if err := firewallSettingService.Operate(c.Request.Context(), request); err != nil { if err := firewallSettingService.Operate(c.Request.Context(), request); err != nil {
if errors.Is(err, service.ErrFirewallBackendCleanupRequired) { var businessErr buserr.BusinessError
helper.ErrorWithBusinessCode( if errors.As(err, &businessErr) && businessErr.Msg == "ErrFirewallBackendCleanupRequired" {
c, c.JSON(http.StatusOK, dto.Response{Code: http.StatusConflict, ErrorCode: "FW_BACKEND_CLEANUP_REQUIRED", Message: err.Error()})
http.StatusConflict, c.Abort()
"FW_BACKEND_CLEANUP_REQUIRED",
"ErrInvalidParams",
err,
)
return return
} }
helper.InternalServer(c, err) helper.InternalServer(c, err)
@@ -619,21 +518,6 @@ func (b *BaseApi) ListDockerPublishedPorts(c *gin.Context) {
helper.SuccessWithData(c, data) 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 // @Tags Firewall
// @Summary Operate Docker port guard // @Summary Operate Docker port guard
// @Accept json // @Accept json
@@ -709,21 +593,88 @@ func (b *BaseApi) UpsertDockerPortGuardPolicies(c *gin.Context) {
} }
func handleDockerPortGuardError(c *gin.Context, err error) { func handleDockerPortGuardError(c *gin.Context, err error) {
if errors.Is(err, service.ErrDockerIptablesChainUnavailable) { var businessErr buserr.BusinessError
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_IPTABLES_CHAIN_UNAVAILABLE", "ErrDockerIptablesChainUnavailable", err) if errors.As(err, &businessErr) {
return code, errorCode := http.StatusInternalServerError, ""
switch businessErr.Msg {
case "ErrDockerIptablesChainUnavailable":
code, errorCode = http.StatusServiceUnavailable, "FW_DOCKER_IPTABLES_CHAIN_UNAVAILABLE"
case "ErrDockerNftablesChainUnavailable":
code, errorCode = http.StatusServiceUnavailable, "FW_DOCKER_NFTABLES_CHAIN_UNAVAILABLE"
case "ErrInvalidParams":
code, errorCode = http.StatusBadRequest, "FW_DOCKER_GUARD_INVALID"
case "ErrDockerFailed":
code, errorCode = http.StatusServiceUnavailable, "FW_DOCKER_UNAVAILABLE"
}
if errorCode != "" {
c.JSON(http.StatusOK, dto.Response{Code: code, ErrorCode: errorCode, Message: err.Error()})
c.Abort()
return
}
} }
if errors.Is(err, service.ErrDockerNftablesChainUnavailable) { if errors.Is(err, docker.ErrUnavailable) {
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_NFTABLES_CHAIN_UNAVAILABLE", "ErrDockerNftablesChainUnavailable", err)
return
}
if errors.Is(err, service.ErrDockerGuardInvalid) {
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_DOCKER_GUARD_INVALID", "ErrInvalidParams", err)
return
}
if errors.Is(err, service.ErrDockerUnavailable) {
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_UNAVAILABLE", "ErrDockerFailed", err) helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_UNAVAILABLE", "ErrDockerFailed", err)
return return
} }
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_DOCKER_GUARD_FAILED", "ErrInternalServer", err) 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
View File
@@ -1,13 +1,9 @@
package v2 package v2
import ( import (
"sort"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/v4/disk"
"github.com/shirou/gopsutil/v4/net"
) )
// @Tags Monitor // @Tags Monitor
@@ -32,14 +28,19 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
} }
// @Tags Monitor // @Tags Monitor
// @Summary Clean monitor data // @Summary Clean host or GPU monitor data
// @Param request body dto.MonitorClean true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Security Timestamp // @Security Timestamp
// @Router /hosts/monitor/clean [post] // @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) { 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) helper.InternalServer(c, err)
return return
} }
@@ -91,14 +92,7 @@ func (b *BaseApi) UpdateMonitorSetting(c *gin.Context) {
// @Security Timestamp // @Security Timestamp
// @Router /hosts/monitor/netoptions [get] // @Router /hosts/monitor/netoptions [get]
func (b *BaseApi) GetNetworkOptions(c *gin.Context) { func (b *BaseApi) GetNetworkOptions(c *gin.Context) {
netStat, _ := net.IOCounters(true) helper.SuccessWithData(c, monitorService.LoadNetworkOptions())
var options []string
options = append(options, "all")
for _, net := range netStat {
options = append(options, net.Name)
}
sort.Strings(options)
helper.SuccessWithData(c, options)
} }
// @Tags Monitor // @Tags Monitor
@@ -108,12 +102,5 @@ func (b *BaseApi) GetNetworkOptions(c *gin.Context) {
// @Security Timestamp // @Security Timestamp
// @Router /hosts/monitor/iooptions [get] // @Router /hosts/monitor/iooptions [get]
func (b *BaseApi) GetIOOptions(c *gin.Context) { func (b *BaseApi) GetIOOptions(c *gin.Context) {
diskStat, _ := disk.IOCounters() helper.SuccessWithData(c, monitorService.LoadIOOptions())
var options []string
options = append(options, "all")
for _, net := range diskStat {
options = append(options, net.Name)
}
sort.Strings(options)
helper.SuccessWithData(c, options)
} }
+45
View File
@@ -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)
}
+1 -1
View File
@@ -169,7 +169,7 @@ func (b *BaseApi) GetNodePackageRunScript(c *gin.Context) {
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Security Timestamp // @Security Timestamp
// @Router /runtimes/operate [post] // @Router /runtimes/operate [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"操作运行环境 [id]","formatEN":"Operate runtime [id]"} // @x-panel-log {"bodyKeys":["ID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ID","isList":false,"db":"runtimes","output_column":"name","output_value":"name"}],"formatZH":"操作运行环境 [name]","formatEN":"Operate runtime [name]"}
func (b *BaseApi) OperateRuntime(c *gin.Context) { func (b *BaseApi) OperateRuntime(c *gin.Context) {
var req request.RuntimeOperate var req request.RuntimeOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil { if err := helper.CheckBindAndValidate(&req, c); err != nil {
+2
View File
@@ -83,6 +83,7 @@ func (b *BaseApi) CreateRootCert(c *gin.Context) {
} }
if err := loadCertAfterDecrypt(&req); err != nil { if err := loadCertAfterDecrypt(&req); err != nil {
helper.BadRequest(c, err) helper.BadRequest(c, err)
return
} }
if err := sshService.CreateRootCert(req); err != nil { if err := sshService.CreateRootCert(req); err != nil {
helper.InternalServer(c, err) helper.InternalServer(c, err)
@@ -107,6 +108,7 @@ func (b *BaseApi) EditRootCert(c *gin.Context) {
} }
if err := loadCertAfterDecrypt(&req); err != nil { if err := loadCertAfterDecrypt(&req); err != nil {
helper.BadRequest(c, err) helper.BadRequest(c, err)
return
} }
if err := sshService.EditRootCert(req); err != nil { if err := sshService.EditRootCert(req); err != nil {
helper.InternalServer(c, err) helper.InternalServer(c, err)
+10
View File
@@ -243,6 +243,16 @@ func loadTerminalIdentity(c *gin.Context) (terminal.Identity, bool) {
UserID: strings.TrimSpace(c.GetHeader(terminal.HeaderUserID)), UserID: strings.TrimSpace(c.GetHeader(terminal.HeaderUserID)),
AuthSessionID: strings.TrimSpace(c.GetHeader(terminal.HeaderAuthSessionID)), AuthSessionID: strings.TrimSpace(c.GetHeader(terminal.HeaderAuthSessionID)),
} }
if value := c.GetHeader(terminal.HeaderAuthLeaseUntil); value != "" {
millis, err := strconv.ParseInt(value, 10, 64)
if err != nil || millis <= 0 {
return terminal.Identity{}, false
}
identity.AuthLeaseUntil = time.UnixMilli(millis)
if maximum := time.Now().Add(90 * time.Second); identity.AuthLeaseUntil.After(maximum) {
identity.AuthLeaseUntil = maximum
}
}
return identity, identity.Valid() return identity, identity.Valid()
} }
+10
View File
@@ -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})
}
+2
View File
@@ -21,6 +21,7 @@ type AlertBase struct {
} }
type PushAlert struct { type PushAlert struct {
Result string `json:"result,omitempty"`
TaskName string `json:"taskName"` TaskName string `json:"taskName"`
AlertType string `json:"alertType"` AlertType string `json:"alertType"`
EntryID uint `json:"entryID"` EntryID uint `json:"entryID"`
@@ -53,6 +54,7 @@ type AlertDTO struct {
Method string `json:"method"` Method string `json:"method"`
Title string `json:"title"` Title string `json:"title"`
Project string `json:"project"` Project string `json:"project"`
TaskName string `json:"taskName,omitempty"`
Status string `json:"status"` Status string `json:"status"`
SendCount uint `json:"sendCount"` SendCount uint `json:"sendCount"`
AdvancedParams string `json:"advancedParams"` AdvancedParams string `json:"advancedParams"`
+10 -7
View File
@@ -51,9 +51,10 @@ type CronjobOperate struct {
Secret string `json:"secret"` Secret string `json:"secret"`
Args string `json:"args"` Args string `json:"args"`
AlertCount uint `json:"alertCount"` AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"` AlertTitle string `json:"alertTitle"`
AlertMethod string `json:"alertMethod"` AlertMethod string `json:"alertMethod"`
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
CleanLogConfig CleanLogConfig
} }
@@ -126,7 +127,8 @@ type CronjobInfo struct {
Secret string `json:"secret"` Secret string `json:"secret"`
Args string `json:"args"` Args string `json:"args"`
AlertCount uint `json:"alertCount"` AlertCount uint `json:"alertCount"`
AlertTriggerMode string `json:"alertTriggerMode"`
} }
type CronjobImport struct { type CronjobImport struct {
@@ -169,9 +171,10 @@ type CronjobTrans struct {
SourceAccounts []string `json:"sourceAccounts"` SourceAccounts []string `json:"sourceAccounts"`
DownloadAccount string `json:"downloadAccount"` DownloadAccount string `json:"downloadAccount"`
AlertCount uint `json:"alertCount"` AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"` AlertTitle string `json:"alertTitle"`
AlertMethod string `json:"alertMethod"` AlertMethod string `json:"alertMethod"`
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
} }
type TransHelper struct { type TransHelper struct {
Name string `json:"name"` Name string `json:"name"`
+4
View File
@@ -22,6 +22,10 @@ type DBBaseInfo struct {
Port int64 `json:"port"` Port int64 `json:"port"`
} }
type RedisCliInstall struct {
TaskID string `json:"taskID" validate:"omitempty,uuid"`
}
// mysql // mysql
type MysqlDBSearch struct { type MysqlDBSearch struct {
PageInfo PageInfo
+91 -123
View File
@@ -2,11 +2,13 @@ package dto
import ( import (
"github.com/1Panel-dev/1Panel/agent/utils/firewall" "github.com/1Panel-dev/1Panel/agent/utils/firewall"
dockerfirewall "github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter" "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
firewallsync "github.com/1Panel-dev/1Panel/agent/utils/firewall/sync" "github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
) )
type FirewallSubsystemStatus struct { type FirewallSubsystemStatus struct {
IPv6Enabled bool `json:"ipv6Enabled"`
Name string `json:"name"` Name string `json:"name"`
Backend string `json:"backend"` Backend string `json:"backend"`
ConflictBackend string `json:"conflictBackend,omitempty"` ConflictBackend string `json:"conflictBackend,omitempty"`
@@ -18,7 +20,6 @@ type FirewallSubsystemStatus struct {
PingStatus string `json:"pingStatus"` PingStatus string `json:"pingStatus"`
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
Reason string `json:"reason,omitempty"` Reason string `json:"reason,omitempty"`
SyncError string `json:"syncError,omitempty"`
LifecycleTaskID string `json:"lifecycleTaskID,omitempty"` LifecycleTaskID string `json:"lifecycleTaskID,omitempty"`
IPv4 FirewallBackendFamilyStatus `json:"ipv4"` IPv4 FirewallBackendFamilyStatus `json:"ipv4"`
IPv6 FirewallBackendFamilyStatus `json:"ipv6"` IPv6 FirewallBackendFamilyStatus `json:"ipv6"`
@@ -49,10 +50,12 @@ type FirewallBackendOption struct {
} }
type FirewallBackendFamilyStatus struct { type FirewallBackendFamilyStatus struct {
Available bool `json:"available"` Partial bool `json:"partial"`
Initialized bool `json:"initialized"` Available bool `json:"available"`
Bound bool `json:"bound"` Initialized bool `json:"initialized"`
Reason string `json:"reason,omitempty"` Bound bool `json:"bound"`
Reason string `json:"reason,omitempty"`
RAInterfaces []string `json:"raInterfaces,omitempty"`
} }
type FirewallBackendGroup struct { type FirewallBackendGroup struct {
@@ -62,6 +65,7 @@ type FirewallBackendGroup struct {
} }
type FirewallSettings struct { type FirewallSettings struct {
IPv6Enabled bool `json:"ipv6Enabled"`
System FirewallBackendGroup `json:"system"` System FirewallBackendGroup `json:"system"`
Forwarding FirewallBackendGroup `json:"forwarding"` Forwarding FirewallBackendGroup `json:"forwarding"`
Docker FirewallBackendGroup `json:"docker"` Docker FirewallBackendGroup `json:"docker"`
@@ -90,6 +94,17 @@ type FirewallBackendOperation struct {
Operation string `json:"operation" validate:"required,oneof=select initialize cleanup"` 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 { type FilterChainOperation struct {
Name string `json:"name" validate:"required,eq=1PANEL_BASIC"` Name string `json:"name" validate:"required,eq=1PANEL_BASIC"`
Operate string `json:"operate" validate:"required,oneof=init-base bind-base unbind-base"` Operate string `json:"operate" validate:"required,oneof=init-base bind-base unbind-base"`
@@ -102,42 +117,55 @@ type FilterChainOperationResponse struct {
} }
type FirewallInitializationTask 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 FirewallSystemPort = firewall.SystemPort
type FirewallRuleInventoryResponse struct { type FirewallRuleInventoryResponse struct {
IPv4Range filter.PositionRange `json:"ipv4Range"` IPv4Range filter.PositionRange `json:"ipv4Range"`
IPv6Range filter.PositionRange `json:"ipv6Range"` IPv6Range filter.PositionRange `json:"ipv6Range"`
Total int64 `json:"total"` Total int64 `json:"total"`
AllTotal int64 `json:"allTotal"` AllTotal int64 `json:"allTotal"`
ManagedTotal int64 `json:"managedTotal"` Items []filter.InventoryItem `json:"items"`
Items []filter.InventoryItem `json:"items"` Notices []filter.ScopeNotice `json:"notices,omitempty"`
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 { type FirewallRuleResetResponse struct {
Removed int `json:"removed"` BackupPath string `json:"backupPath"`
Disabled bool `json:"disabled"` Removed int `json:"removed"`
Disabled bool `json:"disabled"`
} }
type FirewallRuleReset struct { 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"` Provider filter.Provider `json:"provider,omitempty" validate:"omitempty,oneof=firewalld ufw iptables nftables"`
WithDockerRestart bool `json:"withDockerRestart"` WithDockerRestart bool `json:"withDockerRestart"`
} }
type FirewallRuleInventory struct { type FirewallRuleInventory struct {
Refresh bool `json:"refresh,omitempty"`
PageInfo PageInfo
Scope filter.Scope `json:"scope,omitempty"` Scope filter.Scope `json:"scope,omitempty"`
Scopes []filter.Scope `json:"scopes,omitempty" validate:"max=16"` Scopes []filter.Scope `json:"scopes,omitempty" validate:"max=16"`
All bool `json:"all,omitempty"` All bool `json:"all,omitempty"`
Info string `json:"info"` Info string `json:"info"`
Families []filter.Family `json:"families,omitempty" validate:"omitempty,dive,oneof=ipv4 ipv6"` Families []filter.Family `json:"families,omitempty" validate:"omitempty,dive,oneof=ipv4 ipv6"`
Actions []string `json:"actions,omitempty" validate:"omitempty,dive,oneof=accept deny"` 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"`
ExcludeChains []string `json:"excludeChains,omitempty" validate:"omitempty,dive,oneof=1PANEL_BASIC_BEFORE 1PANEL_BASIC 1PANEL_BASIC_AFTER"`
} }
type FirewallNativeDetail struct { type FirewallNativeDetail struct {
@@ -148,6 +176,7 @@ type FirewallNativeDetail struct {
} }
type DockerPortGuardBase struct { type DockerPortGuardBase struct {
IPv6Enabled bool `json:"ipv6Enabled"`
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
IsExist bool `json:"isExist"` IsExist bool `json:"isExist"`
@@ -160,6 +189,7 @@ type DockerPortGuardBase struct {
} }
type DockerPortGuardFamilyStatus struct { type DockerPortGuardFamilyStatus struct {
Partial bool `json:"partial"`
State string `json:"state"` State string `json:"state"`
Reason string `json:"reason,omitempty"` Reason string `json:"reason,omitempty"`
Initialized bool `json:"initialized"` Initialized bool `json:"initialized"`
@@ -180,11 +210,8 @@ type DockerPortGuardEndpoint struct {
Application string `json:"application,omitempty"` Application string `json:"application,omitempty"`
PolicyUUID string `json:"policyUUID,omitempty"` PolicyUUID string `json:"policyUUID,omitempty"`
Mode string `json:"mode,omitempty"` Mode string `json:"mode,omitempty"`
NativeAction string `json:"nativeAction,omitempty"`
ReadOnly bool `json:"readOnly,omitempty"`
Sources []string `json:"sources"` Sources []string `json:"sources"`
Effective bool `json:"effective"` Effective bool `json:"effective"`
Description string `json:"description,omitempty"`
TrafficPath string `json:"trafficPath"` TrafficPath string `json:"trafficPath"`
ManagementTarget string `json:"managementTarget"` ManagementTarget string `json:"managementTarget"`
ManagementReason string `json:"managementReason,omitempty"` ManagementReason string `json:"managementReason,omitempty"`
@@ -221,6 +248,7 @@ type DockerPortGuardEndpointIdentity struct {
type DockerPortGuardPolicyBatch struct { type DockerPortGuardPolicyBatch struct {
Policies []DockerPortGuardPolicy `json:"policies" validate:"required,min=1,dive"` Policies []DockerPortGuardPolicy `json:"policies" validate:"required,min=1,dive"`
Import bool `json:"import"`
} }
type DockerPortGuardPolicyBatchDelete struct { type DockerPortGuardPolicyBatchDelete struct {
@@ -229,29 +257,27 @@ type DockerPortGuardPolicyBatchDelete struct {
type DockerPortGuardPolicy struct { type DockerPortGuardPolicy struct {
DockerPortGuardEndpointIdentity DockerPortGuardEndpointIdentity
Mode string `json:"mode" validate:"required,oneof=deny_sources allow_sources deny_all"` 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"` Sources []string `json:"sources" validate:"dive,required,max=64"`
Description string `json:"description" validate:"max=256"`
} }
type DockerPortGuardOperation struct { type DockerPortGuardOperation struct {
Operation string `json:"operation" validate:"required,oneof=initialize bind unbind"` BackupFile string `json:"backupFile,omitempty" validate:"omitempty,max=255"`
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"` 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"`
} }
type FirewallRuleCreateItem struct { type FirewallRuleCreateItem struct {
Rule filter.FirewallRule `json:"rule" validate:"required"` Raw string `json:"raw,omitempty"`
SourceKind string `json:"sourceKind" validate:"omitempty,oneof=user imported"` ParseStatus filter.ParseStatus `json:"parseStatus,omitempty"`
SourceID string `json:"sourceID"` Rule filter.FirewallRule `json:"rule" validate:"required"`
SourceKind string `json:"sourceKind" validate:"omitempty,oneof=user imported"`
} }
type FirewallRuleCreate struct { 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 { type FirewallRuleCreateResponse struct {
@@ -270,66 +296,13 @@ type FirewallRuleCreateFailure struct {
Error string `json:"error,omitempty"` 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 { type FirewallRuleDelete struct {
UUIDs []string `json:"uuids" validate:"omitempty,dive,required,max=64"` Targets []FirewallRuleDeleteItem `json:"targets" validate:"required,min=1,dive"`
BeforeRules []FirewallRuleDeleteTarget `json:"beforeRules,omitempty" validate:"omitempty,dive"` }
type FirewallRuleDeleteItem struct {
FirewallRuleDeleteTarget
Observed filter.ObservedRule `json:"observed" validate:"required"`
} }
type FirewallRuleDeleteTarget struct { type FirewallRuleDeleteTarget struct {
@@ -346,13 +319,13 @@ type FirewallRuleDeleteResponse struct {
} }
type FirewallRuleDeleteFailure struct { type FirewallRuleDeleteFailure struct {
Index int `json:"index"` Index int `json:"index"`
UUID string `json:"uuid"` InstanceKey string `json:"instanceKey"`
Error string `json:"error"` Error string `json:"error"`
} }
type FirewallRuleUpdate struct { 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"` 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"` Description *string `json:"description,omitempty" validate:"excluded_with=Rule"`
OrderIndex *int64 `json:"orderIndex,omitempty" validate:"excluded_with=Rule Priority"` OrderIndex *int64 `json:"orderIndex,omitempty" validate:"excluded_with=Rule Priority"`
@@ -360,26 +333,21 @@ type FirewallRuleUpdate struct {
} }
type FirewallRuleReorder struct { type FirewallRuleReorder struct {
UUID string `json:"uuid" validate:"required,max=64"` FirewallRuleDeleteTarget
TargetPosition *int64 `json:"targetPosition"` TargetPosition *int64 `json:"targetPosition"`
Priority *int `json:"priority"` Priority *int `json:"priority"`
} }
func (p *FirewallRuleSyncPreview) Add(item FirewallRuleSyncItem) { type FirewallRuleExportItem struct {
p.Items = append(p.Items, item) filter.FirewallRule
switch item.Status { Raw string `json:"raw,omitempty"`
case firewallsync.StatusReady: ParseStatus filter.ParseStatus `json:"parseStatus,omitempty"`
p.Ready++ }
p.Total++
case firewallsync.StatusExisting: type FirewallSubsystemBackup struct {
p.Existing++ Families []string `json:"families,omitempty"`
p.Total++ Subsystem string `json:"subsystem"`
case firewallsync.StatusRemove: Provider filter.Provider `json:"provider"`
p.Removed++ Forwarding []forwarding.Rule `json:"forwarding"`
case firewallsync.StatusBlocked: Docker *dockerfirewall.PolicyInventory `json:"docker,omitempty"`
p.Blocked++
if item.ReasonCode != firewallsync.ReasonReadOnlyRule {
p.Total++
}
}
} }
+2 -6
View File
@@ -24,15 +24,11 @@ type ForwardRule struct {
UsedStatus string `json:"usedStatus"` UsedStatus string `json:"usedStatus"`
Description string `json:"description"` Description string `json:"description"`
IsDesired bool `json:"isDesired"`
IsRuntime bool `json:"isRuntime"`
SyncStatus string `json:"syncStatus"`
} }
type ForwardRuleOperate struct { type ForwardRuleOperate struct {
ForceDelete bool `json:"forceDelete"` Import bool `json:"import"`
Rules []ForwardRuleOperation `json:"rules" validate:"required,min=1,dive"` Rules []ForwardRuleOperation `json:"rules" validate:"required,min=1,dive"`
} }
type ForwardRuleOperation struct { type ForwardRuleOperation struct {
+87 -18
View File
@@ -1,6 +1,10 @@
package dto package dto
import "time" import (
"time"
"github.com/1Panel-dev/1Panel/agent/app/model"
)
type MonitorSearch struct { type MonitorSearch struct {
Param string `json:"param" validate:"required,oneof=all cpu memory load io network"` Param string `json:"param" validate:"required,oneof=all cpu memory load io network"`
@@ -26,24 +30,33 @@ type Process struct {
} }
type MonitorSetting struct { type MonitorSetting struct {
MonitorStatus string `json:"monitorStatus"` GPUMonitorStatus string `json:"gpuMonitorStatus"`
MonitorStoreDays string `json:"monitorStoreDays"` GPUMonitorStoreDays string `json:"gpuMonitorStoreDays"`
MonitorInterval string `json:"monitorInterval"` GPUMonitorInterval string `json:"gpuMonitorInterval"`
DefaultNetwork string `json:"defaultNetwork"` VLLMMonitorStatus string `json:"vllmMonitorStatus"`
DefaultIO string `json:"defaultIO"` 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 { 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"` Value string `json:"value"`
} }
type MonitorGPUOptions struct { type MonitorGPUOptions struct {
Supported bool `json:"supported"`
GPUType string `json:"gpuType"` GPUType string `json:"gpuType"`
ChartHide []GPUChartHide `json:"chartHide"` ChartHide []GPUChartHide `json:"chartHide"`
Options []string `json:"options"` Options []string `json:"options"`
} }
type GPUChartHide struct { type GPUChartHide struct {
DeviceID string `json:"deviceID"`
Legacy bool `json:"legacy"`
ProductName string `json:"productName"` ProductName string `json:"productName"`
Type string `json:"type"` Type string `json:"type"`
Process bool `json:"process"` Process bool `json:"process"`
@@ -55,23 +68,54 @@ type GPUChartHide struct {
Speed bool `json:"speed"` Speed bool `json:"speed"`
} }
type MonitorGPUSearch struct { type MonitorGPUSearch struct {
Aggregation string `json:"aggregation" validate:"omitempty,oneof=avg max"`
DeviceID string `json:"deviceID"`
Legacy bool `json:"legacy"`
ProductName string `json:"productName"` ProductName string `json:"productName"`
StartTime time.Time `json:"startTime"` StartTime time.Time `json:"startTime"`
EndTime time.Time `json:"endTime"` EndTime time.Time `json:"endTime"`
} }
type MonitorGPUData struct { type MonitorGPUData struct {
Date []time.Time `json:"date"` MemoryActivity []*float64 `json:"memoryActivity"`
GPUValue []float64 `json:"gpuValue"` EncoderUtil []*float64 `json:"encoderUtil"`
TemperatureValue []float64 `json:"temperatureValue"` DecoderUtil []*float64 `json:"decoderUtil"`
PowerTotal []float64 `json:"powerTotal"` JPEGUtil []*float64 `json:"jpegUtil"`
PowerUsed []float64 `json:"powerUsed"` OFAUtil []*float64 `json:"ofaUtil"`
PowerPercent []float64 `json:"powerPercent"` MediaUtil []*float64 `json:"mediaUtil"`
MemoryTotal []float64 `json:"memoryTotal"` ComputeUtil []*float64 `json:"computeUtil"`
MemoryUsed []float64 `json:"memoryUsed"` CopyUtil []*float64 `json:"copyUtil"`
MemoryPercent []float64 `json:"memoryPercent"` HotspotTemperature []*float64 `json:"hotspotTemperature"`
SpeedValue []int `json:"speedValue"` FanRPM []*float64 `json:"fanRPM"`
AICPUUtil []*float64 `json:"aiCPUUtil"`
CtrlCPUUtil []*float64 `json:"ctrlCPUUtil"`
DDRUsed []*float64 `json:"ddrUsed"`
DDRTotal []*float64 `json:"ddrTotal"`
HBMUsed []*float64 `json:"hbmUsed"`
HBMTotal []*float64 `json:"hbmTotal"`
DDRBandwidth []*float64 `json:"ddrBandwidth"`
HBMBandwidth []*float64 `json:"hbmBandwidth"`
MemoryBandwidth []*float64 `json:"memoryBandwidth"`
MediaFrequency []*float64 `json:"mediaFrequency"`
HugepagesUsed []*float64 `json:"hugepagesUsed"`
HugepagesTotal []*float64 `json:"hugepagesTotal"`
ProcessCount []int `json:"processCount"` BucketSeconds int64 `json:"bucketSeconds"`
SampleCount int64 `json:"sampleCount"`
MemoryTemperatureValue []*float64 `json:"memoryTemperatureValue"`
FrequencyValue []*float64 `json:"frequencyValue"`
MemoryFrequencyValue []*float64 `json:"memoryFrequencyValue"`
Date []time.Time `json:"date"`
GPUValue []*float64 `json:"gpuValue"`
TemperatureValue []*float64 `json:"temperatureValue"`
PowerTotal []*float64 `json:"powerTotal"`
PowerUsed []*float64 `json:"powerUsed"`
PowerPercent []*float64 `json:"powerPercent"`
MemoryTotal []*float64 `json:"memoryTotal"`
MemoryUsed []*float64 `json:"memoryUsed"`
MemoryPercent []*float64 `json:"memoryPercent"`
SpeedValue []*float64 `json:"speedValue"`
ProcessCount []*float64 `json:"processCount"`
GPUProcesses [][]GPUProcess `json:"gpuProcesses"` GPUProcesses [][]GPUProcess `json:"gpuProcesses"`
} }
@@ -81,3 +125,28 @@ type GPUProcess struct {
ProcessName string `json:"processName"` ProcessName string `json:"processName"`
UsedMemory string `json:"usedMemory"` 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"`
}
+1 -1
View File
@@ -25,7 +25,7 @@ type RootCertOperate struct {
ID uint `json:"id"` ID uint `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Mode string `json:"mode"` Mode string `json:"mode"`
EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"` EncryptionMode string `json:"encryptionMode"`
PassPhrase string `json:"passPhrase"` PassPhrase string `json:"passPhrase"`
PublicKey string `json:"publicKey"` PublicKey string `json:"publicKey"`
PrivateKey string `json:"privateKey"` PrivateKey string `json:"privateKey"`
-222
View File
@@ -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
}
+75 -9
View File
@@ -1,5 +1,7 @@
package model package model
import "time"
type MonitorBase struct { type MonitorBase struct {
BaseModel BaseModel
Cpu float64 `json:"cpu"` Cpu float64 `json:"cpu"`
@@ -33,14 +35,78 @@ type MonitorNetwork struct {
} }
type MonitorGPU struct { type MonitorGPU struct {
MemoryUtil *float64 `json:"memoryUtil"`
MemoryActivity *float64 `json:"memoryActivity"`
EncoderUtil *float64 `json:"encoderUtil"`
DecoderUtil *float64 `json:"decoderUtil"`
JPEGUtil *float64 `json:"jpegUtil"`
OFAUtil *float64 `json:"ofaUtil"`
MediaUtil *float64 `json:"mediaUtil"`
ComputeUtil *float64 `json:"computeUtil"`
CopyUtil *float64 `json:"copyUtil"`
HotspotTemperature *float64 `json:"hotspotTemperature"`
FanRPM *float64 `json:"fanRPM"`
AICPUUtil *float64 `json:"aiCPUUtil"`
CtrlCPUUtil *float64 `json:"ctrlCPUUtil"`
DDRUsed *float64 `json:"ddrUsed"`
DDRTotal *float64 `json:"ddrTotal"`
HBMUsed *float64 `json:"hbmUsed"`
HBMTotal *float64 `json:"hbmTotal"`
DDRBandwidth *float64 `json:"ddrBandwidth"`
HBMBandwidth *float64 `json:"hbmBandwidth"`
MemoryBandwidth *float64 `json:"memoryBandwidth"`
MediaFrequency *float64 `json:"mediaFrequency"`
HugepagesUsed *float64 `json:"hugepagesUsed"`
HugepagesTotal *float64 `json:"hugepagesTotal"`
MemoryTemperature *float64 `json:"memoryTemperature"`
DeviceID string `json:"deviceID"`
DeviceType string `json:"deviceType"`
ProcessStatus string `json:"processStatus"`
Frequency *float64 `json:"frequency"`
MemoryFrequency *float64 `json:"memoryFrequency"`
IntervalSeconds int `json:"intervalSeconds"`
BaseModel BaseModel
ProductName string `json:"productName"` ProductName string `json:"productName"`
GPUUtil float64 `json:"gpuUtil"` GPUUtil *float64 `json:"gpuUtil"`
Temperature float64 `json:"temperature"` Temperature *float64 `json:"temperature"`
PowerDraw float64 `json:"powerDraw"` PowerDraw *float64 `json:"powerDraw"`
MaxPowerLimit float64 `json:"maxPowerLimit"` MaxPowerLimit *float64 `json:"maxPowerLimit"`
MemUsed float64 `json:"memUsed"` MemUsed *float64 `json:"memUsed"`
MemTotal float64 `json:"memTotal"` MemTotal *float64 `json:"memTotal"`
FanSpeed int `json:"fanSpeed"` FanSpeed *float64 `json:"fanSpeed"`
Processes string `json:"processes"` Processes string `json:"processes"`
}
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"`
} }
-77
View File
@@ -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
})
}
-144
View File
@@ -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
}
-36
View File
@@ -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
View File
@@ -1,6 +1,8 @@
package repo package repo
import ( import (
"fmt"
"strings"
"time" "time"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
@@ -10,11 +12,25 @@ import (
type MonitorRepo struct{} type MonitorRepo struct{}
type GPUHistoryPoint struct {
model.MonitorGPU
Bucket int64
PowerPercent *float64
MemoryPercent *float64
ProcessCount *float64
}
type IMonitorRepo interface { type IMonitorRepo interface {
CleanHost() error
CleanGPU() error
GetBase(opts ...DBOption) ([]model.MonitorBase, 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) GetIO(opts ...DBOption) ([]model.MonitorIO, error)
GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error) GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error)
GetIONames() ([]string, error)
GetNetworkNames() ([]string, error)
CreateMonitorBase(model model.MonitorBase) error CreateMonitorBase(model model.MonitorBase) error
BatchCreateMonitorGPU(list []model.MonitorGPU) error BatchCreateMonitorGPU(list []model.MonitorGPU) error
@@ -25,13 +41,26 @@ type IMonitorRepo interface {
DelMonitorIO(timeForDelete time.Time) error DelMonitorIO(timeForDelete time.Time) error
DelMonitorNet(timeForDelete time.Time) error DelMonitorNet(timeForDelete time.Time) error
WithByProductName(name string) DBOption WithByGPUDevice(deviceID, name string, legacy bool) DBOption
} }
func NewIMonitorRepo() IMonitorRepo { func NewIMonitorRepo() IMonitorRepo {
return &MonitorRepo{} 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) { func (u *MonitorRepo) GetBase(opts ...DBOption) ([]model.MonitorBase, error) {
var data []model.MonitorBase var data []model.MonitorBase
db := global.MonitorDB db := global.MonitorDB
@@ -59,14 +88,16 @@ func (u *MonitorRepo) GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, erro
err := db.Find(&data).Error err := db.Find(&data).Error
return data, err return data, err
} }
func (u *MonitorRepo) GetGPU(opts ...DBOption) ([]model.MonitorGPU, error) { func (u *MonitorRepo) GetIONames() ([]string, error) {
var data []model.MonitorGPU var names []string
db := global.GPUMonitorDB err := global.MonitorDB.Model(&model.MonitorIO{}).Distinct().Pluck("name", &names).Error
for _, opt := range opts { return names, err
db = opt(db) }
}
err := db.Find(&data).Error func (u *MonitorRepo) GetNetworkNames() ([]string, error) {
return data, err 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 { 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 return global.GPUMonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorGPU{}).Error
} }
func (s *MonitorRepo) WithByProductName(name string) DBOption { func (u *MonitorRepo) GetGPUDevices() ([]model.MonitorGPU, error) {
return func(g *gorm.DB) *gorm.DB { var data []model.MonitorGPU
return g.Where("product_name = ?", name) 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
}
+78
View File
@@ -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
}
+53
View File
@@ -1,11 +1,13 @@
package repo package repo
import ( import (
"context"
"errors" "errors"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause"
) )
type SettingRepo struct{} type SettingRepo struct{}
@@ -19,12 +21,16 @@ type ISettingRepo interface {
WithByKey(key string) DBOption WithByKey(key string) DBOption
UpdateOrCreate(key, value string) error UpdateOrCreate(key, value string) error
UpdateValues(map[string]string) error
GetDescription(opts ...DBOption) (model.CommonDescription, error) GetDescription(opts ...DBOption) (model.CommonDescription, error)
GetDescriptionList(opts ...DBOption) ([]model.CommonDescription, error) GetDescriptionList(opts ...DBOption) ([]model.CommonDescription, error)
CreateDescription(data *model.CommonDescription) error CreateDescription(data *model.CommonDescription) error
SaveDescriptions(context.Context, []model.CommonDescription) error
UpdateDescription(id string, val map[string]interface{}) error UpdateDescription(id string, val map[string]interface{}) error
DelDescription(id string) error DelDescription(id string) error
DeleteDescriptions(context.Context, string, []string, bool) (int64, error)
WithDescriptionIDs(ids []string) DBOption
WithByDescriptionID(id 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 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) { func (s *SettingRepo) GetDescriptionList(opts ...DBOption) ([]model.CommonDescription, error) {
var lists []model.CommonDescription var lists []model.CommonDescription
db := global.DB.Model(&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 { func (s *SettingRepo) CreateDescription(data *model.CommonDescription) error {
return global.DB.Create(data).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 { func (s *SettingRepo) UpdateDescription(id string, val map[string]interface{}) error {
return global.DB.Model(&model.CommonDescription{}).Where("id = ?", id).Updates(val).Error return global.DB.Model(&model.CommonDescription{}).Where("id = ?", id).Updates(val).Error
} }
func (s *SettingRepo) DelDescription(id string) error { func (s *SettingRepo) DelDescription(id string) error {
return global.DB.Where("id = ?", id).Delete(&model.CommonDescription{}).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 { func (s *SettingRepo) WithByDescriptionID(id string) DBOption {
return func(g *gorm.DB) *gorm.DB { return func(g *gorm.DB) *gorm.DB {
return g.Where("id = ?", id) 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
View File
@@ -4,11 +4,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"mime" "mime"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
@@ -17,14 +14,13 @@ import (
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n" "github.com/1Panel-dev/1Panel/agent/i18n"
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
alertconfig "github.com/1Panel-dev/1Panel/agent/utils/alert_config" alertconfig "github.com/1Panel-dev/1Panel/agent/utils/alert_config"
alertwebhook "github.com/1Panel-dev/1Panel/agent/utils/alert_webhook" alertwebhook "github.com/1Panel-dev/1Panel/agent/utils/alert_webhook"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/copier" "github.com/1Panel-dev/1Panel/agent/utils/copier"
"github.com/1Panel-dev/1Panel/agent/utils/email" "github.com/1Panel-dev/1Panel/agent/utils/email"
"github.com/1Panel-dev/1Panel/agent/utils/xpack" "github.com/1Panel-dev/1Panel/agent/utils/xpack"
"github.com/1Panel-dev/1Panel/agent/utils/xpack/providers" "github.com/1Panel-dev/1Panel/agent/utils/xpack/providers"
"github.com/shirou/gopsutil/v4/disk"
) )
type AlertService struct{} type AlertService struct{}
@@ -109,7 +105,38 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
return 0, nil, err return 0, nil, err
} }
cronjobProjects := make(map[string]uint)
var cronjobIDs []uint
for _, item := range alerts { for _, item := range alerts {
if alertUtil.GetCronJobType(item.Type) != "cronJob" {
continue
}
if _, exists := cronjobProjects[item.Project]; exists {
continue
}
id, parseErr := strconv.ParseUint(item.Project, 10, strconv.IntSize)
if parseErr != nil || id == 0 {
continue
}
cronjobProjects[item.Project] = uint(id)
cronjobIDs = append(cronjobIDs, uint(id))
}
cronjobsByID := make(map[uint]model.Cronjob)
if len(cronjobIDs) > 0 {
cronjobs, err := cronjobRepo.List(repo.WithByIDs(cronjobIDs))
if err != nil {
return 0, nil, err
}
for _, cronjob := range cronjobs {
cronjobsByID[cronjob.ID] = cronjob
}
}
for _, item := range alerts {
var taskName string
if cronjob, exists := cronjobsByID[cronjobProjects[item.Project]]; exists && cronjob.Type == item.Type {
taskName = cronjob.Name
}
result = append(result, dto.AlertDTO{ result = append(result, dto.AlertDTO{
ID: item.ID, ID: item.ID,
@@ -119,6 +146,7 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
Method: item.Method, Method: item.Method,
Title: item.Title, Title: item.Title,
Project: item.Project, Project: item.Project,
TaskName: taskName,
Status: item.Status, Status: item.Status,
SendCount: item.SendCount, SendCount: item.SendCount,
AdvancedParams: item.AdvancedParams, AdvancedParams: item.AdvancedParams,
@@ -190,6 +218,16 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
return err return err
} }
} else { } else {
advanced, err := prepareCronJobAlertParams(create.Type, "", create.AdvancedParams)
if err != nil {
return err
}
create.AdvancedParams = advanced
if create.Status != constant.AlertDisable {
if err := a.validateCronJobAlertChannels(create.Type, advanced, create.Method); err != nil {
return err
}
}
alertInfo.Status = constant.AlertEnable alertInfo.Status = constant.AlertEnable
if err := copier.Copy(&alertInfo, &create); err != nil { if err := copier.Copy(&alertInfo, &create); err != nil {
return buserr.WithErr("ErrStructTransform", err) return buserr.WithErr("ErrStructTransform", err)
@@ -207,11 +245,24 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
} }
func (a AlertService) UpdateAlert(req dto.AlertUpdate, operator string) error { func (a AlertService) UpdateAlert(req dto.AlertUpdate, operator string) error {
if alertUtil.GetCronJobType(req.Type) == "cronJob" {
previous, err := alertRepo.Get(repo.WithByID(req.ID))
if err != nil {
return err
}
req.AdvancedParams, err = prepareCronJobAlertParams(req.Type, previous.AdvancedParams, req.AdvancedParams)
if err != nil {
return err
}
}
methodTypes, err := a.validateAlertMethodReferences(req.Method) methodTypes, err := a.validateAlertMethodReferences(req.Method)
if err != nil { if err != nil {
return err return err
} }
if req.Status != constant.AlertDisable { if req.Status != constant.AlertDisable {
if err := a.validateCronJobAlertChannels(req.Type, req.AdvancedParams, req.Method); err != nil {
return err
}
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil { if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
return err return err
} }
@@ -278,6 +329,9 @@ func (a AlertService) UpdateStatus(id uint, status string) error {
return err return err
} }
if status == constant.AlertEnable { if status == constant.AlertEnable {
if err := a.validateCronJobAlertChannels(alertInfo.Type, alertInfo.AdvancedParams, alertInfo.Method); err != nil {
return err
}
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil { if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
return err return err
} }
@@ -299,122 +353,14 @@ func (a AlertService) UpdateStatus(id uint, status string) error {
} }
func (a AlertService) GetDisks() ([]dto.DiskDTO, error) { func (a AlertService) GetDisks() ([]dto.DiskDTO, error) {
var disks []dto.DiskDTO infos := loadDiskInfo(true)
excludes := map[string]struct{}{ disks := make([]dto.DiskDTO, 0, len(infos))
"/mnt/cdrom": {}, "/boot": {}, "/boot/efi": {}, "/dev": {}, "/dev/shm": {}, for _, item := range infos {
"/run/lock": {}, "/run": {}, "/run/shm": {}, "/run/user": {}, 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 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 && strings.TrimSpace(stdout) == "" {
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) { func (a AlertService) PageAlertLogs(search dto.AlertLogSearch) (int64, []dto.AlertLogDTO, error) {
var ( var (
opts []repo.DBOption opts []repo.DBOption
@@ -1021,6 +967,23 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
alertRepo.WithByType(updateAlert.Type), alertRepo.WithByType(updateAlert.Type),
alertRepo.WithByProject(updateAlert.Project), alertRepo.WithByProject(updateAlert.Project),
) )
advanced, err := prepareCronJobAlertParams(updateAlert.Type, alertInfo.AdvancedParams, updateAlert.AdvancedParams)
if err != nil {
return err
}
updateAlert.AdvancedParams = advanced
if alertUtil.GetCronJobType(updateAlert.Type) == "cronJob" {
upMap["advanced_params"] = advanced
}
if newStatus == constant.AlertEnable {
method := updateAlert.Method
if method == "" {
method = alertInfo.Method
}
if err := a.validateCronJobAlertChannels(updateAlert.Type, advanced, method); err != nil {
return err
}
}
if alertInfo.ID > 0 { if alertInfo.ID > 0 {
shouldUpdate := false shouldUpdate := false
@@ -1034,6 +997,9 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
if val, ok := upMap["method"]; ok && val != "" && val != alertInfo.Method { if val, ok := upMap["method"]; ok && val != "" && val != alertInfo.Method {
shouldUpdate = true shouldUpdate = true
} }
if val, ok := upMap["advanced_params"]; ok && val != alertInfo.AdvancedParams {
shouldUpdate = true
}
if shouldUpdate { if shouldUpdate {
if err := alertRepo.Update( if err := alertRepo.Update(
@@ -1055,3 +1021,22 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
return nil return nil
} }
func prepareCronJobAlertParams(alertType, previous, incoming string) (string, error) {
if alertUtil.GetCronJobType(alertType) != "cronJob" {
return incoming, nil
}
return alertUtil.MergeCronJobAlertParams(previous, incoming)
}
func (a AlertService) validateCronJobAlertChannels(alertType, advanced, method string) error {
if alertUtil.GetCronJobType(alertType) != "cronJob" {
return nil
}
mode, err := alertUtil.CronJobAlertTriggerMode(advanced)
if err != nil || mode != alertUtil.CronJobAlertSuccess {
return err
}
_, err = a.validateAlertMethodReferences(method)
return err
}
+35 -26
View File
@@ -30,10 +30,11 @@ import (
) )
const ( const (
ResourceAlertInterval = 30 ResourceAlertInterval = 30
CheckIntervalSec = 3 CheckIntervalSec = 3
LoadCheckIntervalMin = 5 LoadCheckIntervalMin = 5
sshIPLoginWindow = 30 * time.Minute sshIPLoginWindow = 30 * time.Minute
sslAutoRenewAlertSkipDays = 31
) )
type AlertTaskHelper struct { type AlertTaskHelper struct {
@@ -529,11 +530,15 @@ func loadSSHLogin(alert dto.AlertDTO) {
if err != nil { if err != nil {
global.LOG.Errorf("Failed to load ssh login logs: %v", err) global.LOG.Errorf("Failed to load ssh login logs: %v", err)
} }
interfaceAddrs, err := net.InterfaceAddrs()
if err != nil {
global.LOG.Warnf("Failed to load local IP addresses for ssh login alert: %v", err)
}
count, records := summarizeSSHLoginHistories( count, records := summarizeSSHLoginHistories(
histories, histories,
now, now,
failedWindow, failedWindow,
strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n"), sshSuccessLoginWhitelist(alert.AdvancedParams, interfaceAddrs),
) )
isAlert := count >= int(alert.Count) isAlert := count >= int(alert.Count)
if isAlert { if isAlert {
@@ -569,6 +574,19 @@ func loadSSHLogin(alert dto.AlertDTO) {
} }
} }
func sshSuccessLoginWhitelist(configured string, interfaceAddrs []net.Addr) []string {
whitelist := strings.Split(strings.TrimSpace(configured), "\n")
whitelist = append(whitelist, "127.0.0.0/8", "::1")
for _, addr := range interfaceAddrs {
ipNet, ok := addr.(*net.IPNet)
if !ok || ipNet.IP == nil || ipNet.IP.IsUnspecified() {
continue
}
whitelist = append(whitelist, ipNet.IP.String())
}
return whitelist
}
func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string) []model.LoginLog { func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string) []model.LoginLog {
filtered := make([]model.LoginLog, 0, len(records)) filtered := make([]model.LoginLog, 0, len(records))
for _, record := range records { for _, record := range records {
@@ -864,7 +882,7 @@ func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]s
daysDiff := int(math.Ceil( daysDiff := int(math.Ceil(
ssl.ExpireDate.Sub(currentDate).Hours() / 24, ssl.ExpireDate.Sub(currentDate).Hours() / 24,
)) ))
if daysDiff > 0 && int(cycle) >= daysDiff { if daysDiff > 0 && int(cycle) >= daysDiff && !shouldSuppressSSLExpiryAlert(ssl, daysDiff) {
daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], ssl.PrimaryDomain) daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], ssl.PrimaryDomain)
projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate) projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate)
} }
@@ -872,6 +890,10 @@ func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]s
return daysDiffMap, projectMap return daysDiffMap, projectMap
} }
func shouldSuppressSSLExpiryAlert(ssl model.WebsiteSSL, remainingDays int) bool {
return ssl.AutoRenew && remainingDays < sslAutoRenewAlertSkipDays
}
func calculateWebsiteExpiryDays(websites []model.Website, cycle uint) (map[int][]string, map[uint][]time.Time) { func calculateWebsiteExpiryDays(websites []model.Website, cycle uint) (map[int][]string, map[uint][]time.Time) {
currentDate := time.Now() currentDate := time.Now()
daysDiffMap := make(map[int][]string) daysDiffMap := make(map[int][]string)
@@ -1044,50 +1066,37 @@ func processAllDisks(alert dto.AlertDTO) error {
global.LOG.Errorf("error getting disk list, err: %v", err) global.LOG.Errorf("error getting disk list, err: %v", err)
return err return err
} }
var errMsgs []string
for _, item := range diskList { for _, item := range diskList {
err := checkAndCreateDiskAlert(alert, item.Path) if item.Total == 0 {
if err != nil {
errMsg := fmt.Sprintf("disk path %s process failed: %v", item.Path, err)
errMsgs = append(errMsgs, errMsg)
global.LOG.Errorf("%s", errMsg)
continue continue
} }
} checkAndCreateDiskAlert(alert, item.Path, &disk.UsageStat{Used: item.Used, UsedPercent: item.UsedPercent})
if len(errMsgs) > 0 {
return fmt.Errorf("batch process disks failed, error count: %d, details: %s", len(errMsgs), strings.Join(errMsgs, "; "))
} }
return nil return nil
} }
func processSingleDisk(alert dto.AlertDTO) error { func processSingleDisk(alert dto.AlertDTO) error {
err := checkAndCreateDiskAlert(alert, alert.Project) usageStat, err := loadDiskUsageWithTimeout(alert.Project, true)
if err != nil { 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 return err
} }
checkAndCreateDiskAlert(alert, alert.Project, usageStat)
return nil return nil
} }
func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) error { func checkAndCreateDiskAlert(alert dto.AlertDTO, path string, usageStat *disk.UsageStat) {
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
}
usedTotal, usedStr := calculateUsedTotal(alert.Cycle, usageStat) usedTotal, usedStr := calculateUsedTotal(alert.Cycle, usageStat)
commonTotal := float64(alert.Count) commonTotal := float64(alert.Count)
if alert.Cycle == 1 { if alert.Cycle == 1 {
commonTotal *= 1024 * 1024 * 1024 commonTotal *= 1024 * 1024 * 1024
} }
if usedTotal < commonTotal { if usedTotal < commonTotal {
return nil return
} }
params := createAlertDiskParams(path, usedStr) params := createAlertDiskParams(path, usedStr)
sender := NewAlertSender(alert, alert.Project) sender := NewAlertSender(alert, alert.Project)
sender.ResourceSend(path, params) sender.ResourceSend(path, params)
return nil
} }
func calculateUsedTotal(cycle uint, usageStat *disk.UsageStat) (float64, string) { func calculateUsedTotal(cycle uint, usageStat *disk.UsageStat) (float64, string) {
+24 -57
View File
@@ -40,14 +40,13 @@ const (
appUpgradeDown appUpgradeDown
appUpgradeMutated appUpgradeMutated
appUpgradeStarted appUpgradeStarted
appUpgradeReady
appUpgradeCommitted appUpgradeCommitted
) )
const composeServiceLabel = "com.docker.compose.service"
var appUpgradeLocks sync.Map var appUpgradeLocks sync.Map
const composeServiceLabel = "com.docker.compose.service"
type appUpgradeSnapshot interface { type appUpgradeSnapshot interface {
Restore() error Restore() error
Cleanup() Cleanup()
@@ -439,15 +438,14 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
t.LogSuccess(logStr) t.LogSuccess(logStr)
u.phase = appUpgradeStarted u.phase = appUpgradeStarted
t.LogStart(i18n.GetMsgByKey("UpgradeWaitReady")) containerNames, discoverErr := discoverUpgradeContainerNames(u.candidate, u.envContent)
containerNames, err := waitAppContainersReady(context.Background(), u.candidate) if discoverErr != nil {
if err != nil { t.Logf("WARNING: discover upgraded application containers failed: %v", discoverErr)
t.LogFailedWithErr(i18n.GetMsgByKey("UpgradeWaitReady"), err) } else if len(containerNames) > 0 {
return err u.candidate.ContainerName = strings.Join(containerNames, ",")
} else {
t.Log("WARNING: no containers found for the upgraded application")
} }
t.LogSuccess(i18n.GetMsgByKey("UpgradeWaitReady"))
u.phase = appUpgradeReady
u.candidate.ContainerName = strings.Join(containerNames, ",")
u.candidate.Status = constant.StatusRunning u.candidate.Status = constant.StatusRunning
u.candidate.Message = "" u.candidate.Message = ""
@@ -472,6 +470,11 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
} else if err = appInstallRepo.Save(context.Background(), &u.candidate); err != nil { } else if err = appInstallRepo.Save(context.Background(), &u.candidate); err != nil {
return err return err
} }
if discoverErr == nil && len(containerNames) > 0 {
if syncErr := syncAppInstallStatus(&u.candidate, true); syncErr != nil {
t.Logf("WARNING: sync upgraded application status failed: %v", syncErr)
}
}
u.phase = appUpgradeCommitted u.phase = appUpgradeCommitted
u.deleteOldImages(t) u.deleteOldImages(t)
return nil return nil
@@ -591,9 +594,6 @@ func (u *appUpgradeContext) rollback(t *task.Task) (rollbackErr error) {
} }
func (u *appUpgradeContext) finishRollback() error { func (u *appUpgradeContext) finishRollback() error {
if _, err := waitAppContainersReady(context.Background(), u.original); err != nil {
return err
}
restored := u.original restored := u.original
if err := appInstallRepo.Save(context.Background(), &restored); err != nil { if err := appInstallRepo.Save(context.Background(), &restored); err != nil {
return err return err
@@ -881,28 +881,7 @@ func (s *upgradeFileSnapshot) Cleanup() {
} }
} }
type appContainerReadinessClient interface { func discoverUpgradeContainerNames(install model.AppInstall, envContent []byte) ([]string, error) {
ContainerList(context.Context, container.ListOptions) ([]container.Summary, error)
ContainerInspect(context.Context, string) (container.InspectResponse, error)
}
func waitAppContainersReady(ctx context.Context, install model.AppInstall) ([]string, error) {
client, err := docker.NewDockerClient()
if err != nil {
return nil, err
}
defer client.Close()
return waitAppContainersReadyWithClient(ctx, client, install)
}
func waitAppContainersReadyWithClient(ctx context.Context, client appContainerReadinessClient, install model.AppInstall) ([]string, error) {
envContent, err := os.ReadFile(install.GetEnvPath())
if err != nil {
envContent, err = renderUpgradeEnv(&install, nil)
if err != nil {
return nil, err
}
}
project, err := docker.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), envContent, false) project, err := docker.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), envContent, false)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -916,36 +895,24 @@ func waitAppContainersReadyWithClient(ctx context.Context, client appContainerRe
if len(expectedServices) == 0 { if len(expectedServices) == 0 {
return strings.Split(install.ContainerName, ","), nil return strings.Split(install.ContainerName, ","), nil
} }
options := container.ListOptions{ client, err := docker.NewDockerClient()
All: true, if err != nil {
Filters: filters.NewArgs( return nil, err
filters.Arg("label", composeWorkdirLabel+"="+install.GetPath()),
),
} }
containers, err := client.ContainerList(ctx, options) defer client.Close()
containers, err := client.ContainerList(context.Background(), container.ListOptions{
All: true,
Filters: filters.NewArgs(filters.Arg("label", composeWorkdirLabel+"="+install.GetPath())),
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
foundServices := make(map[string]bool, len(expectedServices))
containerNames := make([]string, 0, len(containers)) containerNames := make([]string, 0, len(containers))
for _, item := range containers { for _, item := range containers {
serviceName := item.Labels[composeServiceLabel] if _, ok := expectedServices[item.Labels[composeServiceLabel]]; ok && len(item.Names) > 0 {
if _, ok := expectedServices[serviceName]; !ok {
continue
}
if err = waitContainerReady(ctx, client, item.ID); err != nil {
return nil, fmt.Errorf("container %s is not ready: %w", serviceName, err)
}
foundServices[serviceName] = true
if len(item.Names) > 0 {
containerNames = append(containerNames, strings.TrimPrefix(item.Names[0], "/")) containerNames = append(containerNames, strings.TrimPrefix(item.Names[0], "/"))
} }
} }
for serviceName := range expectedServices {
if !foundServices[serviceName] {
return nil, fmt.Errorf("container for service %s was not created", serviceName)
}
}
sort.Strings(containerNames) sort.Strings(containerNames)
return containerNames, nil return containerNames, nil
} }
+23 -10
View File
@@ -352,22 +352,34 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
if dir != nil { if dir != nil {
logStr := i18n.GetMsgByKey("Stop") + i18n.GetMsgByKey("App") logStr := i18n.GetMsgByKey("Stop") + i18n.GetMsgByKey("App")
t.Log(logStr) t.Log(logStr)
cleanupFailed := false
if deleteReq.UseLifecycleScripts { if deleteReq.UseLifecycleScripts {
if err = runScript(t, &install, "uninstall"); err != nil { if scriptErr := runScript(t, &install, "uninstall"); scriptErr != nil {
return err cleanupFailed = true
if !deleteReq.ForceDelete {
return scriptErr
}
} }
} else { } else {
out, err := compose.Down(install.GetComposePath()) out, downErr := compose.Down(install.GetComposePath())
if err != nil && !deleteReq.ForceDelete { if downErr != nil {
return handleErr(install, err, out) cleanupFailed = true
if !deleteReq.ForceDelete {
return handleErr(install, downErr, out)
}
} }
if err = runScript(t, &install, "uninstall"); err != nil { if scriptErr := runScript(t, &install, "uninstall"); scriptErr != nil {
_, _ = compose.Up(install.GetComposePath()) cleanupFailed = true
return err if !deleteReq.ForceDelete {
_, _ = compose.Up(install.GetComposePath())
return scriptErr
}
} }
} }
t.LogSuccess(logStr) if !cleanupFailed {
t.LogSuccess(logStr)
}
if deleteReq.DeleteImage { if deleteReq.DeleteImage {
content, err := op.GetContent(install.GetEnvPath()) content, err := op.GetContent(install.GetEnvPath())
if err != nil { if err != nil {
@@ -466,8 +478,9 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
} }
uninstallTask.AddSubTask(task.GetTaskName(install.Name, task.TaskUninstall, task.TaskScopeApp), uninstall, nil) uninstallTask.AddSubTask(task.GetTaskName(install.Name, task.TaskUninstall, task.TaskScopeApp), uninstall, nil)
go func() { go func() {
if err := uninstallTask.Execute(); err != nil && !deleteReq.ForceDelete { if err := uninstallTask.Execute(); err != nil {
install.Status = constant.StatusError install.Status = constant.StatusError
install.Message = err.Error()
_ = appInstallRepo.Save(context.Background(), &install) _ = appInstallRepo.Save(context.Background(), &install)
} }
}() }()
+9 -9
View File
@@ -383,11 +383,6 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
} }
func (u *ContainerService) Prune(req dto.ContainerPrune) error { func (u *ContainerService) Prune(req dto.ContainerPrune) error {
client, err := docker.NewDockerClient()
if err != nil {
return err
}
defer client.Close()
name := "" name := ""
switch req.PruneType { switch req.PruneType {
case "container": case "container":
@@ -408,6 +403,14 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
} }
taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error { taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error {
if err := t.TaskCtx.Err(); err != nil {
return err
}
client, err := docker.NewDockerClient()
if err != nil {
return err
}
defer client.Close()
pruneFilters := filters.NewArgs() pruneFilters := filters.NewArgs()
if req.WithTagAll { if req.WithTagAll {
pruneFilters.Add("dangling", "false") pruneFilters.Add("dangling", "false")
@@ -431,10 +434,7 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
} }
SpaceReclaimed = int(rep.SpaceReclaimed) SpaceReclaimed = int(rep.SpaceReclaimed)
case "network": case "network":
_, err := client.NetworksPrune(context.Background(), pruneFilters) return cleanUnusedNetworks(t, client)
if err != nil {
return err
}
case "volume": case "volume":
versions, err := client.ServerVersion(context.Background()) versions, err := client.ServerVersion(context.Background())
if err != nil { if err != nil {
+119
View File
@@ -2,16 +2,24 @@ package service
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"sort" "sort"
"strings" "strings"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/docker" "github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/docker/errdefs"
) )
var networkCleanupSlot = make(chan struct{}, 1)
func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) { func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) {
client, err := docker.NewDockerClient() client, err := docker.NewDockerClient()
if err != nil { if err != nil {
@@ -172,3 +180,114 @@ func (u *ContainerService) CreateNetwork(req dto.NetworkCreate) error {
} }
return nil return nil
} }
func cleanUnusedNetworks(t *task.Task, cli *client.Client) error {
ctx := t.TaskCtx
select {
case networkCleanupSlot <- struct{}{}:
defer func() { <-networkCleanupSlot }()
case <-ctx.Done():
return ctx.Err()
}
if err := ctx.Err(); err != nil {
return err
}
networks, err := cli.NetworkList(ctx, network.ListOptions{})
if err != nil {
if ctx.Err() != nil {
return ctx.Err()
}
return err
}
deleted, skipped, failed := 0, 0, 0
defer func() {
t.Log(i18n.GetMsgWithMap("NetworkCleanupSummary", map[string]interface{}{
"deleted": deleted, "skipped": skipped, "failed": failed,
}))
}()
var used map[string]bool
sort.Slice(networks, func(i, j int) bool { return networks[i].Name < networks[j].Name })
for _, n := range networks {
if err := ctx.Err(); err != nil {
return err
}
switch n.Name {
case "none", "host", "bridge", "1panel-network":
skipped++
continue
}
if n.Scope != "local" || n.Ingress || n.ConfigOnly {
skipped++
continue
}
values := map[string]interface{}{"name": n.Name, "id": n.ID}
if used == nil {
containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
if err != nil {
if ctx.Err() != nil {
return ctx.Err()
}
return err
}
used = make(map[string]bool)
for _, c := range containers {
if c.NetworkSettings == nil {
continue
}
for name, endpoint := range c.NetworkSettings.Networks {
used[name] = true
if endpoint != nil {
used[endpoint.NetworkID] = true
}
}
}
}
if used[n.Name] || used[n.ID] {
skipped++
t.Log(i18n.GetMsgWithMap("NetworkCleanupConnected", values))
continue
}
inspected, err := cli.NetworkInspect(ctx, n.ID, network.InspectOptions{})
if err != nil {
if ctx.Err() != nil {
return ctx.Err()
}
if errdefs.IsNotFound(err) {
skipped++
} else {
failed++
t.Logf("Failed to inspect network [%s] (%s): %v", n.Name, n.ID, err)
}
continue
}
if len(inspected.Containers) > 0 {
skipped++
t.Log(i18n.GetMsgWithMap("NetworkCleanupConnected", values))
continue
}
if err := cli.NetworkRemove(ctx, n.ID); err != nil {
if ctx.Err() != nil {
return ctx.Err()
}
switch {
case errdefs.IsNotFound(err):
skipped++
case errdefs.IsConflict(err):
skipped++
t.Log(i18n.GetMsgWithMap("NetworkCleanupConnected", values))
default:
failed++
t.Logf("Failed to remove network [%s] (%s): %v", n.Name, n.ID, err)
}
continue
}
deleted++
}
if err := ctx.Err(); err != nil {
return err
}
if failed > 0 {
return errors.New(i18n.GetMsgByKey("NetworkCleanupPartialFailure"))
}
return nil
}
+92 -26
View File
@@ -16,6 +16,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
"github.com/1Panel-dev/1Panel/agent/utils/docker" "github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -75,6 +76,7 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
EntryID: cronjob.ID, EntryID: cronjob.ID,
} }
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable)) alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
if alertInfo.SendCount != 0 { if alertInfo.SendCount != 0 {
item.AlertCount = alertInfo.SendCount item.AlertCount = alertInfo.SendCount
} else { } else {
@@ -98,9 +100,11 @@ func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, err
AlertType: cronjob.Type, AlertType: cronjob.Type,
EntryID: cronjob.ID, EntryID: cronjob.ID,
} }
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable)) alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))))
item.AlertMethod = alertInfo.Method item.AlertMethod = alertInfo.Method
if alertInfo.SendCount != 0 { item.AlertTitle = alertInfo.Title
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
if alertInfo.Status == constant.AlertEnable {
item.AlertCount = alertInfo.SendCount item.AlertCount = alertInfo.SendCount
} else { } else {
item.AlertCount = 0 item.AlertCount = 0
@@ -195,11 +199,12 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
} }
} }
item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID) item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID)
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))), repo.WithByStatus(constant.AlertEnable)) alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))))
if alertInfo.SendCount != 0 { item.AlertTitle = alertInfo.Title
item.AlertMethod = alertInfo.Method
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
if alertInfo.Status == constant.AlertEnable {
item.AlertCount = alertInfo.SendCount item.AlertCount = alertInfo.SendCount
item.AlertTitle = alertInfo.Title
item.AlertMethod = alertInfo.Method
} else { } else {
item.AlertCount = 0 item.AlertCount = 0
} }
@@ -213,6 +218,17 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
} }
func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error { func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
for _, item := range req {
advanced, err := cronJobAlertAdvancedParams(item.AlertTriggerMode)
if err != nil {
return err
}
if item.AlertCount != 0 {
if err := (AlertService{}).validateCronJobAlertChannels(item.Type, advanced, item.AlertMethod); err != nil {
return err
}
}
}
for _, item := range req { for _, item := range req {
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name)) cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
if cronjobItem.ID != 0 { if cronjobItem.ID != 0 {
@@ -395,17 +411,27 @@ func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
} else { } else {
cronjob.Status = constant.StatusDisable cronjob.Status = constant.StatusDisable
} }
_ = cronjobRepo.Create(&cronjob) if err := cronjobRepo.Create(&cronjob); err != nil {
if item.AlertCount != 0 && item.AlertTitle != "" && item.AlertMethod != "" { return err
}
if item.AlertTitle != "" && item.AlertMethod != "" {
advanced, _ := cronJobAlertAdvancedParams(item.AlertTriggerMode)
status := constant.AlertEnable
if item.AlertCount == 0 {
status = constant.AlertDisable
}
createAlert := dto.AlertCreate{ createAlert := dto.AlertCreate{
Title: item.AlertTitle, Title: item.AlertTitle,
SendCount: item.AlertCount, SendCount: item.AlertCount,
Method: item.AlertMethod, Method: item.AlertMethod,
Type: cronjob.Type, Type: cronjob.Type,
Project: strconv.Itoa(int(cronjob.ID)), Project: strconv.Itoa(int(cronjob.ID)),
Status: constant.AlertEnable, Status: status,
AdvancedParams: advanced,
}
if err := NewIAlertService().CreateAlert(createAlert, operator); err != nil {
return err
} }
_ = NewIAlertService().CreateAlert(createAlert, operator)
} }
} }
return nil return nil
@@ -562,6 +588,15 @@ func (u *CronjobService) HandleOnce(id uint) error {
} }
func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error { func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
advanced, err := cronJobAlertAdvancedParams(req.AlertTriggerMode)
if err != nil {
return err
}
if req.AlertCount != 0 {
if err := (AlertService{}).validateCronJobAlertChannels(req.Type, advanced, req.AlertMethod); err != nil {
return err
}
}
cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name)) cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name))
if cronjob.ID != 0 { if cronjob.ID != 0 {
return buserr.New("ErrRecordExist") return buserr.New("ErrRecordExist")
@@ -603,12 +638,13 @@ func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
} }
if req.AlertCount != 0 && req.AlertTitle != "" && req.AlertMethod != "" { if req.AlertCount != 0 && req.AlertTitle != "" && req.AlertMethod != "" {
createAlert := dto.AlertCreate{ createAlert := dto.AlertCreate{
Title: req.AlertTitle, Title: req.AlertTitle,
SendCount: req.AlertCount, SendCount: req.AlertCount,
Method: req.AlertMethod, Method: req.AlertMethod,
Type: cronjob.Type, Type: cronjob.Type,
Project: strconv.Itoa(int(cronjob.ID)), Project: strconv.Itoa(int(cronjob.ID)),
Status: constant.AlertEnable, Status: constant.AlertEnable,
AdvancedParams: advanced,
} }
err := NewIAlertService().CreateAlert(createAlert, operator) err := NewIAlertService().CreateAlert(createAlert, operator)
if err != nil { if err != nil {
@@ -682,6 +718,10 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
} }
func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string) error { func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string) error {
advanced, err := cronJobAlertAdvancedParams(req.AlertTriggerMode)
if err != nil {
return err
}
var cronjob model.Cronjob var cronjob model.Cronjob
if err := copier.Copy(&cronjob, &req); err != nil { if err := copier.Copy(&cronjob, &req); err != nil {
return buserr.WithDetail("ErrStructTransform", err.Error(), nil) return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
@@ -697,6 +737,20 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
if err != nil { if err != nil {
return buserr.New("ErrRecordNotFound") return buserr.New("ErrRecordNotFound")
} }
if req.AlertCount != 0 {
previous, _ := alertRepo.Get(alertRepo.WithByType(cronModel.Type), alertRepo.WithByProject(strconv.Itoa(int(id))))
merged, err := prepareCronJobAlertParams(cronModel.Type, previous.AdvancedParams, advanced)
if err != nil {
return err
}
method := req.AlertMethod
if method == "" {
method = previous.Method
}
if err := (AlertService{}).validateCronJobAlertChannels(cronModel.Type, merged, method); err != nil {
return err
}
}
upMap := make(map[string]interface{}) upMap := make(map[string]interface{})
cronjob.EntryIDs = cronModel.EntryIDs cronjob.EntryIDs = cronModel.EntryIDs
cronjob.Type = cronModel.Type cronjob.Type = cronModel.Type
@@ -753,11 +807,12 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
return err return err
} }
updateAlert := dto.AlertCreate{ updateAlert := dto.AlertCreate{
Title: req.AlertTitle, Title: req.AlertTitle,
SendCount: req.AlertCount, SendCount: req.AlertCount,
Method: req.AlertMethod, Method: req.AlertMethod,
Type: cronjob.Type, Type: cronjob.Type,
Project: strconv.Itoa(int(cronModel.ID)), Project: strconv.Itoa(int(cronModel.ID)),
AdvancedParams: advanced,
} }
err = NewIAlertService().ExternalUpdateAlert(updateAlert, operator) err = NewIAlertService().ExternalUpdateAlert(updateAlert, operator)
if err != nil { if err != nil {
@@ -766,6 +821,17 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
return nil return nil
} }
func cronJobAlertAdvancedParams(mode string) (string, error) {
if mode == "" {
return "", nil
}
data, err := json.Marshal(map[string]string{"alertTriggerMode": mode})
if err != nil {
return "", err
}
return alertUtil.MergeCronJobAlertParams("", string(data))
}
func (u *CronjobService) UpdateStatus(id uint, status string) error { func (u *CronjobService) UpdateStatus(id uint, status string) error {
cronjob, _ := cronjobRepo.Get(repo.WithByID(id)) cronjob, _ := cronjobRepo.Get(repo.WithByID(id))
if cronjob.ID == 0 { if cronjob.ID == 0 {
+1
View File
@@ -412,6 +412,7 @@ func addSkipTask(source string, taskItem *task.Task) {
taskItem.Log(i18n.GetMsgByKey("NoSuchResource")) taskItem.Log(i18n.GetMsgByKey("NoSuchResource"))
return nil return nil
}, nil) }, nil)
taskItem.SubTasks[len(taskItem.SubTasks)-1].StepAlias = cronJobSkippedStep
} }
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper { func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
+35 -6
View File
@@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -23,6 +24,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n" "github.com/1Panel-dev/1Panel/agent/i18n"
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
"github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/files" "github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/1Panel-dev/1Panel/agent/utils/ntp" "github.com/1Panel-dev/1Panel/agent/utils/ntp"
@@ -56,10 +58,11 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
_ = taskRepo.Save(context.Background(), taskItem.Task) _ = taskRepo.Save(context.Background(), taskItem.Task)
} }
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records) cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
handleCronJobAlert(cronjob) handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
return return
} }
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records) cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, nil))
}() }()
return return
} }
@@ -70,19 +73,20 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
record.TaskID = "" record.TaskID = ""
} }
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records) cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
handleCronJobAlert(cronjob) handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
return return
} }
go func() { go func() {
if err := taskItem.Execute(); err != nil { if err := taskItem.Execute(); err != nil {
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID)) storedTask, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
if len(taskItem.ID) == 0 { if len(storedTask.ID) == 0 {
record.TaskID = "" record.TaskID = ""
} }
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records) cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
handleCronJobAlert(cronjob) handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
} else { } else {
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records) cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, nil))
} }
}() }()
} }
@@ -482,8 +486,33 @@ func hasBackup(cronjobType string) bool {
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log" || cronjobType == "cutWebsiteLog" return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log" || cronjobType == "cutWebsiteLog"
} }
func handleCronJobAlert(cronjob *model.Cronjob) { const cronJobSkippedStep = "cronjob-skipped"
func cronJobAlertResult(taskItem *task.Task, err error) string {
if errors.Is(err, context.Canceled) || taskItem.Task.Status == constant.StatusCanceled ||
(taskItem.TaskCtx != nil && taskItem.TaskCtx.Err() != nil) {
return ""
}
if err != nil {
return alertUtil.CronJobAlertFailed
}
if taskItem.Task.Status != constant.StatusSuccess {
return ""
}
for _, subTask := range taskItem.SubTasks {
if subTask.StepAlias != cronJobSkippedStep {
return alertUtil.CronJobAlertSuccess
}
}
return ""
}
func handleCronJobAlert(cronjob *model.Cronjob, result string) {
if result == "" {
return
}
pushAlert := dto.PushAlert{ pushAlert := dto.PushAlert{
Result: result,
TaskName: cronjob.Name, TaskName: cronjob.Name,
AlertType: cronjob.Type, AlertType: cronjob.Type,
EntryID: cronjob.ID, EntryID: cronjob.ID,
+89 -55
View File
@@ -242,7 +242,7 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
currentInfo.SwapMemoryUsed = swapInfo.Used currentInfo.SwapMemoryUsed = swapInfo.Used
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
currentInfo.DiskData = loadDiskInfo() currentInfo.DiskData = loadDiskInfo(false)
currentInfo.GPUData, currentInfo.NPUData, currentInfo.XPUData = loadAcceleratorInfo() currentInfo.GPUData, currentInfo.NPUData, currentInfo.XPUData = loadAcceleratorInfo()
if ioOption == "all" { if ioOption == "all" {
@@ -455,24 +455,9 @@ type diskInfo struct {
Device string Device string
} }
func loadDiskInfo() []dto.DiskInfo { func loadDiskInfo(forceRefresh bool) []dto.DiskInfo {
var datas []dto.DiskInfo var datas []dto.DiskInfo
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second)) stdout := loadDiskMounts()
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 && strings.TrimSpace(stdout) == "" {
return datas
}
}
lines := strings.Split(stdout, "\n") lines := strings.Split(stdout, "\n")
var mounts []diskInfo var mounts []diskInfo
@@ -520,43 +505,22 @@ func loadDiskInfo() []dto.DiskInfo {
itemData.Type = mount.Type itemData.Type = mount.Type
itemData.Device = mount.Device itemData.Device = mount.Device
type diskResult struct { state, err := loadDiskUsageWithTimeout(mount.Mount, forceRefresh)
state *disk.UsageStat if err != nil {
err error global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, err)
} } else {
resultCh := make(chan diskResult, 1) itemData.Total = state.Total
itemData.Free = state.Free
go func() { itemData.Used = state.Used
state, err := psutil.DISK.GetUsage(mount.Mount, false) itemData.UsedPercent = state.UsedPercent
resultCh <- diskResult{state: state, err: err} itemData.InodesTotal = state.InodesTotal
}() itemData.InodesUsed = state.InodesUsed
itemData.InodesFree = state.InodesFree
select { itemData.InodesUsedPercent = state.InodesUsedPercent
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()
} }
mu.Lock()
datas = append(datas, itemData)
mu.Unlock()
}(mounts[i]) }(mounts[i])
} }
wg.Wait() wg.Wait()
@@ -567,6 +531,69 @@ func loadDiskInfo() []dto.DiskInfo {
return datas 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) { func loadAcceleratorInfo() ([]dto.GPUInfo, []dto.NPUInfo, []dto.XPUInfo) {
ok, client := accelerator.New() ok, client := accelerator.New()
if !ok { if !ok {
@@ -586,6 +613,9 @@ func loadAcceleratorInfo() ([]dto.GPUInfo, []dto.NPUInfo, []dto.XPUInfo) {
xpuData []dto.XPUInfo xpuData []dto.XPUInfo
) )
for _, device := range snapshot.Devices { for _, device := range snapshot.Devices {
if device.ParentID != "" {
continue
}
switch device.Kind { switch device.Kind {
case accelerator.KindGPU: case accelerator.KindGPU:
if device.GPU == nil { if device.GPU == nil {
@@ -595,7 +625,11 @@ func loadAcceleratorInfo() ([]dto.GPUInfo, []dto.NPUInfo, []dto.XPUInfo) {
if err := copier.Copy(&dataItem, device.GPU); err != nil { if err := copier.Copy(&dataItem, device.GPU); err != nil {
continue continue
} }
dataItem.PowerUsage = dataItem.PowerDraw + " / " + dataItem.MaxPowerLimit dataItem.MaxPowerLimit = device.GPU.PowerLimit
dataItem.PowerUsage = dataItem.PowerDraw
if dataItem.MaxPowerLimit != "" {
dataItem.PowerUsage += " / " + dataItem.MaxPowerLimit
}
dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal
gpuData = append(gpuData, dataItem) gpuData = append(gpuData, dataItem)
case accelerator.KindNPU: case accelerator.KindNPU:
+63 -19
View File
@@ -8,6 +8,12 @@ import (
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"sync"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/google/uuid"
"gorm.io/gorm"
"github.com/1Panel-dev/1Panel/agent/app/repo" "github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
@@ -17,12 +23,15 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/compose" "github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker" "github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt" "github.com/1Panel-dev/1Panel/agent/utils/encrypt"
"github.com/docker/docker/api/types/container" dockerclient "github.com/docker/docker/client"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
) )
type RedisService struct{} type RedisService struct{}
// The CLI container is shared by all remote Redis databases on this node.
var redisCliInstallMutex sync.Mutex
type IRedisService interface { type IRedisService interface {
UpdateConf(req dto.RedisConfUpdate) error UpdateConf(req dto.RedisConfUpdate) error
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
@@ -33,7 +42,7 @@ type IRedisService interface {
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error) LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
CheckHasCli() bool CheckHasCli() bool
InstallCli() error InstallCli(req dto.RedisCliInstall) (string, error)
} }
func NewIRedisService() IRedisService { func NewIRedisService() IRedisService {
@@ -61,30 +70,65 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
} }
func (u *RedisService) CheckHasCli() bool { func (u *RedisService) CheckHasCli() bool {
client, err := docker.NewDockerClient() installed, _ := u.checkCliInstalled()
if err != nil { return installed
return false
}
defer client.Close()
containerLists, err := client.ContainerList(context.Background(), container.ListOptions{})
if err != nil {
return false
}
for _, item := range containerLists {
if strings.ReplaceAll(item.Names[0], "/", "") == "1Panel-redis-cli-tools" {
return true
}
}
return false
} }
func (u *RedisService) InstallCli() error { func (u *RedisService) checkCliInstalled() (bool, error) {
client, err := docker.NewDockerClient()
if err != nil {
return false, err
}
defer client.Close()
info, err := client.ContainerInspect(context.Background(), "1Panel-redis-cli-tools")
if dockerclient.IsErrNotFound(err) {
return false, nil
}
if err != nil {
return false, err
}
return info.ContainerJSONBase != nil && info.State != nil && info.State.Running, nil
}
func (u *RedisService) InstallCli(req dto.RedisCliInstall) (string, error) {
if !redisCliInstallMutex.TryLock() {
return "", buserr.New("TaskIsExecuting")
}
defer redisCliInstallMutex.Unlock()
item := dto.ContainerOperate{ item := dto.ContainerOperate{
TaskID: req.TaskID,
Name: "1Panel-redis-cli-tools", Name: "1Panel-redis-cli-tools",
Image: "redis:7.4.4", Image: "redis:7.4.4",
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}}, Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
} }
return NewIContainerService().ContainerCreate(item, false) running, err := taskRepo.GetFirst(
repo.WithByName(task.GetTaskName(item.Name, task.TaskCreate, task.TaskScopeContainer)),
repo.WithByType(task.TaskScopeContainer),
taskRepo.WithByStatus(constant.StatusExecuting),
)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return "", err
}
if running.ID != "" {
return running.ID, nil
}
installed, err := u.checkCliInstalled()
if err != nil || installed {
return "", err
}
if item.TaskID == "" {
item.TaskID = uuid.NewString()
}
if _, err := taskRepo.GetFirst(taskRepo.WithByID(item.TaskID)); !errors.Is(err, gorm.ErrRecordNotFound) {
if err != nil {
return "", err
}
return "", buserr.New("TaskIsExecuting")
}
if err := NewIContainerService().ContainerCreate(item, true); err != nil {
return "", err
}
return item.TaskID, nil
} }
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error { func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
+1 -1
View File
@@ -73,7 +73,7 @@ func (u *DeviceService) LoadBaseInfo() (dto.DeviceBaseInfo, error) {
if baseInfo.SwapMemoryTotal != 0 { if baseInfo.SwapMemoryTotal != 0 {
baseInfo.SwapDetails = loadSwap() baseInfo.SwapDetails = loadSwap()
} }
disks := loadDiskInfo() disks := loadDiskInfo(false)
for _, item := range disks { for _, item := range disks {
baseInfo.MaxSize += item.Free baseInfo.MaxSize += item.Free
} }
+8 -3
View File
@@ -18,7 +18,8 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/common" "github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/controller" "github.com/1Panel-dev/1Panel/agent/utils/controller"
"github.com/1Panel-dev/1Panel/agent/utils/docker" "github.com/1Panel-dev/1Panel/agent/utils/docker"
"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" 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) return fmt.Errorf("Docker Engine %s or later is required for the nftables firewall backend", dockerNftablesMinVersion)
} }
if backend == constant.FirewallProviderNftables { if backend == constant.FirewallProviderNftables {
if err := docker_guard.CheckIPv4Forwarding(); err != nil { if err := dockerfirewall.CheckIPv4Forwarding(); err != nil {
return err return err
} }
} }
@@ -282,7 +283,8 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate, withRestart bool) erro
delete(daemonMap, "ipv6") delete(daemonMap, "ipv6")
delete(daemonMap, "fixed-cidr-v6") delete(daemonMap, "fixed-cidr-v6")
delete(daemonMap, "ip6tables") delete(daemonMap, "ip6tables")
if configuredDockerFirewallBackend() != constant.FirewallProviderNftables { backend, _ := settingRepo.GetValueByKey(constant.FirewallDockerBackendKey)
if !strings.EqualFold(strings.TrimSpace(backend), constant.FirewallProviderNftables) {
delete(daemonMap, "experimental") delete(daemonMap, "experimental")
} }
} }
@@ -485,6 +487,9 @@ func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
if err := controller.Handle(req.Operation, service); err != nil { if err := controller.Handle(req.Operation, service); err != nil {
return err return err
} }
if req.Operation == "start" || req.Operation == "restart" {
return RestoreDockerPortGuard(context.Background())
}
return nil return nil
} }
+5 -4
View File
@@ -32,10 +32,11 @@ var (
scriptRepo = repo.NewIScriptRepo() scriptRepo = repo.NewIScriptRepo()
cronjobRepo = repo.NewICronjobRepo() cronjobRepo = repo.NewICronjobRepo()
hostRepo = repo.NewIHostRepo() hostRepo = repo.NewIHostRepo()
ftpRepo = repo.NewIFtpRepo() ftpRepo = repo.NewIFtpRepo()
clamRepo = repo.NewIClamRepo() clamRepo = repo.NewIClamRepo()
monitorRepo = repo.NewIMonitorRepo() monitorRepo = repo.NewIMonitorRepo()
vllmMonitorRepo = &repo.VLLMMonitorRepo{}
settingRepo = repo.NewISettingRepo() settingRepo = repo.NewISettingRepo()
backupRepo = repo.NewIBackupRepo() backupRepo = repo.NewIBackupRepo()
+29 -10
View File
@@ -439,13 +439,15 @@ func (f *FileService) Compress(c request.FileCompress) error {
if err := preflightCompressTool(files.CompressType(c.Type)); err != nil { if err := preflightCompressTool(files.CompressType(c.Type)); err != nil {
return err return err
} }
taskItem, err := task.NewTask(c.Name, task.TaskExec, task.TaskScopeTask, c.TaskID, 1) taskName := i18n.GetMsgWithMap("FileTaskCompress", map[string]interface{}{"dst": strconv.Quote(filepath.Join(c.Dst, c.Name))})
taskItem, err := task.NewTask(taskName, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
if err != nil { if err != nil {
return err return err
} }
go func() { go func() {
taskItem.AddSubTask(c.Name, func(t *task.Task) error { taskItem.AddSubTask(taskName, func(t *task.Task) error {
t.LogStart(c.Name) logFileTaskSources(t, c.Files)
t.Log(i18n.GetMsgWithMap("FileTaskFormat", map[string]interface{}{"format": strconv.Quote(c.Type)}))
compressType := files.CompressType(c.Type) compressType := files.CompressType(c.Type)
dstFile := filepath.Join(c.Dst, c.Name) dstFile := filepath.Join(c.Dst, c.Name)
success := false success := false
@@ -516,13 +518,15 @@ func (f *FileService) DeCompress(c request.FileDeCompress) error {
if err := preflightDecompressTool(files.CompressType(c.Type)); err != nil { if err := preflightDecompressTool(files.CompressType(c.Type)); err != nil {
return err return err
} }
taskItem, err := task.NewTask(c.Path, task.TaskExec, task.TaskScopeTask, c.TaskID, 1) taskName := i18n.GetMsgWithMap("FileTaskDecompress", map[string]interface{}{"dst": strconv.Quote(c.Dst)})
taskItem, err := task.NewTask(taskName, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
if err != nil { if err != nil {
return err return err
} }
go func() { go func() {
taskItem.AddSubTask(c.Path, func(t *task.Task) error { taskItem.AddSubTask(taskName, func(t *task.Task) error {
t.LogStart(c.Path) logFileTaskSources(t, []string{c.Path})
t.Log(i18n.GetMsgWithMap("FileTaskFormat", map[string]interface{}{"format": strconv.Quote(c.Type)}))
dstExisted := fo.Stat(c.Dst) dstExisted := fo.Stat(c.Dst)
parentDir := filepath.Dir(c.Dst) parentDir := filepath.Dir(c.Dst)
if !fo.Stat(parentDir) { if !fo.Stat(parentDir) {
@@ -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) return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key, options)
} }
func logFileTaskSources(t *task.Task, sources []string) {
for _, source := range sources {
t.Log(i18n.GetMsgWithMap("FileTaskSource", map[string]interface{}{"path": strconv.Quote(source)}))
}
}
func (f *FileService) MvFile(m request.FileMove) error { func (f *FileService) MvFile(m request.FileMove) error {
fo := files.NewFileOp() fo := files.NewFileOp()
if err := validateFileMove(fo, m); err != nil { if err := validateFileMove(fo, m); err != nil {
@@ -925,15 +935,24 @@ func (f *FileService) MvFile(m request.FileMove) error {
if !fileTransferLocks.Acquire(m.TaskID, getFileTransferPaths(m)) { if !fileTransferLocks.Acquire(m.TaskID, getFileTransferPaths(m)) {
return buserr.New("TaskIsExecuting") return buserr.New("TaskIsExecuting")
} }
taskItem, err := task.NewTask(m.NewPath, task.TaskExec, task.TaskScopeTask, m.TaskID, 1) nameKey := "FileTaskCopy"
if m.Type == "cut" {
nameKey = "FileTaskMove"
}
taskName := i18n.GetMsgWithMap(nameKey, map[string]interface{}{"dst": strconv.Quote(m.NewPath)})
taskItem, err := task.NewTask(taskName, task.TaskExec, task.TaskScopeTask, m.TaskID, 1)
if err != nil { if err != nil {
fileTransferLocks.Release(m.TaskID) fileTransferLocks.Release(m.TaskID)
return err return err
} }
go func() { go func() {
defer fileTransferLocks.Release(m.TaskID) defer fileTransferLocks.Release(m.TaskID)
taskItem.AddSubTaskWithOps(m.NewPath, func(t *task.Task) error { taskItem.AddSubTaskWithOps(taskName, func(t *task.Task) error {
t.LogStart(m.NewPath) logFileTaskSources(t, m.OldPaths)
logFileTaskSources(t, m.CoverPaths)
if m.Name != "" {
t.Log(i18n.GetMsgWithMap("FileTaskRename", map[string]interface{}{"name": strconv.Quote(m.Name)}))
}
err := f.moveFileWithContext(t.TaskCtx, m) err := f.moveFileWithContext(t.TaskCtx, m)
if err != nil && t.TaskCtx.Err() != nil { if err != nil && t.TaskCtx.Err() != nil {
return t.TaskCtx.Err() return t.TaskCtx.Err()
@@ -1291,7 +1310,7 @@ func (f *FileService) BatchCheckFiles(req request.FilePathsCheck) []response.Exi
} }
func (f *FileService) GetHostMount() []dto.DiskInfo { func (f *FileService) GetHostMount() []dto.DiskInfo {
return loadDiskInfo() return loadDiskInfo(false)
} }
func (f *FileService) GetUsersAndGroups() (*response.UserGroupResponse, error) { func (f *FileService) GetUsersAndGroups() (*response.UserGroupResponse, error) {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-76
View File
@@ -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()
}
}
-65
View File
@@ -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
}
+398 -347
View File
@@ -5,20 +5,20 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"os"
"reflect"
"slices" "slices"
"strings"
"sync" "sync"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/firewall" "github.com/1Panel-dev/1Panel/agent/utils/firewall"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard" dockerfirewall "github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter" "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
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/iptables_helper"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle" "github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/nftables_helper" "github.com/1Panel-dev/1Panel/agent/utils/firewall/nftables_helper"
@@ -31,211 +31,124 @@ type IFirewallSettingService interface {
DeletePortWhitelist(context.Context, dto.FirewallPortWhitelistDelete) error DeletePortWhitelist(context.Context, dto.FirewallPortWhitelistDelete) error
Load(context.Context) (dto.FirewallSettings, error) Load(context.Context) (dto.FirewallSettings, error)
Operate(context.Context, dto.FirewallBackendOperation) error Operate(context.Context, dto.FirewallBackendOperation) error
OperateFamily(dto.FirewallFamilyOperation) (dto.FilterChainOperationResponse, error)
OperateIPv6(dto.FirewallIPv6Operation) (dto.FilterChainOperationResponse, error)
} }
type FirewallSettingService struct{} type FirewallSettingService struct{}
var firewallWhitelistMu sync.Mutex var firewallWhitelistMu sync.Mutex
var ErrFirewallBackendCleanupRequired = errors.New("firewall backend cleanup required") func (s *FirewallSettingService) CreatePortWhitelist(ctx context.Context, request dto.FirewallPortWhitelistCreate) (result error) {
firewallWhitelistMu.Lock()
func firewallBackendCleanupRequired(current, target string) error { firewallRuleMutationMu.Lock()
return fmt.Errorf( defer func() {
"%w: current backend %s still contains 1Panel runtime rules; clean it up before switching to %s", firewallRuleMutationMu.Unlock()
ErrFirewallBackendCleanupRequired, firewallWhitelistMu.Unlock()
current, if result == nil {
target, result = newFirewallService().SyncPortWhitelist(ctx)
)
}
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
} }
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 { 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() firewallWhitelistMu.Lock()
defer firewallWhitelistMu.Unlock()
firewallRuleMutationMu.Lock() firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock() defer func() {
defer filterruntime.InvalidateInventory() firewallRuleMutationMu.Unlock()
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { firewallWhitelistMu.Unlock()
current, err := loadPortWhitelistSetting(tx) }()
if err != nil { current, err := loadFirewallPortWhiteList()
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()
if err != nil { if err != nil {
return err return err
} }
rules, err := record.RulesForProvider(provider) if request.Rule == nil {
return filter.ErrInvalidRule
}
index, err := findPortWhitelistRule(current, *request.Rule)
if err != nil { if err != nil {
return err return err
} }
for _, rule := range rules { current = slices.Delete(current, index, index+1)
if filter.RuleMatchesPortWhitelist(rule, ports) { current, err = firewall.ValidatePortWhitelist(current)
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
}
if err != nil { if err != nil {
return "", err return err
} }
return loadSSHPortValues(directives)[0], nil if err := validateFirewallWhitelistFamilies(current); err != nil {
} return err
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)
}
} }
return result value, err := json.Marshal(current)
} if err != nil {
return err
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
}
} }
return firewall.ValidatePortWhitelist(entries) return settingRepo.UpdateOrCreate(constant.FirewallPortWhiteList, string(value))
}
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
})
} }
func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings, error) { 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) installed := make(map[string]bool)
for _, name := range lifecycle.InstalledProviders() { for _, name := range lifecycle.InstalledProviders() {
installed[name] = true installed[name] = true
} }
result.System.Selected = configuredSystemFirewallBackend() systemBackend, _ := settingRepo.GetValueByKey(constant.FirewallSystemBackendKey)
result.System.Selected = strings.TrimSpace(systemBackend)
if result.System.Selected == "" { if result.System.Selected == "" {
if client, err := lifecycle.NewClient(); err == nil { if client, err := lifecycle.NewClient(""); err == nil {
result.System.Selected = client.Name() result.System.Selected = client.Name()
} }
} }
@@ -248,16 +161,16 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
} { } {
option := dto.FirewallBackendOption{Name: name, Installed: installed[name], Supported: true} option := dto.FirewallBackendOption{Name: name, Installed: installed[name], Supported: true}
if option.Installed && name == result.System.Selected { if option.Installed && name == result.System.Selected {
client, err := lifecycle.NewClientFor(name) client, err := lifecycle.NewClient(name)
if err != nil { if err != nil {
option.Message = err.Error() option.Message = err.Error()
} else if supportsManagedFilterChains(name) { } else if name == constant.FirewallProviderIptables || name == constant.FirewallProviderNftables {
option.Initialized, option.Bound, err = loadFirewallInitStatus(name, "base") overview, err := loadSystemFirewallOverview(name, "base", families)
if err != nil { if err != nil {
option.Message = err.Error() option.Message = err.Error()
} }
option.IPv4 = loadSystemFirewallFamilyInfo(name, constant.FirewallFamilyIPv4) option.Initialized, option.Bound = overview.IsInit, overview.IsBind
option.IPv6 = loadSystemFirewallFamilyInfo(name, constant.FirewallFamilyIPv6) option.IPv4, option.IPv6 = overview.IPv4, overview.IPv6
} else if option.Active, err = client.Status(); err != nil { } else if option.Active, err = client.Status(); err != nil {
option.Message = err.Error() option.Message = err.Error()
} }
@@ -270,32 +183,29 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
result.System.Options = append(result.System.Options, option) result.System.Options = append(result.System.Options, option)
} }
result.Forwarding.Selected = configuredForwardingBackend() forwardingBackend, _ := settingRepo.GetValueByKey(constant.FirewallForwardingBackendKey)
result.Forwarding.Selected = strings.TrimSpace(forwardingBackend)
if result.Forwarding.Selected == "" {
result.Forwarding.Selected = constant.FirewallProviderIptables
}
result.Forwarding.Current = result.Forwarding.Selected result.Forwarding.Current = result.Forwarding.Selected
for _, name := range []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables} { for _, name := range []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables} {
option := dto.FirewallBackendOption{Name: name, Installed: installed[name], Supported: true} option := dto.FirewallBackendOption{Name: name, Installed: installed[name], Supported: true}
if option.Installed && name == result.Forwarding.Selected { if option.Installed && name == result.Forwarding.Selected {
manager, err := newForwardingManagerFor(name) manager, err := newForwardingAdapterFor(ctx, name)
if err != nil { if err != nil {
option.Message = err.Error() option.Message = err.Error()
} else if status, err := manager.Status(); err != nil {
option.Message = err.Error()
} else { } else {
option.Initialized, option.Bound = status.IsInit, status.IsBind status, statusErr := loadForwardingFirewallOverview(manager, families)
ipv4Init, ipv4Bound, ipv4Err := manager.FamilyStatus(constant.FirewallFamilyIPv4) option.IPv4, option.IPv6 = status.IPv4, status.IPv6
ipv6Init, ipv6Bound, ipv6Err := manager.FamilyStatus(constant.FirewallFamilyIPv6) if statusErr != nil {
option.IPv4 = dto.FirewallBackendFamilyStatus{ option.Message = statusErr.Error()
Available: ipv4Err == nil, Initialized: ipv4Init, Bound: ipv4Bound, } else {
option.Initialized, option.Bound = status.IsInit, status.IsBind
} }
option.IPv6 = dto.FirewallBackendFamilyStatus{ if result.IPv6Enabled && name == constant.FirewallProviderIptables && !option.IPv6.Available {
Available: ipv6Err == nil, Initialized: ipv6Init, Bound: ipv6Bound, if commands, err := lifecycle.ResolveIptablesCommands(); err == nil && !commands.IPv6Available() {
} option.IPv6.Reason = dockerfirewall.ReasonCommandMissing
if name == constant.FirewallProviderIptables {
if commands, commandErr := lifecycle.ResolveIptablesCommands(); commandErr == nil {
option.IPv6.Available = option.IPv6.Available && commands.IPv6Available()
if !commands.IPv6Available() {
option.IPv6.Reason = docker_guard.ReasonCommandMissing
}
} }
} }
} }
@@ -313,7 +223,11 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
if dockerInstalled { if dockerInstalled {
dockerVersion = loadDockerEngineVersion(ctx) dockerVersion = loadDockerEngineVersion(ctx)
} }
result.Docker.Selected = configuredDockerFirewallBackend() dockerBackend, _ := settingRepo.GetValueByKey(constant.FirewallDockerBackendKey)
dockerBackend = strings.ToLower(strings.TrimSpace(dockerBackend))
if dockerBackend == constant.FirewallProviderIptables || dockerBackend == constant.FirewallProviderNftables {
result.Docker.Selected = dockerBackend
}
result.Docker.Current = result.Docker.Selected result.Docker.Current = result.Docker.Selected
for _, name := range []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables} { for _, name := range []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables} {
option := dto.FirewallBackendOption{ option := dto.FirewallBackendOption{
@@ -326,20 +240,25 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
option.Active = false option.Active = false
} }
if option.Active { if option.Active {
guard := docker_guard.NewRuntime(name) guard := newDockerFirewallRuntime(ctx, name)
ipv4, ipv6 := guard.Status(docker_guard.FamilyIPv4), guard.Status(docker_guard.FamilyIPv6) for _, family := range families {
option.Initialized = ipv4.Initialized || ipv6.Initialized status := guard.Status(family)
option.Bound = ipv4.Bound || ipv6.Bound option.Initialized = option.Initialized || status.Initialized
option.IPv4.Initialized, option.IPv4.Bound = ipv4.Initialized, ipv4.Bound option.Bound = option.Bound || status.Bound
option.IPv6.Initialized, option.IPv6.Bound = ipv6.Initialized, ipv6.Bound info := dto.FirewallBackendFamilyStatus{
option.IPv4.Available = ipv4.Reason != docker_guard.ReasonCommandMissing Available: status.Reason != dockerfirewall.ReasonCommandMissing,
option.IPv6.Available = ipv6.Reason != docker_guard.ReasonCommandMissing Initialized: status.Initialized, Bound: status.Bound, Reason: status.Reason,
option.IPv4.Reason, option.IPv6.Reason = ipv4.Reason, ipv6.Reason }
if family == constant.FirewallFamilyIPv4 {
option.IPv4 = info
} else {
option.IPv6 = info
}
}
} }
result.Docker.Options = append(result.Docker.Options, option) result.Docker.Options = append(result.Docker.Options, option)
} }
var err error result.PortWhitelist, err = loadPortWhitelistSetting()
result.PortWhitelist, err = loadPortWhitelistSetting(global.DB.WithContext(ctx))
if err != nil { if err != nil {
return result, err return result, err
} }
@@ -353,33 +272,11 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
return result, err 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 { 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 { if err := lockFirewallLifecycleIdle(); err != nil {
return err 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 { if request.Subsystem != "system" && request.Backend != constant.FirewallProviderIptables && request.Backend != constant.FirewallProviderNftables {
return fmt.Errorf("%s only supports iptables or nftables", request.Subsystem) return fmt.Errorf("%s only supports iptables or nftables", request.Subsystem)
} }
if request.Subsystem == "system" && !supportsManagedFilterChains(request.Backend) && request.Operation != "select" { if request.Subsystem == "system" && (request.Backend != constant.FirewallProviderIptables && request.Backend != constant.FirewallProviderNftables) && request.Operation != "select" {
return fmt.Errorf("%s does not support initialization or cleanup", request.Backend) return fmt.Errorf("%s does not support initialization or cleanup", request.Backend)
} }
switch request.Subsystem { switch request.Subsystem {
@@ -397,13 +294,12 @@ func (s *FirewallSettingService) Operate(ctx context.Context, request dto.Firewa
} }
if request.Operation == "initialize" { if request.Operation == "initialize" {
service := newFirewallService() service := newFirewallService()
rulesErr := service.restoreStoredFirewallRules(ctx, filter.Provider(request.Backend), nil)
whitelistErr := service.SyncPortWhitelist(ctx) whitelistErr := service.SyncPortWhitelist(ctx)
return errors.Join(rulesErr, whitelistErr) return whitelistErr
} }
return nil return nil
case "forwarding": case "forwarding":
return s.operateForwarding(request) return s.operateForwarding(ctx, request)
case "docker": case "docker":
return s.operateDocker(ctx, request) return s.operateDocker(ctx, request)
default: 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 { func (s *FirewallSettingService) OperateFamily(request dto.FirewallFamilyOperation) (dto.FilterChainOperationResponse, error) {
guard := docker_guard.NewRuntime(request.Backend) if request.Family != constant.FirewallFamilyIPv4 && request.Family != constant.FirewallFamilyIPv6 {
if request.Operation == "cleanup" { return dto.FilterChainOperationResponse{}, filter.ErrInvalidScope
if err := guard.Cleanup(); err != nil { }
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 err
} }
return settingRepo.UpdateOrCreate(constant.FirewallDockerPortGuardStatusKey, constant.StatusDisable) defer firewallLifecycleTaskMu.Unlock()
} t.Logf("backend=%s family=%s operation=%s", request.Backend, request.Family, request.Operation)
previous, _ := settingRepo.GetValueByKey(constant.FirewallDockerBackendKey) families, err := loadFirewallFamilies()
if request.Operation == "select" {
current := previous
if current == "" {
current = alternateDirectBackend(request.Backend)
}
initialized, err := dockerGuardBackendInitialized(current)
if err != nil { if err != nil {
return err return err
} }
if current != request.Backend && initialized { if !slices.Contains(families, request.Family) {
return firewallBackendCleanupRequired(current, request.Backend) return fmt.Errorf("IPv6 firewall support is disabled")
} }
} initialize := request.Operation != "bind"
if err := settingRepo.UpdateOrCreate(constant.FirewallDockerBackendKey, request.Backend); err != nil { switch request.Subsystem {
return err case "system":
} firewallWhitelistMu.Lock()
if request.Operation == "select" { defer firewallWhitelistMu.Unlock()
if err := (&DockerService{}).UpdateFirewallBackend(request.Backend); err != nil { if err := newFirewallService().checkSelectedProvider(t.TaskCtx, filter.Provider(request.Backend)); err != nil {
_ = settingRepo.UpdateOrCreate(constant.FirewallDockerBackendKey, previous) return err
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) { func (s *FirewallSettingService) OperateIPv6(request dto.FirewallIPv6Operation) (dto.FilterChainOperationResponse, error) {
guard := docker_guard.NewRuntime(backend) if request.Status != constant.StatusEnable && request.Status != constant.StatusDisable {
for _, family := range []string{docker_guard.FamilyIPv4, docker_guard.FamilyIPv6} { return dto.FilterChainOperationResponse{}, filter.ErrInvalidRule
initialized, err := guard.Initialized(family)
if err != nil {
return false, err
}
if initialized {
return true, nil
}
} }
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 { func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperation) error {
firewallRuleMutationMu.Lock() firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock() defer firewallRuleMutationMu.Unlock()
if _, err := lifecycle.NewClientFor(request.Backend); err != nil { if _, err := lifecycle.NewClient(request.Backend); err != nil {
return err return err
} }
if request.Operation == "cleanup" {
return cleanupSystemBackend(request.Backend)
}
previous, _ := settingRepo.GetValueByKey(constant.FirewallSystemBackendKey) previous, _ := settingRepo.GetValueByKey(constant.FirewallSystemBackendKey)
if previous == "" { if previous == "" {
if client, err := lifecycle.NewClient(); err == nil { if client, err := lifecycle.NewClient(""); err == nil {
previous = client.Name() previous = client.Name()
} }
} }
@@ -486,7 +534,7 @@ func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperat
return err return err
} }
if initialized { if initialized {
return firewallBackendCleanupRequired(previous, request.Backend) return buserr.WithMap("ErrFirewallBackendCleanupRequired", map[string]interface{}{"current": previous, "target": request.Backend}, nil)
} }
} }
if err := settingRepo.UpdateOrCreate(constant.FirewallSystemBackendKey, request.Backend); err != nil { if err := settingRepo.UpdateOrCreate(constant.FirewallSystemBackendKey, request.Backend); err != nil {
@@ -512,21 +560,14 @@ func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperat
} }
func systemFirewallBackendInitialized(backend string) (bool, error) { func systemFirewallBackendInitialized(backend string) (bool, error) {
return systemFirewallBackendInitializedWithClientFactory(backend, lifecycle.NewClientFor) client, err := lifecycle.NewClient(backend)
}
func systemFirewallBackendInitializedWithClientFactory(
backend string,
newClient func(string) (lifecycle.Client, error),
) (bool, error) {
client, err := newClient(backend)
if err != nil { if err != nil {
if errors.Is(err, lifecycle.ErrNotInstalled) { if errors.Is(err, lifecycle.ErrNotInstalled) {
return false, nil return false, nil
} }
return false, err return false, err
} }
if supportsManagedFilterChains(backend) { if backend == constant.FirewallProviderIptables || backend == constant.FirewallProviderNftables {
for _, family := range []string{constant.FirewallFamilyIPv4, constant.FirewallFamilyIPv6} { for _, family := range []string{constant.FirewallFamilyIPv4, constant.FirewallFamilyIPv6} {
initialized, _, err := loadSystemFirewallFamilyStatus(backend, family) initialized, _, err := loadSystemFirewallFamilyStatus(backend, family)
if family == constant.FirewallFamilyIPv6 && errors.Is(err, filter.ErrFamilyUnavailable) { if family == constant.FirewallFamilyIPv6 && errors.Is(err, filter.ErrFamilyUnavailable) {
@@ -544,73 +585,39 @@ func systemFirewallBackendInitializedWithClientFactory(
return client.Status() return client.Status()
} }
func cleanupSystemBackend(backend string) error { func (s *FirewallSettingService) operateForwarding(ctx context.Context, request dto.FirewallBackendOperation) error {
switch backend { if _, err := newForwardingAdapterFor(ctx, request.Backend); err != nil {
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 {
return err 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) previous, _ := settingRepo.GetValueByKey(constant.FirewallForwardingBackendKey)
if request.Operation == "select" { if request.Operation == "select" {
current := previous current := previous
if current == "" { if current == "" {
detected, err := newForwardingManager() detected, err := newForwardingAdapter(ctx)
if err != nil { if err != nil {
return err return err
} }
current = detected.Name() current = detected.Name()
} }
initialized, err := forwardingBackendInitialized(current) initialized, err := forwardingBackendInitialized(ctx, current)
if err != nil { if err != nil {
return err return err
} }
if current != request.Backend && initialized { if current != request.Backend && initialized {
return firewallBackendCleanupRequired(current, request.Backend) return buserr.WithMap("ErrFirewallBackendCleanupRequired", map[string]interface{}{"current": current, "target": request.Backend}, nil)
} }
} }
if err := settingRepo.UpdateOrCreate(constant.FirewallForwardingBackendKey, request.Backend); err != nil { if err := settingRepo.UpdateOrCreate(constant.FirewallForwardingBackendKey, request.Backend); err != nil {
return err return err
} }
if request.Operation == "initialize" { if request.Operation == "initialize" {
return newForwardingService().Enable() return newForwardingService().Enable(ctx)
} }
recordForwardingSyncError(nil)
return nil return nil
} }
func forwardingBackendInitialized(backend string) (bool, error) { func forwardingBackendInitialized(ctx context.Context, backend string) (bool, error) {
manager, err := newForwardingManagerFor(backend) manager, err := newForwardingAdapterFor(ctx, backend)
if err != nil { if err != nil {
if errors.Is(err, lifecycle.ErrNotInstalled) { if errors.Is(err, lifecycle.ErrNotInstalled) {
return false, nil return false, nil
@@ -629,9 +636,53 @@ func forwardingBackendInitialized(backend string) (bool, error) {
return false, nil return false, nil
} }
func alternateDirectBackend(backend string) string { func (s *FirewallSettingService) operateDocker(ctx context.Context, request dto.FirewallBackendOperation) error {
if backend == constant.FirewallProviderNftables {
return constant.FirewallProviderIptables 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
+187 -474
View File
@@ -4,78 +4,56 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"os"
"slices"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/task" "github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant" "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/firewall" "github.com/1Panel-dev/1Panel/agent/utils/firewall"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding" "github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle" "github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
) )
type IForwardingService interface { type IForwardingService interface {
LoadBaseInfo() (dto.FirewallSubsystemStatus, error) LoadBaseInfo(ctx context.Context) (dto.FirewallSubsystemStatus, error)
SearchRules(request dto.ForwardRuleSearch) (int64, []dto.ForwardRule, 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) OperateRules(dto.ForwardRuleOperate) (dto.FilterChainOperationResponse, error)
Enable() error Enable(ctx context.Context) error
QueueInitialization(dto.FirewallInitializationTask) (dto.FilterChainOperationResponse, error) QueueInitialization(dto.FirewallInitializationTask) (dto.FilterChainOperationResponse, error)
Restore(context.Context) error Restore(context.Context) error
} }
type ForwardingService struct { type ForwardingService struct {
managerFactory func() (*forwarding.Manager, error) clientFactory func(context.Context) (forwarding.Adapter, error)
rules repo.IForwardingRuleRepo
enabled func() (bool, error)
persistBackend func(string) error
markEnabled func() error
} }
var errForwardingBackendUnavailable = errors.New("no supported forwarding backend detected") var errForwardingBackendUnavailable = errors.New("no supported forwarding backend detected")
var forwardingMutationMu sync.Mutex var forwardingMutationMu sync.Mutex
const ( func (s *ForwardingService) LoadBaseInfo(ctx context.Context) (dto.FirewallSubsystemStatus, error) {
forwardingSyncConverged = "converged" families, err := loadFirewallFamilies()
forwardingSyncMissing = "missing" if err != nil {
forwardingSyncRuntimeOnly = "runtime_only" return dto.FirewallSubsystemStatus{}, err
) }
selected, _ := settingRepo.GetValueByKey(constant.FirewallForwardingBackendKey)
var ( selected = strings.TrimSpace(selected)
forwardingSyncStateMu sync.RWMutex if selected == "" {
forwardingLastSyncErr error selected = constant.FirewallProviderIptables
)
func NewIForwardingService() IForwardingService {
return newForwardingService()
}
func newForwardingService() *ForwardingService {
return &ForwardingService{
managerFactory: newForwardingManager,
rules: repo.NewIForwardingRuleRepo(),
enabled: forwardingPersistedEnabled,
markEnabled: func() error {
return settingRepo.UpdateOrCreate(constant.FirewallForwardingInitializedKey, constant.StatusEnable)
},
persistBackend: func(backend string) error {
return settingRepo.UpdateOrCreate(constant.FirewallForwardingBackendKey, backend)
},
} }
}
func (s *ForwardingService) LoadBaseInfo() (dto.FirewallSubsystemStatus, error) {
selected := configuredForwardingBackend()
baseInfo := dto.FirewallSubsystemStatus{ 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 err != nil {
if errors.Is(err, errForwardingBackendUnavailable) { if errors.Is(err, errForwardingBackendUnavailable) {
baseInfo.Reason = constant.FirewallBackendNotInstalled baseInfo.Reason = constant.FirewallBackendNotInstalled
@@ -83,119 +61,83 @@ func (s *ForwardingService) LoadBaseInfo() (dto.FirewallSubsystemStatus, error)
} }
return baseInfo, err return baseInfo, err
} }
status, err := manager.Status() client, err := lifecycle.NewClient(manager.Name())
if err != nil { if err != nil {
return baseInfo, err return baseInfo, err
} }
version, versionErr := client.Version()
status, statusErr := loadForwardingFirewallOverview(manager, families)
if err := errors.Join(versionErr, statusErr); err != nil {
return baseInfo, err
}
baseInfo.IsExist = true baseInfo.IsExist = true
baseInfo.Name, baseInfo.Backend = forwardingDisplayName(status.Name), status.Name baseInfo.Name, baseInfo.Backend = manager.Name(), manager.Name()
baseInfo.Version = status.Version if baseInfo.Backend == constant.FirewallProviderIptables || baseInfo.Backend == constant.FirewallProviderNftables {
baseInfo.Name += "-forward"
}
baseInfo.Version = version
baseInfo.PingStatus = firewall.LoadPingStatus() baseInfo.PingStatus = firewall.LoadPingStatus()
baseInfo.IsInit, baseInfo.IsBind = status.IsInit, status.IsBind baseInfo.IsInit, baseInfo.IsBind = status.IsInit, status.IsBind
baseInfo.IPv4 = loadForwardingFamilyInfo(manager, status.Name, constant.FirewallFamilyIPv4) baseInfo.IPv4, baseInfo.IPv6 = status.IPv4, status.IPv6
baseInfo.IPv6 = loadForwardingFamilyInfo(manager, status.Name, constant.FirewallFamilyIPv6)
return baseInfo, nil return baseInfo, nil
} }
func loadForwardingFamilyInfo(manager *forwarding.Manager, backend, family string) dto.FirewallBackendFamilyStatus { func (s *ForwardingService) ExportBackup(ctx context.Context, provider filter.Provider) (dto.FirewallSubsystemBackup, error) {
initialized, bound, err := manager.FamilyStatus(family) var manager forwarding.Adapter
available := err == nil var err error
if backend == constant.FirewallProviderIptables && family == constant.FirewallFamilyIPv6 { if provider == "" {
commands, commandErr := lifecycle.ResolveIptablesCommands() manager, err = s.clientFactory(ctx)
available = available && commandErr == nil && commands.IPv6Available() } 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 { func (s *ForwardingService) SearchRules(ctx context.Context, request dto.ForwardRuleSearch) (int64, []dto.ForwardRule, error) {
switch backend { manager, err := s.clientFactory(ctx)
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())
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
manager, err := s.managerFactory() rules, err := manager.List()
if err != nil {
return 0, nil, err
}
runtime, err := manager.List("", "")
if err != nil {
return 0, nil, err
}
inventory, err := mergeForwardingInventory(stored, runtime)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
items := make([]dto.ForwardRule, 0, len(rules))
keyword := strings.ToLower(strings.TrimSpace(request.Info)) keyword := strings.ToLower(strings.TrimSpace(request.Info))
filtered := inventory[:0] for _, rule := range rules {
for _, item := range inventory { if keyword != "" && !strings.Contains(strings.ToLower(strings.Join([]string{rule.Family, rule.Protocol, rule.Port, rule.TargetIP, rule.TargetPort, rule.Interface}, " ")), keyword) {
if keyword == "" || forwardingRuleMatchesKeyword(item, keyword) { continue
filtered = append(filtered, item)
} }
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(items)
total := len(inventory)
start, end := (request.Page-1)*request.PageSize, request.Page*request.PageSize
if request.All { if request.All {
start, end = 0, total return int64(total), items, nil
} }
if start > total { start := min(max(request.Page-1, 0)*max(request.PageSize, 1), total)
return int64(total), make([]dto.ForwardRule, 0), nil end := min(start+max(request.PageSize, 1), total)
} return int64(total), items[start:end], 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
} }
func (s *ForwardingService) OperateRules(request dto.ForwardRuleOperate) (dto.FilterChainOperationResponse, error) { 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 operation := task.TaskCreate
for i, rule := range request.Rules { for _, rule := range request.Rules {
labels[i] = fmt.Sprintf("[%d/%d] %s %s %s %s -> %s:%s", i+1, len(request.Rules), rule.Operation, rule.Family, rule.Protocol, rule.Port, rule.TargetIP, rule.TargetPort)
if rule.Operation != "add" { if rule.Operation != "add" {
operation = task.TaskUpdate operation = task.TaskUpdate
} }
@@ -203,375 +145,146 @@ func (s *ForwardingService) OperateRules(request dto.ForwardRuleOperate) (dto.Fi
if forwardingOperationsOnlyRemove(request.Rules) { if forwardingOperationsOnlyRemove(request.Rules) {
operation = task.TaskDelete operation = task.TaskDelete
} }
return queueFirewallRuleTask(firewallTaskForwarding, operation, labels, func(ctx context.Context) error { taskItem, err := task.NewTask(firewallTaskName(operation, firewallTaskForwarding, ""), operation, task.TaskScopeFirewall, "", 0)
return s.operateRules(ctx, request)
})
}
func (s *ForwardingService) operateRules(ctx context.Context, request dto.ForwardRuleOperate) error {
forwardingMutationMu.Lock()
defer forwardingMutationMu.Unlock()
if err := ctx.Err(); err != nil {
return err
}
stored, err := s.rules.List(ctx)
if err != nil { if err != nil {
return err
}
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 return dto.FilterChainOperationResponse{}, err
} }
taskItem, err := task.NewTask(firewallTaskName(task.TaskExec, firewallTaskForwarding, ""), task.TaskExec, task.TaskScopeFirewall, request.TaskID, 0) taskItem.AddSubTaskWithOps(taskItem.Name, func(t *task.Task) error {
if err != nil { return s.operateRules(t.TaskCtx, request, t, false)
return dto.FilterChainOperationResponse{}, fmt.Errorf("create forwarding initialization task: %w", err) }, nil, 0, 0)
} if err := taskRepo.Save(context.Background(), taskItem.Task); err != nil {
var manager *forwarding.Manager taskItem.LogFailedWithErr(taskItem.Name, err)
var backend string closeUnstartedFirewallTask(taskItem)
taskItem.AddSubTask(i18n.GetMsgByKey("FirewallEnableForwardingStep"), func(t *task.Task) error { return dto.FilterChainOperationResponse{}, err
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)
} }
go func() { _ = taskItem.Execute() }() go func() { _ = taskItem.Execute() }()
return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil 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 { func (s *ForwardingService) Restore(ctx context.Context) error {
forwardingMutationMu.Lock() forwardingMutationMu.Lock()
defer forwardingMutationMu.Unlock() 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) status, err := settingRepo.GetValueByKey(constant.FirewallForwardingInitializedKey)
return status == constant.StatusEnable, err if err != nil || status != constant.StatusEnable {
} return err
func forwardingRulesFromModels(stored []model.ForwardingRule) []forwarding.Rule {
rules := make([]forwarding.Rule, 0, len(stored))
for _, rule := range stored {
rules = append(rules, forwarding.Rule{
Family: rule.Family, Protocol: rule.Protocol, Port: rule.Port, TargetIP: rule.TargetIP,
TargetPort: rule.TargetPort, Interface: rule.Interface,
})
} }
return rules manager, err := s.clientFactory(ctx)
} if err != nil {
return err
func forwardingRuleModels(rules []forwarding.Rule) []model.ForwardingRule {
stored := make([]model.ForwardingRule, 0, len(rules))
for _, rule := range rules {
stored = append(stored, model.ForwardingRule{
Family: rule.Family, Protocol: rule.Protocol, Port: rule.Port, TargetIP: rule.TargetIP,
TargetPort: rule.TargetPort, Interface: rule.Interface,
})
} }
return stored backup, err := readFirewallSubsystemBackup("forwarding-"+manager.Name()+".rules", "forwarding")
} if errors.Is(err, os.ErrNotExist) {
return nil
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
} }
} if err != nil {
return err
func mergeForwardingInventory( }
stored []model.ForwardingRule, families, err := loadFirewallFamilies()
runtime []forwarding.Rule, if err != nil {
) ([]forwardingInventoryItem, error) { return err
items := make([]forwardingInventoryItem, 0, len(stored)+len(runtime)) }
byIdentity := make(map[string]int, len(stored)+len(runtime)) if !slices.Contains(families, constant.FirewallFamilyIPv6) {
for _, record := range stored { before := len(backup.Forwarding)
rule, err := forwarding.NormalizeRule(forwarding.Rule{ backup.Forwarding = slices.DeleteFunc(backup.Forwarding, func(rule forwarding.Rule) bool { return rule.Family == constant.FirewallFamilyIPv6 })
Family: record.Family, Protocol: record.Protocol, Port: record.Port, TargetIP: record.TargetIP, logFirewallIPv6Skipped(nil, "forwarding startup", before-len(backup.Forwarding))
TargetPort: record.TargetPort, Interface: record.Interface, }
}) missing := make(map[string]bool)
needsBind := false
for _, family := range families {
initialized, bound, err := manager.FamilyStatus(family)
if err != nil { if err != nil {
return nil, fmt.Errorf("normalize desired forwarding rule: %w", err) return err
} }
key := rule.Identity() missing[family] = !initialized
byIdentity[key] = len(items) needsBind = needsBind || (initialized && !bound)
items = append(items, forwardingInventoryItem{ID: record.ID, Rule: rule, IsDesired: true})
} }
for _, observed := range runtime { restoreIPv6 := slices.Contains(backup.Families, forwarding.FamilyIPv6)
rule, err := forwarding.NormalizeRule(observed) for _, rule := range backup.Forwarding {
if err != nil { restoreIPv6 = restoreIPv6 || rule.Family == forwarding.FamilyIPv6
return nil, fmt.Errorf("normalize runtime forwarding rule: %w", err) }
} if !missing[forwarding.FamilyIPv4] && (!missing[forwarding.FamilyIPv6] || !restoreIPv6) && !needsBind {
key := rule.Identity() return nil
if index, exists := byIdentity[key]; exists { }
items[index].IsRuntime = true 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 continue
} }
byIdentity[key] = len(items) if err := manager.OperateFamily(family, missing[family]); err != nil {
items = append(items, forwardingInventoryItem{Rule: rule, IsRuntime: true}) return err
}
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)
} }
desired = append(desired, normalized)
} }
for _, operation := range requested { for _, family := range families {
for _, protocol := range strings.Split(operation.Protocol, "/") { if !missing[family] {
rule, err := forwarding.NormalizeRule(forwarding.Rule{ continue
Family: operation.Family, Protocol: protocol, Port: operation.Port, TargetIP: operation.TargetIP, }
TargetPort: operation.TargetPort, Interface: operation.Interface, rules := make([]forwarding.Rule, 0)
}) for _, rule := range backup.Forwarding {
if err != nil { if rule.Family != family {
return nil, err continue
} }
index := forwardingRuleIndex(desired, rule) if seen[rule.Identity()] > 0 {
switch forwarding.OperationType(operation.Operation) { seen[rule.Identity()]--
case forwarding.OperationAdd: continue
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)
} }
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 { func NewIForwardingService() IForwardingService {
wantedIdentity := wanted.Identity() return newForwardingService()
for index, rule := range rules {
if rule.Identity() == wantedIdentity {
return index
}
}
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)
if err != nil {
return nil, fmt.Errorf(
"%w: selected forwarding backend %s: %w",
errForwardingBackendUnavailable, backend, err,
)
}
adapter, err := forwarding.New(client.Name())
if err != nil {
return nil, err
}
return forwarding.NewManager(adapter, client), nil
} }
+1 -1
View File
@@ -28,7 +28,7 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/archive" "github.com/moby/go-archive"
) )
type ImageService struct{} type ImageService struct{}
+437 -138
View File
@@ -33,11 +33,14 @@ import (
type MonitorService struct { type MonitorService struct {
DiskIO chan ([]disk.IOCountersStat) DiskIO chan ([]disk.IOCountersStat)
NetIO chan ([]net.IOCountersStat) NetIO chan ([]net.IOCountersStat)
ctx context.Context
} }
var ( var (
monitorCancel context.CancelFunc monitorSettingMutex sync.Mutex
hostSysPath = loadHostSysPath() gpuMonitorMutex sync.Mutex
monitorCancel context.CancelFunc
hostSysPath = loadHostSysPath()
blockDevicePartitionCache sync.Map blockDevicePartitionCache sync.Map
) )
@@ -47,8 +50,13 @@ type IMonitorService interface {
LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorData, error) LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorData, error)
LoadSetting() (*dto.MonitorSetting, error) LoadSetting() (*dto.MonitorSetting, error)
UpdateSetting(key, value string) 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 LoadGPUOptions() dto.MonitorGPUOptions
LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error) LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error)
@@ -60,6 +68,7 @@ func NewIMonitorService() IMonitorService {
return &MonitorService{ return &MonitorService{
DiskIO: make(chan []disk.IOCountersStat, 2), DiskIO: make(chan []disk.IOCountersStat, 2),
NetIO: make(chan []net.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.TopCPUItems = processes
base.TopCPU = "" base.TopCPU = ""
} }
if req.Param == "all" || req.Param == "mem" { if req.Param == "all" || req.Param == "memory" {
var processes []dto.Process var processes []dto.Process
_ = json.Unmarshal([]byte(base.TopMem), &processes) _ = json.Unmarshal([]byte(base.TopMem), &processes)
base.TopMemItems = processes base.TopMemItems = processes
@@ -96,7 +105,11 @@ func (m *MonitorService) LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorDa
data = append(data, itemData) data = append(data, itemData)
} }
if req.Param == "all" || req.Param == "io" { 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 { if err != nil {
return nil, err return nil, err
} }
@@ -110,7 +123,11 @@ func (m *MonitorService) LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorDa
data = append(data, itemData) data = append(data, itemData)
} }
if req.Param == "all" || req.Param == "network" { 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 { if err != nil {
return nil, err return nil, err
} }
@@ -126,117 +143,171 @@ func (m *MonitorService) LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorDa
return data, nil 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 { func (m *MonitorService) LoadGPUOptions() dto.MonitorGPUOptions {
var data dto.MonitorGPUOptions var data dto.MonitorGPUOptions
exist, client := accelerator.New() exist, client := accelerator.New()
if !exist { if !exist {
return data return data
} }
seen := make(map[string]bool)
snapshot, err := client.Collect(context.Background()) snapshot, err := client.Collect(context.Background())
if err != nil { 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 return data
} }
if warning := snapshot.Warning(); warning != nil { for _, device := range devices {
global.LOG.Warnf("Load accelerator info partially failed, err: %v", warning) key := device.DeviceID
} if key == "" {
return loadGPUOptions(snapshot) key = "legacy:" + device.ProductName
}
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
} }
} if seen[key] {
switch { continue
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 { seen[key] = true
return snapshot.Devices[i].Vendor < snapshot.Devices[j].Vendor data.ChartHide = append(data.ChartHide, dto.GPUChartHide{DeviceID: device.DeviceID, ProductName: device.ProductName, Type: device.DeviceType, Legacy: device.DeviceID == ""})
} data.Options = append(data.Options, device.ProductName)
if snapshot.Devices[i].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)
} }
return data return data
} }
func (m *MonitorService) LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error) { func (m *MonitorService) LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error) {
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
req.StartTime = req.StartTime.In(loc)
req.EndTime = req.EndTime.In(loc)
var data dto.MonitorGPUData var data dto.MonitorGPUData
gpuList, err := monitorRepo.GetGPU(repo.WithByCreatedAt(req.StartTime, req.EndTime), monitorRepo.WithByProductName(req.ProductName)) if req.StartTime.IsZero() || req.EndTime.IsZero() || !req.EndTime.After(req.StartTime) {
return data, fmt.Errorf("invalid GPU history time range")
}
if req.DeviceID == "" && req.ProductName == "" {
return data, fmt.Errorf("GPU history requires a device")
}
if req.Aggregation != "" && req.Aggregation != "avg" && req.Aggregation != "max" {
return data, fmt.Errorf("invalid GPU history aggregation")
}
loc, err := time.LoadLocation(common.LoadTimeZoneByCmd())
if err != nil { if err != nil {
return data, err return data, err
} }
req.StartTime, req.EndTime = req.StartTime.In(loc), req.EndTime.In(loc)
for _, gpu := range gpuList { opts := []repo.DBOption{repo.WithByCreatedAt(req.StartTime, req.EndTime), monitorRepo.WithByGPUDevice(req.DeviceID, req.ProductName, req.Legacy)}
data.Date = append(data.Date, gpu.CreatedAt) data.SampleCount, err = monitorRepo.CountGPU(opts...)
data.GPUValue = append(data.GPUValue, gpu.GPUUtil) if err != nil || data.SampleCount == 0 {
data.TemperatureValue = append(data.TemperatureValue, gpu.Temperature) return data, err
data.PowerUsed = append(data.PowerUsed, gpu.PowerDraw) }
data.PowerTotal = append(data.PowerTotal, gpu.MaxPowerLimit) if data.SampleCount > 1200 {
if gpu.MaxPowerLimit != 0 { seconds := req.EndTime.Unix() - req.StartTime.Unix() + 1
data.PowerPercent = append(data.PowerPercent, gpu.PowerDraw/gpu.MaxPowerLimit*100) data.BucketSeconds = (seconds + 599) / 600
} else { }
data.PowerPercent = append(data.PowerPercent, float64(0)) points, err := monitorRepo.GetGPUHistory(req.StartTime, data.BucketSeconds, req.Aggregation, opts...)
if err != nil {
return data, err
}
samples := make([]repo.GPUHistoryPoint, 0, len(points))
if data.BucketSeconds > 0 {
next := 0
for bucket := int64(0); bucket <= (req.EndTime.Unix()-req.StartTime.Unix())/data.BucketSeconds; bucket++ {
point := repo.GPUHistoryPoint{}
if next < len(points) && points[next].Bucket == bucket {
point = points[next]
next++
}
point.CreatedAt = time.Unix(req.StartTime.Unix()+bucket*data.BucketSeconds, 0).In(loc)
if bucket == 0 {
point.CreatedAt = req.StartTime
}
samples = append(samples, point)
} }
} else {
data.MemoryTotal = append(data.MemoryTotal, gpu.MemTotal) for i, point := range points {
data.MemoryUsed = append(data.MemoryUsed, gpu.MemUsed) if i > 0 && points[i-1].IntervalSeconds > 0 {
if gpu.MemTotal != 0 { interval := time.Duration(points[i-1].IntervalSeconds) * time.Second
data.MemoryPercent = append(data.MemoryPercent, gpu.MemUsed/gpu.MemTotal*100) if point.CreatedAt.Sub(points[i-1].CreatedAt) > 2*interval {
} else { samples = append(samples, repo.GPUHistoryPoint{MonitorGPU: model.MonitorGPU{BaseModel: model.BaseModel{CreatedAt: points[i-1].CreatedAt.Add(interval)}}})
data.MemoryPercent = append(data.MemoryPercent, float64(0)) }
}
samples = append(samples, point)
} }
var process []dto.GPUProcess }
if err := json.Unmarshal([]byte(gpu.Processes), &process); err == nil { for _, point := range samples {
data.ProcessCount = append(data.ProcessCount, len(process)) data.Date = append(data.Date, point.CreatedAt)
data.GPUProcesses = append(data.GPUProcesses, process) data.MemoryActivity = append(data.MemoryActivity, point.MemoryActivity)
} else { data.EncoderUtil = append(data.EncoderUtil, point.EncoderUtil)
data.ProcessCount = append(data.ProcessCount, 0) data.DecoderUtil = append(data.DecoderUtil, point.DecoderUtil)
data.GPUProcesses = append(data.GPUProcesses, []dto.GPUProcess{}) data.JPEGUtil = append(data.JPEGUtil, point.JPEGUtil)
data.OFAUtil = append(data.OFAUtil, point.OFAUtil)
data.MediaUtil = append(data.MediaUtil, point.MediaUtil)
data.ComputeUtil = append(data.ComputeUtil, point.ComputeUtil)
data.CopyUtil = append(data.CopyUtil, point.CopyUtil)
data.HotspotTemperature = append(data.HotspotTemperature, point.HotspotTemperature)
data.FanRPM = append(data.FanRPM, point.FanRPM)
data.AICPUUtil = append(data.AICPUUtil, point.AICPUUtil)
data.CtrlCPUUtil = append(data.CtrlCPUUtil, point.CtrlCPUUtil)
data.DDRUsed = append(data.DDRUsed, point.DDRUsed)
data.DDRTotal = append(data.DDRTotal, point.DDRTotal)
data.HBMUsed = append(data.HBMUsed, point.HBMUsed)
data.HBMTotal = append(data.HBMTotal, point.HBMTotal)
data.DDRBandwidth = append(data.DDRBandwidth, point.DDRBandwidth)
data.HBMBandwidth = append(data.HBMBandwidth, point.HBMBandwidth)
data.MemoryBandwidth = append(data.MemoryBandwidth, point.MemoryBandwidth)
data.MediaFrequency = append(data.MediaFrequency, point.MediaFrequency)
data.HugepagesUsed = append(data.HugepagesUsed, point.HugepagesUsed)
data.HugepagesTotal = append(data.HugepagesTotal, point.HugepagesTotal)
data.GPUValue = append(data.GPUValue, point.GPUUtil)
data.TemperatureValue = append(data.TemperatureValue, point.Temperature)
data.MemoryTemperatureValue = append(data.MemoryTemperatureValue, point.MemoryTemperature)
data.PowerUsed = append(data.PowerUsed, point.PowerDraw)
data.PowerTotal = append(data.PowerTotal, point.MaxPowerLimit)
data.PowerPercent = append(data.PowerPercent, point.PowerPercent)
data.MemoryPercent = append(data.MemoryPercent, point.MemoryPercent)
data.MemoryTotal = append(data.MemoryTotal, point.MemTotal)
data.MemoryUsed = append(data.MemoryUsed, point.MemUsed)
data.SpeedValue = append(data.SpeedValue, point.FanSpeed)
data.FrequencyValue = append(data.FrequencyValue, point.Frequency)
data.MemoryFrequencyValue = append(data.MemoryFrequencyValue, point.MemoryFrequency)
data.ProcessCount = append(data.ProcessCount, point.ProcessCount)
var processes []dto.GPUProcess
if data.BucketSeconds == 0 && point.ProcessCount != nil {
_ = json.Unmarshal([]byte(point.Processes), &processes)
} }
data.SpeedValue = append(data.SpeedValue, gpu.FanSpeed) data.GPUProcesses = append(data.GPUProcesses, processes)
} }
return data, nil return data, nil
} }
@@ -262,9 +333,27 @@ func (m *MonitorService) LoadSetting() (*dto.MonitorSetting, error) {
} }
func (m *MonitorService) UpdateSetting(key, value string) 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 { switch key {
case "MonitorStatus": case "MonitorStatus":
if value == constant.StatusEnable && global.MonitorCronID == 0 { if value == constant.StatusEnable && monitorCancel == nil {
interval, err := settingRepo.Get(settingRepo.WithByKey("MonitorInterval")) interval, err := settingRepo.Get(settingRepo.WithByKey("MonitorInterval"))
if err != nil { if err != nil {
return err return err
@@ -273,8 +362,9 @@ func (m *MonitorService) UpdateSetting(key, value string) error {
return err return err
} }
} }
if value == constant.StatusDisable && global.MonitorCronID != 0 { if value == constant.StatusDisable && monitorCancel != nil {
monitorCancel() monitorCancel()
monitorCancel = nil
global.Cron.Remove(cron.EntryID(global.MonitorCronID)) global.Cron.Remove(cron.EntryID(global.MonitorCronID))
global.MonitorCronID = 0 global.MonitorCronID = 0
} }
@@ -283,31 +373,80 @@ func (m *MonitorService) UpdateSetting(key, value string) error {
if err != nil { if err != nil {
return err return err
} }
if status.Value == constant.StatusEnable && global.MonitorCronID != 0 { if status.Value == constant.StatusEnable && monitorCancel != nil {
if err := StartMonitor(true, value); err != nil { if err := StartMonitor(true, value); err != nil {
return err 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) return settingRepo.Update(key, value)
} }
func (m *MonitorService) CleanData() error { func (m *MonitorService) CleanData(monitorType string) error {
if err := global.MonitorDB.Exec("DELETE FROM monitor_bases").Error; err != nil { switch monitorType {
return err 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() { func (m *MonitorService) Run() {
saveAcceleratorDataToDB() if m.ctx.Err() != nil {
return
}
var itemModel model.MonitorBase var itemModel model.MonitorBase
totalPercent, _ := cpu.Percent(3*time.Second, false) totalPercent, _ := cpu.Percent(3*time.Second, false)
if len(totalPercent) == 1 { if len(totalPercent) == 1 {
@@ -353,30 +492,34 @@ func (m *MonitorService) Run() {
_ = monitorRepo.DelMonitorBase(timeForDelete) _ = monitorRepo.DelMonitorBase(timeForDelete)
_ = monitorRepo.DelMonitorIO(timeForDelete) _ = monitorRepo.DelMonitorIO(timeForDelete)
_ = monitorRepo.DelMonitorNet(timeForDelete) _ = monitorRepo.DelMonitorNet(timeForDelete)
_ = monitorRepo.DelMonitorGPU(timeForDelete)
} }
func (m *MonitorService) loadDiskIO() { func (m *MonitorService) loadDiskIO() {
ioStat, _ := disk.IOCounters() ioStat, _ := disk.IOCountersWithContext(m.ctx)
var diskIOList []disk.IOCountersStat var diskIOList []disk.IOCountersStat
for _, io := range ioStat { for _, io := range ioStat {
diskIOList = append(diskIOList, io) diskIOList = append(diskIOList, io)
} }
diskIOList = append(diskIOList, sumDiskIOCounters(ioStat)) diskIOList = append(diskIOList, sumDiskIOCounters(ioStat))
m.DiskIO <- diskIOList select {
case <-m.ctx.Done():
case m.DiskIO <- diskIOList:
}
} }
func (m *MonitorService) loadNetIO() { func (m *MonitorService) loadNetIO() {
netStat, _ := net.IOCounters(true) netStat, _ := net.IOCountersWithContext(m.ctx, true)
netStatAll, _ := net.IOCounters(false) netStatAll, _ := net.IOCountersWithContext(m.ctx, false)
var netList []net.IOCountersStat var netList []net.IOCountersStat
netList = append(netList, netStat...) netList = append(netList, netStat...)
netList = append(netList, netStatAll...) 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) { func (m *MonitorService) saveIODataToDB(ctx context.Context, interval float64) {
defer close(m.DiskIO)
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
@@ -426,14 +569,17 @@ func (m *MonitorService) saveIODataToDB(ctx context.Context, interval float64) {
} }
} }
_ = monitorRepo.BatchCreateMonitorIO(ioList) _ = monitorRepo.BatchCreateMonitorIO(ioList)
m.DiskIO <- ioStat2 select {
case <-ctx.Done():
return
case m.DiskIO <- ioStat2:
}
} }
} }
} }
} }
func (m *MonitorService) saveNetDataToDB(ctx context.Context, interval float64) { func (m *MonitorService) saveNetDataToDB(ctx context.Context, interval float64) {
defer close(m.NetIO)
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
@@ -463,7 +609,11 @@ func (m *MonitorService) saveNetDataToDB(ctx context.Context, interval float64)
} }
_ = monitorRepo.BatchCreateMonitorNet(netList) _ = 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 { 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 { if removeBefore {
monitorCancel() monitorCancel()
global.Cron.Remove(cron.EntryID(global.MonitorCronID)) 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()) 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() now := time.Now()
nextMinute := now.Truncate(time.Minute).Add(time.Minute) 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) monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %ss", interval), service)
if err != nil { if err != nil {
return return
} }
global.MonitorCronID = monitorID global.MonitorCronID = monitorID
}) })
monitorCancel = func() {
cancel()
timer.Stop()
}
service.Run() service.Run()
@@ -602,7 +763,97 @@ func StartMonitor(removeBefore bool, interval string) error {
return nil return nil
} }
func saveAcceleratorDataToDB() { func loadGPUOptions(snapshot *accelerator.Snapshot) dto.MonitorGPUOptions {
var data dto.MonitorGPUOptions
hasGPUOrNPU := false
hasXPU := false
for _, item := range snapshot.Devices {
if item.Kind == accelerator.KindXPU {
hasXPU = true
} else {
hasGPUOrNPU = true
}
}
switch {
case hasGPUOrNPU && hasXPU:
data.GPUType = "mixed"
case hasXPU:
data.GPUType = "xpu"
case hasGPUOrNPU:
data.GPUType = "gpu"
}
sort.Slice(snapshot.Devices, func(i, j int) bool {
if snapshot.Devices[i].Kind != snapshot.Devices[j].Kind {
return snapshot.Devices[i].Kind < snapshot.Devices[j].Kind
}
if snapshot.Devices[i].Vendor != snapshot.Devices[j].Vendor {
return snapshot.Devices[i].Vendor < snapshot.Devices[j].Vendor
}
if snapshot.Devices[i].NPUIndex != snapshot.Devices[j].NPUIndex {
return snapshot.Devices[i].NPUIndex < snapshot.Devices[j].NPUIndex
}
if snapshot.Devices[i].ChipIndex != snapshot.Devices[j].ChipIndex {
return snapshot.Devices[i].ChipIndex < snapshot.Devices[j].ChipIndex
}
return snapshot.Devices[i].Index < snapshot.Devices[j].Index
})
for _, item := range snapshot.Devices {
chartHide := dto.GPUChartHide{
DeviceID: item.ID,
ProductName: item.Label,
Type: string(item.Kind),
}
data.ChartHide = append(data.ChartHide, chartHide)
data.Options = append(data.Options, chartHide.ProductName)
}
return data
}
func 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() exist, client := accelerator.New()
if !exist { if !exist {
return return
@@ -615,27 +866,64 @@ func saveAcceleratorDataToDB() {
if warning := snapshot.Warning(); warning != nil { if warning := snapshot.Warning(); warning != nil {
global.LOG.Warnf("load accelerator monitor data partially failed, err: %v", warning) global.LOG.Warnf("load accelerator monitor data partially failed, err: %v", warning)
} }
intervalSeconds := 0
if setting, err := settingRepo.Get(settingRepo.WithByKey("GPUMonitorInterval")); err == nil {
intervalSeconds, _ = strconv.Atoi(setting.Value)
}
list := make([]model.MonitorGPU, 0, len(snapshot.Devices)) list := make([]model.MonitorGPU, 0, len(snapshot.Devices))
for _, device := range snapshot.Devices { for _, device := range snapshot.Devices {
list = append(list, newMonitorGPU(device)) item := newMonitorGPU(device)
item.CreatedAt = snapshot.Info.CollectedAt
item.IntervalSeconds = intervalSeconds
list = append(list, item)
} }
if err := repo.NewIMonitorRepo().BatchCreateMonitorGPU(list); err != nil { if err := monitorRepo.BatchCreateMonitorGPU(list); err != nil {
global.LOG.Errorf("batch create accelerator monitor data failed, err: %v", err) global.LOG.Errorf("batch create accelerator monitor data failed, err: %v", err)
} }
} }
func newMonitorGPU(device accelerator.Device) model.MonitorGPU { func newMonitorGPU(device accelerator.Device) model.MonitorGPU {
item := model.MonitorGPU{ item := model.MonitorGPU{
ProductName: device.Label, MemoryUtil: device.Metrics.MemoryUtil.Value,
GPUUtil: device.Metrics.Utilization.ValueOrZero(), MemoryActivity: device.Metrics.MemoryActivity.Value,
Temperature: device.Metrics.Temperature.ValueOrZero(), EncoderUtil: device.Metrics.EncoderUtil.Value,
PowerDraw: device.Metrics.Power.ValueOrZero(), DecoderUtil: device.Metrics.DecoderUtil.Value,
MaxPowerLimit: device.Metrics.PowerLimit.ValueOrZero(), JPEGUtil: device.Metrics.JPEGUtil.Value,
MemUsed: device.Metrics.MemoryUsed.ValueOrZero(), OFAUtil: device.Metrics.OFAUtil.Value,
MemTotal: device.Metrics.MemoryTotal.ValueOrZero(), MediaUtil: device.Metrics.MediaUtil.Value,
FanSpeed: int(device.Metrics.FanSpeed.ValueOrZero()), ComputeUtil: device.Metrics.ComputeUtil.Value,
CopyUtil: device.Metrics.CopyUtil.Value,
HotspotTemperature: device.Metrics.HotspotTemperature.Value,
FanRPM: device.Metrics.FanRPM.Value,
AICPUUtil: device.Metrics.AICPUUtil.Value,
CtrlCPUUtil: device.Metrics.CtrlCPUUtil.Value,
DDRUsed: device.Metrics.DDRUsed.Value,
DDRTotal: device.Metrics.DDRTotal.Value,
HBMUsed: device.Metrics.HBMUsed.Value,
HBMTotal: device.Metrics.HBMTotal.Value,
DDRBandwidth: device.Metrics.DDRBandwidth.Value,
HBMBandwidth: device.Metrics.HBMBandwidth.Value,
MemoryBandwidth: device.Metrics.MemoryBandwidth.Value,
MediaFrequency: device.Metrics.MediaFrequency.Value,
HugepagesUsed: device.Metrics.HugepagesUsed.Value,
HugepagesTotal: device.Metrics.HugepagesTotal.Value,
ProductName: device.Label,
DeviceID: device.ID,
DeviceType: string(device.Kind),
ProcessStatus: device.ProcessStatus,
Frequency: device.Metrics.Frequency.Value,
MemoryFrequency: device.Metrics.MemoryFrequency.Value,
MemoryTemperature: device.Metrics.MemoryTemperature.Value,
GPUUtil: device.Metrics.Utilization.Value,
Temperature: device.Metrics.Temperature.Value,
PowerDraw: device.Metrics.Power.Value,
MaxPowerLimit: device.Metrics.PowerLimit.Value,
MemUsed: device.Metrics.MemoryUsed.Value,
MemTotal: device.Metrics.MemoryTotal.Value,
FanSpeed: device.Metrics.FanSpeed.Value,
} }
if len(device.Processes) == 0 { if device.ProcessStatus != "ok" {
return item return item
} }
processes := make([]dto.GPUProcess, 0, len(device.Processes)) processes := make([]dto.GPUProcess, 0, len(device.Processes))
@@ -695,3 +983,14 @@ func loadHostSysPath() string {
} }
return hostSys 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...)
}
+287
View File
@@ -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
}
+2
View File
@@ -1,6 +1,7 @@
package service package service
import ( import (
"context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
@@ -44,6 +45,7 @@ type ISettingService interface {
GetLocalConnForSSH() (dto.SSHConnData, error) GetLocalConnForSSH() (dto.SSHConnData, error)
SaveDescription(req dto.CommonDescription) error SaveDescription(req dto.CommonDescription) error
CleanupDescriptions(context.Context) (int64, error)
} }
func NewISettingService() ISettingService { func NewISettingService() ISettingService {
+114
View File
@@ -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
View File
@@ -7,6 +7,7 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"encoding/pem"
"fmt" "fmt"
"io" "io"
"net" "net"
@@ -37,6 +38,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/common" "github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/re" "github.com/1Panel-dev/1Panel/agent/utils/re"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/crypto/ssh"
) )
const sshPath = "/etc/ssh/sshd_config" const sshPath = "/etc/ssh/sshd_config"
@@ -228,7 +230,7 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
return err return err
} }
if req.Key == "Port" { 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 { if restoreErr := rewriteSSHManagedDirectives(sshPath, "Port", buildSSHDirectiveLines("Port", oldPortValue)); restoreErr != nil {
return fmt.Errorf("save SSH whitelist: %w; restore SSH configuration: %v", err, restoreErr) 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) global.LOG.Errorf("read pubic key of %s for sync failed, err: %v", item, err)
continue 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) rootCerts = append(rootCerts, cert)
} }
return hostRepo.SyncCert(rootCerts) return hostRepo.SyncCert(rootCerts)
} }
func (u *SSHService) CreateRootCert(req dto.RootCertOperate) error { func (u *SSHService) CreateRootCert(req dto.RootCertOperate) error {
if cmd.CheckIllegal(req.EncryptionMode, req.PassPhrase) { switch req.Mode {
return buserr.New("ErrCmdIllegal") 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)) certItem, _ := hostRepo.GetCert(repo.WithByName(req.Name))
if certItem.ID != 0 { if certItem.ID != 0 {
@@ -543,6 +563,9 @@ func (u *SSHService) CreateRootCert(req dto.RootCertOperate) error {
} }
func (u *SSHService) EditRootCert(req dto.RootCertOperate) error { func (u *SSHService) EditRootCert(req dto.RootCertOperate) error {
if err := validateSSHKeyPair(&req); err != nil {
return err
}
currentUser, err := user.Current() currentUser, err := user.Current()
if err != nil { if err != nil {
return fmt.Errorf("load current user failed, err: %v", err) 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 return itemDate
} }
func loadEncryptioMode(content string) string { func loadSSHKeyType(publicKey ssh.PublicKey) string {
if strings.HasPrefix(content, "ssh-rsa") { switch publicKey.Type() {
case ssh.KeyAlgoRSA:
return "rsa" return "rsa"
} case ssh.KeyAlgoED25519:
if strings.HasPrefix(content, "ssh-ed25519") {
return "ed25519" return "ed25519"
} case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521:
if strings.HasPrefix(content, "ssh-ecdsa") {
return "ecdsa" return "ecdsa"
} case ssh.KeyAlgoDSA:
if strings.HasPrefix(content, "ssh-dsa") {
return "dsa" return "dsa"
} }
return "" 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 { func updateLocalConn(newPort uint) error {
conn, _ := settingRepo.GetValueByKey("LocalSSHConn") conn, _ := settingRepo.GetValueByKey("LocalSSHConn")
if len(conn) == 0 { if len(conn) == 0 {
+4 -1
View File
@@ -8,6 +8,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo" "github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/files" "github.com/1Panel-dev/1Panel/agent/utils/files"
"path" "path"
"strconv" "strconv"
@@ -32,7 +33,9 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
return nil, err return nil, err
} }
go func() { go func() {
_ = ensureFirewallPorts(addPorts) if err := ensureFirewallPorts(addPorts); err != nil {
global.LOG.Errorf("allow website firewall ports failed: %v", err)
}
}() }()
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
+11 -1
View File
@@ -87,6 +87,7 @@ const (
TaskScopeCronjob = "Cronjob" TaskScopeCronjob = "Cronjob"
TaskScopeClam = "Clam" TaskScopeClam = "Clam"
TaskScopeSystem = "System" TaskScopeSystem = "System"
TaskScopeVm = "VirtualMachine"
TaskScopeFirewall = "Firewall" TaskScopeFirewall = "Firewall"
TaskScopeAppStore = "AppStore" TaskScopeAppStore = "AppStore"
TaskScopeSnapshot = "Snapshot" TaskScopeSnapshot = "Snapshot"
@@ -99,6 +100,7 @@ const (
TaskScopeTamper = "Tamper" TaskScopeTamper = "Tamper"
TaskScopeFileConvert = "Convert" TaskScopeFileConvert = "Convert"
TaskScopeTask = "Task" TaskScopeTask = "Task"
TaskScopeVm = "VirtualMachine"
) )
func GetTaskName(resourceName, operate, scope string) string { func GetTaskName(resourceName, operate, scope string) string {
@@ -154,7 +156,7 @@ func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, e
logPath := path.Join(global.Dir.TaskDir, taskScope, taskID+".log") logPath := path.Join(global.Dir.TaskDir, taskScope, taskID+".log")
logger := logrus.New() logger := logrus.New()
logger.SetFormatter(&SimpleFormatter{}) logger.SetFormatter(&SimpleFormatter{})
logFile, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, constant.FilePerm) logFile, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY|os.O_APPEND, constant.FilePerm)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open log file: %w", err) return nil, fmt.Errorf("failed to open log file: %w", err)
} }
@@ -257,7 +259,15 @@ func (s *SubTask) Execute() error {
case <-ctx.Done(): case <-ctx.Done():
s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName)) s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName))
err = errors.New("timeout!") err = errors.New("timeout!")
if s.RootTask.Task.Type == TaskScopeVm {
<-done
}
case err = <-done: case err = <-done:
if s.RootTask.Task.Type == TaskScopeVm && ctx.Err() != nil {
s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName))
err = errors.New("timeout!")
break
}
if err != nil { if err != nil {
s.RootTask.Log(i18n.GetWithNameAndErr("SubTaskFailed", subTaskName, err)) s.RootTask.Log(i18n.GetWithNameAndErr("SubTaskFailed", subTaskName, err))
if err.Error() == i18n.GetMsgByKey("ErrShutDown") { if err.Error() == i18n.GetMsgByKey("ErrShutDown") {
+122 -107
View File
@@ -265,8 +265,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "应用端口修改 [key]-[name] =\u003e [port]", "formatZH": "应用端口修改 [key]-[name] => [port]",
"formatEN": "Application port update [key]-[name] =\u003e [port]" "formatEN": "Application port update [key]-[name] => [port]"
}, },
"/apps/installed/sort/update": { "/apps/installed/sort/update": {
"bodyKeys": [], "bodyKeys": [],
@@ -679,8 +679,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "容器重命名 [name] =\u003e [newName]", "formatZH": "容器重命名 [name] => [newName]",
"formatEN": "rename container [name] =\u003e [newName]" "formatEN": "rename container [name] => [newName]"
}, },
"/containers/repo": { "/containers/repo": {
"bodyKeys": [ "bodyKeys": [
@@ -831,8 +831,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]", "formatZH": "更新 API 接口配置 => IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
"formatEN": "update api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]" "formatEN": "update api config => IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
}, },
"/core/auth/expired/reset": { "/core/auth/expired/reset": {
"bodyKeys": [], "bodyKeys": [],
@@ -1716,8 +1716,8 @@
"output_value": "name" "output_value": "name"
} }
], ],
"formatZH": "更新用户 [name] API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]", "formatZH": "更新用户 [name] API 接口配置 => IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
"formatEN": "update user [name] api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]" "formatEN": "update user [name] api config => IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
}, },
"/core/enterprise/users/del": { "/core/enterprise/users/del": {
"bodyKeys": [ "bodyKeys": [
@@ -1918,8 +1918,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "修改系统监听信息 =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]", "formatZH": "修改系统监听信息 => ipv6: [ipv6], 监听 IP: [bindAddress]",
"formatEN": "update system bind info =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]" "formatEN": "update system bind info => ipv6: [ipv6], 监听 IP: [bindAddress]"
}, },
"/core/settings/memo": { "/core/settings/memo": {
"bodyKeys": [], "bodyKeys": [],
@@ -1948,8 +1948,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "修改系统端口 =\u003e [serverPort]", "formatZH": "修改系统端口 => [serverPort]",
"formatEN": "update system port =\u003e [serverPort]" "formatEN": "update system port => [serverPort]"
}, },
"/core/settings/proxy/update": { "/core/settings/proxy/update": {
"bodyKeys": [ "bodyKeys": [
@@ -1974,8 +1974,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "修改系统 ssl =\u003e [ssl]", "formatZH": "修改系统 ssl => [ssl]",
"formatEN": "update system ssl =\u003e [ssl]" "formatEN": "update system ssl => [ssl]"
}, },
"/core/settings/terminal/update": { "/core/settings/terminal/update": {
"bodyKeys": [], "bodyKeys": [],
@@ -1991,8 +1991,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "修改系统配置 [key] =\u003e [value]", "formatZH": "修改系统配置 [key] => [value]",
"formatEN": "update system setting [key] =\u003e [value]" "formatEN": "update system setting [key] => [value]"
}, },
"/core/settings/upgrade": { "/core/settings/upgrade": {
"bodyKeys": [ "bodyKeys": [
@@ -2000,8 +2000,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "更新系统 =\u003e [version]", "formatZH": "更新系统 => [version]",
"formatEN": "upgrade system =\u003e [version]" "formatEN": "upgrade system => [version]"
}, },
"/core/xapp/appQRCode": { "/core/xapp/appQRCode": {
"bodyKeys": [ "bodyKeys": [
@@ -2459,7 +2459,7 @@
"formatZH": "同步 SSL 证书 [primaryDomain]", "formatZH": "同步 SSL 证书 [primaryDomain]",
"formatEN": "sync SSL certificate [primaryDomain]" "formatEN": "sync SSL certificate [primaryDomain]"
}, },
"/core/xpack/vms": { "/xpack/vms": {
"bodyKeys": [ "bodyKeys": [
"name" "name"
], ],
@@ -2468,7 +2468,7 @@
"formatZH": "创建虚拟机 [name]", "formatZH": "创建虚拟机 [name]",
"formatEN": "create VM [name]" "formatEN": "create VM [name]"
}, },
"/core/xpack/vms/del": { "/xpack/vms/del": {
"bodyKeys": [ "bodyKeys": [
"id" "id"
], ],
@@ -2486,14 +2486,25 @@
"formatZH": "删除虚拟机 [name]", "formatZH": "删除虚拟机 [name]",
"formatEN": "delete VM [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": [], "bodyKeys": [],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "启用虚拟机运行环境", "formatZH": "启用虚拟机运行环境",
"formatEN": "enable VM runtime environment" "formatEN": "enable VM runtime environment"
}, },
"/core/xpack/vms/iso": { "/xpack/vms/iso": {
"bodyKeys": [ "bodyKeys": [
"name", "name",
"type" "type"
@@ -2503,7 +2514,7 @@
"formatZH": "创建虚拟机镜像 [name] [type]", "formatZH": "创建虚拟机镜像 [name] [type]",
"formatEN": "create VM ISO [name] [type]" "formatEN": "create VM ISO [name] [type]"
}, },
"/core/xpack/vms/iso/del": { "/xpack/vms/iso/del": {
"bodyKeys": [ "bodyKeys": [
"id" "id"
], ],
@@ -2521,7 +2532,7 @@
"formatZH": "删除虚拟机镜像 [name]", "formatZH": "删除虚拟机镜像 [name]",
"formatEN": "delete VM ISO [name]" "formatEN": "delete VM ISO [name]"
}, },
"/core/xpack/vms/iso/update": { "/xpack/vms/iso/update": {
"bodyKeys": [ "bodyKeys": [
"name", "name",
"type" "type"
@@ -2531,7 +2542,7 @@
"formatZH": "更新虚拟机镜像 [name] [type]", "formatZH": "更新虚拟机镜像 [name] [type]",
"formatEN": "update VM ISO [name] [type]" "formatEN": "update VM ISO [name] [type]"
}, },
"/core/xpack/vms/networks": { "/xpack/vms/networks": {
"bodyKeys": [ "bodyKeys": [
"name", "name",
"type" "type"
@@ -2541,7 +2552,7 @@
"formatZH": "创建虚拟机网络 [name] [type]", "formatZH": "创建虚拟机网络 [name] [type]",
"formatEN": "create VM network [name] [type]" "formatEN": "create VM network [name] [type]"
}, },
"/core/xpack/vms/networks/del": { "/xpack/vms/networks/del": {
"bodyKeys": [ "bodyKeys": [
"id" "id"
], ],
@@ -2559,7 +2570,7 @@
"formatZH": "删除虚拟机网络 [name]", "formatZH": "删除虚拟机网络 [name]",
"formatEN": "delete VM network [name]" "formatEN": "delete VM network [name]"
}, },
"/core/xpack/vms/networks/update": { "/xpack/vms/networks/update": {
"bodyKeys": [ "bodyKeys": [
"id" "id"
], ],
@@ -2577,7 +2588,7 @@
"formatZH": "更新虚拟机网络 [name]", "formatZH": "更新虚拟机网络 [name]",
"formatEN": "update VM network [name]" "formatEN": "update VM network [name]"
}, },
"/core/xpack/vms/operate": { "/xpack/vms/operate": {
"bodyKeys": [ "bodyKeys": [
"name", "name",
"operate" "operate"
@@ -2587,14 +2598,14 @@
"formatZH": "操作虚拟机 [name] [operate]", "formatZH": "操作虚拟机 [name] [operate]",
"formatEN": "operate VM [name] [operate]" "formatEN": "operate VM [name] [operate]"
}, },
"/core/xpack/vms/orphans/clean": { "/xpack/vms/orphans/clean": {
"bodyKeys": [], "bodyKeys": [],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "清理虚拟机孤立数据", "formatZH": "清理虚拟机孤立数据",
"formatEN": "clean VM orphaned data" "formatEN": "clean VM orphaned data"
}, },
"/core/xpack/vms/rename": { "/xpack/vms/rename": {
"bodyKeys": [ "bodyKeys": [
"name", "name",
"newName" "newName"
@@ -2604,7 +2615,7 @@
"formatZH": "重命名虚拟机 [name] 为 [newName]", "formatZH": "重命名虚拟机 [name] 为 [newName]",
"formatEN": "rename VM [name] to [newName]" "formatEN": "rename VM [name] to [newName]"
}, },
"/core/xpack/vms/snapshots": { "/xpack/vms/snapshots": {
"bodyKeys": [ "bodyKeys": [
"name", "name",
"snapshotName" "snapshotName"
@@ -2614,7 +2625,7 @@
"formatZH": "创建虚拟机 [name] 快照 [snapshotName]", "formatZH": "创建虚拟机 [name] 快照 [snapshotName]",
"formatEN": "create VM [name] snapshot [snapshotName]" "formatEN": "create VM [name] snapshot [snapshotName]"
}, },
"/core/xpack/vms/snapshots/del": { "/xpack/vms/snapshots/del": {
"bodyKeys": [ "bodyKeys": [
"name", "name",
"snapshotName" "snapshotName"
@@ -2624,7 +2635,7 @@
"formatZH": "删除虚拟机 [name] 快照 [snapshotName]", "formatZH": "删除虚拟机 [name] 快照 [snapshotName]",
"formatEN": "delete VM [name] snapshot [snapshotName]" "formatEN": "delete VM [name] snapshot [snapshotName]"
}, },
"/core/xpack/vms/snapshots/recover": { "/xpack/vms/snapshots/recover": {
"bodyKeys": [ "bodyKeys": [
"name", "name",
"snapshotName" "snapshotName"
@@ -2634,7 +2645,7 @@
"formatZH": "恢复虚拟机 [name] 快照 [snapshotName]", "formatZH": "恢复虚拟机 [name] 快照 [snapshotName]",
"formatEN": "recover VM [name] snapshot [snapshotName]" "formatEN": "recover VM [name] snapshot [snapshotName]"
}, },
"/core/xpack/vms/storages": { "/xpack/vms/storages": {
"bodyKeys": [ "bodyKeys": [
"name", "name",
"type" "type"
@@ -2644,7 +2655,7 @@
"formatZH": "创建虚拟机存储 [name] [type]", "formatZH": "创建虚拟机存储 [name] [type]",
"formatEN": "create VM storage [name] [type]" "formatEN": "create VM storage [name] [type]"
}, },
"/core/xpack/vms/storages/del": { "/xpack/vms/storages/del": {
"bodyKeys": [ "bodyKeys": [
"id" "id"
], ],
@@ -2662,7 +2673,7 @@
"formatZH": "删除虚拟机存储 [name]", "formatZH": "删除虚拟机存储 [name]",
"formatEN": "delete VM storage [name]" "formatEN": "delete VM storage [name]"
}, },
"/core/xpack/vms/storages/update": { "/xpack/vms/storages/update": {
"bodyKeys": [ "bodyKeys": [
"id" "id"
], ],
@@ -2680,14 +2691,14 @@
"formatZH": "更新虚拟机存储 [name]", "formatZH": "更新虚拟机存储 [name]",
"formatEN": "update VM storage [name]" "formatEN": "update VM storage [name]"
}, },
"/core/xpack/vms/sync": { "/xpack/vms/sync": {
"bodyKeys": [], "bodyKeys": [],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "同步虚拟机", "formatZH": "同步虚拟机",
"formatEN": "sync virtual machines" "formatEN": "sync virtual machines"
}, },
"/core/xpack/vms/templates": { "/xpack/vms/templates": {
"bodyKeys": [ "bodyKeys": [
"name", "name",
"templateName" "templateName"
@@ -2697,7 +2708,7 @@
"formatZH": "创建虚拟机 [name] 模板 [templateName]", "formatZH": "创建虚拟机 [name] 模板 [templateName]",
"formatEN": "create VM [name] template [templateName]" "formatEN": "create VM [name] template [templateName]"
}, },
"/core/xpack/vms/templates/del": { "/xpack/vms/templates/del": {
"bodyKeys": [ "bodyKeys": [
"id" "id"
], ],
@@ -2715,7 +2726,7 @@
"formatZH": "删除虚拟机模板 [name]", "formatZH": "删除虚拟机模板 [name]",
"formatEN": "delete VM template [name]" "formatEN": "delete VM template [name]"
}, },
"/core/xpack/vms/update": { "/xpack/vms/update": {
"bodyKeys": [ "bodyKeys": [
"name" "name"
], ],
@@ -2877,8 +2888,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "首页应用 [key] =\u003e 显示:[value]", "formatZH": "首页应用 [key] => 显示:[value]",
"formatEN": "app launcher [key] =\u003e show: [value]" "formatEN": "app launcher [key] => show: [value]"
}, },
"/dashboard/quick/change": { "/dashboard/quick/change": {
"bodyKeys": [], "bodyKeys": [],
@@ -3006,7 +3017,7 @@
} }
], ],
"formatZH": "mysql 数据库 [name] 描述信息修改 [description]", "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": { "/databases/grants": {
"bodyKeys": [ "bodyKeys": [
@@ -3087,7 +3098,7 @@
} }
], ],
"formatZH": "mongodb 数据库 [name] 描述信息修改 [description]", "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": { "/databases/mongodb/password": {
"bodyKeys": [ "bodyKeys": [
@@ -3172,7 +3183,7 @@
} }
], ],
"formatZH": "postgresql 数据库 [name] 描述信息修改 [description]", "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": { "/databases/pg/password": {
"bodyKeys": [ "bodyKeys": [
@@ -3277,8 +3288,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "更新 mysql 数据库 [database] 用户 [username] 访问权限 [host] =\u003e [newHost] 描述 [description]", "formatZH": "更新 mysql 数据库 [database] 用户 [username] 访问权限 [host] => [newHost] 描述 [description]",
"formatEN": "update mysql database [database] user [username] access [host] =\u003e [newHost] description [description]" "formatEN": "update mysql database [database] user [username] access [host] => [newHost] description [description]"
}, },
"/databases/variables/update": { "/databases/variables/update": {
"bodyKeys": [], "bodyKeys": [],
@@ -3314,8 +3325,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "批量修改文件权限和用户/组 [paths] =\u003e [mode]/[user]/[group]", "formatZH": "批量修改文件权限和用户/组 [paths] => [mode]/[user]/[group]",
"formatEN": "Batch change file mode and owner [paths] =\u003e [mode]/[user]/[group]" "formatEN": "Batch change file mode and owner [paths] => [mode]/[user]/[group]"
}, },
"/files/chunkdownload": { "/files/chunkdownload": {
"bodyKeys": [ "bodyKeys": [
@@ -3405,8 +3416,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "修改权限 [path] =\u003e [mode]", "formatZH": "修改权限 [path] => [mode]",
"formatEN": "Change mode [path] =\u003e [mode]" "formatEN": "Change mode [path] => [mode]"
}, },
"/files/move": { "/files/move": {
"bodyKeys": [ "bodyKeys": [
@@ -3415,8 +3426,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "移动文件 [oldPaths] =\u003e [newPath]", "formatZH": "移动文件 [oldPaths] => [newPath]",
"formatEN": "Move [oldPaths] =\u003e [newPath]" "formatEN": "Move [oldPaths] => [newPath]"
}, },
"/files/owner": { "/files/owner": {
"bodyKeys": [ "bodyKeys": [
@@ -3426,8 +3437,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "修改用户/组 [path] =\u003e [user]/[group]", "formatZH": "修改用户/组 [path] => [user]/[group]",
"formatEN": "Change owner [path] =\u003e [user]/[group]" "formatEN": "Change owner [path] => [user]/[group]"
}, },
"/files/preview": { "/files/preview": {
"bodyKeys": [ "bodyKeys": [
@@ -3461,8 +3472,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "重命名 [oldName] =\u003e [newName]", "formatZH": "重命名 [oldName] => [newName]",
"formatEN": "Rename [oldName] =\u003e [newName]" "formatEN": "Rename [oldName] => [newName]"
}, },
"/files/save": { "/files/save": {
"bodyKeys": [ "bodyKeys": [
@@ -3519,8 +3530,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "下载 url =\u003e [path]/[name]", "formatZH": "下载 url => [path]/[name]",
"formatEN": "Download url =\u003e [path]/[name]" "formatEN": "Download url => [path]/[name]"
}, },
"/files/wget/process/remove": { "/files/wget/process/remove": {
"bodyKeys": [ "bodyKeys": [
@@ -3670,12 +3681,16 @@
"formatZH": "删除 Docker 端口防护策略 [uuids]", "formatZH": "删除 Docker 端口防护策略 [uuids]",
"formatEN": "delete Docker port guard policies [uuids]" "formatEN": "delete Docker port guard policies [uuids]"
}, },
"/hosts/firewall/docker/sync": { "/hosts/firewall/family/operate": {
"bodyKeys": [], "bodyKeys": [
"subsystem",
"family",
"operation"
],
"formatEN": "[operation] [subsystem] [family] firewall chains",
"formatZH": "[operation] [subsystem] [family] 防火墙链",
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": []
"formatZH": "同步 Docker 端口防护规则",
"formatEN": "sync Docker port guard rules"
}, },
"/hosts/firewall/filter/operate": { "/hosts/firewall/filter/operate": {
"bodyKeys": [ "bodyKeys": [
@@ -3716,13 +3731,6 @@
"formatZH": "添加防火墙规则", "formatZH": "添加防火墙规则",
"formatEN": "create firewall rules" "formatEN": "create firewall rules"
}, },
"/hosts/firewall/rules/adopt": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "纳管防火墙规则",
"formatEN": "adopt firewall rule"
},
"/hosts/firewall/rules/delete": { "/hosts/firewall/rules/delete": {
"bodyKeys": [], "bodyKeys": [],
"paramKeys": [], "paramKeys": [],
@@ -3732,12 +3740,12 @@
}, },
"/hosts/firewall/rules/reorder": { "/hosts/firewall/rules/reorder": {
"bodyKeys": [ "bodyKeys": [
"uuid" "instanceKey"
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "调整防火墙规则顺序 [uuid]", "formatZH": "调整防火墙规则顺序 [instanceKey]",
"formatEN": "reorder firewall rule [uuid]" "formatEN": "reorder firewall rule [instanceKey]"
}, },
"/hosts/firewall/rules/reset": { "/hosts/firewall/rules/reset": {
"bodyKeys": [], "bodyKeys": [],
@@ -3746,25 +3754,23 @@
"formatZH": "重置防火墙规则", "formatZH": "重置防火墙规则",
"formatEN": "reset firewall rules" "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": { "/hosts/firewall/rules/update": {
"bodyKeys": [ "bodyKeys": [
"uuid" "instanceKey"
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "更新防火墙规则 [uuid]", "formatZH": "更新防火墙规则 [instanceKey]",
"formatEN": "update firewall rule [uuid]" "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": { "/hosts/firewall/settings/operate": {
"bodyKeys": [ "bodyKeys": [
@@ -3882,8 +3888,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "修改 SSH 配置 [key] =\u003e [newValue]", "formatZH": "修改 SSH 配置 [key] => [newValue]",
"formatEN": "update SSH setting [key] =\u003e [newValue]" "formatEN": "update SSH setting [key] => [newValue]"
}, },
"/hosts/tool/config/set": { "/hosts/tool/config/set": {
"bodyKeys": [ "bodyKeys": [
@@ -4028,12 +4034,21 @@
}, },
"/runtimes/operate": { "/runtimes/operate": {
"bodyKeys": [ "bodyKeys": [
"id" "ID"
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [
"formatZH": "操作运行环境 [id]", {
"formatEN": "Operate runtime [id]" "input_column": "id",
"input_value": "ID",
"isList": false,
"db": "runtimes",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "操作运行环境 [name]",
"formatEN": "Operate runtime [name]"
}, },
"/runtimes/php/config": { "/runtimes/php/config": {
"bodyKeys": [ "bodyKeys": [
@@ -4135,7 +4150,7 @@
} }
], ],
"formatZH": "快照 [name] 描述信息修改 [description]", "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": { "/settings/snapshot/import": {
"bodyKeys": [ "bodyKeys": [
@@ -4227,8 +4242,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "修改系统配置 [key] =\u003e [value]", "formatZH": "修改系统配置 [key] => [value]",
"formatEN": "update system setting [key] =\u003e [value]" "formatEN": "update system setting [key] => [value]"
}, },
"/toolbox/clam": { "/toolbox/clam": {
"bodyKeys": [ "bodyKeys": [
@@ -4346,8 +4361,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "修改主机参数 [key] =\u003e [value]", "formatZH": "修改主机参数 [key] => [value]",
"formatEN": "update device conf [key] =\u003e [value]" "formatEN": "update device conf [key] => [value]"
}, },
"/toolbox/device/update/host": { "/toolbox/device/update/host": {
"bodyKeys": [ "bodyKeys": [
@@ -4356,8 +4371,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "修改主机 Host [key] =\u003e [value]", "formatZH": "修改主机 Host [key] => [value]",
"formatEN": "update device host [key] =\u003e [value]" "formatEN": "update device host [key] => [value]"
}, },
"/toolbox/device/update/swap": { "/toolbox/device/update/swap": {
"bodyKeys": [ "bodyKeys": [
@@ -4385,8 +4400,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "修改 Fail2ban 配置 [key] =\u003e [value]", "formatZH": "修改 Fail2ban 配置 [key] => [value]",
"formatEN": "update fail2ban conf [key] =\u003e [value]" "formatEN": "update fail2ban conf [key] => [value]"
}, },
"/toolbox/ftp": { "/toolbox/ftp": {
"bodyKeys": [ "bodyKeys": [
@@ -4645,8 +4660,8 @@
"output_value": "domain" "output_value": "domain"
} }
], ],
"formatZH": "修改默认 server =\u003e [domain]", "formatZH": "修改默认 server => [domain]",
"formatEN": "Change default server =\u003e [domain]" "formatEN": "Change default server => [domain]"
}, },
"/websites/del": { "/websites/del": {
"bodyKeys": [ "bodyKeys": [
@@ -5409,8 +5424,8 @@
], ],
"paramKeys": [], "paramKeys": [],
"beforeFunctions": [], "beforeFunctions": [],
"formatZH": "更新防篡改信息 [website][path] =\u003e [status]", "formatZH": "更新防篡改信息 [website][path] => [status]",
"formatEN": "update tamper info [website][path] =\u003e [status]" "formatEN": "update tamper info [website][path] => [status]"
}, },
"/xpack/vllm/command-template/create": { "/xpack/vllm/command-template/create": {
"bodyKeys": [ "bodyKeys": [
+1 -5
View File
@@ -17,6 +17,7 @@ const (
) )
const ( const (
FirewallIPv6SupportKey = "FirewallIPv6Support"
FirewallSystemBackendKey = "FirewallProvider" FirewallSystemBackendKey = "FirewallProvider"
FirewallForwardingBackendKey = "ForwardingBackend" FirewallForwardingBackendKey = "ForwardingBackend"
FirewallDockerBackendKey = "DockerFirewallBackend" FirewallDockerBackendKey = "DockerFirewallBackend"
@@ -33,12 +34,7 @@ const (
const ( const (
FirewallSystemAcceptedPortSourcePrefix = "accepted-port:" FirewallSystemAcceptedPortSourcePrefix = "accepted-port:"
FirewallRuleOriginCreated = "created"
FirewallRuleOriginAdopted = "adopted"
FirewallRuleSourceUser = "user" FirewallRuleSourceUser = "user"
FirewallRuleSourceImported = "imported" FirewallRuleSourceImported = "imported"
FirewallRuleSourcePanel = "panel"
FirewallRuleSourceSecurity = "security" FirewallRuleSourceSecurity = "security"
FirewallRuleSourceApp = "application"
) )
+4
View File
@@ -1,6 +1,10 @@
package constant package constant
const ( const (
StatusInactive = "Inactive"
StatusUnknown = "Unknown"
StatusDegraded = "Degraded"
StatusInaccessible = "Inaccessible"
StatusRunning = "Running" StatusRunning = "Running"
StatusCanceled = "Canceled" StatusCanceled = "Canceled"
StatusDone = "Done" StatusDone = "Done"
+34
View File
@@ -1,6 +1,7 @@
package cron package cron
import ( import (
"context"
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"math/big" "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 { if _, err := global.Cron.AddJob("@daily", job.NewWebsiteJob()); err != nil {
global.LOG.Errorf("can not add website corn job: %s", err.Error()) 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 { 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()) 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 var cronJobs []model.Cronjob
if err := global.DB.Where("status = ?", constant.StatusEnable).Find(&cronJobs).Error; err != nil { if err := global.DB.Where("status = ?", constant.StatusEnable).Find(&cronJobs).Error; err != nil {
+11 -8
View File
@@ -14,12 +14,13 @@ import (
) )
var ( var (
DB *gorm.DB DB *gorm.DB
MonitorDB *gorm.DB MonitorDB *gorm.DB
GPUMonitorDB *gorm.DB GPUMonitorDB *gorm.DB
TaskDB *gorm.DB VLLMMonitorDB *gorm.DB
CoreDB *gorm.DB TaskDB *gorm.DB
AlertDB *gorm.DB CoreDB *gorm.DB
AlertDB *gorm.DB
LOG *logrus.Logger LOG *logrus.Logger
CONF ServerConfig CONF ServerConfig
@@ -29,8 +30,10 @@ var (
Dir SystemDir Dir SystemDir
Cron *cron.Cron Cron *cron.Cron
MonitorCronID cron.EntryID MonitorCronID cron.EntryID
VLLMMonitorCronID cron.EntryID
GPUMonitorCronID cron.EntryID
IsMaster bool IsMaster bool
+17 -20
View File
@@ -1,14 +1,12 @@
module github.com/1Panel-dev/1Panel/agent module github.com/1Panel-dev/1Panel/agent
go 1.26.1 go 1.26.6
replace github.com/moby/go-archive => github.com/moby/go-archive v0.1.0
replace github.com/go-acme/lego/v5 => github.com/1Panel-dev/lego/v5 v5.3.1 replace github.com/go-acme/lego/v5 => github.com/1Panel-dev/lego/v5 v5.3.1
require ( require (
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible 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/creack/pty v1.1.24
github.com/docker/cli v29.7.2+incompatible github.com/docker/cli v29.7.2+incompatible
github.com/docker/docker v28.5.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/mholt/archiver/v4 v4.0.0-alpha.8
github.com/miekg/dns v1.1.73 github.com/miekg/dns v1.1.73
github.com/minio/minio-go/v7 v7.3.0 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/nicksnyder/go-i18n/v2 v2.6.1
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.1
github.com/oschwald/maxminddb-golang v1.13.1 github.com/oschwald/maxminddb-golang v1.13.1
@@ -52,13 +51,13 @@ require (
github.com/tklauser/go-sysconf v0.4.0 github.com/tklauser/go-sysconf v0.4.0
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19 github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19
github.com/upyun/go-sdk v2.1.0+incompatible github.com/upyun/go-sdk v2.1.0+incompatible
go.mongodb.org/mongo-driver/v2 v2.8.2 go.mongodb.org/mongo-driver/v2 v2.9.0
golang.org/x/crypto v0.55.0 golang.org/x/crypto v0.56.0
golang.org/x/net v0.58.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/sys v0.47.0
golang.org/x/text v0.41.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 google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478
gopkg.in/ini.v1 v1.67.3 gopkg.in/ini.v1 v1.67.3
gopkg.in/yaml.v3 v3.0.1 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/alidns-20150109/v5 v5.5.0 // indirect
github.com/go-acme/esa-20240910/v3 v3.4.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-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-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // 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/gofrs/flock v0.13.0 // indirect
github.com/golang/snappy v1.0.0 // indirect github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-querystring v1.2.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/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.205 // indirect github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.205 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // 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/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // 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/patternmatcher v0.6.1 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/sequential v0.7.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/user v0.4.1 // indirect
github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // 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.mongodb.org/mongo-driver v1.17.9 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // 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/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel v1.45.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 // indirect
go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/otel/metric v1.45.0 // indirect
go.opentelemetry.io/otel/sdk v1.44.0 // indirect go.opentelemetry.io/otel/sdk v1.45.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect
go.opentelemetry.io/otel/trace v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.45.0 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect
go4.org v0.0.0-20260112195520-a5071408f32f // indirect go4.org v0.0.0-20260112195520-a5071408f32f // indirect
golang.org/x/arch v0.26.0 // indirect golang.org/x/arch v0.26.0 // indirect
golang.org/x/oauth2 v0.36.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 google.golang.org/protobuf v1.36.11 // indirect
modernc.org/fileutil v1.4.0 // indirect modernc.org/fileutil v1.4.0 // indirect
modernc.org/libc v1.72.0 // indirect modernc.org/libc v1.72.0 // indirect
+41 -40
View File
@@ -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-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/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/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.15.0 h1:tdQw+eMyT+P6ZIb09JfcIVvbMmIa+PjST7cWezVLf00=
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/go.mod h1:Q1+qtN4vhzEjGrnqRtzx1xa8raDZQlMUe3WJxndYNiQ=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= 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 v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= 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.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 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.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.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 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= 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 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= 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/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 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 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= 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/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 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 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.3.0 h1:nos4BtzzUIqB406BgQnWGMI4qib9BZ8XUHU+ucv/n1c=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= 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 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= 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 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= 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.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= github.com/moby/sys/user v0.4.1 h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= 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 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= 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.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 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= 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.9.0 h1:e2mQdOmbkiYz+dj3faM7lVDwl7WdnRD+g5VicafMhL0=
go.mongodb.org/mongo-driver/v2 v2.8.2/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= 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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 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/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 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= 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.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 h1:QBajQ2SrwQijzHyZbQlPsuIzpl/ll8DY6wPWsajeGcI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0/go.mod h1:08ZQLjrPLQ6R4kAXvuOvODEer5Yh4CoFvll5qB2BCI8=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA=
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o=
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= 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 v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= 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.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.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= 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.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 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.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= 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-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-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/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.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.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.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.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= 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-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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-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-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.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= 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-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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/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-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 h1:aLsVTW0lZ8+IY5u/ERjZSCvAmhuR7slKzyha3YikDNA=
google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478/go.mod h1:YJAzKjfHIUHb9T+bfu8L7mthAp7VVXQBUs1PLdBWS7M= 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-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc=
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= 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-20260630182238-925bb5da69e7 h1:eM/YSd5bBFagF51o1E745Ta7RwzpW0h+z+QDNZOgmQ8= 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-20260630182238-925bb5da69e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= 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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 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.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.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= 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-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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+66 -16
View File
@@ -300,6 +300,9 @@ BuildCache: 'Build Cache'
Volume: 'Volume' Volume: 'Volume'
Network: "Network" Network: "Network"
PruneStart: 'Cleaning in progress' PruneStart: 'Cleaning in progress'
NetworkCleanupConnected: "Skipped network [{{ .name }}] ({{ .id }}): containers connected or network in use; not deleted"
NetworkCleanupSummary: "Network cleanup finished: deleted {{ .deleted }}, skipped {{ .skipped }}, failed {{ .failed }}"
NetworkCleanupPartialFailure: "Some networks could not be cleaned; see the task log"
ContainerBackupPrepare: 'Prepare container backup workspace' ContainerBackupPrepare: 'Prepare container backup workspace'
ContainerBackupStop: 'Stop container before backup' ContainerBackupStop: 'Stop container before backup'
ContainerBackupInspect: 'Backup container inspect and network' ContainerBackupInspect: 'Backup container inspect and network'
@@ -649,6 +652,7 @@ CommonAlert: "Panel {{ .node }}{{ .ip }}: {{ .msg }}. Log in to view details."
NodeExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} nodes are abnormal. Log in to view details." NodeExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} nodes are abnormal. Log in to view details."
LicenseExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} licenses are abnormal. Log in to view details." LicenseExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} licenses are abnormal. Log in to view details."
SSHAndPanelLoginAlert: "Panel {{ .node }}{{ .ip }}: abnormal {{ .name }} login from {{ .loginIp }}. Log in to view details." SSHAndPanelLoginAlert: "Panel {{ .node }}{{ .ip }}: abnormal {{ .name }} login from {{ .loginIp }}. Log in to view details."
CronJobSuccessAlert: "Panel {{ .node }}{{ .ip }}: scheduled task {{ .name }} completed successfully. Log in to view details."
# disk # disk
DeviceNotFound: "Device {{ .name }} not found" DeviceNotFound: "Device {{ .name }} not found"
@@ -682,8 +686,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall # Firewall
ErrDockerIptablesChainUnavailable: 'The Docker DOCKER-USER chain was not found. Restart Docker and try again' 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' 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' ErrDockerForwardPolicyDrop: 'This server restricts network forwarding. Container port protection cannot be enabled at this time.'
ErrUFWRuleAdopt: 'Failed to adopt the UFW rule: {{ .detail }}. If the installed UFW version is earlier than 0.35, upgrade UFW and try again'
Firewall: 'Firewall' Firewall: 'Firewall'
FirewallTaskHost: 'Host firewall' FirewallTaskHost: 'Host firewall'
FirewallTaskForwarding: 'Port forwarding' FirewallTaskForwarding: 'Port forwarding'
@@ -697,25 +700,72 @@ FirewallCreateRulesStep: 'Create firewall rules'
FirewallCreateRulesResult: 'Creation result: {{ .succeeded }} succeeded, {{ .failed }} failed, {{ .skipped }} not executed' FirewallCreateRulesResult: 'Creation result: {{ .succeeded }} succeeded, {{ .failed }} failed, {{ .skipped }} not executed'
FirewallRuleOperationResult: 'Operation result: {{ .succeeded }} succeeded, {{ .failed }} failed' FirewallRuleOperationResult: 'Operation result: {{ .succeeded }} succeeded, {{ .failed }} failed'
FirewallCreateRuleSkipped: 'Not executed' FirewallCreateRuleSkipped: 'Not executed'
FirewallCreateBatchStep: 'Submit {{ .count }} rules as a batch to {{ .backend }}'
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; converted to {{ .count }} rules' 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' FirewallCreateRuleExecutionFailed: 'Rule creation failed; 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'
FirewallInitializeChainsStep: 'Initialize and bind {{ .name }} base chains' FirewallInitializeChainsStep: 'Initialize and bind {{ .name }} base chains'
FirewallRestoreRulesStep: 'Restore database rules to {{ .name }}'
FirewallSyncWhitelistStep: 'Synchronize firewall port whitelist' FirewallSyncWhitelistStep: 'Synchronize firewall port whitelist'
FirewallEnableForwardingStep: 'Enable and bind port forwarding chains' FirewallEnableForwardingStep: 'Enable and bind port forwarding chains'
FirewallRestoreForwardingRulesStep: 'Restore database port forwarding rules' FirewallRestoreForwardingRulesStep: 'Restore database port forwarding rules'
FirewallInspectDockerGuardStep: 'Inspect Docker firewall backend and policies' FirewallInspectDockerGuardStep: 'Inspect Docker firewall backend and policies'
FirewallInitializeDockerGuardStep: 'Initialize and bind {{ .name }} port guard chains' FirewallInitializeDockerGuardStep: 'Initialize and bind {{ .name }} port guard chains'
FirewallPersistDockerGuardStep: 'Persist Docker port guard status' FirewallPersistDockerGuardStep: 'Persist Docker port guard status'
ErrFirewallRuleScopeChange: "The current firewall does not support changing a rule's scope (such as its IPv4/IPv6 address family). Please create a new rule." FileTaskCopy: 'Copy files to {{ .dst }}'
FirewallWhitelistReleased: "{{ .name }}: whitelist protection released; allow rule retained. To close the port, delete the rule manually from the rule list" FileTaskMove: 'Move files to {{ .dst }}'
FirewallWhitelistRequired: "{{ .name }}: protected by mandatory system port rules" 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."
ErrIPv6RARisk: "IPv6 forwarding is blocked to prevent possible network loss. See the documentation and system logs for details."
ErrIPv6RACheckFailed: "IPv6 forwarding is blocked because the network configuration could not be checked. See the system logs for details."
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 }}]"
+66 -16
View File
@@ -300,6 +300,9 @@ BuildCache: 'Caché de compilación'
Volume: 'Volumen de almacenamiento' Volume: 'Volumen de almacenamiento'
Network: 'Red' Network: 'Red'
PruneStart: 'Limpiando en progreso espere...' PruneStart: 'Limpiando en progreso espere...'
NetworkCleanupConnected: "Se omitió la red [{{ .name }}] ({{ .id }}): hay contenedores conectados o la red está en uso; no se eliminó"
NetworkCleanupSummary: "Limpieza de redes finalizada: eliminadas {{ .deleted }}, omitidas {{ .skipped }}, fallidas {{ .failed }}"
NetworkCleanupPartialFailure: "No se pudieron limpiar algunas redes; consulte el registro de la tarea"
ContainerBackupPrepare: 'Preparar espacio de respaldo del contenedor' ContainerBackupPrepare: 'Preparar espacio de respaldo del contenedor'
ContainerBackupStop: 'Detener contenedor antes del respaldo' ContainerBackupStop: 'Detener contenedor antes del respaldo'
ContainerBackupInspect: 'Respaldar inspect y red del contenedor' ContainerBackupInspect: 'Respaldar inspect y red del contenedor'
@@ -649,6 +652,7 @@ CommonAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .msg }}. Inicie sesión en el pa
NodeExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} nodos son anómalos. Inicie sesión en el panel para ver los detalles.' NodeExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} nodos son anómalos. Inicie sesión en el panel para ver los detalles.'
LicenseExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} licencias son anómalas. Inicie sesión en el panel para ver los detalles.' LicenseExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} licencias son anómalas. Inicie sesión en el panel para ver los detalles.'
SSHAndPanelLoginAlert: 'Su Panel {{ .node }}{{ .ip }}, el inicio de sesión {{ .name }} desde {{ .loginIp }} es anómalo. Inicie sesión en el panel para ver los detalles.' SSHAndPanelLoginAlert: 'Su Panel {{ .node }}{{ .ip }}, el inicio de sesión {{ .name }} desde {{ .loginIp }} es anómalo. Inicie sesión en el panel para ver los detalles.'
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: la tarea programada {{ .name }} se completó correctamente. Inicie sesión para ver los detalles.'
# disco # disco
DeviceNotFound: 'Dispositivo {{ .name }} no encontrado' DeviceNotFound: 'Dispositivo {{ .name }} no encontrado'
@@ -682,8 +686,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall # Firewall
ErrDockerIptablesChainUnavailable: 'No se encontró la cadena DOCKER-USER de Docker. Reinicie Docker y vuelva a intentarlo' 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' 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' ErrDockerForwardPolicyDrop: 'Este servidor restringe el reenvío de red. Por ahora no se puede habilitar la protección de puertos de contenedores.'
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'
Firewall: 'Firewall' Firewall: 'Firewall'
FirewallTaskHost: 'Cortafuegos del host' FirewallTaskHost: 'Cortafuegos del host'
FirewallTaskForwarding: 'Reenvío de puertos' FirewallTaskForwarding: 'Reenvío de puertos'
@@ -697,25 +700,72 @@ FirewallCreateRulesStep: 'Crear reglas de cortafuegos'
FirewallCreateRulesResult: 'Resultado de creación: {{ .succeeded }} correctas, {{ .failed }} fallidas, {{ .skipped }} sin ejecutar' FirewallCreateRulesResult: 'Resultado de creación: {{ .succeeded }} correctas, {{ .failed }} fallidas, {{ .skipped }} sin ejecutar'
FirewallRuleOperationResult: 'Resultado de la operación: {{ .succeeded }} correctas, {{ .failed }} fallidas' FirewallRuleOperationResult: 'Resultado de la operación: {{ .succeeded }} correctas, {{ .failed }} fallidas'
FirewallCreateRuleSkipped: 'Sin ejecutar' FirewallCreateRuleSkipped: 'Sin ejecutar'
FirewallCreateBatchStep: 'Enviar {{ .count }} reglas en un lote a {{ .backend }}'
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; convertido en {{ .count }} reglas' 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' FirewallCreateRuleExecutionFailed: 'Error al crear la regla. Los comandos ejecutados no se revirtieron'
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'
FirewallInitializeChainsStep: 'Inicializar y vincular las cadenas base de {{ .name }}' 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' FirewallSyncWhitelistStep: 'Sincronizar la lista de puertos permitidos del firewall'
FirewallEnableForwardingStep: 'Habilitar y vincular las cadenas de reenvío de puertos' 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' 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' 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 }}' FirewallInitializeDockerGuardStep: 'Inicializar y vincular las cadenas de protección de puertos de {{ .name }}'
FirewallPersistDockerGuardStep: 'Guardar el estado de protección de puertos de Docker' FirewallPersistDockerGuardStep: 'Guardar el estado de protección de puertos de Docker'
ErrFirewallRuleScopeChange: "El cortafuegos actual no permite cambiar el ámbito de una regla (como su familia de direcciones IPv4/IPv6). Cree una regla nueva." FileTaskCopy: 'Copiar archivos a {{ .dst }}'
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" FileTaskMove: 'Mover archivos a {{ .dst }}'
FirewallWhitelistRequired: "{{ .name }}: protegido por las reglas de puertos obligatorios del sistema" 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."
ErrIPv6RARisk: "El reenvío IPv6 está bloqueado para evitar una posible pérdida de conectividad. Consulte la documentación y los registros del sistema."
ErrIPv6RACheckFailed: "El reenvío IPv6 está bloqueado porque no se pudo comprobar la configuración de red. Consulte los registros del sistema."
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 }}]"
+67 -16
View File
@@ -300,6 +300,9 @@ BuildCache: 'کش ساخت'
Volume: 'حجم' Volume: 'حجم'
Network: "شبکه" Network: "شبکه"
PruneStart: 'پاک‌سازی در حال انجام است' PruneStart: 'پاک‌سازی در حال انجام است'
NetworkCleanupConnected: "از شبکه [{{ .name }}] ({{ .id }}) صرف‌نظر شد: کانتینر متصل دارد یا در حال استفاده است؛ حذف نشد"
NetworkCleanupSummary: "پاک‌سازی شبکه‌ها پایان یافت: حذف‌شده {{ .deleted }}، ردشده {{ .skipped }}، ناموفق {{ .failed }}"
NetworkCleanupPartialFailure: "پاک‌سازی برخی شبکه‌ها ناموفق بود؛ گزارش وظیفه را بررسی کنید"
ContainerBackupPrepare: 'آماده‌سازی فضای کاری پشتیبان کانتینر' ContainerBackupPrepare: 'آماده‌سازی فضای کاری پشتیبان کانتینر'
ContainerBackupStop: 'توقف کانتینر قبل از پشتیبان‌گیری' ContainerBackupStop: 'توقف کانتینر قبل از پشتیبان‌گیری'
ContainerBackupInspect: 'بررسی و شبکه کانتینر پشتیبان' ContainerBackupInspect: 'بررسی و شبکه کانتینر پشتیبان'
@@ -649,6 +652,8 @@ CommonAlert: "پنل {{ .node }}{{ .ip }}: {{ .msg }}. برای مشاهده ج
NodeExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} گره غیرعادی هستند. برای مشاهده جزئیات وارد شوید." NodeExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} گره غیرعادی هستند. برای مشاهده جزئیات وارد شوید."
LicenseExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} مجوز غیرعادی است. برای مشاهده جزئیات وارد شوید." LicenseExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} مجوز غیرعادی است. برای مشاهده جزئیات وارد شوید."
SSHAndPanelLoginAlert: "پنل {{ .node }}{{ .ip }}: ورود غیرعادی {{ .name }} از {{ .loginIp }}. برای مشاهده جزئیات وارد شوید." SSHAndPanelLoginAlert: "پنل {{ .node }}{{ .ip }}: ورود غیرعادی {{ .name }} از {{ .loginIp }}. برای مشاهده جزئیات وارد شوید."
CronJobSuccessAlert: "پنل {{ .node }}{{ .ip }}: وظیفه زمان‌بندی‌شده {{ .name }} با موفقیت تکمیل شد. برای مشاهده جزئیات وارد شوید."
# دیسک # دیسک
DeviceNotFound: "دستگاه {{ .name }} یافت نشد" DeviceNotFound: "دستگاه {{ .name }} یافت نشد"
@@ -682,8 +687,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall # Firewall
ErrDockerIptablesChainUnavailable: 'زنجیره DOCKER-USER داکر یافت نشد. داکر را راه‌اندازی مجدد کنید و دوباره تلاش کنید' ErrDockerIptablesChainUnavailable: 'زنجیره DOCKER-USER داکر یافت نشد. داکر را راه‌اندازی مجدد کنید و دوباره تلاش کنید'
ErrDockerNftablesChainUnavailable: 'زنجیره فایروال IPv4 مربوط به nftables داکر یافت نشد. بررسی کنید نسخه فعلی داکر از بک‌اند فایروال nftables پشتیبانی کند، سپس داکر را راه‌اندازی مجدد کرده و دوباره تلاش کنید' ErrDockerNftablesChainUnavailable: 'زنجیره فایروال IPv4 مربوط به nftables داکر یافت نشد. بررسی کنید نسخه فعلی داکر از بک‌اند فایروال nftables پشتیبانی کند، سپس داکر را راه‌اندازی مجدد کرده و دوباره تلاش کنید'
ErrDockerForwardPolicyDrop: 'سیاست پیش‌فرض FORWARD برای {{ .family }} برابر DROP است. آن را تغییر دهید و دوباره تلاش کنید' ErrDockerForwardPolicyDrop: 'این سرور هدایت ترافیک شبکه را محدود کرده است. در حال حاضر امکان فعال‌سازی محافظت از پورت کانتینرها وجود ندارد.'
ErrUFWRuleAdopt: 'مدیریت قانون UFW ناموفق بود: {{ .detail }}. اگر نسخه فعلی UFW قدیمی‌تر از 0.35 است، ابتدا UFW را ارتقا دهید و دوباره تلاش کنید'
Firewall: 'فایروال' Firewall: 'فایروال'
FirewallTaskHost: 'دیواره آتش میزبان' FirewallTaskHost: 'دیواره آتش میزبان'
FirewallTaskForwarding: 'هدایت پورت' FirewallTaskForwarding: 'هدایت پورت'
@@ -697,25 +701,72 @@ FirewallCreateRulesStep: 'ایجاد قوانین دیوار آتش'
FirewallCreateRulesResult: 'نتیجه ایجاد: {{ .succeeded }} موفق، {{ .failed }} ناموفق، {{ .skipped }} اجرا نشده' FirewallCreateRulesResult: 'نتیجه ایجاد: {{ .succeeded }} موفق، {{ .failed }} ناموفق، {{ .skipped }} اجرا نشده'
FirewallRuleOperationResult: 'نتیجه عملیات: {{ .succeeded }} موفق، {{ .failed }} ناموفق' FirewallRuleOperationResult: 'نتیجه عملیات: {{ .succeeded }} موفق، {{ .failed }} ناموفق'
FirewallCreateRuleSkipped: 'اجرا نشده' FirewallCreateRuleSkipped: 'اجرا نشده'
FirewallCreateBatchStep: 'ارسال {{ .count }} قانون به صورت دسته‌ای به {{ .backend }}'
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}؛ به {{ .count }} قانون تبدیل شد' FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}؛ به {{ .count }} قانون تبدیل شد'
FirewallCreateRuleExecutionFailed: 'ایجاد قانون ناموفق بود. هیچ رکوردی در پایگاه داده ذخیره نشد و دستورات اجراشده بازگردانی نشدند' FirewallCreateRuleExecutionFailed: 'ایجاد قانون ناموفق بود. دستورات اجراشده بازگردانی نشدند'
FirewallCreateRulePersistenceFailed: 'قانون ایجاد شد، اما ذخیره رکورد مدیریتی آن ناموفق بود'
FirewallAdoptRulePersistenceFailed: 'دستور پذیرش قانون برای مدیریت اجرا شد، اما اطلاعات مدیریت ذخیره نشد'
FirewallSyncOperationsResult: 'عملیات همگام‌سازی: {{ .removed }} حذف‌شده، {{ .created }} ایجادشده، {{ .failed }} ناموفق، {{ .skipped }} اجرا‌نشده، {{ .unchanged }} از قبل مطابق (بدون نیاز به تغییر)'
FirewallSyncRuleUnchanged: 'از قبل مطابق است؛ نیازی به تغییر نیست'
FirewallSyncStep: 'همگام‌سازی قوانین با {{ .name }}'
FirewallSyncFailed: 'همگام‌سازی {{ .failed }} قانون فایروال ناموفق بود'
FirewallResetSourceStep: 'بازنشانی و غیرفعال‌کردن فایروال مبدأ {{ .name }}'
FirewallResetSourceResult: 'فایروال مبدأ بازنشانی شد و {{ .removed }} قانون پایگاه داده حذف شد'
FirewallInitializeChainsStep: 'راه‌اندازی و اتصال زنجیره‌های پایه {{ .name }}' FirewallInitializeChainsStep: 'راه‌اندازی و اتصال زنجیره‌های پایه {{ .name }}'
FirewallRestoreRulesStep: 'بازیابی قوانین پایگاه داده در {{ .name }}'
FirewallSyncWhitelistStep: 'همگام‌سازی فهرست مجاز پورت‌های فایروال' FirewallSyncWhitelistStep: 'همگام‌سازی فهرست مجاز پورت‌های فایروال'
FirewallEnableForwardingStep: 'فعال‌سازی و اتصال زنجیره‌های هدایت پورت' FirewallEnableForwardingStep: 'فعال‌سازی و اتصال زنجیره‌های هدایت پورت'
FirewallRestoreForwardingRulesStep: 'بازیابی قوانین هدایت پورت پایگاه داده' FirewallRestoreForwardingRulesStep: 'بازیابی قوانین هدایت پورت پایگاه داده'
FirewallInspectDockerGuardStep: 'بررسی پشتیبان فایروال Docker و سیاست‌ها' FirewallInspectDockerGuardStep: 'بررسی پشتیبان فایروال Docker و سیاست‌ها'
FirewallInitializeDockerGuardStep: 'راه‌اندازی و اتصال زنجیره‌های محافظت پورت {{ .name }}' FirewallInitializeDockerGuardStep: 'راه‌اندازی و اتصال زنجیره‌های محافظت پورت {{ .name }}'
FirewallPersistDockerGuardStep: 'ذخیره وضعیت محافظت پورت Docker' FirewallPersistDockerGuardStep: 'ذخیره وضعیت محافظت پورت Docker'
ErrFirewallRuleScopeChange: "فایروال فعلی از تغییر محدودهٔ قانون (مانند خانوادهٔ آدرس IPv4/IPv6) پشتیبانی نمی‌کند. لطفاً یک قانون جدید ایجاد کنید." FileTaskCopy: 'کپی فایل‌ها به {{ .dst }}'
FirewallWhitelistReleased: "{{ .name }}: حفاظت فهرست مجاز برداشته شد؛ قانون اجازه حفظ می‌شود. برای بستن پورت، قانون را به‌صورت دستی از فهرست قوانین حذف کنید" FileTaskMove: 'انتقال فایل‌ها به {{ .dst }}'
FirewallWhitelistRequired: "{{ .name }}: توسط قوانین پورت‌های ضروری سیستم محافظت می‌شود" 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 }} مشخص‌شده در قانون انتقال وجود ندارد. نام رابط را بررسی کرده و دوباره تلاش کنید."
ErrIPv6RARisk: "فعال‌سازی هدایت IPv6 به دلیل خطر قطع شبکه مسدود شد. مستندات و گزارش‌های سیستم را بررسی کنید."
ErrIPv6RACheckFailed: "فعال‌سازی هدایت IPv6 به دلیل عدم امکان بررسی پیکربندی شبکه مسدود شد. جزئیات در گزارش‌های سیستم موجود است."
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 }}]"
+66 -16
View File
@@ -300,6 +300,9 @@ BuildCache: 'ビルドキャッシュ'
Volume: 'ストレージボリューム' Volume: 'ストレージボリューム'
Network: 'ネットワーク' Network: 'ネットワーク'
PruneStart: 'クリーンアップ中、しばらくお待ちください...' PruneStart: 'クリーンアップ中、しばらくお待ちください...'
NetworkCleanupConnected: "ネットワーク [{{ .name }}] ({{ .id }}) をスキップしました:コンテナが接続されているか使用中のため、削除しませんでした"
NetworkCleanupSummary: "ネットワークのクリーンアップが完了しました:削除 {{ .deleted }} 件、スキップ {{ .skipped }} 件、失敗 {{ .failed }} 件"
NetworkCleanupPartialFailure: "一部のネットワークをクリーンアップできませんでした。タスクログを確認してください"
ContainerBackupPrepare: 'コンテナバックアップ作業領域を準備' ContainerBackupPrepare: 'コンテナバックアップ作業領域を準備'
ContainerBackupStop: 'バックアップ前にコンテナを停止' ContainerBackupStop: 'バックアップ前にコンテナを停止'
ContainerBackupInspect: 'コンテナ inspect とネットワーク情報をバックアップ' ContainerBackupInspect: 'コンテナ inspect とネットワーク情報をバックアップ'
@@ -649,6 +652,7 @@ CommonAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .msg }}。詳細
NodeExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のノードに異常が発生しています。詳細はパネルにログインして' NodeExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のノードに異常が発生しています。詳細はパネルにログインして'
LicenseExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のライセンスに異常が発生しています。詳細はパネルにログインして' LicenseExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のライセンスに異常が発生しています。詳細はパネルにログインして'
SSHAndPanelLoginAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .loginIp }} からの {{ .name }} ログインに異常があります。詳細はパネルにログインして' SSHAndPanelLoginAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .loginIp }} からの {{ .name }} ログインに異常があります。詳細はパネルにログインして'
CronJobSuccessAlert: 'パネル {{ .node }}{{ .ip }}: スケジュールタスク {{ .name }} が正常に完了しました。詳細はパネルにログインして確認してください。'
# ディスク # ディスク
DeviceNotFound: 'デバイス {{ .name }} が見つかりません' DeviceNotFound: 'デバイス {{ .name }} が見つかりません'
@@ -682,8 +686,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall # Firewall
ErrDockerIptablesChainUnavailable: 'Docker の DOCKER-USER チェーンが見つかりません。Docker を再起動してから再試行してください' ErrDockerIptablesChainUnavailable: 'Docker の DOCKER-USER チェーンが見つかりません。Docker を再起動してから再試行してください'
ErrDockerNftablesChainUnavailable: 'Docker の nftables IPv4 ファイアウォールチェーンが見つかりません。現在の Docker バージョンが nftables ファイアウォールバックエンドをサポートしていることを確認し、Docker を再起動してから再試行してください' ErrDockerNftablesChainUnavailable: 'Docker の nftables IPv4 ファイアウォールチェーンが見つかりません。現在の Docker バージョンが nftables ファイアウォールバックエンドをサポートしていることを確認し、Docker を再起動してから再試行してください'
ErrDockerForwardPolicyDrop: '{{ .family }} FORWARD の既定ポリシーが DROP です。変更してから再試行してください' ErrDockerForwardPolicyDrop: 'このサーバーではネットワーク転送が制限されているため、現在コンテナのポート保護を有効にできません。'
ErrUFWRuleAdopt: 'UFW ルールの管理に失敗しました:{{ .detail }}。現在の UFW バージョンが 0.35 未満の場合は、UFW をアップグレードしてから再試行してください'
Firewall: 'ファイアウォール' Firewall: 'ファイアウォール'
FirewallTaskHost: 'ホストファイアウォール' FirewallTaskHost: 'ホストファイアウォール'
FirewallTaskForwarding: 'ポート転送' FirewallTaskForwarding: 'ポート転送'
@@ -697,25 +700,72 @@ FirewallCreateRulesStep: 'ファイアウォールルールを作成'
FirewallCreateRulesResult: '作成結果:成功 {{ .succeeded }} 件、失敗 {{ .failed }} 件、未実行 {{ .skipped }} 件' FirewallCreateRulesResult: '作成結果:成功 {{ .succeeded }} 件、失敗 {{ .failed }} 件、未実行 {{ .skipped }} 件'
FirewallRuleOperationResult: '操作結果:成功 {{ .succeeded }} 件、失敗 {{ .failed }} 件' FirewallRuleOperationResult: '操作結果:成功 {{ .succeeded }} 件、失敗 {{ .failed }} 件'
FirewallCreateRuleSkipped: '未実行' FirewallCreateRuleSkipped: '未実行'
FirewallCreateBatchStep: '{{ .backend }} に {{ .count }} 件のルールを一括送信'
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }};{{ .count }} 件のルールに変換' FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }};{{ .count }} 件のルールに変換'
FirewallCreateRuleExecutionFailed: 'ルールの作成に失敗しました。データベースには保存せず、実行済みのコマンドはロールバックしません' FirewallCreateRuleExecutionFailed: 'ルールの作成に失敗しました。実行済みのコマンドはロールバックしません'
FirewallCreateRulePersistenceFailed: 'ルールは作成されましたが、管理情報の保存に失敗しました'
FirewallAdoptRulePersistenceFailed: 'ルールの管理対象への取り込みコマンドは実行されましたが、管理情報の保存に失敗しました'
FirewallSyncOperationsResult: '同期操作:削除成功 {{ .removed }} 件、作成成功 {{ .created }} 件、失敗 {{ .failed }} 件、未実行 {{ .skipped }} 件、一致済みで変更不要 {{ .unchanged }} 件'
FirewallSyncRuleUnchanged: '一致済み、変更不要'
FirewallSyncStep: '{{ .name }} にルールを同期'
FirewallSyncFailed: '{{ .failed }} 件のファイアウォールルールを同期できませんでした'
FirewallResetSourceStep: '移行元ファイアウォール {{ .name }} をリセットして無効化'
FirewallResetSourceResult: '移行元ファイアウォールをリセットし、データベースルール {{ .removed }} 件を削除しました'
FirewallInitializeChainsStep: '{{ .name }} のベースチェーンを初期化してバインド' FirewallInitializeChainsStep: '{{ .name }} のベースチェーンを初期化してバインド'
FirewallRestoreRulesStep: 'データベースルールを {{ .name }} に復元'
FirewallSyncWhitelistStep: 'ファイアウォールポート許可リストを同期' FirewallSyncWhitelistStep: 'ファイアウォールポート許可リストを同期'
FirewallEnableForwardingStep: 'ポート転送チェーンを有効化してバインド' FirewallEnableForwardingStep: 'ポート転送チェーンを有効化してバインド'
FirewallRestoreForwardingRulesStep: 'データベースのポート転送ルールを復元' FirewallRestoreForwardingRulesStep: 'データベースのポート転送ルールを復元'
FirewallInspectDockerGuardStep: 'Docker ファイアウォールバックエンドと保護ポリシーを確認' FirewallInspectDockerGuardStep: 'Docker ファイアウォールバックエンドと保護ポリシーを確認'
FirewallInitializeDockerGuardStep: '{{ .name }} のポート保護チェーンを初期化してバインド' FirewallInitializeDockerGuardStep: '{{ .name }} のポート保護チェーンを初期化してバインド'
FirewallPersistDockerGuardStep: 'Docker ポート保護状態を保存' FirewallPersistDockerGuardStep: 'Docker ポート保護状態を保存'
ErrFirewallRuleScopeChange: "現在のファイアウォールでは、ルールの適用範囲(IPv4/IPv6 アドレスファミリーなど)を変更できません。新しいルールを作成してください。" FileTaskCopy: 'ファイルを {{ .dst }} にコピー'
FirewallWhitelistReleased: "{{ .name }}:許可リストの保護を解除しました。許可ルールは保持されます。ポートを閉じるには、ルール一覧から手動で削除してください" FileTaskMove: 'ファイルを {{ .dst }} に移動'
FirewallWhitelistRequired: "{{ .name }}:システム必須ポートのルールで保護されています" 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 }} は存在しません。名前を確認して再試行してください。"
ErrIPv6RARisk: "ネットワーク切断のおそれがあるため、IPv6 転送の有効化をブロックしました。ドキュメントとシステムログをご確認ください。"
ErrIPv6RACheckFailed: "ネットワーク設定を確認できないため、IPv6 転送の有効化をブロックしました。詳細はシステムログをご確認ください。"
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 }}] を削除"
+66 -16
View File
@@ -300,6 +300,9 @@ BuildCache: '빌드 캐시'
Volume: '스토리지 볼륨' Volume: '스토리지 볼륨'
Network: '네트워크' Network: '네트워크'
PruneStart: '정리 중입니다. 잠시만 기다려주세요...' PruneStart: '정리 중입니다. 잠시만 기다려주세요...'
NetworkCleanupConnected: "네트워크 [{{ .name }}] ({{ .id }}) 건너뜀: 연결된 컨테이너가 있거나 사용 중이므로 삭제하지 않음"
NetworkCleanupSummary: "네트워크 정리 완료: 삭제 {{ .deleted }}개, 건너뜀 {{ .skipped }}개, 실패 {{ .failed }}개"
NetworkCleanupPartialFailure: "일부 네트워크를 정리하지 못했습니다. 작업 로그를 확인하세요"
ContainerBackupPrepare: '컨테이너 백업 작업 공간 준비' ContainerBackupPrepare: '컨테이너 백업 작업 공간 준비'
ContainerBackupStop: '백업 전에 컨테이너 중지' ContainerBackupStop: '백업 전에 컨테이너 중지'
ContainerBackupInspect: '컨테이너 inspect 및 네트워크 정보 백업' ContainerBackupInspect: '컨테이너 inspect 및 네트워크 정보 백업'
@@ -649,6 +652,7 @@ CommonAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .msg }}。자세한 내
NodeExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.' NodeExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
LicenseExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 라이센스에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.' LicenseExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 라이센스에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
SSHAndPanelLoginAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .loginIp }}에서의 {{ .name }} 로그인에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.' SSHAndPanelLoginAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .loginIp }}에서의 {{ .name }} 로그인에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
CronJobSuccessAlert: '패널 {{ .node }}{{ .ip }}: 예약 작업 {{ .name }}이(가) 성공적으로 완료되었습니다. 로그인하여 자세한 내용을 확인하십시오.'
# 디스크 # 디스크
DeviceNotFound: '장치 {{ .name }} 을(를) 찾을 수 없습니다' DeviceNotFound: '장치 {{ .name }} 을(를) 찾을 수 없습니다'
@@ -682,8 +686,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall # Firewall
ErrDockerIptablesChainUnavailable: 'Docker DOCKER-USER 체인을 찾을 수 없습니다. Docker를 다시 시작한 후 재시도하세요' ErrDockerIptablesChainUnavailable: 'Docker DOCKER-USER 체인을 찾을 수 없습니다. Docker를 다시 시작한 후 재시도하세요'
ErrDockerNftablesChainUnavailable: 'Docker nftables IPv4 방화벽 체인을 찾을 수 없습니다. 현재 Docker 버전이 nftables 방화벽 백엔드를 지원하는지 확인하고 Docker를 다시 시작한 후 재시도하세요' ErrDockerNftablesChainUnavailable: 'Docker nftables IPv4 방화벽 체인을 찾을 수 없습니다. 현재 Docker 버전이 nftables 방화벽 백엔드를 지원하는지 확인하고 Docker를 다시 시작한 후 재시도하세요'
ErrDockerForwardPolicyDrop: '{{ .family }} FORWARD 기본 정책이 DROP입니다. 정책을 조정한 후 다시 시도하세요' ErrDockerForwardPolicyDrop: '현재 서버에서 네트워크 전달이 제한되어 컨테이너 포트 보호를 활성화할 수 없습니다.'
ErrUFWRuleAdopt: 'UFW 규칙 관리에 실패했습니다: {{ .detail }}. 현재 UFW 버전이 0.35 미만이면 UFW를 업그레이드한 후 다시 시도하세요'
Firewall: '방화벽' Firewall: '방화벽'
FirewallTaskHost: '호스트 방화벽' FirewallTaskHost: '호스트 방화벽'
FirewallTaskForwarding: '포트 포워딩' FirewallTaskForwarding: '포트 포워딩'
@@ -697,25 +700,72 @@ FirewallCreateRulesStep: '방화벽 규칙 생성'
FirewallCreateRulesResult: '생성 결과: 성공 {{ .succeeded }}개, 실패 {{ .failed }}개, 미실행 {{ .skipped }}개' FirewallCreateRulesResult: '생성 결과: 성공 {{ .succeeded }}개, 실패 {{ .failed }}개, 미실행 {{ .skipped }}개'
FirewallRuleOperationResult: '작업 결과: 성공 {{ .succeeded }}개, 실패 {{ .failed }}개' FirewallRuleOperationResult: '작업 결과: 성공 {{ .succeeded }}개, 실패 {{ .failed }}개'
FirewallCreateRuleSkipped: '미실행' FirewallCreateRuleSkipped: '미실행'
FirewallCreateBatchStep: '{{ .backend }}에 규칙 {{ .count }}개 일괄 제출'
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; 규칙 {{ .count }}개로 변환' FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; 규칙 {{ .count }}개로 변환'
FirewallCreateRuleExecutionFailed: '규칙 생성에 실패했습니다. 데이터베이스에 저장하지 않았으며 실행된 명령은 롤백하지 않습니다' FirewallCreateRuleExecutionFailed: '규칙 생성에 실패했습니다. 실행된 명령은 롤백하지 않습니다'
FirewallCreateRulePersistenceFailed: '규칙은 생성되었지만 관리 정보를 저장하지 못했습니다'
FirewallAdoptRulePersistenceFailed: '규칙 관리 등록 명령은 실행되었지만 관리 정보를 저장하지 못했습니다'
FirewallSyncOperationsResult: '동기화 작업: 삭제 성공 {{ .removed }}개, 생성 성공 {{ .created }}개, 실패 {{ .failed }}개, 미실행 {{ .skipped }}개, 이미 일치하여 변경 불필요 {{ .unchanged }}개'
FirewallSyncRuleUnchanged: '이미 일치하여 변경이 필요하지 않음'
FirewallSyncStep: '{{ .name }}에 규칙 동기화'
FirewallSyncFailed: '방화벽 규칙 {{ .failed }}개를 동기화하지 못했습니다'
FirewallResetSourceStep: '원본 방화벽 {{ .name }} 초기화 및 비활성화'
FirewallResetSourceResult: '원본 방화벽을 초기화하고 데이터베이스 규칙 {{ .removed }}개를 삭제했습니다'
FirewallInitializeChainsStep: '{{ .name }} 기본 체인 초기화 및 바인딩' FirewallInitializeChainsStep: '{{ .name }} 기본 체인 초기화 및 바인딩'
FirewallRestoreRulesStep: '데이터베이스 규칙을 {{ .name }}에 복원'
FirewallSyncWhitelistStep: '방화벽 포트 허용 목록 동기화' FirewallSyncWhitelistStep: '방화벽 포트 허용 목록 동기화'
FirewallEnableForwardingStep: '포트 전달 체인 활성화 및 바인딩' FirewallEnableForwardingStep: '포트 전달 체인 활성화 및 바인딩'
FirewallRestoreForwardingRulesStep: '데이터베이스 포트 전달 규칙 복원' FirewallRestoreForwardingRulesStep: '데이터베이스 포트 전달 규칙 복원'
FirewallInspectDockerGuardStep: 'Docker 방화벽 백엔드 및 보호 정책 확인' FirewallInspectDockerGuardStep: 'Docker 방화벽 백엔드 및 보호 정책 확인'
FirewallInitializeDockerGuardStep: '{{ .name }} 포트 보호 체인 초기화 및 바인딩' FirewallInitializeDockerGuardStep: '{{ .name }} 포트 보호 체인 초기화 및 바인딩'
FirewallPersistDockerGuardStep: 'Docker 포트 보호 상태 저장' FirewallPersistDockerGuardStep: 'Docker 포트 보호 상태 저장'
ErrFirewallRuleScopeChange: "현재 방화벽에서는 규칙의 적용 범위(예: IPv4/IPv6 주소 패밀리)를 변경할 수 없습니다. 새 규칙을 생성하세요." FileTaskCopy: '{{ .dst }}에 파일 복사'
FirewallWhitelistReleased: "{{ .name }}: 허용 목록 보호가 해제되었으며 허용 규칙은 유지됩니다. 포트를 닫으려면 규칙 목록에서 수동으로 삭제하세요" FileTaskMove: '{{ .dst }}로 파일 이동'
FirewallWhitelistRequired: "{{ .name }}: 시스템 필수 포트 규칙으로 보호됩니다" 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 }}이 존재하지 않습니다. 이름을 확인한 후 다시 시도하세요."
ErrIPv6RARisk: "네트워크 연결이 끊길 위험이 있어 IPv6 전달 활성화를 차단했습니다. 문서와 시스템 로그를 확인하세요."
ErrIPv6RACheckFailed: "네트워크 구성을 확인할 수 없어 IPv6 전달 활성화를 차단했습니다. 자세한 내용은 시스템 로그를 확인하세요."
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 }}] 삭제"
+66 -16
View File
@@ -290,6 +290,9 @@ BuildCache: 'ແຄດການສ້າງ'
Volume: 'ວໍລຸມ (Volume)' Volume: 'ວໍລຸມ (Volume)'
Network: "ເຄືອຂ່າຍ" Network: "ເຄືອຂ່າຍ"
PruneStart: 'ກຳລັງດຳເນີນການທຳຄວາມສະອາດ' PruneStart: 'ກຳລັງດຳເນີນການທຳຄວາມສະອາດ'
NetworkCleanupConnected: "ຂ້າມເຄືອຂ່າຍ [{{ .name }}] ({{ .id }}): ມີຄອນເທນເນີເຊື່ອມຕໍ່ຢູ່ ຫຼື ເຄືອຂ່າຍກຳລັງຖືກໃຊ້ງານ; ບໍ່ໄດ້ລຶບ"
NetworkCleanupSummary: "ການລ້າງເຄືອຂ່າຍສຳເລັດ: ລຶບແລ້ວ {{ .deleted }}, ຂ້າມ {{ .skipped }}, ລົ້ມເຫຼວ {{ .failed }}"
NetworkCleanupPartialFailure: "ບໍ່ສາມາດລ້າງບາງເຄືອຂ່າຍໄດ້; ກະລຸນາເບິ່ງບັນທຶກວຽກ"
ContainerBackupPrepare: 'ກຽມພື້ນທີ່ເຮັດວຽກສຳລັບສຳຮອງຂໍ້ມູນຄອນເທນເນີ' ContainerBackupPrepare: 'ກຽມພື້ນທີ່ເຮັດວຽກສຳລັບສຳຮອງຂໍ້ມູນຄອນເທນເນີ'
ContainerBackupStop: 'ຢຸດຄອນເທນເນີກ່ອນການສຳຮອງຂໍ້ມູນ' ContainerBackupStop: 'ຢຸດຄອນເທນເນີກ່ອນການສຳຮອງຂໍ້ມູນ'
ContainerBackupInspect: 'ກວດສອບຄອນເທນເນີ ແລະ ເຄືອຂ່າຍສຳລັບການສຳຮອງຂໍ້ມູນ' ContainerBackupInspect: 'ກວດສອບຄອນເທນເນີ ແລະ ເຄືອຂ່າຍສຳລັບການສຳຮອງຂໍ້ມູນ'
@@ -639,6 +642,7 @@ CommonAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: {{ .msg }}. ເ
NodeExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໂນດ {{ .num }} ແຫ່ງຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ." NodeExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໂນດ {{ .num }} ແຫ່ງຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
LicenseExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໃບອະນຸຍາດ {{ .num }} ສະບັບຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ." LicenseExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໃບອະນຸຍາດ {{ .num }} ສະບັບຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
SSHAndPanelLoginAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ມີການເຂົ້າສູ່ລະບົບ {{ .name }} ທີ່ຜິດປົກກະຕິຈາກ {{ .loginIp }}. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ." SSHAndPanelLoginAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ມີການເຂົ້າສູ່ລະບົບ {{ .name }} ທີ່ຜິດປົກກະຕິຈາກ {{ .loginIp }}. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
CronJobSuccessAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ວຽກທີ່ຕັ້ງເວລາ {{ .name }} ສຳເລັດແລ້ວ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
#disk #disk
DeviceNotFound: "ບໍ່ພົບອຸປະກອນ {{ .name }}" DeviceNotFound: "ບໍ່ພົບອຸປະກອນ {{ .name }}"
@@ -673,8 +677,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall # Firewall
ErrDockerIptablesChainUnavailable: 'ບໍ່ພົບ chain DOCKER-USER ຂອງ Docker. ກະລຸນາເລີ່ມ Docker ໃໝ່ແລ້ວລອງອີກຄັ້ງ' ErrDockerIptablesChainUnavailable: 'ບໍ່ພົບ chain DOCKER-USER ຂອງ Docker. ກະລຸນາເລີ່ມ Docker ໃໝ່ແລ້ວລອງອີກຄັ້ງ'
ErrDockerNftablesChainUnavailable: 'ບໍ່ພົບ chain ໄຟວໍ nftables IPv4 ຂອງ Docker. ກະລຸນາກວດສອບວ່າ Docker ລຸ້ນປັດຈຸບັນຮອງຮັບ backend ໄຟວໍ nftables, ເລີ່ມ Docker ໃໝ່ແລ້ວລອງອີກຄັ້ງ' ErrDockerNftablesChainUnavailable: 'ບໍ່ພົບ chain ໄຟວໍ nftables IPv4 ຂອງ Docker. ກະລຸນາກວດສອບວ່າ Docker ລຸ້ນປັດຈຸບັນຮອງຮັບ backend ໄຟວໍ nftables, ເລີ່ມ Docker ໃໝ່ແລ້ວລອງອີກຄັ້ງ'
ErrDockerForwardPolicyDrop: 'ນະໂຍບາຍເລີ່ມຕົ້ນ FORWARD ຂອງ {{ .family }} ແມ່ນ DROP. ກະລຸນາປັບແລ້ວລອງອີກຄັ້ງ' ErrDockerForwardPolicyDrop: 'ເຊີບເວີນີ້ຈຳກັດການສົ່ງຕໍ່ເຄືອຂ່າຍ. ຍັງບໍ່ສາມາດເປີດໃຊ້ການປ້ອງກັນພອດຄອນເທນເນີໄດ້ໃນຕອນນີ້.'
ErrUFWRuleAdopt: 'ຈັດການກົດ UFW ບໍ່ສຳເລັດ: {{ .detail }}. ຖ້າ UFW ລຸ້ນປັດຈຸບັນຕ່ຳກວ່າ 0.35, ກະລຸນາອັບເກຣດ UFW ແລ້ວລອງໃໝ່'
Firewall: 'ໄຟວອລ' Firewall: 'ໄຟວອລ'
FirewallTaskHost: 'ໄຟວໍໂຮສ' FirewallTaskHost: 'ໄຟວໍໂຮສ'
FirewallTaskForwarding: 'ການສົ່ງຕໍ່ພອດ' FirewallTaskForwarding: 'ການສົ່ງຕໍ່ພອດ'
@@ -688,25 +691,72 @@ FirewallCreateRulesStep: 'ສ້າງກົດໄຟວໍ'
FirewallCreateRulesResult: 'ຜົນການສ້າງ: ສຳເລັດ {{ .succeeded }}, ລົ້ມເຫຼວ {{ .failed }}, ບໍ່ໄດ້ດຳເນີນການ {{ .skipped }}' FirewallCreateRulesResult: 'ຜົນການສ້າງ: ສຳເລັດ {{ .succeeded }}, ລົ້ມເຫຼວ {{ .failed }}, ບໍ່ໄດ້ດຳເນີນການ {{ .skipped }}'
FirewallRuleOperationResult: 'ຜົນການດຳເນີນການ: ສຳເລັດ {{ .succeeded }} ລາຍການ, ລົ້ມເຫຼວ {{ .failed }} ລາຍການ' FirewallRuleOperationResult: 'ຜົນການດຳເນີນການ: ສຳເລັດ {{ .succeeded }} ລາຍການ, ລົ້ມເຫຼວ {{ .failed }} ລາຍການ'
FirewallCreateRuleSkipped: 'ບໍ່ໄດ້ດຳເນີນການ' FirewallCreateRuleSkipped: 'ບໍ່ໄດ້ດຳເນີນການ'
FirewallCreateBatchStep: 'ສົ່ງ {{ .count }} ກົດເປັນຊຸດໄປຫາ {{ .backend }}'
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; ແປງເປັນ {{ .count }} ກົດ' FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; ແປງເປັນ {{ .count }} ກົດ'
FirewallCreateRuleExecutionFailed: 'ການສ້າງກົດລົ້ມເຫຼວ. ບໍ່ໄດ້ບັນທຶກໃນຖານຂໍ້ມູນ ແລະ ບໍ່ໄດ້ຍ້ອນກັບຄຳສັ່ງທີ່ດຳເນີນການແລ້ວ' FirewallCreateRuleExecutionFailed: 'ການສ້າງກົດລົ້ມເຫຼວ. ຄຳສັ່ງທີ່ດຳເນີນການແລ້ວບໍ່ໄດ້ຖືກຍ້ອນກັບ'
FirewallCreateRulePersistenceFailed: 'ສ້າງກົດແລ້ວ ແຕ່ບັນທຶກຂໍ້ມູນການຈັດການບໍ່ສຳເລັດ'
FirewallAdoptRulePersistenceFailed: 'ຄຳສັ່ງນຳກົດເຂົ້າການຈັດການໄດ້ດຳເນີນການແລ້ວ ແຕ່ບໍ່ສາມາດບັນທຶກຂໍ້ມູນການຈັດການໄດ້'
FirewallSyncOperationsResult: 'ການດຳເນີນການຊິງຄ໌: ລຶບ {{ .removed }}, ສ້າງ {{ .created }}, ລົ້ມເຫຼວ {{ .failed }}, ບໍ່ໄດ້ດຳເນີນການ {{ .skipped }}, ກົງກັນແລ້ວ {{ .unchanged }} (ບໍ່ຕ້ອງປ່ຽນແປງ)'
FirewallSyncRuleUnchanged: 'ກົງກັນແລ້ວ; ບໍ່ຕ້ອງປ່ຽນແປງ'
FirewallSyncStep: 'ຊິງຄ໌ກົດໄປຫາ {{ .name }}'
FirewallSyncFailed: 'ຊິງຄ໌ກົດໄຟວອລ {{ .failed }} ລາຍການບໍ່ສຳເລັດ'
FirewallResetSourceStep: 'ຣີເຊັດ ແລະ ປິດໃຊ້ໄຟວອລຕົ້ນທາງ {{ .name }}'
FirewallResetSourceResult: 'ຣີເຊັດໄຟວອລຕົ້ນທາງແລ້ວ ແລະ ລຶບກົດຖານຂໍ້ມູນ {{ .removed }} ລາຍການ'
FirewallInitializeChainsStep: 'ເລີ່ມຕົ້ນ ແລະ ຜູກ chain ພື້ນຖານ {{ .name }}' FirewallInitializeChainsStep: 'ເລີ່ມຕົ້ນ ແລະ ຜູກ chain ພື້ນຖານ {{ .name }}'
FirewallRestoreRulesStep: 'ກູ້ຄືນກົດຖານຂໍ້ມູນໄປຫາ {{ .name }}'
FirewallSyncWhitelistStep: 'ຊິງຄ໌ລາຍການພອດໄຟວໍທີ່ອະນຸຍາດ' FirewallSyncWhitelistStep: 'ຊິງຄ໌ລາຍການພອດໄຟວໍທີ່ອະນຸຍາດ'
FirewallEnableForwardingStep: 'ເປີດໃຊ້ ແລະ ຜູກ chain ສົ່ງຕໍ່ພອດ' FirewallEnableForwardingStep: 'ເປີດໃຊ້ ແລະ ຜູກ chain ສົ່ງຕໍ່ພອດ'
FirewallRestoreForwardingRulesStep: 'ກູ້ຄືນກົດສົ່ງຕໍ່ພອດຈາກຖານຂໍ້ມູນ' FirewallRestoreForwardingRulesStep: 'ກູ້ຄືນກົດສົ່ງຕໍ່ພອດຈາກຖານຂໍ້ມູນ'
FirewallInspectDockerGuardStep: 'ກວດສອບ backend firewall Docker ແລະ ນະໂຍບາຍ' FirewallInspectDockerGuardStep: 'ກວດສອບ backend firewall Docker ແລະ ນະໂຍບາຍ'
FirewallInitializeDockerGuardStep: 'ເລີ່ມຕົ້ນ ແລະ ຜູກ chain ປ້ອງກັນພອດ {{ .name }}' FirewallInitializeDockerGuardStep: 'ເລີ່ມຕົ້ນ ແລະ ຜູກ chain ປ້ອງກັນພອດ {{ .name }}'
FirewallPersistDockerGuardStep: 'ບັນທຶກສະຖານະປ້ອງກັນພອດ Docker' FirewallPersistDockerGuardStep: 'ບັນທຶກສະຖານະປ້ອງກັນພອດ Docker'
ErrFirewallRuleScopeChange: "ໄຟວໍປັດຈຸບັນບໍ່ຮອງຮັບການປ່ຽນຂອບເຂດຂອງກົດ (ເຊັ່ນ ຕະກູນທີ່ຢູ່ IPv4/IPv6). ກະລຸນາສ້າງກົດໃໝ່." FileTaskCopy: 'ສຳເນົາໄຟລ໌ໄປທີ່ {{ .dst }}'
FirewallWhitelistReleased: "{{ .name }}: ຍົກເລີກການປ້ອງກັນລາຍຊື່ທີ່ອະນຸຍາດແລ້ວ; ຍັງຄົງກົດອະນຸຍາດໄວ້. ຫາກຕ້ອງການປິດພອດ ໃຫ້ລຶບກົດດ້ວຍຕົນເອງຈາກລາຍການກົດ" FileTaskMove: 'ຍ້າຍໄຟລ໌ໄປທີ່ {{ .dst }}'
FirewallWhitelistRequired: "{{ .name }}: ປ້ອງກັນໂດຍກົດພອດທີ່ຈຳເປັນຂອງລະບົບ" 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 }} ທີ່ລະບຸໃນກົດສົ່ງຕໍ່. ກະລຸນາກວດສອບຊື່ ແລ້ວລອງໃໝ່."
ErrIPv6RARisk: "ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກເພື່ອປ້ອງກັນຄວາມສ່ຽງທີ່ເຄືອຂ່າຍຈະຂາດການເຊື່ອມຕໍ່. ເບິ່ງເອກະສານ ແລະ ບັນທຶກລະບົບ."
ErrIPv6RACheckFailed: "ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກເນື່ອງຈາກບໍ່ສາມາດກວດສອບການຕັ້ງຄ່າເຄືອຂ່າຍໄດ້. ເບິ່ງລາຍລະອຽດໃນບັນທຶກລະບົບ."
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 }}]"
+66 -16
View File
@@ -300,6 +300,9 @@ BuildCache: 'Cache binaan'
Volume: 'Jilid storan' Volume: 'Jilid storan'
Network: 'Rangkaian' Network: 'Rangkaian'
PruneStart: 'Pembersihan sedang dijalankan, sila tunggu...' PruneStart: 'Pembersihan sedang dijalankan, sila tunggu...'
NetworkCleanupConnected: "Rangkaian [{{ .name }}] ({{ .id }}) dilangkau: terdapat kontena yang disambungkan atau rangkaian sedang digunakan; tidak dipadamkan"
NetworkCleanupSummary: "Pembersihan rangkaian selesai: dipadamkan {{ .deleted }}, dilangkau {{ .skipped }}, gagal {{ .failed }}"
NetworkCleanupPartialFailure: "Sesetengah rangkaian tidak dapat dibersihkan; semak log tugas"
ContainerBackupPrepare: 'Sediakan ruang kerja sandaran kontena' ContainerBackupPrepare: 'Sediakan ruang kerja sandaran kontena'
ContainerBackupStop: 'Hentikan kontena sebelum sandaran' ContainerBackupStop: 'Hentikan kontena sebelum sandaran'
ContainerBackupInspect: 'Sandarkan inspect dan rangkaian kontena' ContainerBackupInspect: 'Sandarkan inspect dan rangkaian kontena'
@@ -649,6 +652,7 @@ CommonAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .msg }}. Sila log masuk ke pan
NodeExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} nod tidak normal. Sila log masuk ke panel untuk butiran lanjut.' NodeExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} nod tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
LicenseExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} lesen tidak normal. Sila log masuk ke panel untuk butiran lanjut.' LicenseExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} lesen tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
SSHAndPanelLoginAlert: 'Panel {{ .node }}{{ .ip }} Anda, log masuk {{ .name }} dari {{ .loginIp }} tidak normal. Sila log masuk ke panel untuk butiran lanjut.' SSHAndPanelLoginAlert: 'Panel {{ .node }}{{ .ip }} Anda, log masuk {{ .name }} dari {{ .loginIp }} tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: tugas berjadual {{ .name }} berjaya diselesaikan. Log masuk untuk melihat butiran.'
# cakera # cakera
DeviceNotFound: 'Peranti {{ .name }} tidak ditemui' DeviceNotFound: 'Peranti {{ .name }} tidak ditemui'
@@ -682,8 +686,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall # Firewall
ErrDockerIptablesChainUnavailable: 'Rantaian DOCKER-USER Docker tidak ditemui. Mulakan semula Docker dan cuba lagi' 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' 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' ErrDockerForwardPolicyDrop: 'Pelayan ini mengehadkan pemajuan rangkaian. Perlindungan port kontena tidak dapat diaktifkan buat masa ini.'
ErrUFWRuleAdopt: 'Gagal mengurus peraturan UFW: {{ .detail }}. Jika versi UFW semasa lebih lama daripada 0.35, tingkatkan UFW dan cuba lagi'
Firewall: 'Firewall' Firewall: 'Firewall'
FirewallTaskHost: 'Tembok api hos' FirewallTaskHost: 'Tembok api hos'
FirewallTaskForwarding: 'Pemajuan port' FirewallTaskForwarding: 'Pemajuan port'
@@ -697,25 +700,72 @@ FirewallCreateRulesStep: 'Cipta peraturan tembok api'
FirewallCreateRulesResult: 'Hasil penciptaan: {{ .succeeded }} berjaya, {{ .failed }} gagal, {{ .skipped }} tidak dilaksanakan' FirewallCreateRulesResult: 'Hasil penciptaan: {{ .succeeded }} berjaya, {{ .failed }} gagal, {{ .skipped }} tidak dilaksanakan'
FirewallRuleOperationResult: 'Hasil operasi: {{ .succeeded }} berjaya, {{ .failed }} gagal' FirewallRuleOperationResult: 'Hasil operasi: {{ .succeeded }} berjaya, {{ .failed }} gagal'
FirewallCreateRuleSkipped: 'Tidak dilaksanakan' FirewallCreateRuleSkipped: 'Tidak dilaksanakan'
FirewallCreateBatchStep: 'Hantar {{ .count }} peraturan secara kelompok ke {{ .backend }}'
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; ditukar kepada {{ .count }} peraturan' 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' FirewallCreateRuleExecutionFailed: 'Penciptaan peraturan gagal. 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'
FirewallInitializeChainsStep: 'Mulakan dan ikat rantai asas {{ .name }}' FirewallInitializeChainsStep: 'Mulakan dan ikat rantai asas {{ .name }}'
FirewallRestoreRulesStep: 'Pulihkan peraturan pangkalan data ke {{ .name }}'
FirewallSyncWhitelistStep: 'Segerakkan senarai putih port firewall' FirewallSyncWhitelistStep: 'Segerakkan senarai putih port firewall'
FirewallEnableForwardingStep: 'Aktifkan dan ikat rantai pemajuan port' FirewallEnableForwardingStep: 'Aktifkan dan ikat rantai pemajuan port'
FirewallRestoreForwardingRulesStep: 'Pulihkan peraturan pemajuan port pangkalan data' FirewallRestoreForwardingRulesStep: 'Pulihkan peraturan pemajuan port pangkalan data'
FirewallInspectDockerGuardStep: 'Periksa backend firewall Docker dan polisi' FirewallInspectDockerGuardStep: 'Periksa backend firewall Docker dan polisi'
FirewallInitializeDockerGuardStep: 'Mulakan dan ikat rantai perlindungan port {{ .name }}' FirewallInitializeDockerGuardStep: 'Mulakan dan ikat rantai perlindungan port {{ .name }}'
FirewallPersistDockerGuardStep: 'Simpan status perlindungan port Docker' FirewallPersistDockerGuardStep: 'Simpan status perlindungan port Docker'
ErrFirewallRuleScopeChange: "Tembok api semasa tidak menyokong perubahan skop peraturan (seperti keluarga alamat IPv4/IPv6). Sila cipta peraturan baharu." FileTaskCopy: 'Salin fail ke {{ .dst }}'
FirewallWhitelistReleased: "{{ .name }}: perlindungan senarai dibenarkan telah dilepaskan; peraturan izin dikekalkan. Untuk menutup port, padamkan peraturan secara manual daripada senarai peraturan" FileTaskMove: 'Pindahkan fail ke {{ .dst }}'
FirewallWhitelistRequired: "{{ .name }}: dilindungi oleh peraturan port wajib sistem" 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."
ErrIPv6RARisk: "Pemajuan IPv6 disekat bagi mengelakkan risiko kehilangan sambungan rangkaian. Rujuk dokumentasi dan log sistem."
ErrIPv6RACheckFailed: "Pemajuan IPv6 disekat kerana konfigurasi rangkaian tidak dapat diperiksa. Lihat log sistem untuk butiran."
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 }}]"
+66 -16
View File
@@ -300,6 +300,9 @@ BuildCache: 'Cache de construção'
Volume: 'Volume de armazenamento' Volume: 'Volume de armazenamento'
Network: 'Rede' Network: 'Rede'
PruneStart: 'Limpando em andamento aguarde...' PruneStart: 'Limpando em andamento aguarde...'
NetworkCleanupConnected: "Rede [{{ .name }}] ({{ .id }}) ignorada: há contêineres conectados ou a rede está em uso; não foi excluída"
NetworkCleanupSummary: "Limpeza de redes concluída: excluídas {{ .deleted }}, ignoradas {{ .skipped }}, falhas {{ .failed }}"
NetworkCleanupPartialFailure: "Não foi possível limpar algumas redes; consulte o log da tarefa"
ContainerBackupPrepare: 'Preparar área de backup do contêiner' ContainerBackupPrepare: 'Preparar área de backup do contêiner'
ContainerBackupStop: 'Parar contêiner antes do backup' ContainerBackupStop: 'Parar contêiner antes do backup'
ContainerBackupInspect: 'Fazer backup do inspect e rede do contêiner' ContainerBackupInspect: 'Fazer backup do inspect e rede do contêiner'
@@ -649,6 +652,7 @@ CommonAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .msg }}. Faça login no painel
NodeExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} nós estão anormais. Faça login no painel para obter detalhes.' NodeExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} nós estão anormais. Faça login no painel para obter detalhes.'
LicenseExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} licenças estão anormais. Faça login no painel para obter detalhes.' LicenseExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} licenças estão anormais. Faça login no painel para obter detalhes.'
SSHAndPanelLoginAlert: 'Seu Painel {{ .node }}{{ .ip }}, o login {{ .name }} a partir de {{ .loginIp }} é anormal. Faça login no painel para obter detalhes.' SSHAndPanelLoginAlert: 'Seu Painel {{ .node }}{{ .ip }}, o login {{ .name }} a partir de {{ .loginIp }} é anormal. Faça login no painel para obter detalhes.'
CronJobSuccessAlert: 'Painel {{ .node }}{{ .ip }}: a tarefa agendada {{ .name }} foi concluída com sucesso. Faça login para ver os detalhes.'
# disco # disco
DeviceNotFound: 'Dispositivo {{ .name }} não encontrado' DeviceNotFound: 'Dispositivo {{ .name }} não encontrado'
@@ -682,8 +686,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall # Firewall
ErrDockerIptablesChainUnavailable: 'A cadeia DOCKER-USER do Docker não foi encontrada. Reinicie o Docker e tente novamente' 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' 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' ErrDockerForwardPolicyDrop: 'Este servidor restringe o encaminhamento de rede. Não é possível ativar a proteção de portas de contêineres no momento.'
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'
Firewall: 'Firewall' Firewall: 'Firewall'
FirewallTaskHost: 'Firewall do host' FirewallTaskHost: 'Firewall do host'
FirewallTaskForwarding: 'Encaminhamento de portas' FirewallTaskForwarding: 'Encaminhamento de portas'
@@ -697,25 +700,72 @@ FirewallCreateRulesStep: 'Criar regras de firewall'
FirewallCreateRulesResult: 'Resultado da criação: {{ .succeeded }} com sucesso, {{ .failed }} com falha, {{ .skipped }} não executadas' 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' FirewallRuleOperationResult: 'Resultado da operação: {{ .succeeded }} com sucesso, {{ .failed }} com falha'
FirewallCreateRuleSkipped: 'Não executada' FirewallCreateRuleSkipped: 'Não executada'
FirewallCreateBatchStep: 'Enviar {{ .count }} regras em lote para {{ .backend }}'
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; convertido em {{ .count }} regras' 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' FirewallCreateRuleExecutionFailed: 'Falha ao criar a regra. 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'
FirewallInitializeChainsStep: 'Inicializar e vincular as cadeias base do {{ .name }}' 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' FirewallSyncWhitelistStep: 'Sincronizar a lista de portas permitidas do firewall'
FirewallEnableForwardingStep: 'Habilitar e vincular as cadeias de encaminhamento de portas' FirewallEnableForwardingStep: 'Habilitar e vincular as cadeias de encaminhamento de portas'
FirewallRestoreForwardingRulesStep: 'Restaurar regras de encaminhamento de portas do banco de dados' FirewallRestoreForwardingRulesStep: 'Restaurar regras de encaminhamento de portas do banco de dados'
FirewallInspectDockerGuardStep: 'Inspecionar o backend do firewall Docker e as políticas' FirewallInspectDockerGuardStep: 'Inspecionar o backend do firewall Docker e as políticas'
FirewallInitializeDockerGuardStep: 'Inicializar e vincular as cadeias de proteção de portas do {{ .name }}' FirewallInitializeDockerGuardStep: 'Inicializar e vincular as cadeias de proteção de portas do {{ .name }}'
FirewallPersistDockerGuardStep: 'Salvar o status da proteção de portas do Docker' FirewallPersistDockerGuardStep: 'Salvar o status da proteção de portas do Docker'
ErrFirewallRuleScopeChange: "O firewall atual não permite alterar o escopo de uma regra (como a família de endereços IPv4/IPv6). Crie uma nova regra." FileTaskCopy: 'Copiar arquivos para {{ .dst }}'
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" FileTaskMove: 'Mover arquivos para {{ .dst }}'
FirewallWhitelistRequired: "{{ .name }}: protegido pelas regras de portas obrigatórias do sistema" 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."
ErrIPv6RARisk: "O encaminhamento IPv6 foi bloqueado para evitar uma possível perda de conexão. Consulte a documentação e os logs do sistema."
ErrIPv6RACheckFailed: "O encaminhamento IPv6 foi bloqueado porque não foi possível verificar a configuração de rede. Consulte os logs do sistema."
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 }}]"
+66 -16
View File
@@ -300,6 +300,9 @@ BuildCache: 'Кэш сборки'
Volume: 'Том хранилища' Volume: 'Том хранилища'
Network: 'Сеть' Network: 'Сеть'
PruneStart: 'Очистка выполняется, пожалуйста, подождите...' PruneStart: 'Очистка выполняется, пожалуйста, подождите...'
NetworkCleanupConnected: "Сеть [{{ .name }}] ({{ .id }}) пропущена: подключены контейнеры или сеть используется; не удалена"
NetworkCleanupSummary: "Очистка сетей завершена: удалено {{ .deleted }}, пропущено {{ .skipped }}, ошибок {{ .failed }}"
NetworkCleanupPartialFailure: "Не удалось очистить некоторые сети; проверьте журнал задачи"
ContainerBackupPrepare: 'Подготовить рабочую область резервной копии контейнера' ContainerBackupPrepare: 'Подготовить рабочую область резервной копии контейнера'
ContainerBackupStop: 'Остановить контейнер перед резервным копированием' ContainerBackupStop: 'Остановить контейнер перед резервным копированием'
ContainerBackupInspect: 'Сделать резервную копию inspect и сети контейнера' ContainerBackupInspect: 'Сделать резервную копию inspect и сети контейнера'
@@ -649,6 +652,7 @@ CommonAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .msg }}. Войдит
NodeExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} узлов работают с ошибками. Войдите в панель для получения деталей.' NodeExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} узлов работают с ошибками. Войдите в панель для получения деталей.'
LicenseExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} лицензий имеют ошибки. Войдите в панель для получения деталей.' LicenseExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} лицензий имеют ошибки. Войдите в панель для получения деталей.'
SSHAndPanelLoginAlert: 'Ваш панель {{ .node }}{{ .ip }}, вход {{ .name }} с адреса {{ .loginIp }} является аномальным. Войдите в панель для получения деталей.' SSHAndPanelLoginAlert: 'Ваш панель {{ .node }}{{ .ip }}, вход {{ .name }} с адреса {{ .loginIp }} является аномальным. Войдите в панель для получения деталей.'
CronJobSuccessAlert: 'Панель {{ .node }}{{ .ip }}: запланированная задача {{ .name }} успешно завершена. Войдите для просмотра подробностей.'
# диск # диск
DeviceNotFound: 'Устройство {{ .name }} не найдено' DeviceNotFound: 'Устройство {{ .name }} не найдено'
@@ -682,8 +686,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall # Firewall
ErrDockerIptablesChainUnavailable: 'Цепочка Docker DOCKER-USER не найдена. Перезапустите Docker и повторите попытку' ErrDockerIptablesChainUnavailable: 'Цепочка Docker DOCKER-USER не найдена. Перезапустите Docker и повторите попытку'
ErrDockerNftablesChainUnavailable: 'Цепочка IPv4 брандмауэра nftables Docker не найдена. Убедитесь, что текущая версия Docker поддерживает бэкенд брандмауэра nftables, перезапустите Docker и повторите попытку' ErrDockerNftablesChainUnavailable: 'Цепочка IPv4 брандмауэра nftables Docker не найдена. Убедитесь, что текущая версия Docker поддерживает бэкенд брандмауэра nftables, перезапустите Docker и повторите попытку'
ErrDockerForwardPolicyDrop: 'Политика FORWARD по умолчанию для {{ .family }} — DROP. Измените её и повторите попытку' ErrDockerForwardPolicyDrop: 'На этом сервере ограничена пересылка сетевого трафика. Сейчас невозможно включить защиту портов контейнеров.'
ErrUFWRuleAdopt: 'Не удалось принять правило UFW под управление: {{ .detail }}. Если текущая версия UFW ниже 0.35, обновите UFW и повторите попытку'
Firewall: 'Межсетевой экран' Firewall: 'Межсетевой экран'
FirewallTaskHost: 'Межсетевой экран хоста' FirewallTaskHost: 'Межсетевой экран хоста'
FirewallTaskForwarding: 'Перенаправление портов' FirewallTaskForwarding: 'Перенаправление портов'
@@ -697,25 +700,72 @@ FirewallCreateRulesStep: 'Создание правил межсетевого
FirewallCreateRulesResult: 'Результат создания: успешно — {{ .succeeded }}, ошибок — {{ .failed }}, не выполнено — {{ .skipped }}' FirewallCreateRulesResult: 'Результат создания: успешно — {{ .succeeded }}, ошибок — {{ .failed }}, не выполнено — {{ .skipped }}'
FirewallRuleOperationResult: 'Результат операции: успешно — {{ .succeeded }}, ошибок — {{ .failed }}' FirewallRuleOperationResult: 'Результат операции: успешно — {{ .succeeded }}, ошибок — {{ .failed }}'
FirewallCreateRuleSkipped: 'Не выполнено' FirewallCreateRuleSkipped: 'Не выполнено'
FirewallCreateBatchStep: 'Отправка {{ .count }} правил одним пакетом в {{ .backend }}'
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; преобразовано в {{ .count }} правил' FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}: {{ .rule }}; преобразовано в {{ .count }} правил'
FirewallCreateRuleExecutionFailed: 'Не удалось создать правило. Запись в базе данных не сохранена, выполненные команды не отменены' FirewallCreateRuleExecutionFailed: 'Не удалось создать правило. Выполненные команды не отменены'
FirewallCreateRulePersistenceFailed: 'Правило создано, но не удалось сохранить запись управления'
FirewallAdoptRulePersistenceFailed: 'Команды принятия правила под управление выполнены, но сохранить запись управления не удалось'
FirewallSyncOperationsResult: 'Операции синхронизации: удалено {{ .removed }}, создано {{ .created }}, ошибок {{ .failed }}, не выполнено {{ .skipped }}, уже совпадают (изменения не нужны): {{ .unchanged }}'
FirewallSyncRuleUnchanged: 'Уже совпадает; изменения не нужны'
FirewallSyncStep: 'Синхронизировать правила с {{ .name }}'
FirewallSyncFailed: 'Не удалось синхронизировать правил межсетевого экрана: {{ .failed }}'
FirewallResetSourceStep: 'Сбросить и отключить исходный межсетевой экран {{ .name }}'
FirewallResetSourceResult: 'Исходный межсетевой экран сброшен; удалено правил из базы данных: {{ .removed }}'
FirewallInitializeChainsStep: 'Инициализировать и привязать базовые цепочки {{ .name }}' FirewallInitializeChainsStep: 'Инициализировать и привязать базовые цепочки {{ .name }}'
FirewallRestoreRulesStep: 'Восстановить правила базы данных в {{ .name }}'
FirewallSyncWhitelistStep: 'Синхронизировать белый список портов межсетевого экрана' FirewallSyncWhitelistStep: 'Синхронизировать белый список портов межсетевого экрана'
FirewallEnableForwardingStep: 'Включить и привязать цепочки перенаправления портов' FirewallEnableForwardingStep: 'Включить и привязать цепочки перенаправления портов'
FirewallRestoreForwardingRulesStep: 'Восстановить правила перенаправления портов из базы данных' FirewallRestoreForwardingRulesStep: 'Восстановить правила перенаправления портов из базы данных'
FirewallInspectDockerGuardStep: 'Проверить бэкенд межсетевого экрана Docker и политики' FirewallInspectDockerGuardStep: 'Проверить бэкенд межсетевого экрана Docker и политики'
FirewallInitializeDockerGuardStep: 'Инициализировать и привязать цепочки защиты портов {{ .name }}' FirewallInitializeDockerGuardStep: 'Инициализировать и привязать цепочки защиты портов {{ .name }}'
FirewallPersistDockerGuardStep: 'Сохранить состояние защиты портов Docker' FirewallPersistDockerGuardStep: 'Сохранить состояние защиты портов Docker'
ErrFirewallRuleScopeChange: "Текущий межсетевой экран не поддерживает изменение области действия правила (например, семейства адресов IPv4/IPv6). Создайте новое правило." FileTaskCopy: 'Копирование файлов в {{ .dst }}'
FirewallWhitelistReleased: "{{ .name }}: защита списка разрешённых портов снята; разрешающее правило сохранено. Чтобы закрыть порт, удалите правило вручную из списка правил" FileTaskMove: 'Перемещение файлов в {{ .dst }}'
FirewallWhitelistRequired: "{{ .name }}: защищён обязательными правилами системных портов" 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 }}, указанный в правиле перенаправления, не существует. Проверьте имя и повторите попытку."
ErrIPv6RARisk: "Включение переадресации IPv6 заблокировано из-за риска потери связи. См. документацию и системные журналы."
ErrIPv6RACheckFailed: "Включение переадресации IPv6 заблокировано: не удалось проверить настройки сети. Подробности в системных журналах."
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 }}]"
+66 -16
View File
@@ -300,6 +300,9 @@ BuildCache: 'Derleme önbelleği'
Volume: 'Depolama hacmi' Volume: 'Depolama hacmi'
Network: 'Ağ' Network: 'Ağ'
PruneStart: 'Temizlik devam ediyor, lütfen bekleyin...' PruneStart: 'Temizlik devam ediyor, lütfen bekleyin...'
NetworkCleanupConnected: "Ağ [{{ .name }}] ({{ .id }}) atlandı: bağlı konteynerler var veya ağ kullanımda; silinmedi"
NetworkCleanupSummary: "Ağ temizliği tamamlandı: silinen {{ .deleted }}, atlanan {{ .skipped }}, başarısız {{ .failed }}"
NetworkCleanupPartialFailure: "Bazı ağlar temizlenemedi; görev günlüğünü kontrol edin"
ContainerBackupPrepare: 'Konteyner yedekleme çalışma alanını hazırla' ContainerBackupPrepare: 'Konteyner yedekleme çalışma alanını hazırla'
ContainerBackupStop: 'Yedeklemeden önce konteyneri durdur' ContainerBackupStop: 'Yedeklemeden önce konteyneri durdur'
ContainerBackupInspect: 'Konteyner inspect ve ağ bilgisini yedekle' ContainerBackupInspect: 'Konteyner inspect ve ağ bilgisini yedekle'
@@ -649,6 +652,7 @@ CommonAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .msg }}. Detaylar için panelin
NodeExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} düğüm anormal durumda. Detaylar için paneline giriş yapın.' NodeExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} düğüm anormal durumda. Detaylar için paneline giriş yapın.'
LicenseExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} lisans anormal durumda. Detaylar için paneline giriş yapın.' LicenseExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} lisans anormal durumda. Detaylar için paneline giriş yapın.'
SSHAndPanelLoginAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .loginIp }} adresinden {{ .name }} girişi anormal. Detaylar için paneline giriş yapın.' SSHAndPanelLoginAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .loginIp }} adresinden {{ .name }} girişi anormal. Detaylar için paneline giriş yapın.'
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: zamanlanmış görev {{ .name }} başarıyla tamamlandı. Ayrıntıları görmek için giriş yapın.'
# disk # disk
DeviceNotFound: 'Cihaz {{ .name }} bulunamadı' DeviceNotFound: 'Cihaz {{ .name }} bulunamadı'
@@ -682,8 +686,7 @@ AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall # Firewall
ErrDockerIptablesChainUnavailable: 'Docker DOCKER-USER zinciri bulunamadı. Docker’ı yeniden başlatıp tekrar deneyin' 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' 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' ErrDockerForwardPolicyDrop: 'Bu sunucu ağ yönlendirmesini kısıtlıyor. Konteyner port koruması şu anda etkinleştirilemiyor.'
ErrUFWRuleAdopt: 'UFW kuralı yönetilemedi: {{ .detail }}. Geçerli UFW sürümü 0.35’ten eskiyse UFW’yi yükseltip tekrar deneyin'
Firewall: 'Güvenlik duvarı' Firewall: 'Güvenlik duvarı'
FirewallTaskHost: 'Ana makine güvenlik duvarı' FirewallTaskHost: 'Ana makine güvenlik duvarı'
FirewallTaskForwarding: 'Port yönlendirme' FirewallTaskForwarding: 'Port yönlendirme'
@@ -697,25 +700,72 @@ 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' 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' FirewallRuleOperationResult: 'İşlem sonucu: {{ .succeeded }} başarılı, {{ .failed }} başarısız'
FirewallCreateRuleSkipped: 'Yürütülmedi' 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ü' 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ı' FirewallCreateRuleExecutionFailed: 'Kural oluşturma başarısız. 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'
FirewallInitializeChainsStep: '{{ .name }} temel zincirlerini başlat ve bağla' 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' FirewallSyncWhitelistStep: 'Güvenlik duvarı bağlantı noktası izin listesini eşitle'
FirewallEnableForwardingStep: 'Bağlantı noktası yönlendirme zincirlerini etkinleştir ve bağla' FirewallEnableForwardingStep: 'Bağlantı noktası yönlendirme zincirlerini etkinleştir ve bağla'
FirewallRestoreForwardingRulesStep: 'Veritabanı bağlantı noktası yönlendirme kurallarını geri yükle' FirewallRestoreForwardingRulesStep: 'Veritabanı bağlantı noktası yönlendirme kurallarını geri yükle'
FirewallInspectDockerGuardStep: 'Docker güvenlik duvarı arka ucunu ve ilkelerini denetle' FirewallInspectDockerGuardStep: 'Docker güvenlik duvarı arka ucunu ve ilkelerini denetle'
FirewallInitializeDockerGuardStep: '{{ .name }} bağlantı noktası koruma zincirlerini başlat ve bağla' FirewallInitializeDockerGuardStep: '{{ .name }} bağlantı noktası koruma zincirlerini başlat ve bağla'
FirewallPersistDockerGuardStep: 'Docker bağlantı noktası koruma durumunu kaydet' FirewallPersistDockerGuardStep: 'Docker bağlantı noktası koruma durumunu kaydet'
ErrFirewallRuleScopeChange: "Mevcut güvenlik duvarı, kuralın kapsamını (IPv4/IPv6 adres ailesi gibi) değiştirmeyi desteklemiyor. Lütfen yeni bir kural oluşturun." FileTaskCopy: 'Dosyaları {{ .dst }} konumuna kopyala'
FirewallWhitelistReleased: "{{ .name }}: izin listesi koruması kaldırıldı; izin kuralı korundu. Portu kapatmak için kuralı kural listesinden elle silin" FileTaskMove: 'Dosyaları {{ .dst }} konumuna taşı'
FirewallWhitelistRequired: "{{ .name }}: zorunlu sistem portu kuralları tarafından korunuyor" 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."
ErrIPv6RARisk: "Ağ bağlantısının kesilme riski nedeniyle IPv6 yönlendirmesi engellendi. Belgeleri ve sistem günlüklerini inceleyin."
ErrIPv6RACheckFailed: "Ağ yapılandırması denetlenemediği için IPv6 yönlendirmesi engellendi. Ayrıntılar için sistem günlüklerini inceleyin."
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"
+66 -16
View File
@@ -300,6 +300,9 @@ BuildCache: '構建快取'
Volume: '磁碟區' Volume: '磁碟區'
Network: '網路' Network: '網路'
PruneStart: '清理中,請稍候...' PruneStart: '清理中,請稍候...'
NetworkCleanupConnected: "跳過網路 [{{ .name }}] ({{ .id }}):仍有容器連接或正在使用,未刪除"
NetworkCleanupSummary: "網路清理完成:已刪除 {{ .deleted }},已跳過 {{ .skipped }},失敗 {{ .failed }}"
NetworkCleanupPartialFailure: "部分網路清理失敗,請查看任務日誌"
ContainerBackupPrepare: '準備容器備份工作目錄' ContainerBackupPrepare: '準備容器備份工作目錄'
ContainerBackupStop: '備份前停止容器' ContainerBackupStop: '備份前停止容器'
ContainerBackupInspect: '備份容器 Inspect 與網路資訊' ContainerBackupInspect: '備份容器 Inspect 與網路資訊'
@@ -649,6 +652,7 @@ CommonAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},詳情請登入
NodeExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個節點出現異常,詳情請登入面板檢視。' NodeExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個節點出現異常,詳情請登入面板檢視。'
LicenseExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個授權出現異常,詳情請登入面板檢視。' LicenseExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個授權出現異常,詳情請登入面板檢視。'
SSHAndPanelLoginAlert: '您的 {{ .node }}{{ .ip }} 面板,來自 {{ .loginIp }} 的 {{ .name }} 登入出現異常,詳情請登入面板檢視。' SSHAndPanelLoginAlert: '您的 {{ .node }}{{ .ip }} 面板,來自 {{ .loginIp }} 的 {{ .name }} 登入出現異常,詳情請登入面板檢視。'
CronJobSuccessAlert: '您的 {{ .node }}{{ .ip }} 面板,排程任務 {{ .name }} 執行成功,詳情請登入面板檢視。'
# 磁碟 # 磁碟
DeviceNotFound: '裝置 {{ .name }} 未找到' DeviceNotFound: '裝置 {{ .name }} 未找到'
@@ -682,8 +686,7 @@ AIProviderArkCodingPlan: '火山方舟 Coding Plan'
# 防火牆 # 防火牆
ErrDockerIptablesChainUnavailable: '未偵測到 Docker 的 DOCKER-USER 鏈,請重新啟動 Docker 後再試' ErrDockerIptablesChainUnavailable: '未偵測到 Docker 的 DOCKER-USER 鏈,請重新啟動 Docker 後再試'
ErrDockerNftablesChainUnavailable: '未偵測到 Docker 的 nftables IPv4 防火牆鏈,請確認目前 Docker 版本支援 nftables 防火牆後端,重新啟動 Docker 後再試' ErrDockerNftablesChainUnavailable: '未偵測到 Docker 的 nftables IPv4 防火牆鏈,請確認目前 Docker 版本支援 nftables 防火牆後端,重新啟動 Docker 後再試'
ErrDockerForwardPolicyDrop: '偵測到 {{ .family }} FORWARD 預設策略為 DROP,請調整後重試' ErrDockerForwardPolicyDrop: '目前伺服器限制了網路轉送,暫時無法啟用容器連接埠防護。'
ErrUFWRuleAdopt: 'UFW 規則納管失敗:{{ .detail }}。如果目前 UFW 版本低於 0.35,請先升級 UFW 後再試'
Firewall: '防火牆' Firewall: '防火牆'
FirewallTaskHost: '主機防火牆' FirewallTaskHost: '主機防火牆'
FirewallTaskForwarding: '連接埠轉發' FirewallTaskForwarding: '連接埠轉發'
@@ -697,25 +700,72 @@ FirewallCreateRulesStep: '建立防火牆規則'
FirewallCreateRulesResult: '建立結果:成功 {{ .succeeded }} 條,失敗 {{ .failed }} 條,未執行 {{ .skipped }} 條' FirewallCreateRulesResult: '建立結果:成功 {{ .succeeded }} 條,失敗 {{ .failed }} 條,未執行 {{ .skipped }} 條'
FirewallRuleOperationResult: '操作結果:成功 {{ .succeeded }} 條,失敗 {{ .failed }} 條' FirewallRuleOperationResult: '操作結果:成功 {{ .succeeded }} 條,失敗 {{ .failed }} 條'
FirewallCreateRuleSkipped: '未執行' FirewallCreateRuleSkipped: '未執行'
FirewallCreateBatchStep: '向 {{ .backend }} 批次提交 {{ .count }} 條規則'
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}:{{ .rule }};轉換為 {{ .count }} 條規則' FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}:{{ .rule }};轉換為 {{ .count }} 條規則'
FirewallCreateRuleExecutionFailed: '規則建立失敗,未寫入資料庫,已執行的命令不回復' FirewallCreateRuleExecutionFailed: '規則建立失敗,已執行的命令不回復'
FirewallCreateRulePersistenceFailed: '規則已建立,但納管資訊儲存失敗'
FirewallAdoptRulePersistenceFailed: '規則納管命令已執行,但納管資訊儲存失敗'
FirewallSyncOperationsResult: '同步操作彙總:刪除成功 {{ .removed }} 條,建立成功 {{ .created }} 條,失敗 {{ .failed }} 條,未執行 {{ .skipped }} 條,已一致無需變更 {{ .unchanged }} 條'
FirewallSyncRuleUnchanged: '已一致,無需變更'
FirewallSyncStep: '同步規則到 {{ .name }}'
FirewallSyncFailed: '{{ .failed }} 條防火牆規則同步失敗'
FirewallResetSourceStep: '重設並停用來源防火牆 {{ .name }}'
FirewallResetSourceResult: '來源防火牆重設完成,已刪除 {{ .removed }} 條資料庫規則'
FirewallInitializeChainsStep: '初始化並綁定 {{ .name }} 基礎鏈' FirewallInitializeChainsStep: '初始化並綁定 {{ .name }} 基礎鏈'
FirewallRestoreRulesStep: '恢復資料庫規則至 {{ .name }}'
FirewallSyncWhitelistStep: '同步防火牆連接埠白名單' FirewallSyncWhitelistStep: '同步防火牆連接埠白名單'
FirewallEnableForwardingStep: '啟用並綁定連接埠轉發鏈' FirewallEnableForwardingStep: '啟用並綁定連接埠轉發鏈'
FirewallRestoreForwardingRulesStep: '恢復資料庫連接埠轉發規則' FirewallRestoreForwardingRulesStep: '恢復資料庫連接埠轉發規則'
FirewallInspectDockerGuardStep: '檢查 Docker 防火牆後端與防護策略' FirewallInspectDockerGuardStep: '檢查 Docker 防火牆後端與防護策略'
FirewallInitializeDockerGuardStep: '初始化並綁定 {{ .name }} 連接埠防護鏈' FirewallInitializeDockerGuardStep: '初始化並綁定 {{ .name }} 連接埠防護鏈'
FirewallPersistDockerGuardStep: '儲存 Docker 連接埠防護狀態' FirewallPersistDockerGuardStep: '儲存 Docker 連接埠防護狀態'
ErrFirewallRuleScopeChange: "目前的防火牆不支援修改規則的作用範圍(如 IPv4/IPv6 位址族),請建立新規則。" FileTaskCopy: '複製檔案至 {{ .dst }}'
FirewallWhitelistReleased: "{{ .name }}:已解除白名單保護,放行規則保留;如需關閉連接埠,請在規則清單手動刪除" FileTaskMove: '移動檔案至 {{ .dst }}'
FirewallWhitelistRequired: "{{ .name }}:由系統必要連接埠規則保護" 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 }} 不存在,請檢查網卡名稱後重試。"
ErrIPv6RARisk: "IPv6 轉送可能導致網路中斷,已阻止啟用。請參閱文件,詳情見系統日誌。"
ErrIPv6RACheckFailed: "無法檢查 IPv6 網路設定,已阻止啟用轉送。詳情見系統日誌。"
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 }}]"
+66 -16
View File
@@ -300,6 +300,9 @@ BuildCache: "构建缓存"
Volume: "存储卷" Volume: "存储卷"
Network: "网络" Network: "网络"
PruneStart: "清理中,请稍候..." PruneStart: "清理中,请稍候..."
NetworkCleanupConnected: "跳过网络 [{{ .name }}] ({{ .id }}):仍有容器连接或正在使用,未删除"
NetworkCleanupSummary: "网络清理完成:已删除 {{ .deleted }},已跳过 {{ .skipped }},失败 {{ .failed }}"
NetworkCleanupPartialFailure: "部分网络清理失败,请查看任务日志"
ContainerBackupPrepare: "准备容器备份工作目录" ContainerBackupPrepare: "准备容器备份工作目录"
ContainerBackupStop: "备份前停止容器" ContainerBackupStop: "备份前停止容器"
ContainerBackupInspect: "备份容器 Inspect 与网络信息" ContainerBackupInspect: "备份容器 Inspect 与网络信息"
@@ -649,6 +652,7 @@ CommonAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},请登录面板
NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个节点存在异常,请登录面板查看详情。" NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个节点存在异常,请登录面板查看详情。"
LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个许可证存在异常,请登录面板查看详情。" LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个许可证存在异常,请登录面板查看详情。"
SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,面板 {{ .name }} 登录 {{ .loginIp }} 异常,请登录面板查看详情。" SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,面板 {{ .name }} 登录 {{ .loginIp }} 异常,请登录面板查看详情。"
CronJobSuccessAlert: "您的 {{ .node }}{{ .ip }} 面板,计划任务-{{ .name }}执行成功,请登录面板查看详情。"
# 磁盘 # 磁盘
DeviceNotFound: "设备 {{ .name }} 未找到" DeviceNotFound: "设备 {{ .name }} 未找到"
@@ -682,8 +686,7 @@ AIProviderArkCodingPlan: "火山方舟 Coding Plan"
# 防火墙 # 防火墙
ErrDockerIptablesChainUnavailable: "未检测到 Docker 的 DOCKER-USER 链,请重启 Docker 后重试" ErrDockerIptablesChainUnavailable: "未检测到 Docker 的 DOCKER-USER 链,请重启 Docker 后重试"
ErrDockerNftablesChainUnavailable: "未检测到 Docker 的 nftables IPv4 防火墙链,请确认当前 Docker 版本支持 nftables 防火墙后端,重启 Docker 后重试" ErrDockerNftablesChainUnavailable: "未检测到 Docker 的 nftables IPv4 防火墙链,请确认当前 Docker 版本支持 nftables 防火墙后端,重启 Docker 后重试"
ErrDockerForwardPolicyDrop: '检测到 {{ .family }} FORWARD 默认策略为 DROP,请调整后重试' ErrDockerForwardPolicyDrop: '当前服务器限制了网络转发,暂时无法启用容器端口防护。'
ErrUFWRuleAdopt: "UFW 规则纳管失败:{{ .detail }}。如果当前 UFW 版本低于 0.35,请先升级 UFW 后重试"
Firewall: "防火墙" Firewall: "防火墙"
FirewallTaskHost: '主机防火墙' FirewallTaskHost: '主机防火墙'
FirewallTaskForwarding: '端口转发' FirewallTaskForwarding: '端口转发'
@@ -697,25 +700,72 @@ FirewallCreateRulesStep: '创建防火墙规则'
FirewallCreateRulesResult: '创建结果:成功 {{ .succeeded }} 条,失败 {{ .failed }} 条,未执行 {{ .skipped }} 条' FirewallCreateRulesResult: '创建结果:成功 {{ .succeeded }} 条,失败 {{ .failed }} 条,未执行 {{ .skipped }} 条'
FirewallRuleOperationResult: '操作结果:成功 {{ .succeeded }} 条,失败 {{ .failed }} 条' FirewallRuleOperationResult: '操作结果:成功 {{ .succeeded }} 条,失败 {{ .failed }} 条'
FirewallCreateRuleSkipped: '未执行' FirewallCreateRuleSkipped: '未执行'
FirewallCreateBatchStep: '向 {{ .backend }} 批量提交 {{ .count }} 条规则'
FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}:{{ .rule }};转换为 {{ .count }} 条规则' FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{ .target }}:{{ .rule }};转换为 {{ .count }} 条规则'
FirewallCreateRuleExecutionFailed: '规则创建失败,未入库,已执行的命令不回滚' FirewallCreateRuleExecutionFailed: '规则创建失败,已执行的命令不回滚'
FirewallCreateRulePersistenceFailed: '规则已创建,但纳管信息保存失败'
FirewallAdoptRulePersistenceFailed: '规则纳管命令已执行,但纳管信息保存失败'
FirewallSyncOperationsResult: '同步操作汇总:删除成功 {{ .removed }} 条,创建成功 {{ .created }} 条,失败 {{ .failed }} 条,未执行 {{ .skipped }} 条,已一致无需变更 {{ .unchanged }} 条'
FirewallSyncRuleUnchanged: '已一致,无需变更'
FirewallSyncStep: "同步规则到 {{ .name }}"
FirewallSyncFailed: "{{ .failed }} 条防火墙规则同步失败"
FirewallResetSourceStep: "重置并停用源防火墙 {{ .name }}"
FirewallResetSourceResult: "源防火墙重置完成,已删除 {{ .removed }} 条数据库规则"
FirewallInitializeChainsStep: "初始化并绑定 {{ .name }} 基础链" FirewallInitializeChainsStep: "初始化并绑定 {{ .name }} 基础链"
FirewallRestoreRulesStep: "恢复数据库规则到 {{ .name }}"
FirewallSyncWhitelistStep: "同步防火墙端口白名单" FirewallSyncWhitelistStep: "同步防火墙端口白名单"
FirewallEnableForwardingStep: "启用并绑定端口转发链" FirewallEnableForwardingStep: "启用并绑定端口转发链"
FirewallRestoreForwardingRulesStep: "恢复数据库端口转发规则" FirewallRestoreForwardingRulesStep: "恢复数据库端口转发规则"
FirewallInspectDockerGuardStep: "检查 Docker 防火墙后端和防护策略" FirewallInspectDockerGuardStep: "检查 Docker 防火墙后端和防护策略"
FirewallInitializeDockerGuardStep: "初始化并绑定 {{ .name }} 端口防护链" FirewallInitializeDockerGuardStep: "初始化并绑定 {{ .name }} 端口防护链"
FirewallPersistDockerGuardStep: "保存 Docker 端口防护状态" FirewallPersistDockerGuardStep: "保存 Docker 端口防护状态"
ErrFirewallRuleScopeChange: "当前防火墙不支持修改规则的作用范围(如 IPv4/IPv6 地址族),请新建规则。" FileTaskCopy: '复制文件到 {{ .dst }}'
FirewallWhitelistReleased: "{{ .name }}:已解除白名单保护,放行规则保留;如需关闭端口,请在规则列表手动删除" FileTaskMove: '移动文件到 {{ .dst }}'
FirewallWhitelistRequired: "{{ .name }}:由系统必需端口规则保护" 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 }} 不存在,请检查网卡名称后重试。"
ErrIPv6RARisk: "IPv6 转发存在断网风险,已阻止启用。请参阅文档,详情见系统日志。"
ErrIPv6RACheckFailed: "无法检查 IPv6 网络配置,已阻止启用转发。详情见系统日志。"
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 }}]"
+1
View File
@@ -13,6 +13,7 @@ func Init() {
global.TaskDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "task.db"), "task") 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.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.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") 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 { if _, err := os.Stat(path.Join(global.Dir.DbDir, "core.db")); err == nil {
+27 -24
View File
@@ -6,20 +6,26 @@ import (
"os" "os"
"time" "time"
"errors"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/repo" "github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/service" "github.com/1Panel-dev/1Panel/agent/app/service"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/init/migration/migrations" "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"
"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/iptables_helper"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/nftables_helper" "github.com/1Panel-dev/1Panel/agent/utils/firewall/nftables_helper"
"gorm.io/gorm"
) )
func Init() { func Init() {
ctx := context.Background() 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() client, err := service.NewSelectedSystemFirewallClient()
if err != nil { if err != nil {
global.LOG.Errorf("select system firewall provider failed, err: %v", err) 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 { if err := migrations.TransferFirewalldSSHService(ctx, client, service.NewIFirewallService().SyncPortWhitelist); err != nil {
global.LOG.Warnf("synchronize firewall whitelist on startup failed, err: %v", err) global.LOG.Warnf("synchronize firewall whitelist on startup failed, err: %v", err)
} }
if initialize {
initDockerPortGuard(ctx)
}
}() }()
if err := migrationutils.TransferHostFirewall(ctx, clientName); err != nil { families := []string{constant.FirewallFamilyIPv4, constant.FirewallFamilyIPv6}
global.LOG.Errorf("transfer legacy host firewall records failed, err: %v", err) 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 return
} }
if err := migrationutils.TransferLegacyHostFirewallRuleOwnership(ctx, clientName, service.AdoptLegacyHostFirewallRuleOwnership); err != nil { if ipv6Status == constant.StatusDisable {
global.LOG.Warnf("transfer legacy host firewall rule ownership failed, err: %v", err) global.LOG.Info("IPv6 firewall support is disabled; skipping IPv6 host chains and saved rules during startup")
} families = families[:1]
if err := migrationutils.TransferFirewallForwarding(ctx); err != nil { nftFamilies = nftFamilies[:1]
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)
} }
initialize = needInit() initialize = needInit()
if !initialize { if !initialize {
repairIptablesBaseChains(clientName) repairIptablesBaseChains(clientName, families...)
return return
} }
InitPingStatus() InitPingStatus()
global.LOG.Info("initializing firewall settings...") global.LOG.Info("initializing firewall settings...")
if clientName == "nftables" { 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) global.LOG.Errorf("restore nftables rules failed, err: %v", err)
} }
status, _ := repo.NewISettingRepo().GetValueByKey("IptablesStatus") status, _ := repo.NewISettingRepo().GetValueByKey("IptablesStatus")
if status == constant.StatusEnable { 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) 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) global.LOG.Errorf("load required firewall ports failed, err: %v", err)
return 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) global.LOG.Errorf("restore iptables base chains failed, err: %v", err)
return return
} }
@@ -94,7 +95,7 @@ func Init() {
} }
func repairIptablesBaseChains(clientName string) { func repairIptablesBaseChains(clientName string, families ...string) {
if clientName != constant.FirewallProviderIptables { if clientName != constant.FirewallProviderIptables {
return return
} }
@@ -103,10 +104,12 @@ func repairIptablesBaseChains(clientName string) {
if status != constant.StatusEnable { if status != constant.StatusEnable {
return return
} }
manager := iptables_helper.Manager{ ports, err := service.LoadRequiredFirewallPortWhiteList()
LoadRequiredPorts: 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) global.LOG.Warnf("repair iptables base chains failed, err: %v", err)
} }
} }
@@ -118,7 +121,7 @@ func initDockerPortGuard(ctx context.Context) {
) )
var restoreErr error var restoreErr error
for attempt := 1; attempt <= attempts; attempt++ { for attempt := 1; attempt <= attempts; attempt++ {
restoreErr = service.ReconcileDockerPortGuard(ctx) restoreErr = service.RestoreDockerPortGuard(ctx)
if restoreErr == nil { if restoreErr == nil {
return return
} }
+33 -1
View File
@@ -4,6 +4,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model" "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/global"
"github.com/1Panel-dev/1Panel/agent/utils/alert_push" "github.com/1Panel-dev/1Panel/agent/utils/alert_push"
"github.com/1Panel-dev/1Panel/agent/utils/xpack" "github.com/1Panel-dev/1Panel/agent/utils/xpack"
"gorm.io/gorm"
) )
func Init() { func Init() {
@@ -174,6 +176,36 @@ func initAlertTask() {
} }
func initMonitorDB() { 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{}) _ = 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)
}
} }
+4 -2
View File
@@ -108,9 +108,11 @@ func agentDBMigrations() []*gormigrate.Migration {
migrations.AddFirewallRuleTable, migrations.AddFirewallRuleTable,
migrations.InitDockerPortGuardStatus, migrations.InitDockerPortGuardStatus,
migrations.NormalizeFirewallBackendSelections, migrations.NormalizeFirewallBackendSelections,
migrations.SimplifyFirewallRulePolicy, migrations.MigrateFirewallDescriptions,
migrations.AddDockerPortGuardReadOnly,
migrations.MigrateFirewallPortWhitelistSources, migrations.MigrateFirewallPortWhitelistSources,
migrations.AddAcceleratorMetrics,
migrations.AddVLLMMonitor,
migrations.AddMonitorSettings,
} }
} }
@@ -162,6 +162,9 @@ func migrateFirewallPortWhitelist(value string) ([]firewall.PortWhitelist, error
for _, rule := range defaults { for _, rule := range defaults {
index, found := indexes[key(rule)] index, found := indexes[key(rule)]
if !found { if !found {
if rule.Type == "" {
continue
}
index = len(rules) index = len(rules)
indexes[key(rule)] = index indexes[key(rule)] = index
rules = append(rules, rule) rules = append(rules, rule)
+29 -63
View File
@@ -59,7 +59,6 @@ var AddTable = &gormigrate.Migration{
&model.Favorite{}, &model.Favorite{},
&model.FileShare{}, &model.FileShare{},
&model.Host{}, &model.Host{},
&model.FirewallRule{},
&model.Ftp{}, &model.Ftp{},
&model.ImageRepo{}, &model.ImageRepo{},
&model.ScriptLibrary{}, &model.ScriptLibrary{},
@@ -1784,7 +1783,7 @@ var AddComposePinned = &gormigrate.Migration{
var AddFirewallRuleTable = &gormigrate.Migration{ var AddFirewallRuleTable = &gormigrate.Migration{
ID: "20260819-add-firewall-v2-tables", ID: "20260819-add-firewall-v2-tables",
Migrate: func(tx *gorm.DB) error { 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{ var MigrateFirewallDescriptions = &gormigrate.Migration{
ID: "20260826-simplify-firewall-rule-policy", ID: "20260921-migrate-firewall-descriptions",
Migrate: func(tx *gorm.DB) error { Migrate: func(tx *gorm.DB) error {
if !tx.Migrator().HasTable(&model.FirewallRule{}) { return tx.Transaction(migrationutils.MigrateHostFirewallDescriptions)
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
})
}, },
} }
var AddDockerPortGuardReadOnly = &gormigrate.Migration{ var AddAcceleratorMetrics = &gormigrate.Migration{
ID: "20260902-add-docker-port-guard-read-only", ID: "20260928-accelerator-vendor-metrics",
Migrate: func(tx *gorm.DB) error { Migrate: func(tx *gorm.DB) error {
if !tx.Migrator().HasTable(&model.DockerPortGuardPolicy{}) { return global.GPUMonitorDB.AutoMigrate(&model.MonitorGPU{})
return nil },
} }
if err := tx.AutoMigrate(&model.DockerPortGuardPolicy{}); err != nil {
return err var AddVLLMMonitor = &gormigrate.Migration{
} ID: "20260928-vllm-monitor",
if tx.Migrator().HasIndex(&model.DockerPortGuardPolicy{}, "idx_docker_port_guard_endpoint") { Migrate: func(tx *gorm.DB) error {
if err := tx.Migrator().DropIndex(&model.DockerPortGuardPolicy{}, "idx_docker_port_guard_endpoint"); err != nil { 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 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 package utils
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net/netip" "net/netip"
"slices"
"sort"
"strings" "strings"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter" "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" "gorm.io/gorm"
) )
const hostFirewallTransferMigrationID = "host-firewall-transfer"
var errUnsupportedLegacyHostFirewallRule = errors.New("unsupported legacy host firewall rule") var errUnsupportedLegacyHostFirewallRule = errors.New("unsupported legacy host firewall rule")
type legacyHostFirewallRecord struct { type legacyHostFirewallRecord struct {
@@ -34,126 +33,225 @@ type legacyHostFirewallRecord struct {
Description string Description string
} }
func TransferHostFirewall(ctx context.Context, provider string) error { type legacyFirewallRuleDescription struct {
if global.DB == nil { UUID string
return errors.New("host firewall transfer database is required") Provider string
} ScopeKey string
return transferHostFirewall(ctx, global.DB, filter.Provider(strings.ToLower(strings.TrimSpace(provider)))) 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 { func MigrateHostFirewallDescriptions(db *gorm.DB) error {
if global.DB == nil { providers := []filter.Provider{filter.ProviderIptables, filter.ProviderNftables, filter.ProviderFirewalld, filter.ProviderUFW}
return errors.New("host firewall transfer database is required") 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( addRules := func(rules []filter.FirewallRule, description string) bool {
ctx, matched := false
global.DB, for _, rule := range rules {
filter.Provider(strings.ToLower(strings.TrimSpace(provider))), var err error
transfer, switch rule.Scope.Provider {
) case filter.ProviderFirewalld:
} rule, err = (&filterfirewalld.Adapter{}).PrepareRule(rule)
case filter.ProviderUFW:
func transferLegacyHostFirewallRuleOwnership( rule, err = (&filterufw.Adapter{}).PrepareRule(rule)
ctx context.Context, }
db *gorm.DB, if err != nil {
provider filter.Provider, continue
transfer func(context.Context) error, }
) error { id, err := filter.DescriptionID(filter.ObservedRule{Rule: rule, ParseStatus: filter.ParseStatusSupported})
if !legacyHostFirewallOwnershipProvider(provider) { if err == nil {
return 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") { if db.Migrator().HasTable("firewalls") {
var records []legacyHostFirewallRecord var records []legacyHostFirewallRecord
if err := db.WithContext(ctx).Table("firewalls").Order("id ASC").Find(&records).Error; err != nil { if err := db.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 {
return err return err
} }
if legacyHostFirewallOwnershipProvider(provider) { for _, record := range records {
return nil 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 { func legacyFirewallDescriptionRules(record legacyFirewallRuleDescription, provider filter.Provider) ([]filter.FirewallRule, error) {
converted := make([]model.FirewallRule, 0, len(records)) if record.CompatibilityError != "" {
byIdentity := make(map[string]int) return nil, errUnsupportedLegacyHostFirewallRule
for _, record := range records { }
rules, err := legacyHostFirewallRules(record, provider) 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 err != nil {
if global.LOG != nil { return nil, err
global.LOG.Warnf("skip legacy host firewall record %d during transfer: %v", record.ID, err)
}
continue
} }
for _, rule := range rules { result = append(result, expanded...)
item, err := hostFirewallRuleModel(rule) if source == "" && (provider == filter.ProviderIptables || provider == filter.ProviderNftables) && strings.HasPrefix(record.Owner, constant.FirewallRuleSourceSecurity+":"+constant.FirewallSystemAcceptedPortSourcePrefix) {
if err != nil { for _, item := range expanded {
if global.LOG != nil { item.Scope.Chain = filter.BasicBeforeChain
global.LOG.Warnf("skip legacy host firewall record %d during transfer: %v", record.ID, err) result = append(result, item)
}
continue
} }
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) { func legacyHostFirewallRules(record legacyHostFirewallRecord, provider filter.Provider) ([]filter.FirewallRule, error) {
@@ -193,7 +291,7 @@ func legacyHostFirewallRules(record legacyHostFirewallRecord, provider filter.Pr
switch provider { switch provider {
case filter.ProviderIptables: case filter.ProviderIptables:
rule.Scope = filter.Scope{ 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, Chain: legacyIptablesChain(record), Direction: filter.DirectionInput,
} }
rule.NativeKind = filter.NativeKindRule rule.NativeKind = filter.NativeKindRule
@@ -334,43 +432,3 @@ func legacyIPOrPrefix(value string) bool {
_, err := netip.ParsePrefix(value) _, err := netip.ParsePrefix(value)
return err == nil 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
}
+15
View File
@@ -20,6 +20,12 @@ func Init() {
if err := validator.RegisterValidation("password", checkPasswordPattern); err != nil { if err := validator.RegisterValidation("password", checkPasswordPattern); err != nil {
panic(err) 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 global.VALID = validator
} }
@@ -55,3 +61,12 @@ func checkPasswordPattern(fl validator.FieldLevel) bool {
return false 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)
}
+3
View File
@@ -27,6 +27,9 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter.GET("/gpu/load", baseApi.LoadGpuInfo) aiToolsRouter.GET("/gpu/load", baseApi.LoadGpuInfo)
aiToolsRouter.POST("/gpu/search", baseApi.LoadGPUMonitor) aiToolsRouter.POST("/gpu/search", baseApi.LoadGPUMonitor)
aiToolsRouter.GET("/gpu/options", baseApi.GetCPUOptions) 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/search", baseApi.PageMcpServers)
aiToolsRouter.POST("/mcp/server/detail", baseApi.LoadMcpServerDetail) aiToolsRouter.POST("/mcp/server/detail", baseApi.LoadMcpServerDetail)
+4 -5
View File
@@ -11,6 +11,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
hostRouter := Router.Group("hosts") hostRouter := Router.Group("hosts")
baseApi := v2.ApiGroupApp.BaseApi baseApi := v2.ApiGroupApp.BaseApi
Router.POST("/internal/terminal/sessions/revoke", baseApi.RevokeTerminalSessions) Router.POST("/internal/terminal/sessions/revoke", baseApi.RevokeTerminalSessions)
Router.GET("/internal/terminal/capabilities", baseApi.TerminalCapabilities)
{ {
hostRouter.POST("", baseApi.CreateHost) hostRouter.POST("", baseApi.CreateHost)
hostRouter.POST("/info", baseApi.GetHostByID) hostRouter.POST("/info", baseApi.GetHostByID)
@@ -27,6 +28,8 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
hostRouter.POST("/firewall/port", baseApi.UpdatePanelFirewallPort) hostRouter.POST("/firewall/port", baseApi.UpdatePanelFirewallPort)
hostRouter.GET("/firewall/settings", baseApi.LoadFirewallSettings) hostRouter.GET("/firewall/settings", baseApi.LoadFirewallSettings)
hostRouter.POST("/firewall/settings/operate", baseApi.OperateFirewallBackend) hostRouter.POST("/firewall/settings/operate", baseApi.OperateFirewallBackend)
hostRouter.POST("/firewall/family/operate", baseApi.OperateFirewallFamily)
hostRouter.POST("/firewall/settings/ipv6", baseApi.OperateFirewallIPv6)
hostRouter.POST("/firewall/settings/whitelist", baseApi.CreateFirewallPortWhitelist) hostRouter.POST("/firewall/settings/whitelist", baseApi.CreateFirewallPortWhitelist)
hostRouter.POST("/firewall/settings/whitelist/update", baseApi.UpdateFirewallPortWhitelist) hostRouter.POST("/firewall/settings/whitelist/update", baseApi.UpdateFirewallPortWhitelist)
hostRouter.POST("/firewall/settings/whitelist/delete", baseApi.DeleteFirewallPortWhitelist) 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/operate", baseApi.OperateForwardingRules)
hostRouter.POST("/firewall/forward/enable", baseApi.EnableForwarding) hostRouter.POST("/firewall/forward/enable", baseApi.EnableForwarding)
hostRouter.POST("/firewall/rules/search", baseApi.SearchFirewallRules) 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/reset", baseApi.ResetFirewallRules)
hostRouter.POST("/firewall/rules/native/detail", baseApi.LoadFirewallNativeDetail) 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", 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/update", baseApi.UpdateFirewallRule)
hostRouter.POST("/firewall/rules/delete", baseApi.DeleteFirewallRules) hostRouter.POST("/firewall/rules/delete", baseApi.DeleteFirewallRules)
hostRouter.POST("/firewall/rules/reorder", baseApi.ReorderFirewallRule) 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.POST("/firewall/filter/operate", baseApi.OperateFilterChain)
hostRouter.GET("/firewall/docker/ports", baseApi.ListDockerPortGuard) hostRouter.GET("/firewall/docker/ports", baseApi.ListDockerPortGuard)
hostRouter.GET("/firewall/docker/endpoints", baseApi.ListDockerPublishedPorts) 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/operate", baseApi.OperateDockerPortGuard)
hostRouter.POST("/firewall/docker/policies/batch", baseApi.UpsertDockerPortGuardPolicies) hostRouter.POST("/firewall/docker/policies/batch", baseApi.UpsertDockerPortGuardPolicies)
hostRouter.POST("/firewall/docker/policies/delete/batch", baseApi.DeleteDockerPortGuardPolicies) hostRouter.POST("/firewall/docker/policies/delete/batch", baseApi.DeleteDockerPortGuardPolicies)
+21 -14
View File
@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
"time"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu" "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu" "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
@@ -22,14 +23,6 @@ type providerResult struct {
} }
func New() (bool, Client) { func New() (bool, Client) {
return newClient()
}
func NewAll() (bool, Client) {
return New()
}
func newClient() (bool, Client) {
client := Client{} client := Client{}
if available, gpuClient := gpu.New(); available { if available, gpuClient := gpu.New(); available {
client.providers = append(client.providers, gpuProvider{client: gpuClient}) 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) { func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
defer cancel()
results := make([]providerResult, len(c.providers)) results := make([]providerResult, len(c.providers))
var wg sync.WaitGroup var wg sync.WaitGroup
for index, item := range c.providers { for index, item := range c.providers {
@@ -66,8 +64,11 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
}() }()
} }
wg.Wait() wg.Wait()
if err := ctx.Err(); err != nil {
return nil, err
}
snapshot := &Snapshot{DriverVersions: make(map[string]string)} snapshot := &Snapshot{}
var ( var (
errs []error errs []error
active []*ProviderSnapshot active []*ProviderSnapshot
@@ -78,6 +79,11 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
errs = append(errs, result.err) errs = append(errs, result.err)
continue continue
} }
if result.snapshot != nil {
for _, warning := range result.snapshot.Warnings {
errs = append(errs, errors.New(warning))
}
}
if result.snapshot == nil || len(result.snapshot.Devices) == 0 { if result.snapshot == nil || len(result.snapshot.Devices) == 0 {
continue continue
} }
@@ -90,11 +96,8 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
if item.CudaVersion != "" { if item.CudaVersion != "" {
snapshot.Info.CudaVersion = item.CudaVersion snapshot.Info.CudaVersion = item.CudaVersion
} }
if item.DriverVersion != "" { if item.Type == "xpu" {
snapshot.DriverVersions[item.Type] = item.DriverVersion xpuVersion = item.DriverVersion
if item.Type == "xpu" {
xpuVersion = item.DriverVersion
}
} }
} }
snapshot.Warnings = append(snapshot.Warnings, errs...) snapshot.Warnings = append(snapshot.Warnings, errs...)
@@ -102,6 +105,10 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
return nil, fmt.Errorf("calling accelerator monitoring tools failed: %w", errors.Join(errs...)) return nil, fmt.Errorf("calling accelerator monitoring tools failed: %w", errors.Join(errs...))
} }
snapshot.Info.CollectedAt = time.Now()
for _, err := range errs {
snapshot.Info.Warnings = append(snapshot.Info.Warnings, err.Error())
}
snapshot.Info.XPUDriverVersion = xpuVersion snapshot.Info.XPUDriverVersion = xpuVersion
snapshot.Info.Type, snapshot.Info.DriverVersion = mergeProviderMetadata(active) snapshot.Info.Type, snapshot.Info.DriverVersion = mergeProviderMetadata(active)
return snapshot, nil return snapshot, nil
+3 -2
View File
@@ -1,6 +1,7 @@
package accelerator package accelerator
import ( import (
"math"
"strconv" "strconv"
"strings" "strings"
@@ -18,11 +19,11 @@ func metric(display, unit string) Metric {
return result return result
} }
parsed, err := strconv.ParseFloat(matched[1], 64) parsed, err := strconv.ParseFloat(matched[1], 64)
if err != nil { if err != nil || math.IsNaN(parsed) || math.IsInf(parsed, 0) || (parsed < 0 && unit != "°C") {
return result return result
} }
normalized, ok := convertMetricUnit(parsed, matched[2], unit) normalized, ok := convertMetricUnit(parsed, matched[2], unit)
if !ok { if !ok || math.IsNaN(normalized) || math.IsInf(normalized, 0) || (unit == "%" && normalized > 100) {
return result return result
} }
result.Value = &normalized result.Value = &normalized
+79 -63
View File
@@ -12,26 +12,38 @@ import (
func normalizeGPU(item *gpu.Device) Device { func normalizeGPU(item *gpu.Device) Device {
metrics := Metrics{ metrics := Metrics{
Utilization: metric(item.GPUUtil, "%"), MemoryActivity: metric(item.MemoryActivity, "%"),
Temperature: metric(item.Temperature, "°C"), EncoderUtil: metric(item.EncoderUtil, "%"),
Power: metric(item.PowerDraw, "W"), DecoderUtil: metric(item.DecoderUtil, "%"),
PowerLimit: metric(item.MaxPowerLimit, "W"), JPEGUtil: metric(item.JPEGUtil, "%"),
MemoryUsed: memoryMetric(item.MemUsed), OFAUtil: metric(item.OFAUtil, "%"),
MemoryTotal: memoryMetric(item.MemTotal), MediaUtil: metric(item.MediaUtil, "%"),
FanSpeed: metric(item.FanSpeed, "%"), HotspotTemperature: metric(item.HotspotTemperature, "°C"),
FanRPM: metric(item.FanRPM, "RPM"),
MediaFrequency: metric(item.MediaFrequency, "MHz"),
Utilization: metric(item.GPUUtil, "%"),
Temperature: metric(item.Temperature, "°C"),
MemoryTemperature: metric(item.MemoryTemperature, "°C"),
Power: metric(item.PowerDraw, "W"),
PowerLimit: metric(item.PowerLimit, "W"),
Frequency: metric(item.Frequency, "MHz"),
MemoryFrequency: metric(item.MemoryFrequency, "MHz"),
MemoryUsed: memoryMetric(item.MemUsed),
MemoryTotal: memoryMetric(item.MemTotal),
FanSpeed: metric(item.FanSpeed, "%"),
} }
device := Device{ device := Device{
ID: stableID(item.Type, item.BusID, strconv.FormatUint(uint64(item.Index), 10)), ProcessStatus: item.ProcessStatus,
Kind: KindGPU, ID: stableID(item.Type, item.UUID, stableID("pci", item.BusID, strconv.FormatUint(uint64(item.Index), 10))),
Vendor: item.Type, Kind: KindGPU,
Index: int(item.Index), Vendor: item.Type,
Name: item.ProductName, Index: int(item.Index),
Label: fmt.Sprintf("%d - %s", item.Index, item.ProductName), Name: item.ProductName,
BusID: item.BusID, Label: fmt.Sprintf("%d - %s", item.Index, item.ProductName),
Metrics: metrics, BusID: item.BusID,
GPU: item, Metrics: metrics,
GPU: item,
} }
device.Capabilities = capabilities(metrics)
for _, process := range item.Processes { for _, process := range item.Processes {
device.Processes = append(device.Processes, Process{ device.Processes = append(device.Processes, Process{
PID: process.PID, PID: process.PID,
@@ -45,26 +57,36 @@ func normalizeGPU(item *gpu.Device) Device {
func normalizeNPU(item *npu.Device) Device { func normalizeNPU(item *npu.Device) Device {
metrics := Metrics{ metrics := Metrics{
Utilization: metric(item.AICore, "%"), AICPUUtil: metric(item.AICPUUtil, "%"),
Temperature: metric(item.Temperature, "°C"), CtrlCPUUtil: metric(item.CtrlCPUUtil, "%"),
Power: metric(item.PowerDraw, "W"), DDRBandwidth: metric(item.DDRBandwidth, "%"),
MemoryUsed: memoryMetric(item.MemUsed), HBMBandwidth: metric(item.HBMBandwidth, "%"),
MemoryTotal: memoryMetric(item.MemTotal), DDRUsed: metric(item.MemoryUsed, "MiB"),
DDRTotal: metric(item.MemoryTotal, "MiB"),
HBMUsed: metric(item.HBMUsed, "MiB"),
HBMTotal: metric(item.HBMTotal, "MiB"),
HugepagesUsed: metric(item.HugepagesUsed, "pages"),
HugepagesTotal: metric(item.HugepagesTotal, "pages"),
Utilization: metric(item.AICore, "%"),
Temperature: metric(item.Temperature, "°C"),
Power: metric(item.PowerDraw, "W"),
MemoryUsed: memoryMetric(item.MemUsed),
MemoryTotal: memoryMetric(item.MemTotal),
} }
device := Device{ device := Device{
ID: fmt.Sprintf("ascend:%d:%d", item.NPUIndex, item.ChipIndex), ProcessStatus: item.ProcessStatus,
Kind: KindNPU, ID: fmt.Sprintf("%s:%d", stableID("ascend", item.BusID, strconv.FormatUint(uint64(item.NPUIndex), 10)), item.ChipIndex),
Vendor: "ascend", Kind: KindNPU,
Index: int(item.Index), Vendor: "ascend",
NPUIndex: int(item.NPUIndex), Index: int(item.Index),
ChipIndex: int(item.ChipIndex), NPUIndex: int(item.NPUIndex),
Name: item.ProductName, ChipIndex: int(item.ChipIndex),
Label: fmt.Sprintf("NPU %d / Chip %d - %s", item.NPUIndex, item.ChipIndex, item.ProductName), Name: item.ProductName,
BusID: item.BusID, Label: fmt.Sprintf("NPU %d / Chip %d - %s", item.NPUIndex, item.ChipIndex, item.ProductName),
Metrics: metrics, BusID: item.BusID,
NPU: item, Metrics: metrics,
NPU: item,
} }
device.Capabilities = capabilities(metrics)
for _, process := range item.Processes { for _, process := range item.Processes {
device.Processes = append(device.Processes, Process{ device.Processes = append(device.Processes, Process{
PID: process.PID, PID: process.PID,
@@ -78,26 +100,32 @@ func normalizeNPU(item *npu.Device) Device {
func normalizeXPU(item *xpu.Device) Device { func normalizeXPU(item *xpu.Device) Device {
metrics := Metrics{ metrics := Metrics{
Utilization: metric(item.Stats.GPUUtil, "%"), MediaUtil: metric(item.Stats.MediaUtil, "%"),
Temperature: metric(item.Stats.Temperature, "°C"), ComputeUtil: metric(item.Stats.ComputeUtil, "%"),
Power: metric(item.Stats.Power, "W"), CopyUtil: metric(item.Stats.CopyUtil, "%"),
MemoryUsed: memoryMetric(item.Stats.MemoryUsed), MediaFrequency: metric(item.Stats.MediaFrequency, "MHz"),
MemoryTotal: memoryMetric(item.Basic.Memory), MemoryTemperature: metric(item.Stats.MemoryTemperature, "°C"),
MemoryUtil: metric(item.Stats.MemoryUtil, "%"), MemoryBandwidth: metric(item.Stats.MemoryBandwidthUtil, "%"),
Frequency: metric(item.Stats.Frequency, "MHz"), Utilization: metric(item.Stats.GPUUtil, "%"),
Temperature: metric(item.Stats.Temperature, "°C"),
Power: metric(item.Stats.Power, "W"),
MemoryUsed: memoryMetric(item.Stats.MemoryUsed),
MemoryTotal: memoryMetric(item.Basic.Memory),
MemoryUtil: metric(item.Stats.MemoryUtil, "%"),
Frequency: metric(item.Stats.Frequency, "MHz"),
} }
device := Device{ device := Device{
ID: stableID("xpu", item.Basic.PciBdfAddress, strconv.Itoa(item.Basic.DeviceID)), ProcessStatus: item.ProcessStatus,
Kind: KindXPU, ID: stableID("xpu", item.Basic.UUID, stableID("pci", item.Basic.PciBdfAddress, strconv.Itoa(item.Basic.DeviceID))),
Vendor: item.Basic.VendorName, Kind: KindXPU,
Index: item.Basic.DeviceID, Vendor: item.Basic.VendorName,
Name: item.Basic.DeviceName, Index: item.Basic.DeviceID,
Label: fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName), Name: item.Basic.DeviceName,
BusID: item.Basic.PciBdfAddress, Label: fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName),
Metrics: metrics, BusID: item.Basic.PciBdfAddress,
XPU: item, Metrics: metrics,
XPU: item,
} }
device.Capabilities = capabilities(metrics)
for _, process := range item.Processes { for _, process := range item.Processes {
device.Processes = append(device.Processes, Process{ device.Processes = append(device.Processes, Process{
PID: strconv.Itoa(process.PID), PID: strconv.Itoa(process.PID),
@@ -110,18 +138,6 @@ func normalizeXPU(item *xpu.Device) Device {
return device return device
} }
func capabilities(metrics Metrics) Capabilities {
return Capabilities{
Utilization: metrics.Utilization.Available(),
Temperature: metrics.Temperature.Available(),
Power: metrics.Power.Available(),
PowerLimit: metrics.PowerLimit.Available(),
Memory: metrics.MemoryUsed.Available() || metrics.MemoryTotal.Available(),
FanSpeed: metrics.FanSpeed.Available(),
Frequency: metrics.Frequency.Available(),
}
}
func stableID(vendor, busID, fallback string) string { func stableID(vendor, busID, fallback string) string {
if busID != "" && !strings.EqualFold(busID, "N/A") { if busID != "" && !strings.EqualFold(busID, "N/A") {
return vendor + ":" + busID return vendor + ":" + busID
+17 -1
View File
@@ -2,6 +2,7 @@ package accelerator
import ( import (
"context" "context"
"fmt"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu" "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu" "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
@@ -26,6 +27,7 @@ func (p gpuProvider) Collect(ctx context.Context) (*ProviderSnapshot, error) {
} }
result := &ProviderSnapshot{ result := &ProviderSnapshot{
Type: info.Type, Type: info.Type,
Warnings: info.Warnings,
DriverVersion: info.DriverVersion, DriverVersion: info.DriverVersion,
CudaVersion: info.CudaVersion, CudaVersion: info.CudaVersion,
GPUs: info.Devices, GPUs: info.Devices,
@@ -49,6 +51,7 @@ func (p npuProvider) Collect(ctx context.Context) (*ProviderSnapshot, error) {
} }
result := &ProviderSnapshot{ result := &ProviderSnapshot{
Type: info.Type, Type: info.Type,
Warnings: info.Warnings,
DriverVersion: info.DriverVersion, DriverVersion: info.DriverVersion,
NPUs: info.Devices, NPUs: info.Devices,
} }
@@ -71,11 +74,24 @@ func (p xpuProvider) Collect(ctx context.Context) (*ProviderSnapshot, error) {
} }
result := &ProviderSnapshot{ result := &ProviderSnapshot{
Type: info.Type, Type: info.Type,
Warnings: info.Warnings,
DriverVersion: info.DriverVersion, DriverVersion: info.DriverVersion,
XPUs: info.Devices, XPUs: info.Devices,
} }
for index := range result.XPUs { for index := range result.XPUs {
result.Devices = append(result.Devices, normalizeXPU(&result.XPUs[index]))
parent := normalizeXPU(&result.XPUs[index])
result.Devices = append(result.Devices, parent)
for _, tile := range result.XPUs[index].Tiles {
raw := xpu.Device{Basic: result.XPUs[index].Basic, Stats: tile.Stats, ProcessStatus: "unavailable"}
raw.Basic.Memory = ""
raw.Basic.FreeMemory = ""
device := normalizeXPU(&raw)
device.ParentID = parent.ID
device.ID = fmt.Sprintf("%s:tile:%d", parent.ID, tile.TileID)
device.Label = fmt.Sprintf("%s / Tile %d", parent.Label, tile.TileID)
result.Devices = append(result.Devices, device)
}
} }
return result, nil return result, nil
} }
+54 -34
View File
@@ -2,6 +2,7 @@ package accelerator
import ( import (
"errors" "errors"
"time"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu" "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu" "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
@@ -17,6 +18,9 @@ const (
) )
type Info struct { type Info struct {
CollectedAt time.Time `json:"collectedAt"`
Warnings []string `json:"warnings"`
Type string `json:"type"` Type string `json:"type"`
CudaVersion string `json:"cudaVersion"` CudaVersion string `json:"cudaVersion"`
DriverVersion string `json:"driverVersion"` DriverVersion string `json:"driverVersion"`
@@ -44,25 +48,40 @@ func (m Metric) ValueOrZero() float64 {
} }
type Metrics struct { type Metrics struct {
Utilization Metric MemoryActivity Metric
Temperature Metric EncoderUtil Metric
Power Metric DecoderUtil Metric
PowerLimit Metric JPEGUtil Metric
MemoryUsed Metric OFAUtil Metric
MemoryTotal Metric MediaUtil Metric
MemoryUtil Metric ComputeUtil Metric
FanSpeed Metric CopyUtil Metric
Frequency Metric HotspotTemperature Metric
} FanRPM Metric
AICPUUtil Metric
CtrlCPUUtil Metric
DDRUsed Metric
DDRTotal Metric
HBMUsed Metric
HBMTotal Metric
DDRBandwidth Metric
HBMBandwidth Metric
MemoryBandwidth Metric
MediaFrequency Metric
HugepagesUsed Metric
HugepagesTotal Metric
type Capabilities struct { MemoryTemperature Metric
Utilization bool Utilization Metric
Temperature bool Temperature Metric
Power bool Power Metric
PowerLimit bool PowerLimit Metric
Memory bool MemoryUsed Metric
FanSpeed bool MemoryTotal Metric
Frequency bool MemoryUtil Metric
FanSpeed Metric
Frequency Metric
MemoryFrequency Metric
} }
type Process struct { type Process struct {
@@ -74,18 +93,19 @@ type Process struct {
} }
type Device struct { type Device struct {
ID string ParentID string
Kind Kind ProcessStatus string
Vendor string ID string
Index int Kind Kind
NPUIndex int Vendor string
ChipIndex int Index int
Name string NPUIndex int
Label string ChipIndex int
BusID string Name string
Metrics Metrics Label string
Capabilities Capabilities BusID string
Processes []Process Metrics Metrics
Processes []Process
GPU *gpu.Device `json:"-"` GPU *gpu.Device `json:"-"`
NPU *npu.Device `json:"-"` NPU *npu.Device `json:"-"`
@@ -93,10 +113,9 @@ type Device struct {
} }
type Snapshot struct { type Snapshot struct {
Info Info Info Info
Devices []Device Devices []Device
DriverVersions map[string]string Warnings []error
Warnings []error
} }
func (s Snapshot) Warning() error { func (s Snapshot) Warning() error {
@@ -104,6 +123,7 @@ func (s Snapshot) Warning() error {
} }
type ProviderSnapshot struct { type ProviderSnapshot struct {
Warnings []string
Type string Type string
DriverVersion string DriverVersion string
CudaVersion string CudaVersion string
+58 -17
View File
@@ -3,12 +3,13 @@ package gpu
import ( import (
"context" "context"
"fmt" "fmt"
"math"
"sort" "sort"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/cmd"
) )
@@ -32,16 +33,22 @@ func findAMDSMI() (string, bool) {
func (a amdSMI) LoadInfo(ctx context.Context) (*Info, error) { func (a amdSMI) LoadInfo(ctx context.Context) (*Info, error) {
var ( var (
staticData string staticData string
metricData string metricData string
processData string extendedData string
staticErr error extendedErr error
metricErr error processData string
processErr error staticErr error
wg sync.WaitGroup metricErr error
processErr error
wg sync.WaitGroup
) )
wg.Add(3) wg.Add(4)
go func() {
defer wg.Done()
extendedData, extendedErr = runAMDSMI(ctx, a.command, "metric", "--clock", "--ecc", "--json")
}()
go func() { go func() {
defer wg.Done() defer wg.Done()
staticData, staticErr = runAMDSMI(ctx, a.command, "static", "--asic", "--bus", "--driver", "--limit", "--json") staticData, staticErr = runAMDSMI(ctx, a.command, "static", "--asic", "--bus", "--driver", "--limit", "--json")
@@ -64,16 +71,25 @@ func (a amdSMI) LoadInfo(ctx context.Context) (*Info, error) {
return nil, fmt.Errorf("parsing %s static output failed: %w", a.command, err) return nil, fmt.Errorf("parsing %s static output failed: %w", a.command, err)
} }
if metricErr == nil {
metricErr = applyAMDMetrics(info, metricData)
}
if metricErr != nil { if metricErr != nil {
global.LOG.Warnf("calling %s metric failed, metrics will be omitted: %v", a.command, metricErr) info.Warnings = append(info.Warnings, fmt.Sprintf("%s metrics: %v", a.command, metricErr))
} else if err := applyAMDMetrics(info, metricData); err != nil { }
global.LOG.Warnf("parsing %s metric output failed, metrics will be omitted: %v", a.command, err) if extendedErr == nil {
extendedErr = applyAMDMetrics(info, extendedData)
}
if extendedErr != nil {
info.Warnings = append(info.Warnings, fmt.Sprintf("%s extended metrics: %v", a.command, extendedErr))
}
if processErr == nil {
processErr = applyAMDProcesses(info, processData)
} }
if processErr != nil { if processErr != nil {
global.LOG.Warnf("calling %s process failed, process information will be omitted: %v", a.command, processErr) info.Warnings = append(info.Warnings, fmt.Sprintf("%s processes: %v", a.command, processErr))
} else if err := applyAMDProcesses(info, processData); err != nil {
global.LOG.Warnf("parsing %s process output failed, process information will be omitted: %v", a.command, err)
} }
return info, nil return info, nil
} }
@@ -96,6 +112,9 @@ func parseAMDStatic(data string) (*Info, error) {
} }
device := Device{ device := Device{
Type: "amd", Type: "amd",
UUID: amdStringAt(row, "uuid", "asic.uuid"),
DriverVersion: amdStringAt(row, "driver.version", "driver_version", "amdgpu_version"),
ProcessStatus: "unavailable",
Index: index, Index: index,
ProductName: amdStringAt(row, "asic.market_name", "market_name", "gpu_name"), ProductName: amdStringAt(row, "asic.market_name", "market_name", "gpu_name"),
PersistenceMode: "N/A", PersistenceMode: "N/A",
@@ -111,6 +130,7 @@ func parseAMDStatic(data string) (*Info, error) {
"limit.max_power_limit", "limit.max_power_limit",
"limit.max_power", "limit.max_power",
), ),
PowerLimit: amdMetricAt(row, "W", "limit.ppt0.socket_power_limit", "limit.socket_power_limit"),
MemUsed: "N/A", MemUsed: "N/A",
MemTotal: "N/A", MemTotal: "N/A",
GPUUtil: "N/A", GPUUtil: "N/A",
@@ -150,11 +170,31 @@ func applyAMDMetrics(info *Info, data string) error {
continue continue
} }
setAMDMetric(&device.GPUUtil, row, "%", "usage.gfx_activity", "usage.gfx", "gfx_activity", "gfx_usage") setAMDMetric(&device.GPUUtil, row, "%", "usage.gfx_activity", "usage.gfx", "gfx_activity", "gfx_usage")
setAMDMetric(&device.Temperature, row, "°C", "temperature.hotspot", "temperature.edge", "hotspot_temperature", "gpu_temperature", "gpu_temp") setAMDMetric(&device.Temperature, row, "°C", "temperature.edge", "gpu_temperature", "gpu_temp")
setAMDMetric(&device.PowerDraw, row, "W", "power.socket_power", "socket_power", "power_usage") setAMDMetric(&device.PowerDraw, row, "W", "power.socket_power", "socket_power", "power_usage")
setAMDMetric(&device.MemUsed, row, "MB", "mem_usage.used_vram", "vram.used", "used_vram", "vram_used") setAMDMetric(&device.MemUsed, row, "MB", "mem_usage.used_vram", "vram.used", "used_vram", "vram_used")
setAMDMetric(&device.MemTotal, row, "MB", "mem_usage.total_vram", "vram.total", "total_vram", "vram_total") setAMDMetric(&device.MemTotal, row, "MB", "mem_usage.total_vram", "vram.total", "total_vram", "vram_total")
setAMDMetric(&device.FanSpeed, row, "%", "fan.speed", "fan_speed")
setAMDMetric(&device.FanSpeed, row, "%", "fan.usage")
if amdMetricAt(row, "%", "fan.usage") == "" {
speed, speedErr := strconv.ParseFloat(amdStringAt(row, "fan.speed"), 64)
maximum, maxErr := strconv.ParseFloat(amdStringAt(row, "fan.max"), 64)
if speedErr == nil && maxErr == nil && !math.IsNaN(speed) && !math.IsInf(speed, 0) && maximum > 0 && !math.IsInf(maximum, 0) && speed >= 0 && speed <= maximum {
device.FanSpeed = fmt.Sprintf("%.2f %%", speed/maximum*100)
}
}
setAMDMetric(&device.FanRPM, row, "RPM", "fan.rpm")
setAMDMetric(&device.HotspotTemperature, row, "°C", "temperature.hotspot", "hotspot_temperature")
setAMDMetric(&device.MemoryTemperature, row, "°C", "temperature.mem")
setAMDMetric(&device.MemoryActivity, row, "%", "usage.umc_activity")
setAMDMetric(&device.MediaUtil, row, "%", "usage.mm_activity")
setAMDMetric(&device.Frequency, row, "MHz", "clock.gfx_0.clk")
setAMDMetric(&device.MemoryFrequency, row, "MHz", "clock.mem_0.clk")
correctable := amdStringAt(row, "ecc.total_correctable_count", "ecc.correctable_count")
uncorrectable := amdStringAt(row, "ecc.total_uncorrectable_count", "ecc.uncorrectable_count")
if correctable != "" || uncorrectable != "" {
device.ECCErrors = []ECCError{{Scope: "Total", Correctable: correctable, Uncorrectable: uncorrectable}}
}
if value := amdStringAt(row, "perf_level", "performance_level"); value != "" { if value := amdStringAt(row, "perf_level", "performance_level"); value != "" {
device.PerformanceState = value device.PerformanceState = value
} }
@@ -177,6 +217,7 @@ func applyAMDProcesses(info *Info, data string) error {
if !ok { if !ok {
continue continue
} }
device.ProcessStatus = "ok"
processList, _ := amdValueAt(row, "process_list") processList, _ := amdValueAt(row, "process_list")
items := amdObjectList(processList) items := amdObjectList(processList)
if len(items) == 0 { if len(items) == 0 {
+4
View File
@@ -64,6 +64,7 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
if result.info == nil { if result.info == nil {
continue continue
} }
merged.Warnings = append(merged.Warnings, result.info.Warnings...)
if result.info.Type != "" { if result.info.Type != "" {
types = append(types, result.info.Type) types = append(types, result.info.Type)
} }
@@ -86,6 +87,9 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
if len(merged.Devices) == 0 && len(errs) > 0 { if len(merged.Devices) == 0 && len(errs) > 0 {
return nil, fmt.Errorf("calling GPU monitoring tools failed: %w", errors.Join(errs...)) return nil, fmt.Errorf("calling GPU monitoring tools failed: %w", errors.Join(errs...))
} }
for _, err := range errs {
merged.Warnings = append(merged.Warnings, err.Error())
}
return merged, nil return merged, nil
} }
+76 -69
View File
@@ -1,16 +1,12 @@
package gpu package gpu
import ( import (
"bytes"
"context" "context"
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
"io"
"strings" "strings"
"time" "time"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/cmd"
) )
@@ -24,41 +20,10 @@ func (n nvidiaSMI) LoadInfo(ctx context.Context) (*Info, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("calling %s failed: %w", nvidiaSMICommand, err) return nil, fmt.Errorf("calling %s failed: %w", nvidiaSMICommand, err)
} }
data := []byte(itemData) return parseNvidiaSMI([]byte(itemData))
version := "v11"
buf := bytes.NewBuffer(data)
decoder := xml.NewDecoder(buf)
for {
token, err := decoder.Token()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, fmt.Errorf("reading token failed: %w", err)
}
d, ok := token.(xml.Directive)
if !ok {
continue
}
directive := string(d)
if !strings.HasPrefix(directive, "DOCTYPE") {
continue
}
parts := strings.Split(directive, " ")
s := strings.Trim(parts[len(parts)-1], "\" ")
if strings.HasPrefix(s, "nvsmi_device_") && strings.HasSuffix(s, ".dtd") {
version = strings.TrimSuffix(strings.TrimPrefix(s, "nvsmi_device_"), ".dtd")
} else {
global.LOG.Debugf("Cannot find schema version in %q", directive)
}
break
}
return parseNvidiaSMI(data, version)
} }
func parseNvidiaSMI(buf []byte, version string) (*Info, error) { func parseNvidiaSMI(buf []byte) (*Info, error) {
var ( var (
s nvidiaSMIResponse s nvidiaSMIResponse
info Info info Info
@@ -72,42 +37,84 @@ func parseNvidiaSMI(buf []byte, version string) (*Info, error) {
info.DriverVersion = s.DriverVersion info.DriverVersion = s.DriverVersion
for i := range s.Gpu { for i := range s.Gpu {
gpuItem := Device{ gpuItem := Device{
Type: "nvidia", Type: "nvidia",
Index: uint(i), MemoryActivity: s.Gpu[i].Utilization.MemoryUtil,
ProductName: s.Gpu[i].ProductName, EncoderUtil: s.Gpu[i].Utilization.EncoderUtil,
PersistenceMode: s.Gpu[i].PersistenceMode, DecoderUtil: s.Gpu[i].Utilization.DecoderUtil,
BusID: s.Gpu[i].ID, JPEGUtil: s.Gpu[i].Utilization.JpegUtil,
DisplayActive: s.Gpu[i].DisplayActive, OFAUtil: s.Gpu[i].Utilization.OfaUtil,
ECC: s.Gpu[i].EccErrors.Volatile.DramUncorrectable, MediaFrequency: s.Gpu[i].Clocks.VideoClock,
FanSpeed: s.Gpu[i].FanSpeed, UUID: s.Gpu[i].UUID,
Temperature: s.Gpu[i].Temperature.GpuTemp, DriverVersion: s.DriverVersion,
PerformanceState: s.Gpu[i].PerformanceState, Architecture: s.Gpu[i].ProductArchitecture,
MemUsed: s.Gpu[i].FbMemoryUsage.Used, Frequency: s.Gpu[i].Clocks.GraphicsClock,
MemTotal: s.Gpu[i].FbMemoryUsage.Total, MemoryFrequency: s.Gpu[i].Clocks.MemClock,
GPUUtil: s.Gpu[i].Utilization.GpuUtil, MemoryTemperature: s.Gpu[i].Temperature.MemoryTemp,
ComputeMode: s.Gpu[i].ComputeMode, MemoryFree: s.Gpu[i].FbMemoryUsage.Free,
MigMode: s.Gpu[i].MigMode.CurrentMig, MemoryReserved: s.Gpu[i].FbMemoryUsage.Reserved,
} PCIeGeneration: firstSMIValue(s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.DeviceCurrentLinkGen, s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.CurrentLinkGen),
if version == "v12" || version == "v13" { PCIeMaxGeneration: firstSMIValue(s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.MaxDeviceLinkGen, s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.MaxLinkGen),
gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.PowerDraw PCIeWidth: s.Gpu[i].Pci.PciGpuLinkInfo.LinkWidths.CurrentLinkWidth,
if gpuItem.PowerDraw == "" { PCIeMaxWidth: s.Gpu[i].Pci.PciGpuLinkInfo.LinkWidths.MaxLinkWidth,
gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.InstantPowerDraw ProcessStatus: "unavailable",
} Index: uint(i),
gpuItem.MaxPowerLimit = s.Gpu[i].GpuPowerReadings.CurrentPowerLimit ProductName: s.Gpu[i].ProductName,
} else { PersistenceMode: s.Gpu[i].PersistenceMode,
gpuItem.PowerDraw = s.Gpu[i].PowerReadings.PowerDraw BusID: s.Gpu[i].ID,
gpuItem.MaxPowerLimit = s.Gpu[i].PowerReadings.MaxPowerLimit DisplayActive: s.Gpu[i].DisplayActive,
ECC: s.Gpu[i].EccMode.CurrentEcc,
FanSpeed: s.Gpu[i].FanSpeed,
Temperature: s.Gpu[i].Temperature.GpuTemp,
PerformanceState: s.Gpu[i].PerformanceState,
MemUsed: s.Gpu[i].FbMemoryUsage.Used,
MemTotal: s.Gpu[i].FbMemoryUsage.Total,
GPUUtil: s.Gpu[i].Utilization.GpuUtil,
ComputeMode: s.Gpu[i].ComputeMode,
MigMode: s.Gpu[i].MigMode.CurrentMig,
} }
gpuItem.ECCPending = s.Gpu[i].EccMode.PendingEcc
gpuItem.ECCErrors = []ECCError{
{Scope: "Volatile Total", Correctable: s.Gpu[i].EccErrors.Volatile.SingleBit.Total, Uncorrectable: s.Gpu[i].EccErrors.Volatile.DoubleBit.Total},
{Scope: "Aggregate Total", Correctable: s.Gpu[i].EccErrors.Aggregate.SingleBit.Total, Uncorrectable: s.Gpu[i].EccErrors.Aggregate.DoubleBit.Total},
for _, process := range s.Gpu[i].Processes.ProcessInfo { {Scope: "Volatile DRAM", Correctable: s.Gpu[i].EccErrors.Volatile.DramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Volatile.DramUncorrectable},
gpuItem.Processes = append(gpuItem.Processes, Process{ {Scope: "Volatile SRAM", Correctable: s.Gpu[i].EccErrors.Volatile.SramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Volatile.SramUncorrectable},
PID: process.Pid, {Scope: "Aggregate DRAM", Correctable: s.Gpu[i].EccErrors.Aggregate.DramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Aggregate.DramUncorrectable},
Type: process.Type, {Scope: "Aggregate SRAM", Correctable: s.Gpu[i].EccErrors.Aggregate.SramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Aggregate.SramUncorrectable},
ProcessName: process.ProcessName, }
UsedMemory: process.UsedMemory, gpuItem.PowerDraw = firstSMIValue(s.Gpu[i].GpuPowerReadings.PowerDraw, s.Gpu[i].GpuPowerReadings.InstantPowerDraw, s.Gpu[i].PowerReadings.PowerDraw)
}) gpuItem.PowerLimit = firstSMIValue(s.Gpu[i].GpuPowerReadings.CurrentPowerLimit, s.Gpu[i].PowerReadings.EnforcedPowerLimit, s.Gpu[i].PowerReadings.PowerLimit)
gpuItem.MaxPowerLimit = firstSMIValue(s.Gpu[i].GpuPowerReadings.MaxPowerLimit, s.Gpu[i].PowerReadings.MaxPowerLimit)
gpuItem.DefaultPowerLimit = firstSMIValue(s.Gpu[i].GpuPowerReadings.DefaultPowerLimit, s.Gpu[i].PowerReadings.DefaultPowerLimit)
for _, event := range append(s.Gpu[i].ClocksEventReasons.Reasons, s.Gpu[i].ClocksThrottleReasons.Reasons...) {
if strings.EqualFold(strings.TrimSpace(event.Value), "Active") {
gpuItem.ClockEvents = append(gpuItem.ClockEvents, strings.TrimPrefix(strings.TrimPrefix(event.XMLName.Local, "clocks_event_reason_"), "clocks_throttle_reason_"))
}
}
if s.Gpu[i].Processes != nil && strings.TrimSpace(s.Gpu[i].Processes.Text) == "" {
gpuItem.ProcessStatus = "ok"
}
if s.Gpu[i].Processes != nil {
for _, process := range s.Gpu[i].Processes.ProcessInfo {
gpuItem.Processes = append(gpuItem.Processes, Process{
PID: process.Pid,
Type: process.Type,
ProcessName: process.ProcessName,
UsedMemory: process.UsedMemory,
})
}
} }
info.Devices = append(info.Devices, gpuItem) info.Devices = append(info.Devices, gpuItem)
} }
return &info, nil return &info, nil
} }
func firstSMIValue(values ...string) string {
for _, value := range values {
value = strings.TrimSpace(value)
if value != "" && !strings.EqualFold(value, "N/A") && !strings.EqualFold(value, "Not Supported") {
return value
}
}
return ""
}
+30 -13
View File
@@ -1,9 +1,12 @@
package gpu package gpu
import "encoding/xml"
type nvidiaSMIResponse struct { type nvidiaSMIResponse struct {
AttachedGpus string `xml:"attached_gpus"` XMLName xml.Name `xml:"nvidia_smi_log"`
CudaVersion string `xml:"cuda_version"` AttachedGpus string `xml:"attached_gpus"`
DriverVersion string `xml:"driver_version"` CudaVersion string `xml:"cuda_version"`
DriverVersion string `xml:"driver_version"`
Gpu []struct { Gpu []struct {
ID string `xml:"id,attr"` ID string `xml:"id,attr"`
AccountedProcesses struct{} `xml:"accounted_processes"` AccountedProcesses struct{} `xml:"accounted_processes"`
@@ -37,16 +40,17 @@ type nvidiaSMIResponse struct {
VideoClock string `xml:"video_clock"` VideoClock string `xml:"video_clock"`
} `xml:"clocks"` } `xml:"clocks"`
ClocksEventReasons struct { ClocksEventReasons struct {
ClocksEventReasonApplicationsClocksSetting string `xml:"clocks_event_reason_applications_clocks_setting"` Reasons []struct {
ClocksEventReasonDisplayClocksSetting string `xml:"clocks_event_reason_display_clocks_setting"` XMLName xml.Name
ClocksEventReasonGpuIdle string `xml:"clocks_event_reason_gpu_idle"` Value string `xml:",chardata"`
ClocksEventReasonHwPowerBrakeSlowdown string `xml:"clocks_event_reason_hw_power_brake_slowdown"` } `xml:",any"`
ClocksEventReasonHwSlowdown string `xml:"clocks_event_reason_hw_slowdown"`
ClocksEventReasonHwThermalSlowdown string `xml:"clocks_event_reason_hw_thermal_slowdown"`
ClocksEventReasonSwPowerCap string `xml:"clocks_event_reason_sw_power_cap"`
ClocksEventReasonSwThermalSlowdown string `xml:"clocks_event_reason_sw_thermal_slowdown"`
ClocksEventReasonSyncBoost string `xml:"clocks_event_reason_sync_boost"`
} `xml:"clocks_event_reasons"` } `xml:"clocks_event_reasons"`
ClocksThrottleReasons struct {
Reasons []struct {
XMLName xml.Name
Value string `xml:",chardata"`
} `xml:",any"`
} `xml:"clocks_throttle_reasons"`
ComputeMode string `xml:"compute_mode"` ComputeMode string `xml:"compute_mode"`
DefaultApplicationsClocks struct { DefaultApplicationsClocks struct {
GraphicsClock string `xml:"graphics_clock"` GraphicsClock string `xml:"graphics_clock"`
@@ -63,12 +67,24 @@ type nvidiaSMIResponse struct {
} `xml:"driver_model"` } `xml:"driver_model"`
EccErrors struct { EccErrors struct {
Aggregate struct { Aggregate struct {
SingleBit struct {
Total string `xml:"total"`
} `xml:"single_bit"`
DoubleBit struct {
Total string `xml:"total"`
} `xml:"double_bit"`
DramCorrectable string `xml:"dram_correctable"` DramCorrectable string `xml:"dram_correctable"`
DramUncorrectable string `xml:"dram_uncorrectable"` DramUncorrectable string `xml:"dram_uncorrectable"`
SramCorrectable string `xml:"sram_correctable"` SramCorrectable string `xml:"sram_correctable"`
SramUncorrectable string `xml:"sram_uncorrectable"` SramUncorrectable string `xml:"sram_uncorrectable"`
} `xml:"aggregate"` } `xml:"aggregate"`
Volatile struct { Volatile struct {
SingleBit struct {
Total string `xml:"total"`
} `xml:"single_bit"`
DoubleBit struct {
Total string `xml:"total"`
} `xml:"double_bit"`
DramCorrectable string `xml:"dram_correctable"` DramCorrectable string `xml:"dram_correctable"`
DramUncorrectable string `xml:"dram_uncorrectable"` DramUncorrectable string `xml:"dram_uncorrectable"`
SramCorrectable string `xml:"sram_correctable"` SramCorrectable string `xml:"sram_correctable"`
@@ -226,7 +242,8 @@ type nvidiaSMIResponse struct {
MinPowerLimit string `xml:"min_power_limit"` MinPowerLimit string `xml:"min_power_limit"`
MaxPowerLimit string `xml:"max_power_limit"` MaxPowerLimit string `xml:"max_power_limit"`
} `xml:"power_readings"` } `xml:"power_readings"`
Processes struct { Processes *struct {
Text string `xml:",chardata"`
ProcessInfo []struct { ProcessInfo []struct {
Pid string `xml:"pid"` Pid string `xml:"pid"`
Type string `xml:"type"` Type string `xml:"type"`
+37
View File
@@ -1,6 +1,8 @@
package gpu package gpu
type Info struct { type Info struct {
Warnings []string `json:"warnings"`
CudaVersion string `json:"cudaVersion"` CudaVersion string `json:"cudaVersion"`
DriverVersion string `json:"driverVersion"` DriverVersion string `json:"driverVersion"`
Type string `json:"type"` Type string `json:"type"`
@@ -8,7 +10,42 @@ type Info struct {
Devices []Device `json:"gpu"` Devices []Device `json:"gpu"`
} }
type ECCError struct {
Scope string `json:"scope"`
Correctable string `json:"correctable"`
Uncorrectable string `json:"uncorrectable"`
}
type Device struct { type Device struct {
ECCPending string `json:"eccPending"`
ECCErrors []ECCError `json:"eccErrors"`
MemoryActivity string `json:"memoryActivity"`
EncoderUtil string `json:"encoderUtil"`
DecoderUtil string `json:"decoderUtil"`
JPEGUtil string `json:"jpegUtil"`
OFAUtil string `json:"ofaUtil"`
MediaUtil string `json:"mediaUtil"`
HotspotTemperature string `json:"hotspotTemperature"`
FanRPM string `json:"fanRPM"`
MediaFrequency string `json:"mediaFrequency"`
UUID string `json:"uuid"`
DriverVersion string `json:"driverVersion"`
Architecture string `json:"architecture"`
Frequency string `json:"frequency"`
MemoryFrequency string `json:"memoryFrequency"`
MemoryTemperature string `json:"memoryTemperature"`
MemoryFree string `json:"memoryFree"`
MemoryReserved string `json:"memoryReserved"`
PowerLimit string `json:"powerLimit"`
DefaultPowerLimit string `json:"defaultPowerLimit"`
PCIeGeneration string `json:"pcieGeneration"`
PCIeMaxGeneration string `json:"pcieMaxGeneration"`
PCIeWidth string `json:"pcieWidth"`
PCIeMaxWidth string `json:"pcieMaxWidth"`
ProcessStatus string `json:"processStatus"`
ClockEvents []string `json:"clockEvents"`
Type string `json:"type"` Type string `json:"type"`
Index uint `json:"index"` Index uint `json:"index"`
ProductName string `json:"productName"` ProductName string `json:"productName"`
+61 -1
View File
@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/cmd"
@@ -34,7 +35,31 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("calling %s failed: %w", ascendSMICommand, err) return nil, fmt.Errorf("calling %s failed: %w", ascendSMICommand, err)
} }
return parseAscendSMI(itemData), nil
info := parseAscendSMI(itemData)
var wg sync.WaitGroup
warnings := make([]string, len(info.Devices))
for i := range info.Devices {
wg.Add(1)
go func(index int) {
defer wg.Done()
device := &info.Devices[index]
mgr := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(5*time.Second))
data, err := mgr.RunWithStdout(ascendSMICommand, "info", "-t", "usages", "-i", strconv.FormatUint(uint64(device.NPUIndex), 10), "-c", strconv.FormatUint(uint64(device.ChipIndex), 10))
if err != nil {
warnings[index] = fmt.Sprintf("npu-smi usages %d/%d: %v", device.NPUIndex, device.ChipIndex, err)
return
}
applyAscendUsages(device, data)
}(i)
}
wg.Wait()
for _, warning := range warnings {
if warning != "" {
info.Warnings = append(info.Warnings, warning)
}
}
return info, nil
} }
func parseAscendSMI(data string) *Info { func parseAscendSMI(data string) *Info {
@@ -158,6 +183,12 @@ func parseAscendSMI(data string) *Info {
pending = nil pending = nil
} }
for i := range info.Devices {
info.Devices[i].ProcessStatus = "unavailable"
if processSection {
info.Devices[i].ProcessStatus = "ok"
}
}
return info return info
} }
@@ -208,6 +239,9 @@ func ascendMemoryPools(value, header string) (string, string, string, string) {
} }
hbm := usage[len(usage)-1] hbm := usage[len(usage)-1]
if !strings.Contains(normalizedHeader, "MEMORYUSAGE") {
memoryUsed, memoryTotal = "", ""
}
return memoryUsed, memoryTotal, hbm[0] + " MB", hbm[1] + " MB" return memoryUsed, memoryTotal, hbm[0] + " MB", hbm[1] + " MB"
} }
@@ -233,3 +267,29 @@ func ascendValueWithUnit(value, unit string) string {
} }
return value + " " + unit return value + " " + unit
} }
func applyAscendUsages(device *Device, data string) {
for _, line := range strings.Split(data, "\n") {
key, value, ok := strings.Cut(line, ":")
if !ok {
continue
}
key = strings.ToLower(strings.Join(strings.Fields(key), ""))
value = strings.TrimSpace(value)
switch key {
case "aicoreusagerate(%)":
usage, err := strconv.ParseFloat(strings.TrimSpace(strings.TrimSuffix(value, "%")), 64)
if err == nil && usage >= 0 && usage <= 100 {
device.AICore = ascendValueWithUnit(value, "%")
}
case "aicpuusagerate(%)":
device.AICPUUtil = ascendValueWithUnit(value, "%")
case "ctrlcpuusagerate(%)":
device.CtrlCPUUtil = ascendValueWithUnit(value, "%")
case "ddrbandwidthusagerate(%)", "memorybandwidthusagerate(%)":
device.DDRBandwidth = ascendValueWithUnit(value, "%")
case "hbmbandwidthusagerate(%)":
device.HBMBandwidth = ascendValueWithUnit(value, "%")
}
}
}
+8
View File
@@ -1,12 +1,20 @@
package npu package npu
type Info struct { type Info struct {
Warnings []string `json:"warnings"`
Type string `json:"type"` Type string `json:"type"`
DriverVersion string `json:"driverVersion"` DriverVersion string `json:"driverVersion"`
Devices []Device `json:"npu"` Devices []Device `json:"npu"`
} }
type Device struct { type Device struct {
AICPUUtil string `json:"aiCPUUtil"`
CtrlCPUUtil string `json:"ctrlCPUUtil"`
DDRBandwidth string `json:"ddrBandwidth"`
HBMBandwidth string `json:"hbmBandwidth"`
ProcessStatus string `json:"processStatus"`
Type string `json:"type"` Type string `json:"type"`
Index uint `json:"index"` Index uint `json:"index"`
NPUIndex uint `json:"npuIndex"` NPUIndex uint `json:"npuIndex"`
+335
View File
@@ -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
}
+28 -2
View File
@@ -33,16 +33,29 @@ type discoveryInfo struct {
} }
type DeviceLevelMetric struct { type DeviceLevelMetric struct {
MetricsType string `json:"metrics_type"` MetricsType string `json:"metrics_type"`
Value float64 `json:"value"` Value *float64 `json:"value"`
}
type TileMetrics struct {
TileID int `json:"tile_id"`
DataList []DeviceLevelMetric `json:"data_list"`
}
type TileStats struct {
TileID int `json:"tileID"`
Stats Stats `json:"stats"`
} }
type DeviceStats struct { type DeviceStats struct {
TileLevel []TileMetrics `json:"tile_level"`
DeviceID int `json:"device_id"` DeviceID int `json:"device_id"`
DeviceLevel []DeviceLevelMetric `json:"device_level"` DeviceLevel []DeviceLevelMetric `json:"device_level"`
} }
type Info struct { type Info struct {
Warnings []string `json:"warnings"`
Type string `json:"type"` Type string `json:"type"`
DriverVersion string `json:"driverVersion"` DriverVersion string `json:"driverVersion"`
@@ -50,12 +63,17 @@ type Info struct {
} }
type Device struct { type Device struct {
Tiles []TileStats `json:"tiles"`
ProcessStatus string `json:"processStatus"`
Basic Basic `json:"basic"` Basic Basic `json:"basic"`
Stats Stats `json:"stats"` Stats Stats `json:"stats"`
Processes []Process `json:"processes"` Processes []Process `json:"processes"`
} }
type Basic struct { type Basic struct {
UUID string `json:"uuid"`
DeviceID int `json:"deviceID"` DeviceID int `json:"deviceID"`
DeviceName string `json:"deviceName"` DeviceName string `json:"deviceName"`
VendorName string `json:"vendorName"` VendorName string `json:"vendorName"`
@@ -66,6 +84,14 @@ type Basic struct {
} }
type Stats struct { type Stats struct {
MediaUtil string `json:"mediaUtil"`
ComputeUtil string `json:"computeUtil"`
CopyUtil string `json:"copyUtil"`
MediaFrequency string `json:"mediaFrequency"`
MemoryTemperature string `json:"memoryTemperature"`
MemoryBandwidthUtil string `json:"memoryBandwidthUtil"`
Power string `json:"power"` Power string `json:"power"`
GPUUtil string `json:"gpuUtil"` GPUUtil string `json:"gpuUtil"`
Frequency string `json:"frequency"` Frequency string `json:"frequency"`
+53 -13
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math"
"sort" "sort"
"strconv" "strconv"
"sync" "sync"
@@ -51,12 +52,15 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
processData, err := cmdMgr.RunWithStdout(xpuSMICommand, "ps", "-j") processData, err := cmdMgr.RunWithStdout(xpuSMICommand, "ps", "-j")
if err != nil { if err != nil {
global.LOG.Warnf("calling xpu-smi ps failed, process information will be omitted: %v", err) res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi ps: %v", err))
} else { } else {
var psList DeviceUtilByProcList var psList DeviceUtilByProcList
if err := json.Unmarshal([]byte(processData), &psList); err != nil { if err := json.Unmarshal([]byte(processData), &psList); err != nil {
global.LOG.Warnf("processData json unmarshal failed, process information will be omitted: %v", err) res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi ps output: %v", err))
} else { } else {
for i := range res.Devices {
res.Devices[i].ProcessStatus = "ok"
}
for _, ps := range psList.DeviceUtilByProcList { for _, ps := range psList.DeviceUtilByProcList {
process := Process{ process := Process{
PID: ps.ProcessID, PID: ps.ProcessID,
@@ -87,7 +91,9 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
defer wg.Done() defer wg.Done()
xpu := Device{ xpu := Device{
ProcessStatus: "unavailable",
Basic: Basic{ Basic: Basic{
UUID: device.UUID,
DeviceID: device.DeviceID, DeviceID: device.DeviceID,
DeviceName: device.DeviceName, DeviceName: device.DeviceName,
VendorName: device.VendorName, VendorName: device.VendorName,
@@ -115,13 +121,19 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
wgCmd.Wait() wgCmd.Wait()
if xpuErr != nil { if xpuErr != nil {
global.LOG.Errorf("calling xpu-smi discovery failed for device %d, %v", device.DeviceID, xpuErr) mu.Lock()
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi device %d: %v", device.DeviceID, xpuErr))
res.Devices = append(res.Devices, xpu)
mu.Unlock()
return return
} }
var info discoveryDevice var info discoveryDevice
if err := json.Unmarshal([]byte(xpuData), &info); err != nil { if err := json.Unmarshal([]byte(xpuData), &info); err != nil {
global.LOG.Errorf("xpuData json unmarshal failed for device %d, err: %v", device.DeviceID, err) mu.Lock()
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi device %d output: %v", device.DeviceID, err))
res.Devices = append(res.Devices, xpu)
mu.Unlock()
return return
} }
@@ -142,13 +154,22 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
} }
if statsErr != nil { if statsErr != nil {
global.LOG.Warnf("calling xpu-smi stats failed for device %d, metrics will be omitted: %v", device.DeviceID, statsErr) mu.Lock()
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi stats %d: %v", device.DeviceID, statsErr))
mu.Unlock()
} else { } else {
var stats DeviceStats var stats DeviceStats
if err := json.Unmarshal([]byte(statsData), &stats); err != nil { if err := json.Unmarshal([]byte(statsData), &stats); err != nil {
global.LOG.Warnf("statsData json unmarshal failed for device %d, metrics will be omitted: %v", device.DeviceID, err) mu.Lock()
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi stats %d output: %v", device.DeviceID, err))
mu.Unlock()
} else { } else {
loadStats(&xpu.Stats, stats.DeviceLevel) loadStats(&xpu.Stats, stats.DeviceLevel)
for _, tile := range stats.TileLevel {
item := TileStats{TileID: tile.TileID}
loadStats(&item.Stats, tile.DataList)
xpu.Tiles = append(xpu.Tiles, item)
}
} }
} }
@@ -162,19 +183,38 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
func loadStats(stats *Stats, metrics []DeviceLevelMetric) { func loadStats(stats *Stats, metrics []DeviceLevelMetric) {
for _, stat := range metrics { for _, stat := range metrics {
if stat.Value == nil || math.IsNaN(*stat.Value) || math.IsInf(*stat.Value, 0) {
continue
}
value := *stat.Value
if value < 0 && stat.MetricsType != "XPUM_STATS_GPU_CORE_TEMPERATURE" && stat.MetricsType != "XPUM_STATS_MEMORY_TEMPERATURE" {
continue
}
switch stat.MetricsType { switch stat.MetricsType {
case "XPUM_STATS_MEMORY_TEMPERATURE":
stats.MemoryTemperature = fmt.Sprintf("%.1f°C", value)
case "XPUM_STATS_ENGINE_GROUP_COMPUTE_ALL_UTILIZATION":
stats.ComputeUtil = fmt.Sprintf("%.1f%%", value)
case "XPUM_STATS_ENGINE_GROUP_MEDIA_ALL_UTILIZATION":
stats.MediaUtil = fmt.Sprintf("%.1f%%", value)
case "XPUM_STATS_ENGINE_GROUP_COPY_ALL_UTILIZATION":
stats.CopyUtil = fmt.Sprintf("%.1f%%", value)
case "XPUM_STATS_MEDIA_ENGINE_FREQUENCY":
stats.MediaFrequency = fmt.Sprintf("%.1fMHz", value)
case "XPUM_STATS_POWER": case "XPUM_STATS_POWER":
stats.Power = fmt.Sprintf("%.1fW", stat.Value) stats.Power = fmt.Sprintf("%.1fW", value)
case "XPUM_STATS_GPU_UTILIZATION": case "XPUM_STATS_GPU_UTILIZATION":
stats.GPUUtil = fmt.Sprintf("%.1f%%", stat.Value) stats.GPUUtil = fmt.Sprintf("%.1f%%", value)
case "XPUM_STATS_GPU_FREQUENCY": case "XPUM_STATS_GPU_FREQUENCY":
stats.Frequency = fmt.Sprintf("%.1fMHz", stat.Value) stats.Frequency = fmt.Sprintf("%.1fMHz", value)
case "XPUM_STATS_GPU_CORE_TEMPERATURE": case "XPUM_STATS_GPU_CORE_TEMPERATURE":
stats.Temperature = fmt.Sprintf("%.1f°C", stat.Value) stats.Temperature = fmt.Sprintf("%.1f°C", value)
case "XPUM_STATS_MEMORY_USED": case "XPUM_STATS_MEMORY_USED":
stats.MemoryUsed = fmt.Sprintf("%.1f MiB", stat.Value) stats.MemoryUsed = fmt.Sprintf("%.1f MiB", value)
case "XPUM_STATS_MEMORY_UTILIZATION", "XPUM_STATS_MEMORY_BANDWIDTH", "XPUM_STATS_MEMORY_BANDWIDTH_UTILIZATION": case "XPUM_STATS_MEMORY_UTILIZATION":
stats.MemoryUtil = fmt.Sprintf("%.1f%%", stat.Value) stats.MemoryUtil = fmt.Sprintf("%.1f%%", value)
case "XPUM_STATS_MEMORY_BANDWIDTH", "XPUM_STATS_MEMORY_BANDWIDTH_UTILIZATION":
stats.MemoryBandwidthUtil = fmt.Sprintf("%.1f%%", value)
} }
} }
} }
+48 -7
View File
@@ -24,10 +24,10 @@ import (
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
) )
var cronJobAlertTypes = []string{"shell", "app", "website", "database", "directory", "log", "snapshot", "curl", "cutWebsiteLog", "clean", "ntp"} var cronJobAlertTypes = []string{"shell", "app", "website", "database", "directory", "log", "snapshot", "curl", "cutWebsiteLog", "clean", "ntp", "syncIpGroup", "cleanLog"}
func CreateTaskScanEmailAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo, emailConfig model.AlertConfig) error { func CreateTaskScanEmailAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo, emailConfig model.AlertConfig) error {
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param)) params := CreateTaskAlertParams(pushAlert)
alertDetail := ProcessAlertDetail(alert, pushAlert.TaskName, params, method) alertDetail := ProcessAlertDetail(alert, pushAlert.TaskName, params, method)
alertRule := ProcessAlertRule(alert) alertRule := ProcessAlertRule(alert)
create.AlertRule = alertRule create.AlertRule = alertRule
@@ -76,14 +76,14 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
Encryption: emailInfo.Encryption, Encryption: emailInfo.Encryption,
Recipient: emailInfo.Recipient, Recipient: emailInfo.Recipient,
} }
content := GetSendContent(alert.Type, params, agentInfo) content := GetAlertLogContent(create, alert, params, agentInfo)
if content == "" { if content == "" {
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title}) content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
} }
msg := email.EmailMessage{ msg := email.EmailMessage{
Subject: i18n.GetMsgByKey("PanelAlertTitle"), Subject: i18n.GetMsgByKey("PanelAlertTitle"),
Body: content, Body: content,
IsHTML: true, IsHTML: GetCronJobType(alert.Type) != "cronJob",
} }
if err = email.SendMail(smtpConfig, msg, transport); err != nil { if err = email.SendMail(smtpConfig, msg, transport); err != nil {
@@ -110,7 +110,7 @@ func CreateBarkAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []
return SaveAlertLog(create, &alertLog) return SaveAlertLog(create, &alertLog)
} }
content := GetSendContent(alert.Type, params, agentInfo) content := GetAlertLogContent(create, alert, params, agentInfo)
if content == "" { if content == "" {
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title}) content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
} }
@@ -268,7 +268,7 @@ func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param,
alertDetail := dto.AlertDetail{ alertDetail := dto.AlertDetail{
Type: GetCronJobType(alert.Type), Type: GetCronJobType(alert.Type),
SubType: alert.Type, SubType: alert.Type,
Title: alert.Title, Title: TaskAlertTitle(alert.Type, alert.Title, project, params),
Method: method, Method: method,
Project: project, Project: project,
Params: params, Params: params,
@@ -281,6 +281,14 @@ func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param,
return string(marshal) return string(marshal)
} }
func TaskAlertTitle(alertType, title, project string, params []dto.Param) string {
if GetCronJobType(alertType) != "cronJob" || CronJobAlertResultFromParams(params) != CronJobAlertSuccess {
return title
}
name := cronJobTaskName(project, params)
return i18n.GetMsgWithMap("TaskSuccess", map[string]interface{}{"name": name})
}
func ProcessAlertRule(alert dto.AlertDTO) string { func ProcessAlertRule(alert dto.AlertDTO) string {
marshal, err := json.Marshal(alert) marshal, err := json.Marshal(alert)
if err != nil { if err != nil {
@@ -324,6 +332,10 @@ func GetCronJobTypeName(cronJobType string) string {
module = "系统快照" module = "系统快照"
case "ntp": case "ntp":
module = "同步服务器时间" module = "同步服务器时间"
case "syncIpGroup":
module = "同步 IP 组"
case "cleanLog":
module = "清理日志"
default: default:
} }
return module return module
@@ -444,6 +456,30 @@ func isWithinTimeRange(savedTimeString string) bool {
} }
func GetSendContent(alertType string, params []dto.Param, agentInfo *dto.AgentInfo) string { func GetSendContent(alertType string, params []dto.Param, agentInfo *dto.AgentInfo) string {
return GetAlertDetailContent(dto.AlertDetail{Type: alertType, Params: params}, agentInfo)
}
func GetAlertLogContent(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, agentInfo *dto.AgentInfo) string {
detail := dto.AlertDetail{Type: alert.Type, Params: params}
var stored dto.AlertDetail
if json.Unmarshal([]byte(create.AlertDetail), &stored) == nil {
detail = stored
if detail.Type == "" {
detail.Type = alert.Type
}
if detail.Params == nil {
detail.Params = params
}
}
return GetAlertDetailContent(detail, agentInfo)
}
func GetAlertDetailContent(detail dto.AlertDetail, agentInfo *dto.AgentInfo) string {
alertType := detail.SubType
if alertType == "" {
alertType = detail.Type
}
params := detail.Params
switch GetCronJobType(alertType) { switch GetCronJobType(alertType) {
case "ssl": case "ssl":
return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
@@ -464,7 +500,12 @@ func GetSendContent(alertType string, params []dto.Param, agentInfo *dto.AgentIn
case "disk": case "disk":
return i18n.GetMsgWithMap("DiskUsedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "used": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) return i18n.GetMsgWithMap("DiskUsedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "used": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "cronJob": case "cronJob":
return i18n.GetMsgWithMap("CronJobFailedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) messageKey := "CronJobFailedAlert"
if CronJobAlertResultFromParams(params) == CronJobAlertSuccess {
messageKey = "CronJobSuccessAlert"
}
name := cronJobTaskName(detail.Project, params)
return i18n.GetMsgWithMap(messageKey, map[string]interface{}{"name": name, "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "clams": case "clams":
return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "panelLogin": case "panelLogin":

Some files were not shown because too many files have changed in this diff Show More